Implement stream cancellation in OllamaClient
Add `cancelStream` method to allow aborting active requests. Store the current `AbortController` on the client instance and reset it when the stream completes or is cancelled. Update `ChatView` to call `cancelStream` on close. Add comprehensive tests for stream cancellation scenarios, including aborting active requests, handling cancellation when no stream is active, clearing the controller after normal completion, and allowing new streams after cancellation.
This commit is contained in:
@@ -6,6 +6,7 @@ lib/
|
|||||||
dist/
|
dist/
|
||||||
out/
|
out/
|
||||||
build/
|
build/
|
||||||
|
coverage/
|
||||||
|
|
||||||
# IDE files
|
# IDE files
|
||||||
.idea/
|
.idea/
|
||||||
|
|||||||
@@ -23,9 +23,9 @@
|
|||||||
<div class='clearfix'>
|
<div class='clearfix'>
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
<div class='fl pad1y space-right2'>
|
||||||
<span class="strong">92.52% </span>
|
<span class="strong">92.55% </span>
|
||||||
<span class="quiet">Statements</span>
|
<span class="quiet">Statements</span>
|
||||||
<span class='fraction'>198/214</span>
|
<span class='fraction'>199/215</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -44,9 +44,9 @@
|
|||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
<div class='fl pad1y space-right2'>
|
||||||
<span class="strong">94.5% </span>
|
<span class="strong">94.52% </span>
|
||||||
<span class="quiet">Lines</span>
|
<span class="quiet">Lines</span>
|
||||||
<span class='fraction'>189/200</span>
|
<span class='fraction'>190/201</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -628,7 +628,7 @@
|
|||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-yes">3x</span>
|
||||||
<span class="cline-any cline-yes">3x</span>
|
<span class="cline-any cline-yes">3x</span>
|
||||||
<span class="cline-any cline-yes">3x</span>
|
<span class="cline-any cline-yes">3x</span>
|
||||||
<span class="cline-any cline-yes">3x</span>
|
<span class="cline-any cline-yes">3x</span>
|
||||||
@@ -1105,7 +1105,7 @@ export class ChatView extends ItemView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onClose(): Promise<void> {
|
onClose(): Promise<void> {
|
||||||
// Removed cancelStream call as we now use local controllers
|
this.ollamaClient.cancelStream();
|
||||||
this.removeEventListeners();
|
this.removeEventListeners();
|
||||||
this.cleanupStreamingResources();
|
this.cleanupStreamingResources();
|
||||||
this.lastMessageEl = null;
|
this.lastMessageEl = null;
|
||||||
@@ -1501,7 +1501,7 @@ export class ChatView extends ItemView {
|
|||||||
<div class='footer quiet pad2 space-top1 center small'>
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
Code coverage generated by
|
Code coverage generated by
|
||||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
at 2026-05-06T22:58:43.715Z
|
at 2026-05-07T10:09:29.372Z
|
||||||
</div>
|
</div>
|
||||||
<script src="prettify.js"></script>
|
<script src="prettify.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -475,7 +475,7 @@ export class ErrorHandler {
|
|||||||
<div class='footer quiet pad2 space-top1 center small'>
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
Code coverage generated by
|
Code coverage generated by
|
||||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
at 2026-05-06T22:58:43.715Z
|
at 2026-05-07T10:09:29.372Z
|
||||||
</div>
|
</div>
|
||||||
<script src="prettify.js"></script>
|
<script src="prettify.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -23,30 +23,30 @@
|
|||||||
<div class='clearfix'>
|
<div class='clearfix'>
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
<div class='fl pad1y space-right2'>
|
||||||
<span class="strong">92.3% </span>
|
<span class="strong">92.38% </span>
|
||||||
<span class="quiet">Statements</span>
|
<span class="quiet">Statements</span>
|
||||||
<span class='fraction'>660/715</span>
|
<span class='fraction'>667/722</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
<div class='fl pad1y space-right2'>
|
||||||
<span class="strong">81.43% </span>
|
<span class="strong">81.49% </span>
|
||||||
<span class="quiet">Branches</span>
|
<span class="quiet">Branches</span>
|
||||||
<span class='fraction'>250/307</span>
|
<span class='fraction'>251/308</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
<div class='fl pad1y space-right2'>
|
||||||
<span class="strong">88.59% </span>
|
<span class="strong">88.69% </span>
|
||||||
<span class="quiet">Functions</span>
|
<span class="quiet">Functions</span>
|
||||||
<span class='fraction'>101/114</span>
|
<span class='fraction'>102/115</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
<div class='fl pad1y space-right2'>
|
||||||
<span class="strong">93.24% </span>
|
<span class="strong">93.31% </span>
|
||||||
<span class="quiet">Lines</span>
|
<span class="quiet">Lines</span>
|
||||||
<span class='fraction'>635/681</span>
|
<span class='fraction'>642/688</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -80,17 +80,17 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody><tr>
|
<tbody><tr>
|
||||||
<td class="file high" data-value="chat-view.ts"><a href="chat-view.ts.html">chat-view.ts</a></td>
|
<td class="file high" data-value="chat-view.ts"><a href="chat-view.ts.html">chat-view.ts</a></td>
|
||||||
<td data-value="92.52" class="pic high">
|
<td data-value="92.55" class="pic high">
|
||||||
<div class="chart"><div class="cover-fill" style="width: 92%"></div><div class="cover-empty" style="width: 8%"></div></div>
|
<div class="chart"><div class="cover-fill" style="width: 92%"></div><div class="cover-empty" style="width: 8%"></div></div>
|
||||||
</td>
|
</td>
|
||||||
<td data-value="92.52" class="pct high">92.52%</td>
|
<td data-value="92.55" class="pct high">92.55%</td>
|
||||||
<td data-value="214" class="abs high">198/214</td>
|
<td data-value="215" class="abs high">199/215</td>
|
||||||
<td data-value="85.71" class="pct high">85.71%</td>
|
<td data-value="85.71" class="pct high">85.71%</td>
|
||||||
<td data-value="70" class="abs high">60/70</td>
|
<td data-value="70" class="abs high">60/70</td>
|
||||||
<td data-value="82.85" class="pct high">82.85%</td>
|
<td data-value="82.85" class="pct high">82.85%</td>
|
||||||
<td data-value="35" class="abs high">29/35</td>
|
<td data-value="35" class="abs high">29/35</td>
|
||||||
<td data-value="94.5" class="pct high">94.5%</td>
|
<td data-value="94.52" class="pct high">94.52%</td>
|
||||||
<td data-value="200" class="abs high">189/200</td>
|
<td data-value="201" class="abs high">190/201</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
@@ -110,17 +110,17 @@
|
|||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td class="file high" data-value="ollama-client.ts"><a href="ollama-client.ts.html">ollama-client.ts</a></td>
|
<td class="file high" data-value="ollama-client.ts"><a href="ollama-client.ts.html">ollama-client.ts</a></td>
|
||||||
<td data-value="91.37" class="pic high">
|
<td data-value="91.8" class="pic high">
|
||||||
<div class="chart"><div class="cover-fill" style="width: 91%"></div><div class="cover-empty" style="width: 9%"></div></div>
|
<div class="chart"><div class="cover-fill" style="width: 91%"></div><div class="cover-empty" style="width: 9%"></div></div>
|
||||||
</td>
|
</td>
|
||||||
|
<td data-value="91.8" class="pct high">91.8%</td>
|
||||||
|
<td data-value="122" class="abs high">112/122</td>
|
||||||
|
<td data-value="72.72" class="pct medium">72.72%</td>
|
||||||
|
<td data-value="55" class="abs medium">40/55</td>
|
||||||
|
<td data-value="88.23" class="pct high">88.23%</td>
|
||||||
|
<td data-value="17" class="abs high">15/17</td>
|
||||||
<td data-value="91.37" class="pct high">91.37%</td>
|
<td data-value="91.37" class="pct high">91.37%</td>
|
||||||
<td data-value="116" class="abs high">106/116</td>
|
<td data-value="116" class="abs high">106/116</td>
|
||||||
<td data-value="72.22" class="pct medium">72.22%</td>
|
|
||||||
<td data-value="54" class="abs medium">39/54</td>
|
|
||||||
<td data-value="87.5" class="pct high">87.5%</td>
|
|
||||||
<td data-value="16" class="abs high">14/16</td>
|
|
||||||
<td data-value="90.9" class="pct high">90.9%</td>
|
|
||||||
<td data-value="110" class="abs high">100/110</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
@@ -191,7 +191,7 @@
|
|||||||
<div class='footer quiet pad2 space-top1 center small'>
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
Code coverage generated by
|
Code coverage generated by
|
||||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
at 2026-05-06T22:58:43.715Z
|
at 2026-05-07T10:09:29.372Z
|
||||||
</div>
|
</div>
|
||||||
<script src="prettify.js"></script>
|
<script src="prettify.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -23,30 +23,30 @@
|
|||||||
<div class='clearfix'>
|
<div class='clearfix'>
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
<div class='fl pad1y space-right2'>
|
||||||
<span class="strong">91.37% </span>
|
<span class="strong">91.8% </span>
|
||||||
<span class="quiet">Statements</span>
|
<span class="quiet">Statements</span>
|
||||||
<span class='fraction'>106/116</span>
|
<span class='fraction'>112/122</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
<div class='fl pad1y space-right2'>
|
||||||
<span class="strong">72.22% </span>
|
<span class="strong">72.72% </span>
|
||||||
<span class="quiet">Branches</span>
|
<span class="quiet">Branches</span>
|
||||||
<span class='fraction'>39/54</span>
|
<span class='fraction'>40/55</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
<div class='fl pad1y space-right2'>
|
||||||
<span class="strong">87.5% </span>
|
<span class="strong">88.23% </span>
|
||||||
<span class="quiet">Functions</span>
|
<span class="quiet">Functions</span>
|
||||||
<span class='fraction'>14/16</span>
|
<span class='fraction'>15/17</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
<div class='fl pad1y space-right2'>
|
||||||
<span class="strong">90.9% </span>
|
<span class="strong">91.37% </span>
|
||||||
<span class="quiet">Lines</span>
|
<span class="quiet">Lines</span>
|
||||||
<span class='fraction'>100/110</span>
|
<span class='fraction'>106/116</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -339,7 +339,18 @@
|
|||||||
<a name='L274'></a><a href='#L274'>274</a>
|
<a name='L274'></a><a href='#L274'>274</a>
|
||||||
<a name='L275'></a><a href='#L275'>275</a>
|
<a name='L275'></a><a href='#L275'>275</a>
|
||||||
<a name='L276'></a><a href='#L276'>276</a>
|
<a name='L276'></a><a href='#L276'>276</a>
|
||||||
<a name='L277'></a><a href='#L277'>277</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
<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></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-yes">2x</span>
|
<span class="cline-any cline-yes">2x</span>
|
||||||
@@ -353,19 +364,27 @@
|
|||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-yes">59x</span>
|
<span class="cline-any cline-yes">61x</span>
|
||||||
|
<span class="cline-any cline-yes">61x</span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-yes">59x</span>
|
<span class="cline-any cline-yes">61x</span>
|
||||||
<span class="cline-any cline-yes">59x</span>
|
<span class="cline-any cline-yes">61x</span>
|
||||||
<span class="cline-any cline-yes">59x</span>
|
<span class="cline-any cline-yes">61x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span>
|
||||||
|
<span class="cline-any cline-neutral"> </span>
|
||||||
|
<span class="cline-any cline-neutral"> </span>
|
||||||
|
<span class="cline-any cline-yes">6x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
|
<span class="cline-any cline-yes">2x</span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-yes">20x</span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
|
<span class="cline-any cline-yes">24x</span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
@@ -385,10 +404,11 @@
|
|||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-yes">25x</span>
|
<span class="cline-any cline-yes">29x</span>
|
||||||
|
<span class="cline-any cline-yes">29x</span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-yes">25x</span>
|
<span class="cline-any cline-yes">29x</span>
|
||||||
<span class="cline-any cline-yes">25x</span>
|
<span class="cline-any cline-yes">29x</span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
@@ -402,7 +422,7 @@
|
|||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-yes">25x</span>
|
<span class="cline-any cline-yes">27x</span>
|
||||||
<span class="cline-any cline-yes">8x</span>
|
<span class="cline-any cline-yes">8x</span>
|
||||||
<span class="cline-any cline-yes">5x</span>
|
<span class="cline-any cline-yes">5x</span>
|
||||||
<span class="cline-any cline-yes">5x</span>
|
<span class="cline-any cline-yes">5x</span>
|
||||||
@@ -439,45 +459,45 @@
|
|||||||
<span class="cline-any cline-yes">3x</span>
|
<span class="cline-any cline-yes">3x</span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-yes">13x</span>
|
<span class="cline-any cline-yes">15x</span>
|
||||||
<span class="cline-any cline-yes">1x</span>
|
<span class="cline-any cline-yes">1x</span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-yes">12x</span>
|
|
||||||
<span class="cline-any cline-yes">12x</span>
|
|
||||||
<span class="cline-any cline-yes">1x</span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-yes">11x</span>
|
|
||||||
<span class="cline-any cline-yes">11x</span>
|
|
||||||
<span class="cline-any cline-yes">11x</span>
|
|
||||||
<span class="cline-any cline-yes">11x</span>
|
|
||||||
<span class="cline-any cline-yes">11x</span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-yes">11x</span>
|
|
||||||
<span class="cline-any cline-yes">11x</span>
|
|
||||||
<span class="cline-any cline-yes">19x</span>
|
|
||||||
<span class="cline-any cline-yes">19x</span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-yes">10x</span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-yes">10x</span>
|
|
||||||
<span class="cline-any cline-yes">10x</span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-yes">10x</span>
|
|
||||||
<span class="cline-any cline-yes">68x</span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-yes">67x</span>
|
|
||||||
<span class="cline-any cline-yes">67x</span>
|
|
||||||
<span class="cline-any cline-yes">14x</span>
|
<span class="cline-any cline-yes">14x</span>
|
||||||
|
<span class="cline-any cline-yes">14x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-yes">13x</span>
|
<span class="cline-any cline-yes">13x</span>
|
||||||
<span class="cline-any cline-yes">13x</span>
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-yes">22x</span>
|
||||||
|
<span class="cline-any cline-yes">22x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span>
|
||||||
|
<span class="cline-any cline-yes">11x</span>
|
||||||
|
<span class="cline-any cline-yes">69x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span>
|
||||||
|
<span class="cline-any cline-yes">68x</span>
|
||||||
|
<span class="cline-any cline-yes">68x</span>
|
||||||
|
<span class="cline-any cline-yes">15x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span>
|
||||||
|
<span class="cline-any cline-yes">14x</span>
|
||||||
|
<span class="cline-any cline-yes">14x</span>
|
||||||
<span class="cline-any cline-no"> </span>
|
<span class="cline-any cline-no"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-yes">13x</span>
|
<span class="cline-any cline-yes">14x</span>
|
||||||
<span class="cline-any cline-yes">13x</span>
|
<span class="cline-any cline-yes">14x</span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-yes">54x</span>
|
<span class="cline-any cline-yes">54x</span>
|
||||||
<span class="cline-any cline-yes">1x</span>
|
<span class="cline-any cline-yes">1x</span>
|
||||||
@@ -496,7 +516,7 @@
|
|||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-yes">9x</span>
|
<span class="cline-any cline-yes">11x</span>
|
||||||
<span class="cline-any cline-yes">1x</span>
|
<span class="cline-any cline-yes">1x</span>
|
||||||
<span class="cline-any cline-yes">1x</span>
|
<span class="cline-any cline-yes">1x</span>
|
||||||
<span class="cline-any cline-yes">1x</span>
|
<span class="cline-any cline-yes">1x</span>
|
||||||
@@ -516,15 +536,17 @@
|
|||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
|
<span class="cline-any cline-yes">13x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span>
|
||||||
|
<span class="cline-any cline-neutral"> </span>
|
||||||
|
<span class="cline-any cline-neutral"> </span>
|
||||||
|
<span class="cline-any cline-yes">29x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span>
|
||||||
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-yes">11x</span>
|
<span class="cline-any cline-yes">11x</span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-yes">25x</span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-yes">5x</span>
|
<span class="cline-any cline-yes">5x</span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
@@ -597,18 +619,18 @@
|
|||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-yes">15x</span>
|
<span class="cline-any cline-yes">16x</span>
|
||||||
<span class="cline-any cline-yes">1x</span>
|
<span class="cline-any cline-yes">1x</span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
|
<span class="cline-any cline-yes">19x</span>
|
||||||
|
<span class="cline-any cline-yes">1x</span>
|
||||||
|
<span class="cline-any cline-neutral"> </span>
|
||||||
|
<span class="cline-any cline-neutral"> </span>
|
||||||
|
<span class="cline-any cline-yes">18x</span>
|
||||||
<span class="cline-any cline-yes">18x</span>
|
<span class="cline-any cline-yes">18x</span>
|
||||||
<span class="cline-any cline-yes">1x</span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-yes">17x</span>
|
|
||||||
<span class="cline-any cline-yes">17x</span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
<span class="cline-any cline-neutral"> </span>
|
<span class="cline-any cline-neutral"> </span>
|
||||||
@@ -630,12 +652,20 @@ export class OllamaClient {
|
|||||||
private model: string;
|
private model: string;
|
||||||
private fetchFn: typeof fetch;
|
private fetchFn: typeof fetch;
|
||||||
private readonly maxRetries: number = 3;
|
private readonly maxRetries: number = 3;
|
||||||
|
private currentStreamController: AbortController | null = null;
|
||||||
|
|
||||||
constructor(baseURL: string, model: string, fetchFn?: typeof fetch) {
|
constructor(baseURL: string, model: string, fetchFn?: typeof fetch) {
|
||||||
this.baseURL = baseURL;
|
this.baseURL = baseURL;
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.fetchFn = fetchFn ?? fetch;
|
this.fetchFn = fetchFn ?? fetch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cancelStream(): void {
|
||||||
|
if (this.currentStreamController) {
|
||||||
|
this.currentStreamController.abort();
|
||||||
|
this.currentStreamController = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async *streamChat(
|
async *streamChat(
|
||||||
messages: OllamaMessage[],
|
messages: OllamaMessage[],
|
||||||
@@ -662,6 +692,7 @@ export class OllamaClient {
|
|||||||
): AsyncGenerator<OllamaMessage, void, unknown> {
|
): AsyncGenerator<OllamaMessage, void, unknown> {
|
||||||
// Create a local controller for this request instead of using the instance variable
|
// Create a local controller for this request instead of using the instance variable
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
this.currentStreamController = controller;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.fetchFn(`${this.baseURL}/api/chat`, {
|
const response = await this.fetchFn(`${this.baseURL}/api/chat`, {
|
||||||
@@ -798,6 +829,8 @@ export class OllamaClient {
|
|||||||
// Abort the local controller
|
// Abort the local controller
|
||||||
controller.abort();
|
controller.abort();
|
||||||
}
|
}
|
||||||
|
// Clean up the reference
|
||||||
|
this.currentStreamController = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async chat(messages: OllamaMessage[], tools: OllamaTool[] = <span class="branch-0 cbranch-no" title="branch not covered" >[])</span>: Promise<OllamaMessage> {
|
async chat(messages: OllamaMessage[], tools: OllamaTool[] = <span class="branch-0 cbranch-no" title="branch not covered" >[])</span>: Promise<OllamaMessage> {
|
||||||
@@ -898,7 +931,7 @@ export class OllamaClient {
|
|||||||
<div class='footer quiet pad2 space-top1 center small'>
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
Code coverage generated by
|
Code coverage generated by
|
||||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
at 2026-05-06T22:58:43.715Z
|
at 2026-05-07T10:09:29.372Z
|
||||||
</div>
|
</div>
|
||||||
<script src="prettify.js"></script>
|
<script src="prettify.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -463,7 +463,7 @@ export class ToolExecutor {
|
|||||||
<div class='footer quiet pad2 space-top1 center small'>
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
Code coverage generated by
|
Code coverage generated by
|
||||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
at 2026-05-06T22:58:43.715Z
|
at 2026-05-07T10:09:29.372Z
|
||||||
</div>
|
</div>
|
||||||
<script src="prettify.js"></script>
|
<script src="prettify.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -604,7 +604,7 @@ export interface VaultIndexEntry {
|
|||||||
<div class='footer quiet pad2 space-top1 center small'>
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
Code coverage generated by
|
Code coverage generated by
|
||||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
at 2026-05-06T22:58:43.715Z
|
at 2026-05-07T10:09:29.372Z
|
||||||
</div>
|
</div>
|
||||||
<script src="prettify.js"></script>
|
<script src="prettify.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -643,7 +643,7 @@ export function safeParseJson(jsonString: string): unknown {
|
|||||||
<div class='footer quiet pad2 space-top1 center small'>
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
Code coverage generated by
|
Code coverage generated by
|
||||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
at 2026-05-06T22:58:43.715Z
|
at 2026-05-07T10:09:29.372Z
|
||||||
</div>
|
</div>
|
||||||
<script src="prettify.js"></script>
|
<script src="prettify.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -1183,7 +1183,7 @@ export function <span class="fstat-no" title="function not covered" >createVault
|
|||||||
<div class='footer quiet pad2 space-top1 center small'>
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
Code coverage generated by
|
Code coverage generated by
|
||||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
at 2026-05-06T22:58:43.715Z
|
at 2026-05-07T10:09:29.372Z
|
||||||
</div>
|
</div>
|
||||||
<script src="prettify.js"></script>
|
<script src="prettify.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
+205
-195
@@ -111,6 +111,7 @@ DA:79,5
|
|||||||
DA:80,5
|
DA:80,5
|
||||||
DA:81,5
|
DA:81,5
|
||||||
DA:85,0
|
DA:85,0
|
||||||
|
DA:89,3
|
||||||
DA:90,3
|
DA:90,3
|
||||||
DA:91,3
|
DA:91,3
|
||||||
DA:92,3
|
DA:92,3
|
||||||
@@ -272,8 +273,8 @@ DA:468,12
|
|||||||
DA:470,6
|
DA:470,6
|
||||||
DA:472,21
|
DA:472,21
|
||||||
DA:473,21
|
DA:473,21
|
||||||
LF:200
|
LF:201
|
||||||
LH:189
|
LH:190
|
||||||
BRDA:102,0,0,0
|
BRDA:102,0,0,0
|
||||||
BRDA:102,1,0,28
|
BRDA:102,1,0,28
|
||||||
BRDA:102,1,1,6
|
BRDA:102,1,1,6
|
||||||
@@ -482,208 +483,217 @@ BRH:42
|
|||||||
end_of_record
|
end_of_record
|
||||||
TN:
|
TN:
|
||||||
SF:src/ollama-client.ts
|
SF:src/ollama-client.ts
|
||||||
FN:17,(anonymous_0)
|
FN:18,(anonymous_0)
|
||||||
FN:23,(anonymous_1)
|
FN:24,(anonymous_1)
|
||||||
FN:30,(anonymous_2)
|
FN:31,(anonymous_2)
|
||||||
FN:41,(anonymous_3)
|
FN:38,(anonymous_3)
|
||||||
FN:72,(anonymous_4)
|
FN:49,(anonymous_4)
|
||||||
FN:73,(anonymous_5)
|
FN:81,(anonymous_5)
|
||||||
FN:82,(anonymous_6)
|
FN:82,(anonymous_6)
|
||||||
FN:83,(anonymous_7)
|
FN:91,(anonymous_7)
|
||||||
FN:186,(anonymous_8)
|
FN:92,(anonymous_8)
|
||||||
FN:190,(anonymous_9)
|
FN:197,(anonymous_9)
|
||||||
FN:220,(anonymous_10)
|
FN:201,(anonymous_10)
|
||||||
FN:221,(anonymous_11)
|
FN:231,(anonymous_11)
|
||||||
FN:230,(anonymous_12)
|
FN:232,(anonymous_12)
|
||||||
FN:231,(anonymous_13)
|
FN:241,(anonymous_13)
|
||||||
FN:258,(anonymous_14)
|
FN:242,(anonymous_14)
|
||||||
FN:264,(anonymous_15)
|
FN:269,(anonymous_15)
|
||||||
FNF:16
|
FN:275,(anonymous_16)
|
||||||
FNH:14
|
FNF:17
|
||||||
FNDA:59,(anonymous_0)
|
FNH:15
|
||||||
FNDA:20,(anonymous_1)
|
FNDA:61,(anonymous_0)
|
||||||
FNDA:3,(anonymous_2)
|
FNDA:6,(anonymous_1)
|
||||||
FNDA:25,(anonymous_3)
|
FNDA:24,(anonymous_2)
|
||||||
FNDA:4,(anonymous_4)
|
FNDA:3,(anonymous_3)
|
||||||
FNDA:0,(anonymous_5)
|
FNDA:29,(anonymous_4)
|
||||||
FNDA:4,(anonymous_6)
|
FNDA:4,(anonymous_5)
|
||||||
|
FNDA:0,(anonymous_6)
|
||||||
FNDA:4,(anonymous_7)
|
FNDA:4,(anonymous_7)
|
||||||
FNDA:5,(anonymous_8)
|
FNDA:4,(anonymous_8)
|
||||||
FNDA:8,(anonymous_9)
|
FNDA:5,(anonymous_9)
|
||||||
FNDA:2,(anonymous_10)
|
FNDA:8,(anonymous_10)
|
||||||
FNDA:0,(anonymous_11)
|
FNDA:2,(anonymous_11)
|
||||||
FNDA:2,(anonymous_12)
|
FNDA:0,(anonymous_12)
|
||||||
FNDA:2,(anonymous_13)
|
FNDA:2,(anonymous_13)
|
||||||
FNDA:15,(anonymous_14)
|
FNDA:2,(anonymous_14)
|
||||||
FNDA:18,(anonymous_15)
|
FNDA:16,(anonymous_15)
|
||||||
|
FNDA:19,(anonymous_16)
|
||||||
DA:4,2
|
DA:4,2
|
||||||
DA:5,2
|
DA:5,2
|
||||||
DA:11,2
|
DA:11,2
|
||||||
DA:15,59
|
DA:15,61
|
||||||
DA:18,59
|
DA:16,61
|
||||||
DA:19,59
|
DA:19,61
|
||||||
DA:20,59
|
DA:20,61
|
||||||
DA:27,20
|
DA:21,61
|
||||||
DA:34,3
|
DA:25,6
|
||||||
DA:35,3
|
DA:26,2
|
||||||
DA:36,0
|
DA:27,2
|
||||||
DA:38,0
|
DA:35,24
|
||||||
DA:47,25
|
DA:42,3
|
||||||
DA:49,25
|
DA:43,3
|
||||||
DA:50,25
|
DA:44,0
|
||||||
DA:64,25
|
DA:46,0
|
||||||
DA:65,8
|
DA:55,29
|
||||||
DA:66,5
|
DA:56,29
|
||||||
DA:67,5
|
DA:58,29
|
||||||
DA:71,5
|
DA:59,29
|
||||||
DA:72,4
|
DA:73,27
|
||||||
DA:73,4
|
DA:74,8
|
||||||
DA:74,0
|
DA:75,5
|
||||||
DA:76,4
|
DA:76,5
|
||||||
DA:77,4
|
DA:80,5
|
||||||
DA:78,4
|
DA:81,4
|
||||||
DA:79,4
|
DA:82,4
|
||||||
DA:80,4
|
DA:83,0
|
||||||
DA:83,4
|
DA:85,4
|
||||||
|
DA:86,4
|
||||||
|
DA:87,4
|
||||||
|
DA:88,4
|
||||||
DA:89,4
|
DA:89,4
|
||||||
DA:92,0
|
DA:92,4
|
||||||
DA:95,5
|
DA:98,4
|
||||||
DA:96,2
|
DA:101,0
|
||||||
DA:98,3
|
DA:104,5
|
||||||
DA:101,13
|
DA:105,2
|
||||||
DA:102,1
|
DA:107,3
|
||||||
DA:105,12
|
DA:110,15
|
||||||
DA:106,12
|
DA:111,1
|
||||||
DA:107,1
|
DA:114,14
|
||||||
DA:110,11
|
DA:115,14
|
||||||
DA:111,11
|
DA:116,1
|
||||||
DA:112,11
|
DA:119,13
|
||||||
DA:113,11
|
DA:120,13
|
||||||
DA:114,11
|
DA:121,13
|
||||||
DA:116,11
|
DA:122,13
|
||||||
DA:117,11
|
DA:123,13
|
||||||
DA:118,19
|
DA:125,13
|
||||||
DA:119,19
|
DA:126,13
|
||||||
DA:121,10
|
DA:127,22
|
||||||
DA:123,10
|
DA:128,22
|
||||||
DA:124,10
|
DA:130,11
|
||||||
DA:126,10
|
DA:132,11
|
||||||
DA:127,68
|
DA:133,11
|
||||||
DA:129,67
|
DA:135,11
|
||||||
DA:130,67
|
DA:136,69
|
||||||
DA:131,14
|
DA:138,68
|
||||||
DA:133,13
|
DA:139,68
|
||||||
DA:134,13
|
DA:140,15
|
||||||
DA:135,0
|
DA:142,14
|
||||||
DA:138,13
|
DA:143,14
|
||||||
DA:139,13
|
DA:144,0
|
||||||
DA:141,54
|
DA:147,14
|
||||||
DA:142,1
|
DA:148,14
|
||||||
DA:145,53
|
DA:150,54
|
||||||
DA:146,53
|
DA:151,1
|
||||||
DA:147,1
|
DA:154,53
|
||||||
DA:150,52
|
DA:155,53
|
||||||
DA:158,9
|
DA:156,1
|
||||||
DA:159,1
|
DA:159,52
|
||||||
DA:160,1
|
DA:167,11
|
||||||
DA:161,1
|
DA:168,1
|
||||||
DA:163,1
|
DA:169,1
|
||||||
DA:164,1
|
DA:170,1
|
||||||
DA:165,1
|
DA:172,1
|
||||||
DA:168,0
|
DA:173,1
|
||||||
DA:169,0
|
DA:174,1
|
||||||
DA:171,0
|
DA:177,0
|
||||||
DA:178,11
|
DA:178,0
|
||||||
DA:182,25
|
DA:180,0
|
||||||
DA:187,5
|
DA:187,13
|
||||||
DA:196,8
|
DA:191,29
|
||||||
DA:197,8
|
DA:194,11
|
||||||
DA:198,8
|
DA:198,5
|
||||||
DA:212,8
|
DA:207,8
|
||||||
DA:213,4
|
DA:208,8
|
||||||
DA:214,3
|
DA:209,8
|
||||||
DA:215,3
|
DA:223,8
|
||||||
DA:219,3
|
DA:224,4
|
||||||
DA:220,2
|
DA:225,3
|
||||||
DA:221,2
|
DA:226,3
|
||||||
DA:222,0
|
DA:230,3
|
||||||
DA:224,2
|
|
||||||
DA:225,2
|
|
||||||
DA:226,2
|
|
||||||
DA:227,2
|
|
||||||
DA:228,2
|
|
||||||
DA:231,2
|
DA:231,2
|
||||||
|
DA:232,2
|
||||||
|
DA:233,0
|
||||||
|
DA:235,2
|
||||||
|
DA:236,2
|
||||||
DA:237,2
|
DA:237,2
|
||||||
DA:240,0
|
DA:238,2
|
||||||
DA:243,3
|
DA:239,2
|
||||||
DA:245,1
|
DA:242,2
|
||||||
DA:248,4
|
DA:248,2
|
||||||
DA:249,4
|
DA:251,0
|
||||||
DA:254,8
|
DA:254,3
|
||||||
DA:259,15
|
DA:256,1
|
||||||
DA:260,1
|
DA:259,4
|
||||||
DA:265,18
|
DA:260,4
|
||||||
DA:266,1
|
DA:265,8
|
||||||
DA:269,17
|
DA:270,16
|
||||||
DA:270,17
|
DA:271,1
|
||||||
LF:110
|
DA:276,19
|
||||||
LH:100
|
DA:277,1
|
||||||
BRDA:20,0,0,59
|
DA:280,18
|
||||||
BRDA:20,0,1,36
|
DA:281,18
|
||||||
BRDA:25,1,0,0
|
LF:116
|
||||||
BRDA:32,2,0,0
|
LH:106
|
||||||
BRDA:43,3,0,0
|
BRDA:21,0,0,61
|
||||||
BRDA:44,4,0,0
|
BRDA:21,0,1,36
|
||||||
BRDA:64,5,0,8
|
BRDA:25,1,0,2
|
||||||
BRDA:65,6,0,5
|
BRDA:33,2,0,0
|
||||||
BRDA:65,7,0,8
|
BRDA:40,3,0,0
|
||||||
BRDA:65,7,1,6
|
BRDA:51,4,0,0
|
||||||
BRDA:71,8,0,4
|
BRDA:52,5,0,0
|
||||||
BRDA:77,9,0,4
|
BRDA:73,6,0,8
|
||||||
BRDA:77,9,1,0
|
BRDA:74,7,0,5
|
||||||
BRDA:101,10,0,1
|
BRDA:74,8,0,8
|
||||||
BRDA:106,11,0,1
|
BRDA:74,8,1,6
|
||||||
BRDA:106,12,0,12
|
BRDA:80,9,0,4
|
||||||
BRDA:106,12,1,12
|
BRDA:86,10,0,4
|
||||||
BRDA:106,12,2,1
|
BRDA:86,10,1,0
|
||||||
BRDA:119,13,0,9
|
BRDA:110,11,0,1
|
||||||
BRDA:124,14,0,10
|
BRDA:115,12,0,1
|
||||||
BRDA:124,14,1,0
|
BRDA:115,13,0,14
|
||||||
BRDA:127,15,0,1
|
BRDA:115,13,1,14
|
||||||
BRDA:134,16,0,0
|
BRDA:115,13,2,1
|
||||||
BRDA:141,17,0,1
|
BRDA:128,14,0,11
|
||||||
BRDA:141,18,0,54
|
BRDA:133,15,0,11
|
||||||
BRDA:141,18,1,54
|
BRDA:133,15,1,0
|
||||||
BRDA:146,19,0,1
|
BRDA:136,16,0,1
|
||||||
BRDA:158,20,0,1
|
BRDA:143,17,0,0
|
||||||
BRDA:164,21,0,1
|
BRDA:150,18,0,1
|
||||||
BRDA:168,22,0,0
|
BRDA:150,19,0,54
|
||||||
BRDA:168,23,0,0
|
BRDA:150,19,1,54
|
||||||
BRDA:168,23,1,0
|
BRDA:155,20,0,1
|
||||||
BRDA:186,24,0,0
|
BRDA:167,21,0,1
|
||||||
BRDA:192,25,0,0
|
BRDA:173,22,0,1
|
||||||
BRDA:193,26,0,0
|
BRDA:177,23,0,0
|
||||||
BRDA:212,27,0,4
|
BRDA:177,24,0,0
|
||||||
BRDA:213,28,0,3
|
BRDA:177,24,1,0
|
||||||
BRDA:213,29,0,4
|
BRDA:197,25,0,0
|
||||||
BRDA:213,29,1,4
|
BRDA:203,26,0,0
|
||||||
BRDA:219,30,0,2
|
BRDA:204,27,0,0
|
||||||
BRDA:225,31,0,2
|
BRDA:223,28,0,4
|
||||||
BRDA:225,31,1,0
|
BRDA:224,29,0,3
|
||||||
BRDA:250,32,0,4
|
BRDA:224,30,0,4
|
||||||
BRDA:250,32,1,1
|
BRDA:224,30,1,4
|
||||||
BRDA:259,33,0,1
|
BRDA:230,31,0,2
|
||||||
BRDA:265,34,0,1
|
BRDA:236,32,0,2
|
||||||
BRDA:265,35,0,18
|
BRDA:236,32,1,0
|
||||||
BRDA:265,35,1,17
|
BRDA:261,33,0,4
|
||||||
BRDA:271,36,0,17
|
BRDA:261,33,1,1
|
||||||
BRDA:271,36,1,17
|
BRDA:270,34,0,1
|
||||||
BRDA:272,37,0,17
|
BRDA:276,35,0,1
|
||||||
BRDA:272,37,1,0
|
BRDA:276,36,0,19
|
||||||
BRDA:273,38,0,17
|
BRDA:276,36,1,18
|
||||||
BRDA:273,38,1,15
|
BRDA:282,37,0,18
|
||||||
BRF:54
|
BRDA:282,37,1,18
|
||||||
BRH:39
|
BRDA:283,38,0,18
|
||||||
|
BRDA:283,38,1,0
|
||||||
|
BRDA:284,39,0,18
|
||||||
|
BRDA:284,39,1,16
|
||||||
|
BRF:55
|
||||||
|
BRH:40
|
||||||
end_of_record
|
end_of_record
|
||||||
TN:
|
TN:
|
||||||
SF:src/tool-executor.ts
|
SF:src/tool-executor.ts
|
||||||
|
|||||||
+1
-1
@@ -60,7 +60,7 @@ class ChatView extends obsidian_1.ItemView {
|
|||||||
this.updateSettings(newSettings);
|
this.updateSettings(newSettings);
|
||||||
}
|
}
|
||||||
onClose() {
|
onClose() {
|
||||||
// Removed cancelStream call as we now use local controllers
|
this.ollamaClient.cancelStream();
|
||||||
this.removeEventListeners();
|
this.removeEventListeners();
|
||||||
this.cleanupStreamingResources();
|
this.cleanupStreamingResources();
|
||||||
this.lastMessageEl = null;
|
this.lastMessageEl = null;
|
||||||
|
|||||||
+1
-1
@@ -86,7 +86,7 @@ export class ChatView extends ItemView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onClose(): Promise<void> {
|
onClose(): Promise<void> {
|
||||||
// Removed cancelStream call as we now use local controllers
|
this.ollamaClient.cancelStream();
|
||||||
this.removeEventListeners();
|
this.removeEventListeners();
|
||||||
this.cleanupStreamingResources();
|
this.cleanupStreamingResources();
|
||||||
this.lastMessageEl = null;
|
this.lastMessageEl = null;
|
||||||
|
|||||||
@@ -7,10 +7,17 @@ const utils_1 = require("./utils");
|
|||||||
class OllamaClient {
|
class OllamaClient {
|
||||||
constructor(baseURL, model, fetchFn) {
|
constructor(baseURL, model, fetchFn) {
|
||||||
this.maxRetries = 3;
|
this.maxRetries = 3;
|
||||||
|
this.currentStreamController = null;
|
||||||
this.baseURL = baseURL;
|
this.baseURL = baseURL;
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.fetchFn = fetchFn ?? fetch;
|
this.fetchFn = fetchFn ?? fetch;
|
||||||
}
|
}
|
||||||
|
cancelStream() {
|
||||||
|
if (this.currentStreamController) {
|
||||||
|
this.currentStreamController.abort();
|
||||||
|
this.currentStreamController = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
async *streamChat(messages, tools = []) {
|
async *streamChat(messages, tools = []) {
|
||||||
yield* this.streamChatWithRetry(messages, tools, 0);
|
yield* this.streamChatWithRetry(messages, tools, 0);
|
||||||
}
|
}
|
||||||
@@ -24,6 +31,7 @@ class OllamaClient {
|
|||||||
async *streamChatWithRetry(messages, tools = [], attempt = 0) {
|
async *streamChatWithRetry(messages, tools = [], attempt = 0) {
|
||||||
// Create a local controller for this request instead of using the instance variable
|
// Create a local controller for this request instead of using the instance variable
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
this.currentStreamController = controller;
|
||||||
try {
|
try {
|
||||||
const response = await this.fetchFn(`${this.baseURL}/api/chat`, {
|
const response = await this.fetchFn(`${this.baseURL}/api/chat`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -143,6 +151,8 @@ class OllamaClient {
|
|||||||
// Abort the local controller
|
// Abort the local controller
|
||||||
controller.abort();
|
controller.abort();
|
||||||
}
|
}
|
||||||
|
// Clean up the reference
|
||||||
|
this.currentStreamController = null;
|
||||||
}
|
}
|
||||||
async chat(messages, tools = []) {
|
async chat(messages, tools = []) {
|
||||||
return this.chatWithRetry(messages, tools, 0);
|
return this.chatWithRetry(messages, tools, 0);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export class OllamaClient {
|
|||||||
private model: string;
|
private model: string;
|
||||||
private fetchFn: typeof fetch;
|
private fetchFn: typeof fetch;
|
||||||
private readonly maxRetries: number = 3;
|
private readonly maxRetries: number = 3;
|
||||||
|
private currentStreamController: AbortController | null = null;
|
||||||
|
|
||||||
constructor(baseURL: string, model: string, fetchFn?: typeof fetch) {
|
constructor(baseURL: string, model: string, fetchFn?: typeof fetch) {
|
||||||
this.baseURL = baseURL;
|
this.baseURL = baseURL;
|
||||||
@@ -20,6 +21,13 @@ export class OllamaClient {
|
|||||||
this.fetchFn = fetchFn ?? fetch;
|
this.fetchFn = fetchFn ?? fetch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cancelStream(): void {
|
||||||
|
if (this.currentStreamController) {
|
||||||
|
this.currentStreamController.abort();
|
||||||
|
this.currentStreamController = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async *streamChat(
|
async *streamChat(
|
||||||
messages: OllamaMessage[],
|
messages: OllamaMessage[],
|
||||||
tools: OllamaTool[] = []
|
tools: OllamaTool[] = []
|
||||||
@@ -45,6 +53,7 @@ export class OllamaClient {
|
|||||||
): AsyncGenerator<OllamaMessage, void, unknown> {
|
): AsyncGenerator<OllamaMessage, void, unknown> {
|
||||||
// Create a local controller for this request instead of using the instance variable
|
// Create a local controller for this request instead of using the instance variable
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
this.currentStreamController = controller;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.fetchFn(`${this.baseURL}/api/chat`, {
|
const response = await this.fetchFn(`${this.baseURL}/api/chat`, {
|
||||||
@@ -181,6 +190,8 @@ export class OllamaClient {
|
|||||||
// Abort the local controller
|
// Abort the local controller
|
||||||
controller.abort();
|
controller.abort();
|
||||||
}
|
}
|
||||||
|
// Clean up the reference
|
||||||
|
this.currentStreamController = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async chat(messages: OllamaMessage[], tools: OllamaTool[] = []): Promise<OllamaMessage> {
|
async chat(messages: OllamaMessage[], tools: OllamaTool[] = []): Promise<OllamaMessage> {
|
||||||
|
|||||||
+140
-6
@@ -423,15 +423,149 @@ describe('OllamaClient', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('cancelStream', () => {
|
describe('cancelStream', () => {
|
||||||
it('should abort the current request', () => {
|
it('should abort an active streaming request when cancelled before fetch resolves', async () => {
|
||||||
// Removed cancelStream call as we now use local controllers
|
let capturedSignal: AbortSignal | undefined;
|
||||||
// expect(client['abortController']).toBeNull(); // Removed as abortController no longer exists
|
|
||||||
|
mockFetch.mockImplementation((_url, options?: any) => {
|
||||||
|
capturedSignal = options?.signal;
|
||||||
|
|
||||||
|
return Promise.race([
|
||||||
|
// Simulate slow network response
|
||||||
|
new Promise(() => {
|
||||||
|
// Never resolves on its own - relies on abort
|
||||||
|
}),
|
||||||
|
// Reject when signal is aborted (like real fetch does)
|
||||||
|
new Promise<never>((_, reject) => {
|
||||||
|
if (capturedSignal?.aborted) {
|
||||||
|
reject(new DOMException('The operation was aborted.', 'AbortError'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
capturedSignal?.addEventListener('abort', () => {
|
||||||
|
reject(new DOMException('The operation was aborted.', 'AbortError'));
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Consume the stream in an async function so we can await the rejection
|
||||||
|
const consumeStream = async () => {
|
||||||
|
const stream = await client.streamChat(mockMessages, mockTools);
|
||||||
|
for await (const _ of stream) {
|
||||||
|
/* consume */
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start consuming - this triggers the fetch
|
||||||
|
const consumePromise = consumeStream();
|
||||||
|
|
||||||
|
// Wait a tick for fetch to be invoked
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||||
|
|
||||||
|
// Verify controller is set after fetch starts
|
||||||
|
expect(client['currentStreamController']).not.toBeNull();
|
||||||
|
|
||||||
|
// Cancel the stream - this aborts the controller and triggers fetch rejection
|
||||||
|
client.cancelStream();
|
||||||
|
|
||||||
|
// Verify signal was aborted
|
||||||
|
expect(capturedSignal?.aborted).toBe(true);
|
||||||
|
|
||||||
|
// Verify controller was cleared by cancelStream
|
||||||
|
expect(client['currentStreamController']).toBeNull();
|
||||||
|
|
||||||
|
// The stream consumption must reject with an abort error
|
||||||
|
await expect(consumePromise).rejects.toThrow('The operation was aborted.');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle cancel when no active stream', () => {
|
it('should handle cancel when no active stream', () => {
|
||||||
// Removed cancelStream test as we now use local controllers
|
// Calling cancelStream with no active stream must not throw
|
||||||
// expect(() => client.cancelStream()).not.toThrow(); // Removed as cancelStream no longer exists
|
expect(() => client.cancelStream()).not.toThrow();
|
||||||
// expect(client['abortController']).toBeNull(); // Removed as abortController no longer exists
|
expect(client['currentStreamController']).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear the controller after stream completes normally', async () => {
|
||||||
|
const streamData = JSON.stringify({ message: { content: 'done' } }) + '\n';
|
||||||
|
|
||||||
|
const mockReader = createMockReader(streamData);
|
||||||
|
|
||||||
|
mockFetch.mockResolvedValue({
|
||||||
|
ok: true,
|
||||||
|
body: { getReader: () => mockReader },
|
||||||
|
headers: {
|
||||||
|
get: (name: string) => (name === 'content-type' ? 'application/x-ndjson' : null),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const stream = await client.streamChat(mockMessages, mockTools);
|
||||||
|
for await (const _ of stream) {
|
||||||
|
/* consume */
|
||||||
|
}
|
||||||
|
|
||||||
|
// Controller should be cleared after normal completion
|
||||||
|
expect(client['currentStreamController']).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow a new stream after cancelling a previous one', async () => {
|
||||||
|
// First fetch: pending and abortable
|
||||||
|
mockFetch.mockImplementationOnce((_url, options?: any) => {
|
||||||
|
const signal = options?.signal;
|
||||||
|
|
||||||
|
return Promise.race([
|
||||||
|
new Promise(() => {
|
||||||
|
// Never resolves on its own
|
||||||
|
}),
|
||||||
|
new Promise<never>((_, reject) => {
|
||||||
|
if (signal?.aborted) {
|
||||||
|
reject(new DOMException('The operation was aborted.', 'AbortError'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
signal?.addEventListener('abort', () => {
|
||||||
|
reject(new DOMException('The operation was aborted.', 'AbortError'));
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Second fetch: resolves immediately with valid stream
|
||||||
|
mockFetch.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
body: {
|
||||||
|
getReader: () => ({
|
||||||
|
read: () => Promise.resolve({ done: true, value: new Uint8Array(0) }),
|
||||||
|
releaseLock: () => {},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
get: (name: string) => (name === 'content-type' ? 'application/x-ndjson' : null),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start first stream and consume it
|
||||||
|
const consumeFirst = async () => {
|
||||||
|
const stream = await client.streamChat(mockMessages, mockTools);
|
||||||
|
for await (const _ of stream) {
|
||||||
|
/* consume */
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const firstStreamPromise = consumeFirst();
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||||
|
|
||||||
|
// Cancel first stream
|
||||||
|
client.cancelStream();
|
||||||
|
await expect(firstStreamPromise).rejects.toThrow('The operation was aborted.');
|
||||||
|
|
||||||
|
// Controller is cleared, can start a new stream
|
||||||
|
expect(client['currentStreamController']).toBeNull();
|
||||||
|
|
||||||
|
// Start second stream - should succeed independently
|
||||||
|
const stream2 = await client.streamChat(mockMessages, mockTools);
|
||||||
|
for await (const _ of stream2) {
|
||||||
|
/* consume */
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second stream completes and clears controller
|
||||||
|
expect(client['currentStreamController']).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user