"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __asyncValues = (this && this.__asyncValues) || function (o) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var m = o[Symbol.asyncIterator], i; return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } }; Object.defineProperty(exports, "__esModule", { value: true }); var ollama_client_1 = require("../src/ollama-client"); describe('OllamaClient', function () { var client; var mockFetch; function createMockReader(data) { var encoder = new TextEncoder(); var encoded = encoder.encode(data); var called = false; return { read: function () { if (!called) { called = true; return Promise.resolve({ done: false, value: encoded }); } return Promise.resolve({ done: true, value: new Uint8Array(0) }); }, releaseLock: jest.fn(), }; } var mockMessages = [ { role: 'system', content: 'You are helpful.' }, { role: 'user', content: 'Hello' }, ]; var mockTools = [ { type: 'function', function: { name: 'test_tool', description: 'A test tool', parameters: { type: 'object', properties: { input: { type: 'string' } }, required: ['input'], }, }, }, ]; beforeEach(function () { mockFetch = jest.fn(); client = new ollama_client_1.OllamaClient('http://localhost:11434', 'llama3', mockFetch); }); afterEach(function () { jest.clearAllMocks(); // Removed cancelStream call as we now use local controllers }); describe('chat (non-streaming)', function () { it('should send a non-streaming request and return the response', function () { return __awaiter(void 0, void 0, void 0, function () { var mockResponse, result; return __generator(this, function (_a) { switch (_a.label) { case 0: mockResponse = { ok: true, json: function () { return Promise.resolve({ message: { content: 'Hello back!' } }); }, }; mockFetch.mockResolvedValue(mockResponse); return [4 /*yield*/, client.chat(mockMessages, mockTools)]; case 1: result = _a.sent(); expect(result.content).toBe('Hello back!'); expect(mockFetch).toHaveBeenCalledWith('http://localhost:11434/api/chat', expect.objectContaining({ method: 'POST', body: JSON.stringify({ model: 'llama3', messages: mockMessages, tools: mockTools, stream: false, }), })); return [2 /*return*/]; } }); }); }); it('should throw on non-OK response', function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: mockFetch.mockResolvedValue({ ok: false, status: 500 }); return [4 /*yield*/, expect(client.chat(mockMessages, mockTools)).rejects.toThrow('Ollama API error: 500')]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }); it('should handle missing message content gracefully', function () { return __awaiter(void 0, void 0, void 0, function () { var result; return __generator(this, function (_a) { switch (_a.label) { case 0: mockFetch.mockResolvedValue({ ok: true, json: function () { return Promise.resolve({}); }, }); return [4 /*yield*/, client.chat(mockMessages, mockTools)]; case 1: result = _a.sent(); expect(result.content).toBe(''); expect(result.tool_calls).toEqual([]); return [2 /*return*/]; } }); }); }); it('should include abort signal in fetch options', function () { return __awaiter(void 0, void 0, void 0, function () { var fetchOptions; return __generator(this, function (_a) { switch (_a.label) { case 0: mockFetch.mockResolvedValue({ ok: true, json: function () { return Promise.resolve({ message: { content: 'ok' } }); }, }); return [4 /*yield*/, client.chat(mockMessages, mockTools)]; case 1: _a.sent(); fetchOptions = mockFetch.mock.calls[0][1]; expect(fetchOptions.signal).toBeInstanceOf(AbortSignal); return [2 /*return*/]; } }); }); }); it('should forward tool_calls from response when present', function () { return __awaiter(void 0, void 0, void 0, function () { var result; return __generator(this, function (_a) { switch (_a.label) { case 0: mockFetch.mockResolvedValue({ ok: true, json: function () { return Promise.resolve({ message: { content: 'result', tool_calls: [{ function: { name: 'create_file', arguments: '{}' } }], }, }); }, }); return [4 /*yield*/, client.chat(mockMessages, mockTools)]; case 1: result = _a.sent(); expect(result.tool_calls).toEqual([{ function: { name: 'create_file', arguments: '{}' } }]); return [2 /*return*/]; } }); }); }); }); describe('streamChat', function () { it('should send a streaming request and yield chunks', function () { return __awaiter(void 0, void 0, void 0, function () { var streamData, mockReader, stream, chunks, _a, stream_1, stream_1_1, chunk, e_1_1; var _b, e_1, _c, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: streamData = [ JSON.stringify({ message: { content: 'He' } }), JSON.stringify({ message: { content: 'llo' } }), JSON.stringify({ message: { content: '!' } }), '', ].join('\n'); mockReader = createMockReader(streamData); mockFetch.mockResolvedValue({ ok: true, body: { getReader: function () { return mockReader; } }, headers: { get: function (name) { return (name === 'content-type' ? 'application/x-ndjson' : null); }, }, }); return [4 /*yield*/, client.streamChat(mockMessages, mockTools)]; case 1: stream = _e.sent(); chunks = []; _e.label = 2; case 2: _e.trys.push([2, 7, 8, 13]); _a = true, stream_1 = __asyncValues(stream); _e.label = 3; case 3: return [4 /*yield*/, stream_1.next()]; case 4: if (!(stream_1_1 = _e.sent(), _b = stream_1_1.done, !_b)) return [3 /*break*/, 6]; _d = stream_1_1.value; _a = false; chunk = _d; chunks.push(chunk.content); _e.label = 5; case 5: _a = true; return [3 /*break*/, 3]; case 6: return [3 /*break*/, 13]; case 7: e_1_1 = _e.sent(); e_1 = { error: e_1_1 }; return [3 /*break*/, 13]; case 8: _e.trys.push([8, , 11, 12]); if (!(!_a && !_b && (_c = stream_1.return))) return [3 /*break*/, 10]; return [4 /*yield*/, _c.call(stream_1)]; case 9: _e.sent(); _e.label = 10; case 10: return [3 /*break*/, 12]; case 11: if (e_1) throw e_1.error; return [7 /*endfinally*/]; case 12: return [7 /*endfinally*/]; case 13: expect(chunks).toEqual(['He', 'llo', '!']); expect(mockReader.releaseLock).toHaveBeenCalled(); return [2 /*return*/]; } }); }); }); it('should skip malformed JSON chunks and log a warning', function () { return __awaiter(void 0, void 0, void 0, function () { var streamData, mockReader, consoleWarnSpy, stream, chunks, _a, stream_2, stream_2_1, chunk, e_2_1; var _b, e_2, _c, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: streamData = [ JSON.stringify({ message: { content: 'valid' } }), 'this is not json', JSON.stringify({ message: { content: 'also valid' } }), '', ].join('\n'); mockReader = createMockReader(streamData); consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); mockFetch.mockResolvedValue({ ok: true, body: { getReader: function () { return mockReader; } }, headers: { get: function (name) { return (name === 'content-type' ? 'application/x-ndjson' : null); }, }, }); return [4 /*yield*/, client.streamChat(mockMessages, mockTools)]; case 1: stream = _e.sent(); chunks = []; _e.label = 2; case 2: _e.trys.push([2, 7, 8, 13]); _a = true, stream_2 = __asyncValues(stream); _e.label = 3; case 3: return [4 /*yield*/, stream_2.next()]; case 4: if (!(stream_2_1 = _e.sent(), _b = stream_2_1.done, !_b)) return [3 /*break*/, 6]; _d = stream_2_1.value; _a = false; chunk = _d; chunks.push(chunk.content); _e.label = 5; case 5: _a = true; return [3 /*break*/, 3]; case 6: return [3 /*break*/, 13]; case 7: e_2_1 = _e.sent(); e_2 = { error: e_2_1 }; return [3 /*break*/, 13]; case 8: _e.trys.push([8, , 11, 12]); if (!(!_a && !_b && (_c = stream_2.return))) return [3 /*break*/, 10]; return [4 /*yield*/, _c.call(stream_2)]; case 9: _e.sent(); _e.label = 10; case 10: return [3 /*break*/, 12]; case 11: if (e_2) throw e_2.error; return [7 /*endfinally*/]; case 12: return [7 /*endfinally*/]; case 13: expect(chunks).toEqual(['valid', 'also valid']); expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('[ollama-client] WARN: Skipped malformed chunk: this is not json... - Unexpected token \'h\', "this is not json" is not valid JSON')); consoleWarnSpy.mockRestore(); return [2 /*return*/]; } }); }); }); it('should throw when too many chunks are malformed', function () { return __awaiter(void 0, void 0, void 0, function () { var streamData, mockReader, stream; return __generator(this, function (_a) { switch (_a.label) { case 0: streamData = Array(51).fill('invalid json').join('\n') + '\n'; mockReader = createMockReader(streamData); mockFetch.mockResolvedValue({ ok: true, body: { getReader: function () { return mockReader; } }, headers: { get: function (name) { return (name === 'content-type' ? 'application/x-ndjson' : null); }, }, }); return [4 /*yield*/, client.streamChat(mockMessages, mockTools)]; case 1: stream = _a.sent(); return [4 /*yield*/, expect((function () { return __awaiter(void 0, void 0, void 0, function () { var _a, stream_3, stream_3_1, _, e_3_1; var _b, e_3, _c, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: _e.trys.push([0, 5, 6, 11]); _a = true, stream_3 = __asyncValues(stream); _e.label = 1; case 1: return [4 /*yield*/, stream_3.next()]; case 2: if (!(stream_3_1 = _e.sent(), _b = stream_3_1.done, !_b)) return [3 /*break*/, 4]; _d = stream_3_1.value; _a = false; _ = _d; _e.label = 3; case 3: _a = true; return [3 /*break*/, 1]; case 4: return [3 /*break*/, 11]; case 5: e_3_1 = _e.sent(); e_3 = { error: e_3_1 }; return [3 /*break*/, 11]; case 6: _e.trys.push([6, , 9, 10]); if (!(!_a && !_b && (_c = stream_3.return))) return [3 /*break*/, 8]; return [4 /*yield*/, _c.call(stream_3)]; case 7: _e.sent(); _e.label = 8; case 8: return [3 /*break*/, 10]; case 9: if (e_3) throw e_3.error; return [7 /*endfinally*/]; case 10: return [7 /*endfinally*/]; case 11: return [2 /*return*/]; } }); }); })()).rejects.toThrow(/malformed/)]; case 2: _a.sent(); return [2 /*return*/]; } }); }); }); it('should throw on non-OK response', function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: mockFetch.mockResolvedValue({ ok: false, status: 404 }); return [4 /*yield*/, expect(client.streamChatAsPromise(mockMessages, mockTools)).rejects.toThrow('Ollama API error: 404')]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }); it('should throw when response has no body', function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: mockFetch.mockResolvedValue({ ok: true, body: undefined }); return [4 /*yield*/, expect(client.streamChatAsPromise(mockMessages, mockTools)).rejects.toThrow('No response body')]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }); it('should throw on invalid content type', function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: mockFetch.mockResolvedValue({ ok: true, body: { getReader: function () { return ({ read: function () { return Promise.resolve({ done: true, value: new Uint8Array(0) }); }, }); }, }, headers: { get: function (name) { return (name === 'content-type' ? 'text/html' : null); }, }, }); return [4 /*yield*/, expect(client.streamChatAsPromise(mockMessages, mockTools)).rejects.toThrow('Invalid response format')]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }); it('should propagate Ollama error messages from the stream', function () { return __awaiter(void 0, void 0, void 0, function () { var streamData, mockReader, stream; return __generator(this, function (_a) { switch (_a.label) { case 0: streamData = JSON.stringify({ error: 'model not found' }) + '\n'; mockReader = createMockReader(streamData); mockFetch.mockResolvedValue({ ok: true, body: { getReader: function () { return mockReader; } }, headers: { get: function (name) { return (name === 'content-type' ? 'application/x-ndjson' : null); }, }, }); return [4 /*yield*/, client.streamChat(mockMessages, mockTools)]; case 1: stream = _a.sent(); return [4 /*yield*/, expect((function () { return __awaiter(void 0, void 0, void 0, function () { var _a, stream_4, stream_4_1, _, e_4_1; var _b, e_4, _c, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: _e.trys.push([0, 5, 6, 11]); _a = true, stream_4 = __asyncValues(stream); _e.label = 1; case 1: return [4 /*yield*/, stream_4.next()]; case 2: if (!(stream_4_1 = _e.sent(), _b = stream_4_1.done, !_b)) return [3 /*break*/, 4]; _d = stream_4_1.value; _a = false; _ = _d; _e.label = 3; case 3: _a = true; return [3 /*break*/, 1]; case 4: return [3 /*break*/, 11]; case 5: e_4_1 = _e.sent(); e_4 = { error: e_4_1 }; return [3 /*break*/, 11]; case 6: _e.trys.push([6, , 9, 10]); if (!(!_a && !_b && (_c = stream_4.return))) return [3 /*break*/, 8]; return [4 /*yield*/, _c.call(stream_4)]; case 7: _e.sent(); _e.label = 8; case 8: return [3 /*break*/, 10]; case 9: if (e_4) throw e_4.error; return [7 /*endfinally*/]; case 10: return [7 /*endfinally*/]; case 11: return [2 /*return*/]; } }); }); })()).rejects.toThrow('Ollama error: model not found')]; case 2: _a.sent(); return [2 /*return*/]; } }); }); }); it('should yield tool_calls when present in streamed response', function () { return __awaiter(void 0, void 0, void 0, function () { var streamData, mockReader, stream, lastChunk, _a, stream_5, stream_5_1, chunk, e_5_1; var _b, e_5, _c, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: streamData = [ JSON.stringify({ message: { content: '', tool_calls: [{ function: { name: 'create_file', arguments: '{"path":"a.md"}' } }], }, }), '', ].join('\n'); mockReader = createMockReader(streamData); mockFetch.mockResolvedValue({ ok: true, body: { getReader: function () { return mockReader; } }, headers: { get: function (name) { return (name === 'content-type' ? 'application/x-ndjson' : null); }, }, }); return [4 /*yield*/, client.streamChat(mockMessages, mockTools)]; case 1: stream = _e.sent(); _e.label = 2; case 2: _e.trys.push([2, 7, 8, 13]); _a = true, stream_5 = __asyncValues(stream); _e.label = 3; case 3: return [4 /*yield*/, stream_5.next()]; case 4: if (!(stream_5_1 = _e.sent(), _b = stream_5_1.done, !_b)) return [3 /*break*/, 6]; _d = stream_5_1.value; _a = false; chunk = _d; lastChunk = chunk; _e.label = 5; case 5: _a = true; return [3 /*break*/, 3]; case 6: return [3 /*break*/, 13]; case 7: e_5_1 = _e.sent(); e_5 = { error: e_5_1 }; return [3 /*break*/, 13]; case 8: _e.trys.push([8, , 11, 12]); if (!(!_a && !_b && (_c = stream_5.return))) return [3 /*break*/, 10]; return [4 /*yield*/, _c.call(stream_5)]; case 9: _e.sent(); _e.label = 10; case 10: return [3 /*break*/, 12]; case 11: if (e_5) throw e_5.error; return [7 /*endfinally*/]; case 12: return [7 /*endfinally*/]; case 13: expect(lastChunk.tool_calls).toEqual([ { function: { name: 'create_file', arguments: '{"path":"a.md"}' } }, ]); return [2 /*return*/]; } }); }); }); it('should default tool_calls to empty array when not present', function () { return __awaiter(void 0, void 0, void 0, function () { var streamData, mockReader, stream, lastChunk, _a, stream_6, stream_6_1, chunk, e_6_1; var _b, e_6, _c, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: streamData = JSON.stringify({ message: { content: 'hello' } }) + '\n'; mockReader = createMockReader(streamData); mockFetch.mockResolvedValue({ ok: true, body: { getReader: function () { return mockReader; } }, headers: { get: function (name) { return (name === 'content-type' ? 'application/x-ndjson' : null); }, }, }); return [4 /*yield*/, client.streamChat(mockMessages, mockTools)]; case 1: stream = _e.sent(); _e.label = 2; case 2: _e.trys.push([2, 7, 8, 13]); _a = true, stream_6 = __asyncValues(stream); _e.label = 3; case 3: return [4 /*yield*/, stream_6.next()]; case 4: if (!(stream_6_1 = _e.sent(), _b = stream_6_1.done, !_b)) return [3 /*break*/, 6]; _d = stream_6_1.value; _a = false; chunk = _d; lastChunk = chunk; _e.label = 5; case 5: _a = true; return [3 /*break*/, 3]; case 6: return [3 /*break*/, 13]; case 7: e_6_1 = _e.sent(); e_6 = { error: e_6_1 }; return [3 /*break*/, 13]; case 8: _e.trys.push([8, , 11, 12]); if (!(!_a && !_b && (_c = stream_6.return))) return [3 /*break*/, 10]; return [4 /*yield*/, _c.call(stream_6)]; case 9: _e.sent(); _e.label = 10; case 10: return [3 /*break*/, 12]; case 11: if (e_6) throw e_6.error; return [7 /*endfinally*/]; case 12: return [7 /*endfinally*/]; case 13: expect(lastChunk.tool_calls).toEqual([]); return [2 /*return*/]; } }); }); }); it('should send correct request body with stream:true', function () { return __awaiter(void 0, void 0, void 0, function () { var streamData, mockReader, stream, _a, stream_7, stream_7_1, _, e_7_1; var _b, e_7, _c, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: streamData = JSON.stringify({ message: { content: 'ok' } }) + '\n'; mockReader = createMockReader(streamData); mockFetch.mockResolvedValue({ ok: true, body: { getReader: function () { return mockReader; } }, headers: { get: function (name) { return (name === 'content-type' ? 'application/x-ndjson' : null); }, }, }); return [4 /*yield*/, client.streamChat(mockMessages, mockTools)]; case 1: stream = _e.sent(); _e.label = 2; case 2: _e.trys.push([2, 7, 8, 13]); _a = true, stream_7 = __asyncValues(stream); _e.label = 3; case 3: return [4 /*yield*/, stream_7.next()]; case 4: if (!(stream_7_1 = _e.sent(), _b = stream_7_1.done, !_b)) return [3 /*break*/, 6]; _d = stream_7_1.value; _a = false; _ = _d; _e.label = 5; case 5: _a = true; return [3 /*break*/, 3]; case 6: return [3 /*break*/, 13]; case 7: e_7_1 = _e.sent(); e_7 = { error: e_7_1 }; return [3 /*break*/, 13]; case 8: _e.trys.push([8, , 11, 12]); if (!(!_a && !_b && (_c = stream_7.return))) return [3 /*break*/, 10]; return [4 /*yield*/, _c.call(stream_7)]; case 9: _e.sent(); _e.label = 10; case 10: return [3 /*break*/, 12]; case 11: if (e_7) throw e_7.error; return [7 /*endfinally*/]; case 12: return [7 /*endfinally*/]; case 13: expect(mockFetch).toHaveBeenCalledWith('http://localhost:11434/api/chat', expect.objectContaining({ method: 'POST', body: JSON.stringify({ model: 'llama3', messages: mockMessages, tools: mockTools, stream: true, }), signal: expect.any(AbortSignal), })); return [2 /*return*/]; } }); }); }); }); describe('streamChat with retry logic', function () { it('should retry on 5xx errors and eventually succeed', function () { return __awaiter(void 0, void 0, void 0, function () { var callCount, stream, chunks, _a, stream_8, stream_8_1, _, e_8_1; var _b, e_8, _c, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: callCount = 0; mockFetch.mockImplementation(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { callCount++; if (callCount === 1) { return [2 /*return*/, { ok: false, status: 500 }]; } if (callCount === 2) { return [2 /*return*/, { ok: false, status: 502 }]; } return [2 /*return*/, { ok: true, body: { getReader: function () { return ({ read: function () { return Promise.resolve({ done: true, value: new Uint8Array(0) }); }, releaseLock: function () { }, }); }, }, headers: { get: function (name) { return (name === 'content-type' ? 'application/x-ndjson' : null); }, }, }]; }); }); }); return [4 /*yield*/, client.streamChat(mockMessages, mockTools)]; case 1: stream = _e.sent(); chunks = []; _e.label = 2; case 2: _e.trys.push([2, 7, 8, 13]); _a = true, stream_8 = __asyncValues(stream); _e.label = 3; case 3: return [4 /*yield*/, stream_8.next()]; case 4: if (!(stream_8_1 = _e.sent(), _b = stream_8_1.done, !_b)) return [3 /*break*/, 6]; _d = stream_8_1.value; _a = false; _ = _d; chunks.push('chunk'); _e.label = 5; case 5: _a = true; return [3 /*break*/, 3]; case 6: return [3 /*break*/, 13]; case 7: e_8_1 = _e.sent(); e_8 = { error: e_8_1 }; return [3 /*break*/, 13]; case 8: _e.trys.push([8, , 11, 12]); if (!(!_a && !_b && (_c = stream_8.return))) return [3 /*break*/, 10]; return [4 /*yield*/, _c.call(stream_8)]; case 9: _e.sent(); _e.label = 10; case 10: return [3 /*break*/, 12]; case 11: if (e_8) throw e_8.error; return [7 /*endfinally*/]; case 12: return [7 /*endfinally*/]; case 13: expect(callCount).toBe(3); expect(chunks.length).toBe(0); return [2 /*return*/]; } }); }); }); it('should give up after maxRetries attempts', function () { return __awaiter(void 0, void 0, void 0, function () { var stream; return __generator(this, function (_a) { switch (_a.label) { case 0: mockFetch.mockResolvedValue({ ok: false, status: 500 }); return [4 /*yield*/, client.streamChat(mockMessages, mockTools)]; case 1: stream = _a.sent(); return [4 /*yield*/, expect((function () { return __awaiter(void 0, void 0, void 0, function () { var _a, stream_9, stream_9_1, _, e_9_1; var _b, e_9, _c, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: _e.trys.push([0, 5, 6, 11]); _a = true, stream_9 = __asyncValues(stream); _e.label = 1; case 1: return [4 /*yield*/, stream_9.next()]; case 2: if (!(stream_9_1 = _e.sent(), _b = stream_9_1.done, !_b)) return [3 /*break*/, 4]; _d = stream_9_1.value; _a = false; _ = _d; _e.label = 3; case 3: _a = true; return [3 /*break*/, 1]; case 4: return [3 /*break*/, 11]; case 5: e_9_1 = _e.sent(); e_9 = { error: e_9_1 }; return [3 /*break*/, 11]; case 6: _e.trys.push([6, , 9, 10]); if (!(!_a && !_b && (_c = stream_9.return))) return [3 /*break*/, 8]; return [4 /*yield*/, _c.call(stream_9)]; case 7: _e.sent(); _e.label = 8; case 8: return [3 /*break*/, 10]; case 9: if (e_9) throw e_9.error; return [7 /*endfinally*/]; case 10: return [7 /*endfinally*/]; case 11: return [2 /*return*/]; } }); }); })()).rejects.toThrow('Ollama API error: 500')]; case 2: _a.sent(); return [2 /*return*/]; } }); }); }); it('should not retry on 4xx errors', function () { return __awaiter(void 0, void 0, void 0, function () { var stream; return __generator(this, function (_a) { switch (_a.label) { case 0: mockFetch.mockResolvedValue({ ok: false, status: 404 }); return [4 /*yield*/, client.streamChat(mockMessages, mockTools)]; case 1: stream = _a.sent(); return [4 /*yield*/, expect((function () { return __awaiter(void 0, void 0, void 0, function () { var _a, stream_10, stream_10_1, _, e_10_1; var _b, e_10, _c, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: _e.trys.push([0, 5, 6, 11]); _a = true, stream_10 = __asyncValues(stream); _e.label = 1; case 1: return [4 /*yield*/, stream_10.next()]; case 2: if (!(stream_10_1 = _e.sent(), _b = stream_10_1.done, !_b)) return [3 /*break*/, 4]; _d = stream_10_1.value; _a = false; _ = _d; _e.label = 3; case 3: _a = true; return [3 /*break*/, 1]; case 4: return [3 /*break*/, 11]; case 5: e_10_1 = _e.sent(); e_10 = { error: e_10_1 }; return [3 /*break*/, 11]; case 6: _e.trys.push([6, , 9, 10]); if (!(!_a && !_b && (_c = stream_10.return))) return [3 /*break*/, 8]; return [4 /*yield*/, _c.call(stream_10)]; case 7: _e.sent(); _e.label = 8; case 8: return [3 /*break*/, 10]; case 9: if (e_10) throw e_10.error; return [7 /*endfinally*/]; case 10: return [7 /*endfinally*/]; case 11: return [2 /*return*/]; } }); }); })()).rejects.toThrow('Ollama API error: 404')]; case 2: _a.sent(); return [2 /*return*/]; } }); }); }); }); describe('cancelStream', function () { it('should abort an active streaming request when cancelled before fetch resolves', function () { return __awaiter(void 0, void 0, void 0, function () { var capturedSignal, consumeStream, consumePromise; return __generator(this, function (_a) { switch (_a.label) { case 0: mockFetch.mockImplementation(function (_url, options) { capturedSignal = options === null || options === void 0 ? void 0 : options.signal; return Promise.race([ // Simulate slow network response new Promise(function () { // Never resolves on its own - relies on abort }), // Reject when signal is aborted (like real fetch does) new Promise(function (_, reject) { if (capturedSignal === null || capturedSignal === void 0 ? void 0 : capturedSignal.aborted) { reject(new DOMException('The operation was aborted.', 'AbortError')); return; } capturedSignal === null || capturedSignal === void 0 ? void 0 : capturedSignal.addEventListener('abort', function () { reject(new DOMException('The operation was aborted.', 'AbortError')); }); }), ]); }); consumeStream = function () { return __awaiter(void 0, void 0, void 0, function () { var stream, _a, stream_11, stream_11_1, _, e_11_1; var _b, e_11, _c, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: return [4 /*yield*/, client.streamChat(mockMessages, mockTools)]; case 1: stream = _e.sent(); _e.label = 2; case 2: _e.trys.push([2, 7, 8, 13]); _a = true, stream_11 = __asyncValues(stream); _e.label = 3; case 3: return [4 /*yield*/, stream_11.next()]; case 4: if (!(stream_11_1 = _e.sent(), _b = stream_11_1.done, !_b)) return [3 /*break*/, 6]; _d = stream_11_1.value; _a = false; _ = _d; _e.label = 5; case 5: _a = true; return [3 /*break*/, 3]; case 6: return [3 /*break*/, 13]; case 7: e_11_1 = _e.sent(); e_11 = { error: e_11_1 }; return [3 /*break*/, 13]; case 8: _e.trys.push([8, , 11, 12]); if (!(!_a && !_b && (_c = stream_11.return))) return [3 /*break*/, 10]; return [4 /*yield*/, _c.call(stream_11)]; case 9: _e.sent(); _e.label = 10; case 10: return [3 /*break*/, 12]; case 11: if (e_11) throw e_11.error; return [7 /*endfinally*/]; case 12: return [7 /*endfinally*/]; case 13: return [2 /*return*/]; } }); }); }; consumePromise = consumeStream(); // Wait a tick for fetch to be invoked return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 10); })]; case 1: // Wait a tick for fetch to be invoked _a.sent(); // 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 === null || capturedSignal === void 0 ? void 0 : capturedSignal.aborted).toBe(true); // Verify controller was cleared by cancelStream expect(client['currentStreamController']).toBeNull(); // The stream consumption must reject with an abort error return [4 /*yield*/, expect(consumePromise).rejects.toThrow('The operation was aborted.')]; case 2: // The stream consumption must reject with an abort error _a.sent(); return [2 /*return*/]; } }); }); }); it('should handle cancel when no active stream', function () { // Calling cancelStream with no active stream must not throw expect(function () { return client.cancelStream(); }).not.toThrow(); expect(client['currentStreamController']).toBeNull(); }); it('should clear the controller after stream completes normally', function () { return __awaiter(void 0, void 0, void 0, function () { var streamData, mockReader, stream, _a, stream_12, stream_12_1, _, e_12_1; var _b, e_12, _c, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: streamData = JSON.stringify({ message: { content: 'done' } }) + '\n'; mockReader = createMockReader(streamData); mockFetch.mockResolvedValue({ ok: true, body: { getReader: function () { return mockReader; } }, headers: { get: function (name) { return (name === 'content-type' ? 'application/x-ndjson' : null); }, }, }); return [4 /*yield*/, client.streamChat(mockMessages, mockTools)]; case 1: stream = _e.sent(); _e.label = 2; case 2: _e.trys.push([2, 7, 8, 13]); _a = true, stream_12 = __asyncValues(stream); _e.label = 3; case 3: return [4 /*yield*/, stream_12.next()]; case 4: if (!(stream_12_1 = _e.sent(), _b = stream_12_1.done, !_b)) return [3 /*break*/, 6]; _d = stream_12_1.value; _a = false; _ = _d; _e.label = 5; case 5: _a = true; return [3 /*break*/, 3]; case 6: return [3 /*break*/, 13]; case 7: e_12_1 = _e.sent(); e_12 = { error: e_12_1 }; return [3 /*break*/, 13]; case 8: _e.trys.push([8, , 11, 12]); if (!(!_a && !_b && (_c = stream_12.return))) return [3 /*break*/, 10]; return [4 /*yield*/, _c.call(stream_12)]; case 9: _e.sent(); _e.label = 10; case 10: return [3 /*break*/, 12]; case 11: if (e_12) throw e_12.error; return [7 /*endfinally*/]; case 12: return [7 /*endfinally*/]; case 13: // Controller should be cleared after normal completion expect(client['currentStreamController']).toBeNull(); return [2 /*return*/]; } }); }); }); it('should allow a new stream after cancelling a previous one', function () { return __awaiter(void 0, void 0, void 0, function () { var consumeFirst, firstStreamPromise, stream2, _a, stream2_1, stream2_1_1, _, e_13_1; var _b, e_13, _c, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: // First fetch: pending and abortable mockFetch.mockImplementationOnce(function (_url, options) { var signal = options === null || options === void 0 ? void 0 : options.signal; return Promise.race([ new Promise(function () { // Never resolves on its own }), new Promise(function (_, reject) { if (signal === null || signal === void 0 ? void 0 : signal.aborted) { reject(new DOMException('The operation was aborted.', 'AbortError')); return; } signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', function () { reject(new DOMException('The operation was aborted.', 'AbortError')); }); }), ]); }); // Second fetch: resolves immediately with valid stream mockFetch.mockResolvedValueOnce({ ok: true, body: { getReader: function () { return ({ read: function () { return Promise.resolve({ done: true, value: new Uint8Array(0) }); }, releaseLock: function () { }, }); }, }, headers: { get: function (name) { return (name === 'content-type' ? 'application/x-ndjson' : null); }, }, }); consumeFirst = function () { return __awaiter(void 0, void 0, void 0, function () { var stream, _a, stream_13, stream_13_1, _, e_14_1; var _b, e_14, _c, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: return [4 /*yield*/, client.streamChat(mockMessages, mockTools)]; case 1: stream = _e.sent(); _e.label = 2; case 2: _e.trys.push([2, 7, 8, 13]); _a = true, stream_13 = __asyncValues(stream); _e.label = 3; case 3: return [4 /*yield*/, stream_13.next()]; case 4: if (!(stream_13_1 = _e.sent(), _b = stream_13_1.done, !_b)) return [3 /*break*/, 6]; _d = stream_13_1.value; _a = false; _ = _d; _e.label = 5; case 5: _a = true; return [3 /*break*/, 3]; case 6: return [3 /*break*/, 13]; case 7: e_14_1 = _e.sent(); e_14 = { error: e_14_1 }; return [3 /*break*/, 13]; case 8: _e.trys.push([8, , 11, 12]); if (!(!_a && !_b && (_c = stream_13.return))) return [3 /*break*/, 10]; return [4 /*yield*/, _c.call(stream_13)]; case 9: _e.sent(); _e.label = 10; case 10: return [3 /*break*/, 12]; case 11: if (e_14) throw e_14.error; return [7 /*endfinally*/]; case 12: return [7 /*endfinally*/]; case 13: return [2 /*return*/]; } }); }); }; firstStreamPromise = consumeFirst(); return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 10); })]; case 1: _e.sent(); // Cancel first stream client.cancelStream(); return [4 /*yield*/, expect(firstStreamPromise).rejects.toThrow('The operation was aborted.')]; case 2: _e.sent(); // Controller is cleared, can start a new stream expect(client['currentStreamController']).toBeNull(); return [4 /*yield*/, client.streamChat(mockMessages, mockTools)]; case 3: stream2 = _e.sent(); _e.label = 4; case 4: _e.trys.push([4, 9, 10, 15]); _a = true, stream2_1 = __asyncValues(stream2); _e.label = 5; case 5: return [4 /*yield*/, stream2_1.next()]; case 6: if (!(stream2_1_1 = _e.sent(), _b = stream2_1_1.done, !_b)) return [3 /*break*/, 8]; _d = stream2_1_1.value; _a = false; _ = _d; _e.label = 7; case 7: _a = true; return [3 /*break*/, 5]; case 8: return [3 /*break*/, 15]; case 9: e_13_1 = _e.sent(); e_13 = { error: e_13_1 }; return [3 /*break*/, 15]; case 10: _e.trys.push([10, , 13, 14]); if (!(!_a && !_b && (_c = stream2_1.return))) return [3 /*break*/, 12]; return [4 /*yield*/, _c.call(stream2_1)]; case 11: _e.sent(); _e.label = 12; case 12: return [3 /*break*/, 14]; case 13: if (e_13) throw e_13.error; return [7 /*endfinally*/]; case 14: return [7 /*endfinally*/]; case 15: // Second stream completes and clears controller expect(client['currentStreamController']).toBeNull(); return [2 /*return*/]; } }); }); }); }); describe('cancelStream race condition', function () { it('should not clear new stream controller when old stream finally block executes', function () { return __awaiter(void 0, void 0, void 0, function () { var fireFirstAbort, stream1, consumeFirst, firstPromise, firstController, stream2, consumeSecond, secondPromise, secondController; return __generator(this, function (_a) { switch (_a.label) { case 0: mockFetch.mockImplementationOnce(function (_url, options) { var signal = options === null || options === void 0 ? void 0 : options.signal; return new Promise(function (_, reject) { if (signal === null || signal === void 0 ? void 0 : signal.aborted) { reject(new DOMException('The operation was aborted.', 'AbortError')); return; } signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', function () { reject(new DOMException('The operation was aborted.', 'AbortError')); }, { once: true }); // Capture abort trigger for manual control fireFirstAbort = function () { signal === null || signal === void 0 ? void 0 : signal.dispatchEvent(new CustomEvent('abort')); }; }); }); // Second fetch call: also pending, so controller stays assigned // We don't need it to complete - just need to verify controller survives abort mockFetch.mockImplementationOnce(function (_url, options) { var signal = options === null || options === void 0 ? void 0 : options.signal; return new Promise(function (_, reject) { if (signal === null || signal === void 0 ? void 0 : signal.aborted) { reject(new DOMException('The operation was aborted.', 'AbortError')); return; } signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', function () { reject(new DOMException('The operation was aborted.', 'AbortError')); }, { once: true }); }); }); return [4 /*yield*/, client.streamChat(mockMessages, mockTools)]; case 1: stream1 = _a.sent(); consumeFirst = function () { return __awaiter(void 0, void 0, void 0, function () { var _a, stream1_1, stream1_1_1, _, e_15_1; var _b, e_15, _c, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: _e.trys.push([0, 5, 6, 11]); _a = true, stream1_1 = __asyncValues(stream1); _e.label = 1; case 1: return [4 /*yield*/, stream1_1.next()]; case 2: if (!(stream1_1_1 = _e.sent(), _b = stream1_1_1.done, !_b)) return [3 /*break*/, 4]; _d = stream1_1_1.value; _a = false; _ = _d; _e.label = 3; case 3: _a = true; return [3 /*break*/, 1]; case 4: return [3 /*break*/, 11]; case 5: e_15_1 = _e.sent(); e_15 = { error: e_15_1 }; return [3 /*break*/, 11]; case 6: _e.trys.push([6, , 9, 10]); if (!(!_a && !_b && (_c = stream1_1.return))) return [3 /*break*/, 8]; return [4 /*yield*/, _c.call(stream1_1)]; case 7: _e.sent(); _e.label = 8; case 8: return [3 /*break*/, 10]; case 9: if (e_15) throw e_15.error; return [7 /*endfinally*/]; case 10: return [7 /*endfinally*/]; case 11: return [2 /*return*/]; } }); }); }; firstPromise = consumeFirst(); // Wait for fetch to be triggered (controller should be set) return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 10); })]; case 2: // Wait for fetch to be triggered (controller should be set) _a.sent(); firstController = client['currentStreamController']; expect(firstController).not.toBeNull(); return [4 /*yield*/, client.streamChat(mockMessages, mockTools)]; case 3: stream2 = _a.sent(); consumeSecond = function () { return __awaiter(void 0, void 0, void 0, function () { var _a, stream2_2, stream2_2_1, _, e_16_1; var _b, e_16, _c, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: _e.trys.push([0, 5, 6, 11]); _a = true, stream2_2 = __asyncValues(stream2); _e.label = 1; case 1: return [4 /*yield*/, stream2_2.next()]; case 2: if (!(stream2_2_1 = _e.sent(), _b = stream2_2_1.done, !_b)) return [3 /*break*/, 4]; _d = stream2_2_1.value; _a = false; _ = _d; _e.label = 3; case 3: _a = true; return [3 /*break*/, 1]; case 4: return [3 /*break*/, 11]; case 5: e_16_1 = _e.sent(); e_16 = { error: e_16_1 }; return [3 /*break*/, 11]; case 6: _e.trys.push([6, , 9, 10]); if (!(!_a && !_b && (_c = stream2_2.return))) return [3 /*break*/, 8]; return [4 /*yield*/, _c.call(stream2_2)]; case 7: _e.sent(); _e.label = 8; case 8: return [3 /*break*/, 10]; case 9: if (e_16) throw e_16.error; return [7 /*endfinally*/]; case 10: return [7 /*endfinally*/]; case 11: return [2 /*return*/]; } }); }); }; secondPromise = consumeSecond(); // Wait for second stream fetch to trigger and assign its controller return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 10); })]; case 4: // Wait for second stream fetch to trigger and assign its controller _a.sent(); secondController = client['currentStreamController']; expect(secondController).not.toBeNull(); expect(secondController).not.toBe(firstController); // Defer abort to next microtask so Jest associates rejection with expectation // Then immediately await the rejection queueMicrotask(function () { return fireFirstAbort === null || fireFirstAbort === void 0 ? void 0 : fireFirstAbort(); }); return [4 /*yield*/, expect(firstPromise).rejects.toThrow('The operation was aborted.')]; case 5: _a.sent(); // Second stream's controller should still be intact // (not cleared by first stream's finally block due to guard) expect(client['currentStreamController']).toBe(secondController); // Clean up second stream so it doesn't leak secondController === null || secondController === void 0 ? void 0 : secondController.abort(); return [4 /*yield*/, secondPromise.catch(function () { })]; case 6: _a.sent(); return [2 /*return*/]; } }); }); }); }); describe('streamChat final buffer parsing', function () { it('should parse final buffer content when stream ends with partial line', function () { return __awaiter(void 0, void 0, void 0, function () { var streamData, mockReader, stream, chunks, _a, stream_14, stream_14_1, chunk, e_17_1; var _b, e_17, _c, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: streamData = [ JSON.stringify({ message: { content: 'First' } }), JSON.stringify({ message: { content: 'Second' } }), JSON.stringify({ message: { content: 'Third' } }), '', // Final line should be empty to signal end ].join('\n'); mockReader = createMockReader(streamData); mockFetch.mockResolvedValue({ ok: true, body: { getReader: function () { return mockReader; } }, headers: { get: function (name) { return (name === 'content-type' ? 'application/x-ndjson' : null); }, }, }); return [4 /*yield*/, client.streamChat(mockMessages, mockTools)]; case 1: stream = _e.sent(); chunks = []; _e.label = 2; case 2: _e.trys.push([2, 7, 8, 13]); _a = true, stream_14 = __asyncValues(stream); _e.label = 3; case 3: return [4 /*yield*/, stream_14.next()]; case 4: if (!(stream_14_1 = _e.sent(), _b = stream_14_1.done, !_b)) return [3 /*break*/, 6]; _d = stream_14_1.value; _a = false; chunk = _d; chunks.push(chunk.content); _e.label = 5; case 5: _a = true; return [3 /*break*/, 3]; case 6: return [3 /*break*/, 13]; case 7: e_17_1 = _e.sent(); e_17 = { error: e_17_1 }; return [3 /*break*/, 13]; case 8: _e.trys.push([8, , 11, 12]); if (!(!_a && !_b && (_c = stream_14.return))) return [3 /*break*/, 10]; return [4 /*yield*/, _c.call(stream_14)]; case 9: _e.sent(); _e.label = 10; case 10: return [3 /*break*/, 12]; case 11: if (e_17) throw e_17.error; return [7 /*endfinally*/]; case 12: return [7 /*endfinally*/]; case 13: expect(chunks).toEqual(['First', 'Second', 'Third']); expect(mockReader.releaseLock).toHaveBeenCalled(); return [2 /*return*/]; } }); }); }); it('should handle malformed final buffer content gracefully', function () { return __awaiter(void 0, void 0, void 0, function () { var streamData, mockReader, consoleWarnSpy, stream, chunks, _a, stream_15, stream_15_1, chunk, e_18_1; var _b, e_18, _c, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: streamData = [ JSON.stringify({ message: { content: 'Valid' } }), 'malformed json', '', // Final line should be empty to signal end ].join('\n'); mockReader = createMockReader(streamData); consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); mockFetch.mockResolvedValue({ ok: true, body: { getReader: function () { return mockReader; } }, headers: { get: function (name) { return (name === 'content-type' ? 'application/x-ndjson' : null); }, }, }); return [4 /*yield*/, client.streamChat(mockMessages, mockTools)]; case 1: stream = _e.sent(); chunks = []; _e.label = 2; case 2: _e.trys.push([2, 7, 8, 13]); _a = true, stream_15 = __asyncValues(stream); _e.label = 3; case 3: return [4 /*yield*/, stream_15.next()]; case 4: if (!(stream_15_1 = _e.sent(), _b = stream_15_1.done, !_b)) return [3 /*break*/, 6]; _d = stream_15_1.value; _a = false; chunk = _d; chunks.push(chunk.content); _e.label = 5; case 5: _a = true; return [3 /*break*/, 3]; case 6: return [3 /*break*/, 13]; case 7: e_18_1 = _e.sent(); e_18 = { error: e_18_1 }; return [3 /*break*/, 13]; case 8: _e.trys.push([8, , 11, 12]); if (!(!_a && !_b && (_c = stream_15.return))) return [3 /*break*/, 10]; return [4 /*yield*/, _c.call(stream_15)]; case 9: _e.sent(); _e.label = 10; case 10: return [3 /*break*/, 12]; case 11: if (e_18) throw e_18.error; return [7 /*endfinally*/]; case 12: return [7 /*endfinally*/]; case 13: expect(chunks).toEqual(['Valid']); expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('Skipped malformed chunk')); consoleWarnSpy.mockRestore(); return [2 /*return*/]; } }); }); }); it('should parse final buffer content even when it contains message data', function () { return __awaiter(void 0, void 0, void 0, function () { var streamData, mockReader, stream, chunks, _a, stream_16, stream_16_1, chunk, e_19_1; var _b, e_19, _c, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: streamData = [ JSON.stringify({ message: { content: 'First' } }), '', // Final line should be empty to signal end JSON.stringify({ message: { content: 'Final' } }), ].join('\n'); mockReader = createMockReader(streamData); mockFetch.mockResolvedValue({ ok: true, body: { getReader: function () { return mockReader; } }, headers: { get: function (name) { return (name === 'content-type' ? 'application/x-ndjson' : null); }, }, }); return [4 /*yield*/, client.streamChat(mockMessages, mockTools)]; case 1: stream = _e.sent(); chunks = []; _e.label = 2; case 2: _e.trys.push([2, 7, 8, 13]); _a = true, stream_16 = __asyncValues(stream); _e.label = 3; case 3: return [4 /*yield*/, stream_16.next()]; case 4: if (!(stream_16_1 = _e.sent(), _b = stream_16_1.done, !_b)) return [3 /*break*/, 6]; _d = stream_16_1.value; _a = false; chunk = _d; chunks.push(chunk.content); _e.label = 5; case 5: _a = true; return [3 /*break*/, 3]; case 6: return [3 /*break*/, 13]; case 7: e_19_1 = _e.sent(); e_19 = { error: e_19_1 }; return [3 /*break*/, 13]; case 8: _e.trys.push([8, , 11, 12]); if (!(!_a && !_b && (_c = stream_16.return))) return [3 /*break*/, 10]; return [4 /*yield*/, _c.call(stream_16)]; case 9: _e.sent(); _e.label = 10; case 10: return [3 /*break*/, 12]; case 11: if (e_19) throw e_19.error; return [7 /*endfinally*/]; case 12: return [7 /*endfinally*/]; case 13: expect(chunks).toEqual(['First', 'Final']); expect(mockReader.releaseLock).toHaveBeenCalled(); return [2 /*return*/]; } }); }); }); }); });