Files
zclaw_openfang/desktop/coverage/src/lib/llm-service.ts.html
iven d0c6319fc1
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
feat: 添加ESLint和Prettier配置并优化代码结构
style: 格式化代码文件并修复样式问题

docs: 新增部署文档和系统要求文档

test: 更新测试截图和覆盖率报告

refactor: 重构SchedulerPanel加载状态逻辑

ci: 添加lint和format脚本到package.json

build: 更新依赖项并添加开发工具

chore: 添加验证报告和上线审查计划
2026-03-26 08:02:23 +08:00

1843 lines
94 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src/lib/llm-service.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../../prettify.css" />
<link rel="stylesheet" href="../../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../../index.html">All files</a> / <a href="index.html">src/lib</a> llm-service.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Statements</span>
<span class='fraction'>0/373</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Branches</span>
<span class='fraction'>0/1</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Functions</span>
<span class='fraction'>0/1</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Lines</span>
<span class='fraction'>0/373</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line low'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a>
<a name='L102'></a><a href='#L102'>102</a>
<a name='L103'></a><a href='#L103'>103</a>
<a name='L104'></a><a href='#L104'>104</a>
<a name='L105'></a><a href='#L105'>105</a>
<a name='L106'></a><a href='#L106'>106</a>
<a name='L107'></a><a href='#L107'>107</a>
<a name='L108'></a><a href='#L108'>108</a>
<a name='L109'></a><a href='#L109'>109</a>
<a name='L110'></a><a href='#L110'>110</a>
<a name='L111'></a><a href='#L111'>111</a>
<a name='L112'></a><a href='#L112'>112</a>
<a name='L113'></a><a href='#L113'>113</a>
<a name='L114'></a><a href='#L114'>114</a>
<a name='L115'></a><a href='#L115'>115</a>
<a name='L116'></a><a href='#L116'>116</a>
<a name='L117'></a><a href='#L117'>117</a>
<a name='L118'></a><a href='#L118'>118</a>
<a name='L119'></a><a href='#L119'>119</a>
<a name='L120'></a><a href='#L120'>120</a>
<a name='L121'></a><a href='#L121'>121</a>
<a name='L122'></a><a href='#L122'>122</a>
<a name='L123'></a><a href='#L123'>123</a>
<a name='L124'></a><a href='#L124'>124</a>
<a name='L125'></a><a href='#L125'>125</a>
<a name='L126'></a><a href='#L126'>126</a>
<a name='L127'></a><a href='#L127'>127</a>
<a name='L128'></a><a href='#L128'>128</a>
<a name='L129'></a><a href='#L129'>129</a>
<a name='L130'></a><a href='#L130'>130</a>
<a name='L131'></a><a href='#L131'>131</a>
<a name='L132'></a><a href='#L132'>132</a>
<a name='L133'></a><a href='#L133'>133</a>
<a name='L134'></a><a href='#L134'>134</a>
<a name='L135'></a><a href='#L135'>135</a>
<a name='L136'></a><a href='#L136'>136</a>
<a name='L137'></a><a href='#L137'>137</a>
<a name='L138'></a><a href='#L138'>138</a>
<a name='L139'></a><a href='#L139'>139</a>
<a name='L140'></a><a href='#L140'>140</a>
<a name='L141'></a><a href='#L141'>141</a>
<a name='L142'></a><a href='#L142'>142</a>
<a name='L143'></a><a href='#L143'>143</a>
<a name='L144'></a><a href='#L144'>144</a>
<a name='L145'></a><a href='#L145'>145</a>
<a name='L146'></a><a href='#L146'>146</a>
<a name='L147'></a><a href='#L147'>147</a>
<a name='L148'></a><a href='#L148'>148</a>
<a name='L149'></a><a href='#L149'>149</a>
<a name='L150'></a><a href='#L150'>150</a>
<a name='L151'></a><a href='#L151'>151</a>
<a name='L152'></a><a href='#L152'>152</a>
<a name='L153'></a><a href='#L153'>153</a>
<a name='L154'></a><a href='#L154'>154</a>
<a name='L155'></a><a href='#L155'>155</a>
<a name='L156'></a><a href='#L156'>156</a>
<a name='L157'></a><a href='#L157'>157</a>
<a name='L158'></a><a href='#L158'>158</a>
<a name='L159'></a><a href='#L159'>159</a>
<a name='L160'></a><a href='#L160'>160</a>
<a name='L161'></a><a href='#L161'>161</a>
<a name='L162'></a><a href='#L162'>162</a>
<a name='L163'></a><a href='#L163'>163</a>
<a name='L164'></a><a href='#L164'>164</a>
<a name='L165'></a><a href='#L165'>165</a>
<a name='L166'></a><a href='#L166'>166</a>
<a name='L167'></a><a href='#L167'>167</a>
<a name='L168'></a><a href='#L168'>168</a>
<a name='L169'></a><a href='#L169'>169</a>
<a name='L170'></a><a href='#L170'>170</a>
<a name='L171'></a><a href='#L171'>171</a>
<a name='L172'></a><a href='#L172'>172</a>
<a name='L173'></a><a href='#L173'>173</a>
<a name='L174'></a><a href='#L174'>174</a>
<a name='L175'></a><a href='#L175'>175</a>
<a name='L176'></a><a href='#L176'>176</a>
<a name='L177'></a><a href='#L177'>177</a>
<a name='L178'></a><a href='#L178'>178</a>
<a name='L179'></a><a href='#L179'>179</a>
<a name='L180'></a><a href='#L180'>180</a>
<a name='L181'></a><a href='#L181'>181</a>
<a name='L182'></a><a href='#L182'>182</a>
<a name='L183'></a><a href='#L183'>183</a>
<a name='L184'></a><a href='#L184'>184</a>
<a name='L185'></a><a href='#L185'>185</a>
<a name='L186'></a><a href='#L186'>186</a>
<a name='L187'></a><a href='#L187'>187</a>
<a name='L188'></a><a href='#L188'>188</a>
<a name='L189'></a><a href='#L189'>189</a>
<a name='L190'></a><a href='#L190'>190</a>
<a name='L191'></a><a href='#L191'>191</a>
<a name='L192'></a><a href='#L192'>192</a>
<a name='L193'></a><a href='#L193'>193</a>
<a name='L194'></a><a href='#L194'>194</a>
<a name='L195'></a><a href='#L195'>195</a>
<a name='L196'></a><a href='#L196'>196</a>
<a name='L197'></a><a href='#L197'>197</a>
<a name='L198'></a><a href='#L198'>198</a>
<a name='L199'></a><a href='#L199'>199</a>
<a name='L200'></a><a href='#L200'>200</a>
<a name='L201'></a><a href='#L201'>201</a>
<a name='L202'></a><a href='#L202'>202</a>
<a name='L203'></a><a href='#L203'>203</a>
<a name='L204'></a><a href='#L204'>204</a>
<a name='L205'></a><a href='#L205'>205</a>
<a name='L206'></a><a href='#L206'>206</a>
<a name='L207'></a><a href='#L207'>207</a>
<a name='L208'></a><a href='#L208'>208</a>
<a name='L209'></a><a href='#L209'>209</a>
<a name='L210'></a><a href='#L210'>210</a>
<a name='L211'></a><a href='#L211'>211</a>
<a name='L212'></a><a href='#L212'>212</a>
<a name='L213'></a><a href='#L213'>213</a>
<a name='L214'></a><a href='#L214'>214</a>
<a name='L215'></a><a href='#L215'>215</a>
<a name='L216'></a><a href='#L216'>216</a>
<a name='L217'></a><a href='#L217'>217</a>
<a name='L218'></a><a href='#L218'>218</a>
<a name='L219'></a><a href='#L219'>219</a>
<a name='L220'></a><a href='#L220'>220</a>
<a name='L221'></a><a href='#L221'>221</a>
<a name='L222'></a><a href='#L222'>222</a>
<a name='L223'></a><a href='#L223'>223</a>
<a name='L224'></a><a href='#L224'>224</a>
<a name='L225'></a><a href='#L225'>225</a>
<a name='L226'></a><a href='#L226'>226</a>
<a name='L227'></a><a href='#L227'>227</a>
<a name='L228'></a><a href='#L228'>228</a>
<a name='L229'></a><a href='#L229'>229</a>
<a name='L230'></a><a href='#L230'>230</a>
<a name='L231'></a><a href='#L231'>231</a>
<a name='L232'></a><a href='#L232'>232</a>
<a name='L233'></a><a href='#L233'>233</a>
<a name='L234'></a><a href='#L234'>234</a>
<a name='L235'></a><a href='#L235'>235</a>
<a name='L236'></a><a href='#L236'>236</a>
<a name='L237'></a><a href='#L237'>237</a>
<a name='L238'></a><a href='#L238'>238</a>
<a name='L239'></a><a href='#L239'>239</a>
<a name='L240'></a><a href='#L240'>240</a>
<a name='L241'></a><a href='#L241'>241</a>
<a name='L242'></a><a href='#L242'>242</a>
<a name='L243'></a><a href='#L243'>243</a>
<a name='L244'></a><a href='#L244'>244</a>
<a name='L245'></a><a href='#L245'>245</a>
<a name='L246'></a><a href='#L246'>246</a>
<a name='L247'></a><a href='#L247'>247</a>
<a name='L248'></a><a href='#L248'>248</a>
<a name='L249'></a><a href='#L249'>249</a>
<a name='L250'></a><a href='#L250'>250</a>
<a name='L251'></a><a href='#L251'>251</a>
<a name='L252'></a><a href='#L252'>252</a>
<a name='L253'></a><a href='#L253'>253</a>
<a name='L254'></a><a href='#L254'>254</a>
<a name='L255'></a><a href='#L255'>255</a>
<a name='L256'></a><a href='#L256'>256</a>
<a name='L257'></a><a href='#L257'>257</a>
<a name='L258'></a><a href='#L258'>258</a>
<a name='L259'></a><a href='#L259'>259</a>
<a name='L260'></a><a href='#L260'>260</a>
<a name='L261'></a><a href='#L261'>261</a>
<a name='L262'></a><a href='#L262'>262</a>
<a name='L263'></a><a href='#L263'>263</a>
<a name='L264'></a><a href='#L264'>264</a>
<a name='L265'></a><a href='#L265'>265</a>
<a name='L266'></a><a href='#L266'>266</a>
<a name='L267'></a><a href='#L267'>267</a>
<a name='L268'></a><a href='#L268'>268</a>
<a name='L269'></a><a href='#L269'>269</a>
<a name='L270'></a><a href='#L270'>270</a>
<a name='L271'></a><a href='#L271'>271</a>
<a name='L272'></a><a href='#L272'>272</a>
<a name='L273'></a><a href='#L273'>273</a>
<a name='L274'></a><a href='#L274'>274</a>
<a name='L275'></a><a href='#L275'>275</a>
<a name='L276'></a><a href='#L276'>276</a>
<a name='L277'></a><a href='#L277'>277</a>
<a name='L278'></a><a href='#L278'>278</a>
<a name='L279'></a><a href='#L279'>279</a>
<a name='L280'></a><a href='#L280'>280</a>
<a name='L281'></a><a href='#L281'>281</a>
<a name='L282'></a><a href='#L282'>282</a>
<a name='L283'></a><a href='#L283'>283</a>
<a name='L284'></a><a href='#L284'>284</a>
<a name='L285'></a><a href='#L285'>285</a>
<a name='L286'></a><a href='#L286'>286</a>
<a name='L287'></a><a href='#L287'>287</a>
<a name='L288'></a><a href='#L288'>288</a>
<a name='L289'></a><a href='#L289'>289</a>
<a name='L290'></a><a href='#L290'>290</a>
<a name='L291'></a><a href='#L291'>291</a>
<a name='L292'></a><a href='#L292'>292</a>
<a name='L293'></a><a href='#L293'>293</a>
<a name='L294'></a><a href='#L294'>294</a>
<a name='L295'></a><a href='#L295'>295</a>
<a name='L296'></a><a href='#L296'>296</a>
<a name='L297'></a><a href='#L297'>297</a>
<a name='L298'></a><a href='#L298'>298</a>
<a name='L299'></a><a href='#L299'>299</a>
<a name='L300'></a><a href='#L300'>300</a>
<a name='L301'></a><a href='#L301'>301</a>
<a name='L302'></a><a href='#L302'>302</a>
<a name='L303'></a><a href='#L303'>303</a>
<a name='L304'></a><a href='#L304'>304</a>
<a name='L305'></a><a href='#L305'>305</a>
<a name='L306'></a><a href='#L306'>306</a>
<a name='L307'></a><a href='#L307'>307</a>
<a name='L308'></a><a href='#L308'>308</a>
<a name='L309'></a><a href='#L309'>309</a>
<a name='L310'></a><a href='#L310'>310</a>
<a name='L311'></a><a href='#L311'>311</a>
<a name='L312'></a><a href='#L312'>312</a>
<a name='L313'></a><a href='#L313'>313</a>
<a name='L314'></a><a href='#L314'>314</a>
<a name='L315'></a><a href='#L315'>315</a>
<a name='L316'></a><a href='#L316'>316</a>
<a name='L317'></a><a href='#L317'>317</a>
<a name='L318'></a><a href='#L318'>318</a>
<a name='L319'></a><a href='#L319'>319</a>
<a name='L320'></a><a href='#L320'>320</a>
<a name='L321'></a><a href='#L321'>321</a>
<a name='L322'></a><a href='#L322'>322</a>
<a name='L323'></a><a href='#L323'>323</a>
<a name='L324'></a><a href='#L324'>324</a>
<a name='L325'></a><a href='#L325'>325</a>
<a name='L326'></a><a href='#L326'>326</a>
<a name='L327'></a><a href='#L327'>327</a>
<a name='L328'></a><a href='#L328'>328</a>
<a name='L329'></a><a href='#L329'>329</a>
<a name='L330'></a><a href='#L330'>330</a>
<a name='L331'></a><a href='#L331'>331</a>
<a name='L332'></a><a href='#L332'>332</a>
<a name='L333'></a><a href='#L333'>333</a>
<a name='L334'></a><a href='#L334'>334</a>
<a name='L335'></a><a href='#L335'>335</a>
<a name='L336'></a><a href='#L336'>336</a>
<a name='L337'></a><a href='#L337'>337</a>
<a name='L338'></a><a href='#L338'>338</a>
<a name='L339'></a><a href='#L339'>339</a>
<a name='L340'></a><a href='#L340'>340</a>
<a name='L341'></a><a href='#L341'>341</a>
<a name='L342'></a><a href='#L342'>342</a>
<a name='L343'></a><a href='#L343'>343</a>
<a name='L344'></a><a href='#L344'>344</a>
<a name='L345'></a><a href='#L345'>345</a>
<a name='L346'></a><a href='#L346'>346</a>
<a name='L347'></a><a href='#L347'>347</a>
<a name='L348'></a><a href='#L348'>348</a>
<a name='L349'></a><a href='#L349'>349</a>
<a name='L350'></a><a href='#L350'>350</a>
<a name='L351'></a><a href='#L351'>351</a>
<a name='L352'></a><a href='#L352'>352</a>
<a name='L353'></a><a href='#L353'>353</a>
<a name='L354'></a><a href='#L354'>354</a>
<a name='L355'></a><a href='#L355'>355</a>
<a name='L356'></a><a href='#L356'>356</a>
<a name='L357'></a><a href='#L357'>357</a>
<a name='L358'></a><a href='#L358'>358</a>
<a name='L359'></a><a href='#L359'>359</a>
<a name='L360'></a><a href='#L360'>360</a>
<a name='L361'></a><a href='#L361'>361</a>
<a name='L362'></a><a href='#L362'>362</a>
<a name='L363'></a><a href='#L363'>363</a>
<a name='L364'></a><a href='#L364'>364</a>
<a name='L365'></a><a href='#L365'>365</a>
<a name='L366'></a><a href='#L366'>366</a>
<a name='L367'></a><a href='#L367'>367</a>
<a name='L368'></a><a href='#L368'>368</a>
<a name='L369'></a><a href='#L369'>369</a>
<a name='L370'></a><a href='#L370'>370</a>
<a name='L371'></a><a href='#L371'>371</a>
<a name='L372'></a><a href='#L372'>372</a>
<a name='L373'></a><a href='#L373'>373</a>
<a name='L374'></a><a href='#L374'>374</a>
<a name='L375'></a><a href='#L375'>375</a>
<a name='L376'></a><a href='#L376'>376</a>
<a name='L377'></a><a href='#L377'>377</a>
<a name='L378'></a><a href='#L378'>378</a>
<a name='L379'></a><a href='#L379'>379</a>
<a name='L380'></a><a href='#L380'>380</a>
<a name='L381'></a><a href='#L381'>381</a>
<a name='L382'></a><a href='#L382'>382</a>
<a name='L383'></a><a href='#L383'>383</a>
<a name='L384'></a><a href='#L384'>384</a>
<a name='L385'></a><a href='#L385'>385</a>
<a name='L386'></a><a href='#L386'>386</a>
<a name='L387'></a><a href='#L387'>387</a>
<a name='L388'></a><a href='#L388'>388</a>
<a name='L389'></a><a href='#L389'>389</a>
<a name='L390'></a><a href='#L390'>390</a>
<a name='L391'></a><a href='#L391'>391</a>
<a name='L392'></a><a href='#L392'>392</a>
<a name='L393'></a><a href='#L393'>393</a>
<a name='L394'></a><a href='#L394'>394</a>
<a name='L395'></a><a href='#L395'>395</a>
<a name='L396'></a><a href='#L396'>396</a>
<a name='L397'></a><a href='#L397'>397</a>
<a name='L398'></a><a href='#L398'>398</a>
<a name='L399'></a><a href='#L399'>399</a>
<a name='L400'></a><a href='#L400'>400</a>
<a name='L401'></a><a href='#L401'>401</a>
<a name='L402'></a><a href='#L402'>402</a>
<a name='L403'></a><a href='#L403'>403</a>
<a name='L404'></a><a href='#L404'>404</a>
<a name='L405'></a><a href='#L405'>405</a>
<a name='L406'></a><a href='#L406'>406</a>
<a name='L407'></a><a href='#L407'>407</a>
<a name='L408'></a><a href='#L408'>408</a>
<a name='L409'></a><a href='#L409'>409</a>
<a name='L410'></a><a href='#L410'>410</a>
<a name='L411'></a><a href='#L411'>411</a>
<a name='L412'></a><a href='#L412'>412</a>
<a name='L413'></a><a href='#L413'>413</a>
<a name='L414'></a><a href='#L414'>414</a>
<a name='L415'></a><a href='#L415'>415</a>
<a name='L416'></a><a href='#L416'>416</a>
<a name='L417'></a><a href='#L417'>417</a>
<a name='L418'></a><a href='#L418'>418</a>
<a name='L419'></a><a href='#L419'>419</a>
<a name='L420'></a><a href='#L420'>420</a>
<a name='L421'></a><a href='#L421'>421</a>
<a name='L422'></a><a href='#L422'>422</a>
<a name='L423'></a><a href='#L423'>423</a>
<a name='L424'></a><a href='#L424'>424</a>
<a name='L425'></a><a href='#L425'>425</a>
<a name='L426'></a><a href='#L426'>426</a>
<a name='L427'></a><a href='#L427'>427</a>
<a name='L428'></a><a href='#L428'>428</a>
<a name='L429'></a><a href='#L429'>429</a>
<a name='L430'></a><a href='#L430'>430</a>
<a name='L431'></a><a href='#L431'>431</a>
<a name='L432'></a><a href='#L432'>432</a>
<a name='L433'></a><a href='#L433'>433</a>
<a name='L434'></a><a href='#L434'>434</a>
<a name='L435'></a><a href='#L435'>435</a>
<a name='L436'></a><a href='#L436'>436</a>
<a name='L437'></a><a href='#L437'>437</a>
<a name='L438'></a><a href='#L438'>438</a>
<a name='L439'></a><a href='#L439'>439</a>
<a name='L440'></a><a href='#L440'>440</a>
<a name='L441'></a><a href='#L441'>441</a>
<a name='L442'></a><a href='#L442'>442</a>
<a name='L443'></a><a href='#L443'>443</a>
<a name='L444'></a><a href='#L444'>444</a>
<a name='L445'></a><a href='#L445'>445</a>
<a name='L446'></a><a href='#L446'>446</a>
<a name='L447'></a><a href='#L447'>447</a>
<a name='L448'></a><a href='#L448'>448</a>
<a name='L449'></a><a href='#L449'>449</a>
<a name='L450'></a><a href='#L450'>450</a>
<a name='L451'></a><a href='#L451'>451</a>
<a name='L452'></a><a href='#L452'>452</a>
<a name='L453'></a><a href='#L453'>453</a>
<a name='L454'></a><a href='#L454'>454</a>
<a name='L455'></a><a href='#L455'>455</a>
<a name='L456'></a><a href='#L456'>456</a>
<a name='L457'></a><a href='#L457'>457</a>
<a name='L458'></a><a href='#L458'>458</a>
<a name='L459'></a><a href='#L459'>459</a>
<a name='L460'></a><a href='#L460'>460</a>
<a name='L461'></a><a href='#L461'>461</a>
<a name='L462'></a><a href='#L462'>462</a>
<a name='L463'></a><a href='#L463'>463</a>
<a name='L464'></a><a href='#L464'>464</a>
<a name='L465'></a><a href='#L465'>465</a>
<a name='L466'></a><a href='#L466'>466</a>
<a name='L467'></a><a href='#L467'>467</a>
<a name='L468'></a><a href='#L468'>468</a>
<a name='L469'></a><a href='#L469'>469</a>
<a name='L470'></a><a href='#L470'>470</a>
<a name='L471'></a><a href='#L471'>471</a>
<a name='L472'></a><a href='#L472'>472</a>
<a name='L473'></a><a href='#L473'>473</a>
<a name='L474'></a><a href='#L474'>474</a>
<a name='L475'></a><a href='#L475'>475</a>
<a name='L476'></a><a href='#L476'>476</a>
<a name='L477'></a><a href='#L477'>477</a>
<a name='L478'></a><a href='#L478'>478</a>
<a name='L479'></a><a href='#L479'>479</a>
<a name='L480'></a><a href='#L480'>480</a>
<a name='L481'></a><a href='#L481'>481</a>
<a name='L482'></a><a href='#L482'>482</a>
<a name='L483'></a><a href='#L483'>483</a>
<a name='L484'></a><a href='#L484'>484</a>
<a name='L485'></a><a href='#L485'>485</a>
<a name='L486'></a><a href='#L486'>486</a>
<a name='L487'></a><a href='#L487'>487</a>
<a name='L488'></a><a href='#L488'>488</a>
<a name='L489'></a><a href='#L489'>489</a>
<a name='L490'></a><a href='#L490'>490</a>
<a name='L491'></a><a href='#L491'>491</a>
<a name='L492'></a><a href='#L492'>492</a>
<a name='L493'></a><a href='#L493'>493</a>
<a name='L494'></a><a href='#L494'>494</a>
<a name='L495'></a><a href='#L495'>495</a>
<a name='L496'></a><a href='#L496'>496</a>
<a name='L497'></a><a href='#L497'>497</a>
<a name='L498'></a><a href='#L498'>498</a>
<a name='L499'></a><a href='#L499'>499</a>
<a name='L500'></a><a href='#L500'>500</a>
<a name='L501'></a><a href='#L501'>501</a>
<a name='L502'></a><a href='#L502'>502</a>
<a name='L503'></a><a href='#L503'>503</a>
<a name='L504'></a><a href='#L504'>504</a>
<a name='L505'></a><a href='#L505'>505</a>
<a name='L506'></a><a href='#L506'>506</a>
<a name='L507'></a><a href='#L507'>507</a>
<a name='L508'></a><a href='#L508'>508</a>
<a name='L509'></a><a href='#L509'>509</a>
<a name='L510'></a><a href='#L510'>510</a>
<a name='L511'></a><a href='#L511'>511</a>
<a name='L512'></a><a href='#L512'>512</a>
<a name='L513'></a><a href='#L513'>513</a>
<a name='L514'></a><a href='#L514'>514</a>
<a name='L515'></a><a href='#L515'>515</a>
<a name='L516'></a><a href='#L516'>516</a>
<a name='L517'></a><a href='#L517'>517</a>
<a name='L518'></a><a href='#L518'>518</a>
<a name='L519'></a><a href='#L519'>519</a>
<a name='L520'></a><a href='#L520'>520</a>
<a name='L521'></a><a href='#L521'>521</a>
<a name='L522'></a><a href='#L522'>522</a>
<a name='L523'></a><a href='#L523'>523</a>
<a name='L524'></a><a href='#L524'>524</a>
<a name='L525'></a><a href='#L525'>525</a>
<a name='L526'></a><a href='#L526'>526</a>
<a name='L527'></a><a href='#L527'>527</a>
<a name='L528'></a><a href='#L528'>528</a>
<a name='L529'></a><a href='#L529'>529</a>
<a name='L530'></a><a href='#L530'>530</a>
<a name='L531'></a><a href='#L531'>531</a>
<a name='L532'></a><a href='#L532'>532</a>
<a name='L533'></a><a href='#L533'>533</a>
<a name='L534'></a><a href='#L534'>534</a>
<a name='L535'></a><a href='#L535'>535</a>
<a name='L536'></a><a href='#L536'>536</a>
<a name='L537'></a><a href='#L537'>537</a>
<a name='L538'></a><a href='#L538'>538</a>
<a name='L539'></a><a href='#L539'>539</a>
<a name='L540'></a><a href='#L540'>540</a>
<a name='L541'></a><a href='#L541'>541</a>
<a name='L542'></a><a href='#L542'>542</a>
<a name='L543'></a><a href='#L543'>543</a>
<a name='L544'></a><a href='#L544'>544</a>
<a name='L545'></a><a href='#L545'>545</a>
<a name='L546'></a><a href='#L546'>546</a>
<a name='L547'></a><a href='#L547'>547</a>
<a name='L548'></a><a href='#L548'>548</a>
<a name='L549'></a><a href='#L549'>549</a>
<a name='L550'></a><a href='#L550'>550</a>
<a name='L551'></a><a href='#L551'>551</a>
<a name='L552'></a><a href='#L552'>552</a>
<a name='L553'></a><a href='#L553'>553</a>
<a name='L554'></a><a href='#L554'>554</a>
<a name='L555'></a><a href='#L555'>555</a>
<a name='L556'></a><a href='#L556'>556</a>
<a name='L557'></a><a href='#L557'>557</a>
<a name='L558'></a><a href='#L558'>558</a>
<a name='L559'></a><a href='#L559'>559</a>
<a name='L560'></a><a href='#L560'>560</a>
<a name='L561'></a><a href='#L561'>561</a>
<a name='L562'></a><a href='#L562'>562</a>
<a name='L563'></a><a href='#L563'>563</a>
<a name='L564'></a><a href='#L564'>564</a>
<a name='L565'></a><a href='#L565'>565</a>
<a name='L566'></a><a href='#L566'>566</a>
<a name='L567'></a><a href='#L567'>567</a>
<a name='L568'></a><a href='#L568'>568</a>
<a name='L569'></a><a href='#L569'>569</a>
<a name='L570'></a><a href='#L570'>570</a>
<a name='L571'></a><a href='#L571'>571</a>
<a name='L572'></a><a href='#L572'>572</a>
<a name='L573'></a><a href='#L573'>573</a>
<a name='L574'></a><a href='#L574'>574</a>
<a name='L575'></a><a href='#L575'>575</a>
<a name='L576'></a><a href='#L576'>576</a>
<a name='L577'></a><a href='#L577'>577</a>
<a name='L578'></a><a href='#L578'>578</a>
<a name='L579'></a><a href='#L579'>579</a>
<a name='L580'></a><a href='#L580'>580</a>
<a name='L581'></a><a href='#L581'>581</a>
<a name='L582'></a><a href='#L582'>582</a>
<a name='L583'></a><a href='#L583'>583</a>
<a name='L584'></a><a href='#L584'>584</a>
<a name='L585'></a><a href='#L585'>585</a>
<a name='L586'></a><a href='#L586'>586</a>
<a name='L587'></a><a href='#L587'>587</a></td><td class="line-coverage quiet"><span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js"><span class="cstat-no" title="statement not covered" >/**<span class="fstat-no" title="function not covered" ><span class="branch-0 cbranch-no" title="branch not covered" ></span></span></span>
* LLM Service Adapter - Unified LLM interface for L4 self-evolution engines
*
* Provides a unified interface for:
* - ReflectionEngine: Semantic analysis + deep reflection
* - ContextCompactor: High-quality summarization
* - MemoryExtractor: Semantic importance scoring
*
* Supports multiple backends:
* - OpenAI (GPT-4, GPT-3.5)
* - Volcengine (Doubao)
* - OpenFang Gateway (passthrough)
*
* Part of ZCLAW L4 Self-Evolution capability.
*/
&nbsp;
import { DEFAULT_MODEL_ID, DEFAULT_OPENAI_BASE_URL } from '../constants/models';
&nbsp;
// === Types ===
&nbsp;
export type LLMProvider = 'openai' | 'volcengine' | 'gateway' | 'mock';
&nbsp;
export interface LLMConfig {
provider: LLMProvider;
model?: string;
apiKey?: string;
apiBase?: string;
maxTokens?: number;
temperature?: number;
timeout?: number;
}
&nbsp;
export interface LLMMessage {
role: 'system' | 'user' | 'assistant';
content: string;
}
&nbsp;
export interface LLMResponse {
content: string;
tokensUsed?: {
input: number;
output: number;
};
model?: string;
latencyMs?: number;
}
&nbsp;
export interface LLMServiceAdapter {
complete(messages: LLMMessage[], options?: Partial&lt;LLMConfig&gt;): Promise&lt;LLMResponse&gt;;
isAvailable(): boolean;
getProvider(): LLMProvider;
}
&nbsp;
// === Default Configs ===
&nbsp;
<span class="cstat-no" title="statement not covered" >const DEFAULT_CONFIGS: Record&lt;LLMProvider, LLMConfig&gt; = {</span>
<span class="cstat-no" title="statement not covered" > openai: {</span>
<span class="cstat-no" title="statement not covered" > provider: 'openai',</span>
<span class="cstat-no" title="statement not covered" > model: DEFAULT_MODEL_ID,</span>
<span class="cstat-no" title="statement not covered" > apiBase: DEFAULT_OPENAI_BASE_URL,</span>
<span class="cstat-no" title="statement not covered" > maxTokens: 2000,</span>
<span class="cstat-no" title="statement not covered" > temperature: 0.7,</span>
<span class="cstat-no" title="statement not covered" > timeout: 30000,</span>
<span class="cstat-no" title="statement not covered" > },</span>
<span class="cstat-no" title="statement not covered" > volcengine: {</span>
<span class="cstat-no" title="statement not covered" > provider: 'volcengine',</span>
<span class="cstat-no" title="statement not covered" > model: 'doubao-pro-32k',</span>
<span class="cstat-no" title="statement not covered" > apiBase: 'https://ark.cn-beijing.volces.com/api/v3',</span>
<span class="cstat-no" title="statement not covered" > maxTokens: 2000,</span>
<span class="cstat-no" title="statement not covered" > temperature: 0.7,</span>
<span class="cstat-no" title="statement not covered" > timeout: 30000,</span>
<span class="cstat-no" title="statement not covered" > },</span>
<span class="cstat-no" title="statement not covered" > gateway: {</span>
<span class="cstat-no" title="statement not covered" > provider: 'gateway',</span>
<span class="cstat-no" title="statement not covered" > apiBase: '/api/llm',</span>
<span class="cstat-no" title="statement not covered" > maxTokens: 2000,</span>
<span class="cstat-no" title="statement not covered" > temperature: 0.7,</span>
<span class="cstat-no" title="statement not covered" > timeout: 60000,</span>
<span class="cstat-no" title="statement not covered" > },</span>
<span class="cstat-no" title="statement not covered" > mock: {</span>
<span class="cstat-no" title="statement not covered" > provider: 'mock',</span>
<span class="cstat-no" title="statement not covered" > maxTokens: 100,</span>
<span class="cstat-no" title="statement not covered" > temperature: 0,</span>
<span class="cstat-no" title="statement not covered" > timeout: 100,</span>
<span class="cstat-no" title="statement not covered" > },</span>
<span class="cstat-no" title="statement not covered" >};</span>
&nbsp;
// === Storage ===
&nbsp;
<span class="cstat-no" title="statement not covered" >const LLM_CONFIG_KEY = 'zclaw-llm-config';</span>
&nbsp;
// === Mock Adapter (for testing) ===
&nbsp;
<span class="cstat-no" title="statement not covered" >class MockLLMAdapter implements LLMServiceAdapter {</span>
<span class="cstat-no" title="statement not covered" > constructor(_config: LLMConfig) {</span>
// Config is stored for future use (e.g., custom mock behavior based on config)
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > async complete(messages: LLMMessage[]): Promise&lt;LLMResponse&gt; {</span>
// Simulate latency
<span class="cstat-no" title="statement not covered" > await new Promise((resolve) =&gt; setTimeout(resolve, 50));</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > const lastMessage = messages[messages.length - 1];</span>
<span class="cstat-no" title="statement not covered" > const content = lastMessage?.content || '';</span>
&nbsp;
// Generate mock response based on content type
<span class="cstat-no" title="statement not covered" > let response = '[Mock LLM Response] ';</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > if (content.includes('reflect') || content.includes('反思')) {</span>
<span class="cstat-no" title="statement not covered" > response += JSON.stringify({</span>
<span class="cstat-no" title="statement not covered" > patterns: [</span>
<span class="cstat-no" title="statement not covered" > {</span>
<span class="cstat-no" title="statement not covered" > observation: '用户经常询问代码优化相关问题',</span>
<span class="cstat-no" title="statement not covered" > frequency: 5,</span>
<span class="cstat-no" title="statement not covered" > sentiment: 'positive',</span>
<span class="cstat-no" title="statement not covered" > evidence: ['多次讨论性能优化', '关注代码质量'],</span>
<span class="cstat-no" title="statement not covered" > },</span>
<span class="cstat-no" title="statement not covered" > ],</span>
<span class="cstat-no" title="statement not covered" > improvements: [</span>
<span class="cstat-no" title="statement not covered" > {</span>
<span class="cstat-no" title="statement not covered" > area: '代码解释',</span>
<span class="cstat-no" title="statement not covered" > suggestion: '可以提供更详细的代码注释',</span>
<span class="cstat-no" title="statement not covered" > priority: 'medium',</span>
<span class="cstat-no" title="statement not covered" > },</span>
<span class="cstat-no" title="statement not covered" > ],</span>
<span class="cstat-no" title="statement not covered" > identityProposals: [],</span>
<span class="cstat-no" title="statement not covered" > });</span>
<span class="cstat-no" title="statement not covered" > } else if (content.includes('summarize') || content.includes('摘要')) {</span>
<span class="cstat-no" title="statement not covered" > response += '这是一个关于对话内容的摘要,包含了主要讨论的要点和结论。';</span>
<span class="cstat-no" title="statement not covered" > } else if (content.includes('importance') || content.includes('重要性')) {</span>
<span class="cstat-no" title="statement not covered" > response += JSON.stringify({</span>
<span class="cstat-no" title="statement not covered" > memories: [</span>
<span class="cstat-no" title="statement not covered" > { content: '用户偏好简洁的回答', importance: 7, type: 'preference' },</span>
<span class="cstat-no" title="statement not covered" > ],</span>
<span class="cstat-no" title="statement not covered" > });</span>
<span class="cstat-no" title="statement not covered" > } else {</span>
<span class="cstat-no" title="statement not covered" > response += 'Processed: ' + content.slice(0, 50);</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > return {</span>
<span class="cstat-no" title="statement not covered" > content: response,</span>
<span class="cstat-no" title="statement not covered" > tokensUsed: { input: content.length / 4, output: response.length / 4 },</span>
<span class="cstat-no" title="statement not covered" > model: 'mock-model',</span>
<span class="cstat-no" title="statement not covered" > latencyMs: 50,</span>
<span class="cstat-no" title="statement not covered" > };</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > isAvailable(): boolean {</span>
<span class="cstat-no" title="statement not covered" > return true;</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > getProvider(): LLMProvider {</span>
<span class="cstat-no" title="statement not covered" > return 'mock';</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
// === OpenAI Adapter ===
&nbsp;
<span class="cstat-no" title="statement not covered" >class OpenAILLMAdapter implements LLMServiceAdapter {</span>
<span class="cstat-no" title="statement not covered" > private config: LLMConfig;</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > constructor(config: LLMConfig) {</span>
<span class="cstat-no" title="statement not covered" > this.config = { ...DEFAULT_CONFIGS.openai, ...config };</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > async complete(messages: LLMMessage[], options?: Partial&lt;LLMConfig&gt;): Promise&lt;LLMResponse&gt; {</span>
<span class="cstat-no" title="statement not covered" > const config = { ...this.config, ...options };</span>
<span class="cstat-no" title="statement not covered" > const startTime = Date.now();</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > if (!config.apiKey) {</span>
<span class="cstat-no" title="statement not covered" > throw new Error('[OpenAI] API key not configured');</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > const response = await fetch(`${config.apiBase}/chat/completions`, {</span>
<span class="cstat-no" title="statement not covered" > method: 'POST',</span>
<span class="cstat-no" title="statement not covered" > headers: {</span>
<span class="cstat-no" title="statement not covered" > 'Content-Type': 'application/json',</span>
<span class="cstat-no" title="statement not covered" > Authorization: `Bearer ${config.apiKey}`,</span>
<span class="cstat-no" title="statement not covered" > },</span>
<span class="cstat-no" title="statement not covered" > body: JSON.stringify({</span>
<span class="cstat-no" title="statement not covered" > model: config.model,</span>
<span class="cstat-no" title="statement not covered" > messages,</span>
<span class="cstat-no" title="statement not covered" > max_tokens: config.maxTokens,</span>
<span class="cstat-no" title="statement not covered" > temperature: config.temperature,</span>
<span class="cstat-no" title="statement not covered" > }),</span>
<span class="cstat-no" title="statement not covered" > signal: AbortSignal.timeout(config.timeout || 30000),</span>
<span class="cstat-no" title="statement not covered" > });</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > if (!response.ok) {</span>
<span class="cstat-no" title="statement not covered" > const errorBody = await response.text();</span>
// Log full error in development only
<span class="cstat-no" title="statement not covered" > if (import.meta.env.DEV) {</span>
<span class="cstat-no" title="statement not covered" > console.error('[OpenAI] API error:', errorBody);</span>
<span class="cstat-no" title="statement not covered" > }</span>
// Return sanitized error to caller
<span class="cstat-no" title="statement not covered" > throw new Error(`[OpenAI] API error: ${response.status} - Request failed`);</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > const data = await response.json();</span>
<span class="cstat-no" title="statement not covered" > const latencyMs = Date.now() - startTime;</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > return {</span>
<span class="cstat-no" title="statement not covered" > content: data.choices[0]?.message?.content || '',</span>
<span class="cstat-no" title="statement not covered" > tokensUsed: {</span>
<span class="cstat-no" title="statement not covered" > input: data.usage?.prompt_tokens || 0,</span>
<span class="cstat-no" title="statement not covered" > output: data.usage?.completion_tokens || 0,</span>
<span class="cstat-no" title="statement not covered" > },</span>
<span class="cstat-no" title="statement not covered" > model: data.model,</span>
<span class="cstat-no" title="statement not covered" > latencyMs,</span>
<span class="cstat-no" title="statement not covered" > };</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > isAvailable(): boolean {</span>
<span class="cstat-no" title="statement not covered" > return !!this.config.apiKey;</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > getProvider(): LLMProvider {</span>
<span class="cstat-no" title="statement not covered" > return 'openai';</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
// === Volcengine Adapter ===
&nbsp;
<span class="cstat-no" title="statement not covered" >class VolcengineLLMAdapter implements LLMServiceAdapter {</span>
<span class="cstat-no" title="statement not covered" > private config: LLMConfig;</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > constructor(config: LLMConfig) {</span>
<span class="cstat-no" title="statement not covered" > this.config = { ...DEFAULT_CONFIGS.volcengine, ...config };</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > async complete(messages: LLMMessage[], options?: Partial&lt;LLMConfig&gt;): Promise&lt;LLMResponse&gt; {</span>
<span class="cstat-no" title="statement not covered" > const config = { ...this.config, ...options };</span>
<span class="cstat-no" title="statement not covered" > const startTime = Date.now();</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > if (!config.apiKey) {</span>
<span class="cstat-no" title="statement not covered" > throw new Error('[Volcengine] API key not configured');</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > const response = await fetch(`${config.apiBase}/chat/completions`, {</span>
<span class="cstat-no" title="statement not covered" > method: 'POST',</span>
<span class="cstat-no" title="statement not covered" > headers: {</span>
<span class="cstat-no" title="statement not covered" > 'Content-Type': 'application/json',</span>
<span class="cstat-no" title="statement not covered" > Authorization: `Bearer ${config.apiKey}`,</span>
<span class="cstat-no" title="statement not covered" > },</span>
<span class="cstat-no" title="statement not covered" > body: JSON.stringify({</span>
<span class="cstat-no" title="statement not covered" > model: config.model,</span>
<span class="cstat-no" title="statement not covered" > messages,</span>
<span class="cstat-no" title="statement not covered" > max_tokens: config.maxTokens,</span>
<span class="cstat-no" title="statement not covered" > temperature: config.temperature,</span>
<span class="cstat-no" title="statement not covered" > }),</span>
<span class="cstat-no" title="statement not covered" > signal: AbortSignal.timeout(config.timeout || 30000),</span>
<span class="cstat-no" title="statement not covered" > });</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > if (!response.ok) {</span>
<span class="cstat-no" title="statement not covered" > const errorBody = await response.text();</span>
// Log full error in development only
<span class="cstat-no" title="statement not covered" > if (import.meta.env.DEV) {</span>
<span class="cstat-no" title="statement not covered" > console.error('[Volcengine] API error:', errorBody);</span>
<span class="cstat-no" title="statement not covered" > }</span>
// Return sanitized error to caller
<span class="cstat-no" title="statement not covered" > throw new Error(`[Volcengine] API error: ${response.status} - Request failed`);</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > const data = await response.json();</span>
<span class="cstat-no" title="statement not covered" > const latencyMs = Date.now() - startTime;</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > return {</span>
<span class="cstat-no" title="statement not covered" > content: data.choices[0]?.message?.content || '',</span>
<span class="cstat-no" title="statement not covered" > tokensUsed: {</span>
<span class="cstat-no" title="statement not covered" > input: data.usage?.prompt_tokens || 0,</span>
<span class="cstat-no" title="statement not covered" > output: data.usage?.completion_tokens || 0,</span>
<span class="cstat-no" title="statement not covered" > },</span>
<span class="cstat-no" title="statement not covered" > model: data.model,</span>
<span class="cstat-no" title="statement not covered" > latencyMs,</span>
<span class="cstat-no" title="statement not covered" > };</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > isAvailable(): boolean {</span>
<span class="cstat-no" title="statement not covered" > return !!this.config.apiKey;</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > getProvider(): LLMProvider {</span>
<span class="cstat-no" title="statement not covered" > return 'volcengine';</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
// === Gateway Adapter (pass through to OpenFang or internal Kernel) ===
&nbsp;
<span class="cstat-no" title="statement not covered" >class GatewayLLMAdapter implements LLMServiceAdapter {</span>
<span class="cstat-no" title="statement not covered" > private config: LLMConfig;</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > constructor(config: LLMConfig) {</span>
<span class="cstat-no" title="statement not covered" > this.config = { ...DEFAULT_CONFIGS.gateway, ...config };</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > async complete(messages: LLMMessage[], options?: Partial&lt;LLMConfig&gt;): Promise&lt;LLMResponse&gt; {</span>
<span class="cstat-no" title="statement not covered" > const config = { ...this.config, ...options };</span>
<span class="cstat-no" title="statement not covered" > const startTime = Date.now();</span>
&nbsp;
// Build a single prompt from messages
<span class="cstat-no" title="statement not covered" > const systemMessage = messages.find(m =&gt; m.role === 'system')?.content || '';</span>
<span class="cstat-no" title="statement not covered" > const userMessage = messages.find(m =&gt; m.role === 'user')?.content || '';</span>
&nbsp;
// Combine system and user messages into a single prompt
<span class="cstat-no" title="statement not covered" > const fullPrompt = systemMessage</span>
<span class="cstat-no" title="statement not covered" > ? `${systemMessage}\n\n${userMessage}`</span>
<span class="cstat-no" title="statement not covered" > : userMessage;</span>
&nbsp;
// Check if running in Tauri with internal kernel
// Use the same detection as kernel-client.ts
<span class="cstat-no" title="statement not covered" > const isTauri = typeof window !== 'undefined' &amp;&amp;</span>
<span class="cstat-no" title="statement not covered" > '__TAURI_INTERNALS__' in window;</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > if (isTauri) {</span>
// Use internal Kernel via Tauri invoke
<span class="cstat-no" title="statement not covered" > try {</span>
<span class="cstat-no" title="statement not covered" > const { invoke } = await import('@tauri-apps/api/core');</span>
&nbsp;
// Get the default agent ID from connectionStore or use the first agent
<span class="cstat-no" title="statement not covered" > const agentId = localStorage.getItem('zclaw-default-agent-id');</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > const response = await invoke&lt;{ content: string; input_tokens: number; output_tokens: number }&gt;('agent_chat', {</span>
<span class="cstat-no" title="statement not covered" > request: {</span>
<span class="cstat-no" title="statement not covered" > agentId: agentId || null, // null will use default agent</span>
<span class="cstat-no" title="statement not covered" > message: fullPrompt,</span>
<span class="cstat-no" title="statement not covered" > },</span>
<span class="cstat-no" title="statement not covered" > });</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > const latencyMs = Date.now() - startTime;</span>
<span class="cstat-no" title="statement not covered" > return {</span>
<span class="cstat-no" title="statement not covered" > content: response.content || '',</span>
<span class="cstat-no" title="statement not covered" > tokensUsed: {</span>
<span class="cstat-no" title="statement not covered" > input: response.input_tokens || 0,</span>
<span class="cstat-no" title="statement not covered" > output: response.output_tokens || 0,</span>
<span class="cstat-no" title="statement not covered" > },</span>
<span class="cstat-no" title="statement not covered" > latencyMs,</span>
<span class="cstat-no" title="statement not covered" > };</span>
<span class="cstat-no" title="statement not covered" > } catch (err) {</span>
<span class="cstat-no" title="statement not covered" > console.warn('[LLMService] Kernel chat failed, falling back to mock:', err);</span>
// Return empty response instead of throwing
<span class="cstat-no" title="statement not covered" > return {</span>
<span class="cstat-no" title="statement not covered" > content: '',</span>
<span class="cstat-no" title="statement not covered" > tokensUsed: { input: 0, output: 0 },</span>
<span class="cstat-no" title="statement not covered" > latencyMs: Date.now() - startTime,</span>
<span class="cstat-no" title="statement not covered" > };</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
// External Gateway mode: Use OpenFang's chat endpoint
<span class="cstat-no" title="statement not covered" > const agentId = localStorage.getItem('zclaw-default-agent-id') || 'default';</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > const response = await fetch(`/api/agents/${agentId}/message`, {</span>
<span class="cstat-no" title="statement not covered" > method: 'POST',</span>
<span class="cstat-no" title="statement not covered" > headers: {</span>
<span class="cstat-no" title="statement not covered" > 'Content-Type': 'application/json',</span>
<span class="cstat-no" title="statement not covered" > },</span>
<span class="cstat-no" title="statement not covered" > body: JSON.stringify({</span>
<span class="cstat-no" title="statement not covered" > message: fullPrompt,</span>
<span class="cstat-no" title="statement not covered" > max_tokens: config.maxTokens,</span>
<span class="cstat-no" title="statement not covered" > temperature: config.temperature ?? 0.3, // Lower temperature for extraction tasks</span>
<span class="cstat-no" title="statement not covered" > }),</span>
<span class="cstat-no" title="statement not covered" > signal: AbortSignal.timeout(config.timeout || 60000),</span>
<span class="cstat-no" title="statement not covered" > });</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > if (!response.ok) {</span>
<span class="cstat-no" title="statement not covered" > const error = await response.text();</span>
// If agent not found, try without agent ID (direct /api/chat)
<span class="cstat-no" title="statement not covered" > if (response.status === 404) {</span>
<span class="cstat-no" title="statement not covered" > const fallbackResponse = await fetch('/api/chat', {</span>
<span class="cstat-no" title="statement not covered" > method: 'POST',</span>
<span class="cstat-no" title="statement not covered" > headers: { 'Content-Type': 'application/json' },</span>
<span class="cstat-no" title="statement not covered" > body: JSON.stringify({</span>
<span class="cstat-no" title="statement not covered" > message: fullPrompt,</span>
<span class="cstat-no" title="statement not covered" > max_tokens: config.maxTokens,</span>
<span class="cstat-no" title="statement not covered" > temperature: config.temperature ?? 0.3,</span>
<span class="cstat-no" title="statement not covered" > }),</span>
<span class="cstat-no" title="statement not covered" > signal: AbortSignal.timeout(config.timeout || 60000),</span>
<span class="cstat-no" title="statement not covered" > });</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > if (!fallbackResponse.ok) {</span>
<span class="cstat-no" title="statement not covered" > throw new Error(`[Gateway] Both endpoints failed: ${fallbackResponse.status}`);</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > const data = await fallbackResponse.json();</span>
<span class="cstat-no" title="statement not covered" > const latencyMs = Date.now() - startTime;</span>
<span class="cstat-no" title="statement not covered" > return {</span>
<span class="cstat-no" title="statement not covered" > content: data.response || data.content || '',</span>
<span class="cstat-no" title="statement not covered" > tokensUsed: { input: data.input_tokens || 0, output: data.output_tokens || 0 },</span>
<span class="cstat-no" title="statement not covered" > latencyMs,</span>
<span class="cstat-no" title="statement not covered" > };</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > throw new Error(`[Gateway] API error: ${response.status} - ${error}`);</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > const data = await response.json();</span>
<span class="cstat-no" title="statement not covered" > const latencyMs = Date.now() - startTime;</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > return {</span>
<span class="cstat-no" title="statement not covered" > content: data.response || data.content || '',</span>
<span class="cstat-no" title="statement not covered" > tokensUsed: { input: data.input_tokens || 0, output: data.output_tokens || 0 },</span>
<span class="cstat-no" title="statement not covered" > latencyMs,</span>
<span class="cstat-no" title="statement not covered" > };</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > isAvailable(): boolean {</span>
// Gateway is available if we're in browser (can connect to OpenFang)
<span class="cstat-no" title="statement not covered" > return typeof window !== 'undefined';</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > getProvider(): LLMProvider {</span>
<span class="cstat-no" title="statement not covered" > return 'gateway';</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
// === Factory ===
&nbsp;
<span class="cstat-no" title="statement not covered" >let cachedAdapter: LLMServiceAdapter | null = null;</span>
&nbsp;
<span class="cstat-no" title="statement not covered" >export function createLLMAdapter(config?: Partial&lt;LLMConfig&gt;): LLMServiceAdapter {</span>
<span class="cstat-no" title="statement not covered" > const savedConfig = loadConfig();</span>
<span class="cstat-no" title="statement not covered" > const finalConfig = { ...savedConfig, ...config };</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > switch (finalConfig.provider) {</span>
<span class="cstat-no" title="statement not covered" > case 'openai':</span>
<span class="cstat-no" title="statement not covered" > return new OpenAILLMAdapter(finalConfig);</span>
<span class="cstat-no" title="statement not covered" > case 'volcengine':</span>
<span class="cstat-no" title="statement not covered" > return new VolcengineLLMAdapter(finalConfig);</span>
<span class="cstat-no" title="statement not covered" > case 'gateway':</span>
<span class="cstat-no" title="statement not covered" > return new GatewayLLMAdapter(finalConfig);</span>
<span class="cstat-no" title="statement not covered" > case 'mock':</span>
<span class="cstat-no" title="statement not covered" > default:</span>
<span class="cstat-no" title="statement not covered" > return new MockLLMAdapter(finalConfig);</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
<span class="cstat-no" title="statement not covered" >export function getLLMAdapter(): LLMServiceAdapter {</span>
<span class="cstat-no" title="statement not covered" > if (!cachedAdapter) {</span>
<span class="cstat-no" title="statement not covered" > cachedAdapter = createLLMAdapter();</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > return cachedAdapter;</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
<span class="cstat-no" title="statement not covered" >export function resetLLMAdapter(): void {</span>
<span class="cstat-no" title="statement not covered" > cachedAdapter = null;</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
// === Config Management ===
&nbsp;
<span class="cstat-no" title="statement not covered" >export function loadConfig(): LLMConfig {</span>
<span class="cstat-no" title="statement not covered" > if (typeof window === 'undefined') {</span>
<span class="cstat-no" title="statement not covered" > return DEFAULT_CONFIGS.mock;</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > try {</span>
<span class="cstat-no" title="statement not covered" > const saved = localStorage.getItem(LLM_CONFIG_KEY);</span>
<span class="cstat-no" title="statement not covered" > if (saved) {</span>
<span class="cstat-no" title="statement not covered" > return JSON.parse(saved);</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > } catch {</span>
// Ignore parse errors
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
// Default to gateway (OpenFang passthrough) for L4 self-evolution
<span class="cstat-no" title="statement not covered" > return DEFAULT_CONFIGS.gateway;</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
<span class="cstat-no" title="statement not covered" >export function saveConfig(config: LLMConfig): void {</span>
<span class="cstat-no" title="statement not covered" > if (typeof window === 'undefined') return;</span>
&nbsp;
// Don't save API key to localStorage for security
<span class="cstat-no" title="statement not covered" > const safeConfig = { ...config };</span>
<span class="cstat-no" title="statement not covered" > delete safeConfig.apiKey;</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > localStorage.setItem(LLM_CONFIG_KEY, JSON.stringify(safeConfig));</span>
<span class="cstat-no" title="statement not covered" > resetLLMAdapter();</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
// === Prompt Templates ===
&nbsp;
<span class="cstat-no" title="statement not covered" >export const LLM_PROMPTS = {</span>
<span class="cstat-no" title="statement not covered" > reflection: {</span>
<span class="cstat-no" title="statement not covered" > system: `你是一个 AI Agent 的自我反思引擎。分析最近的对话历史,识别行为模式,并生成改进建议。</span>
&nbsp;
输出 JSON 格式:
{
"patterns": [
{
"observation": "观察到的模式描述",
"frequency": 数字,
"sentiment": "positive/negative/neutral",
"evidence": ["证据1", "证据2"]
}
],
"improvements": [
{
"area": "改进领域",
"suggestion": "具体建议",
"priority": "high/medium/low"
}
],
"identityProposals": []
}`,
<span class="cstat-no" title="statement not covered" > user: (context: string) =&gt; `分析以下对话历史,进行自我反思:</span>
&nbsp;
<span class="cstat-no" title="statement not covered" >${context}</span>
&nbsp;
请识别行为模式(积极和消极),并提供具体的改进建议。`,
<span class="cstat-no" title="statement not covered" > },</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > compaction: {</span>
<span class="cstat-no" title="statement not covered" > system: `你是一个对话摘要专家。将长对话压缩为简洁的摘要,保留关键信息。</span>
&nbsp;
要求:
1. 保留所有重要决策和结论
2. 保留用户偏好和约束
3. 保留未完成的任务
4. 保持时间顺序
5. 摘要应能在后续对话中替代原始内容`,
<span class="cstat-no" title="statement not covered" > user: (messages: string) =&gt; `请将以下对话压缩为简洁摘要,保留关键信息:</span>
&nbsp;
<span class="cstat-no" title="statement not covered" >${messages}`,</span>
<span class="cstat-no" title="statement not covered" > },</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > extraction: {</span>
<span class="cstat-no" title="statement not covered" > system: `你是一个记忆提取专家。从对话中提取值得长期记住的信息。</span>
&nbsp;
提取类型:
- fact: 用户告知的事实(如"我的公司叫XXX"
- preference: 用户的偏好(如"我喜欢简洁的回答"
- lesson: 本次对话的经验教训
- task: 未完成的任务或承诺
&nbsp;
输出 JSON 数组:
[
{
"content": "记忆内容",
"type": "fact/preference/lesson/task",
"importance": 1-10,
"tags": ["标签1", "标签2"]
}
]`,
<span class="cstat-no" title="statement not covered" > user: (conversation: string) =&gt; `从以下对话中提取值得长期记住的信息:</span>
&nbsp;
<span class="cstat-no" title="statement not covered" >${conversation}</span>
&nbsp;
如果没有值得记忆的内容,返回空数组 []。`,
<span class="cstat-no" title="statement not covered" > },</span>
<span class="cstat-no" title="statement not covered" >};</span>
&nbsp;
// === Helper Functions ===
&nbsp;
<span class="cstat-no" title="statement not covered" >export async function llmReflect(context: string, adapter?: LLMServiceAdapter): Promise&lt;string&gt; {</span>
<span class="cstat-no" title="statement not covered" > const llm = adapter || getLLMAdapter();</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > const response = await llm.complete([</span>
<span class="cstat-no" title="statement not covered" > { role: 'system', content: LLM_PROMPTS.reflection.system },</span>
<span class="cstat-no" title="statement not covered" > { role: 'user', content: LLM_PROMPTS.reflection.user(context) },</span>
<span class="cstat-no" title="statement not covered" > ]);</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > return response.content;</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
<span class="cstat-no" title="statement not covered" >export async function llmCompact(messages: string, adapter?: LLMServiceAdapter): Promise&lt;string&gt; {</span>
<span class="cstat-no" title="statement not covered" > const llm = adapter || getLLMAdapter();</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > const response = await llm.complete([</span>
<span class="cstat-no" title="statement not covered" > { role: 'system', content: LLM_PROMPTS.compaction.system },</span>
<span class="cstat-no" title="statement not covered" > { role: 'user', content: LLM_PROMPTS.compaction.user(messages) },</span>
<span class="cstat-no" title="statement not covered" > ]);</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > return response.content;</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
<span class="cstat-no" title="statement not covered" >export async function llmExtract(</span>
<span class="cstat-no" title="statement not covered" > conversation: string,</span>
<span class="cstat-no" title="statement not covered" > adapter?: LLMServiceAdapter</span>
<span class="cstat-no" title="statement not covered" >): Promise&lt;string&gt; {</span>
<span class="cstat-no" title="statement not covered" > const llm = adapter || getLLMAdapter();</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > const response = await llm.complete([</span>
<span class="cstat-no" title="statement not covered" > { role: 'system', content: LLM_PROMPTS.extraction.system },</span>
<span class="cstat-no" title="statement not covered" > { role: 'user', content: LLM_PROMPTS.extraction.user(conversation) },</span>
<span class="cstat-no" title="statement not covered" > ]);</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > return response.content;</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-03-25T14:02:06.489Z
</div>
<script src="../../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../../sorter.js"></script>
<script src="../../block-navigation.js"></script>
</body>
</html>