import assert from 'node:assert/strict'; import test from 'node:test'; import { isStepCount, streamText, tool } from 'ai'; import { z } from 'zod'; import { createBridgeFetchForSDK, createModelFromConfig } from './sdk/providers'; import type { OpenAIChatAssistantFields } from './providerContinuation'; test('buffers stream events emitted before the Response stream starts', async (t) => { const originalWindow = (globalThis as typeof globalThis & { window?: unknown }).window; t.after(() => { (globalThis as typeof globalThis & { window?: unknown }).window = originalWindow; }); const dataHandlers = new Map void>(); const endHandlers = new Map void>(); let receivedIdleTimeoutMs: number | undefined; (globalThis as typeof globalThis & { window?: unknown }).window = { netcatty: { aiFetch: async () => ({ ok: true, status: 200, data: '{}' }), aiChatCancel: async () => true, onAiStreamData: (requestId: string, cb: (data: string) => void) => { dataHandlers.set(requestId, cb); return () => dataHandlers.delete(requestId); }, onAiStreamEnd: (requestId: string, cb: () => void) => { endHandlers.set(requestId, cb); return () => endHandlers.delete(requestId); }, onAiStreamError: () => () => undefined, aiChatStream: async ( requestId: string, _url: string, _headers: Record, _body: string, _providerId?: string, idleTimeoutMs?: number, ) => { receivedIdleTimeoutMs = idleTimeoutMs; const emit = dataHandlers.get(requestId); assert.ok(emit, 'stream data handler should be registered before aiChatStream starts'); emit(JSON.stringify({ id: 'chatcmpl-fast-stream', object: 'chat.completion.chunk', choices: [{ index: 0, delta: { content: 'fast' } }], })); endHandlers.get(requestId)?.(); return { ok: true, statusCode: 200, statusText: 'OK' }; }, }, }; const fetch = createBridgeFetchForSDK('deepseek-custom', { streamIdleTimeoutMs: 10 * 60 * 1000, }); const response = await fetch('https://api.example.test/v1/chat/completions', { method: 'POST', body: JSON.stringify({ stream: true, messages: [{ role: 'user', content: 'hello' }], }), }); const text = await response.text(); assert.match(text, /"content":"fast"/); assert.equal(receivedIdleTimeoutMs, 10 * 60 * 1000); }); test('captures OpenAI-compatible reasoning_content before the tool follow-up request', async (t) => { const originalWindow = (globalThis as typeof globalThis & { window?: unknown }).window; t.after(() => { (globalThis as typeof globalThis & { window?: unknown }).window = originalWindow; }); const dataHandlers = new Map void>(); const endHandlers = new Map void>(); const sentBodies: Array> = []; const assistantFields: Array = []; const toolCall = { id: 'call_1', type: 'function', function: { name: 'terminal_exec', arguments: '{}' }, }; (globalThis as typeof globalThis & { window?: unknown }).window = { netcatty: { aiFetch: async () => ({ ok: true, status: 200, data: '{}' }), aiChatCancel: async () => true, onAiStreamData: (requestId: string, cb: (data: string) => void) => { dataHandlers.set(requestId, cb); return () => dataHandlers.delete(requestId); }, onAiStreamEnd: (requestId: string, cb: () => void) => { endHandlers.set(requestId, cb); return () => endHandlers.delete(requestId); }, onAiStreamError: () => () => undefined, aiChatStream: async ( requestId: string, _url: string, _headers: Record, body: string, ) => { sentBodies.push(JSON.parse(body)); if (sentBodies.length === 1) { const emit = dataHandlers.get(requestId); assert.ok(emit, 'stream data handler should be registered before aiChatStream starts'); emit(JSON.stringify({ choices: [{ index: 0, delta: { reasoning_content: 'need shell ' } }] })); emit(JSON.stringify({ choices: [{ index: 0, delta: { reasoning_content: 'context' } }] })); emit(JSON.stringify({ choices: [{ index: 0, delta: { tool_calls: [toolCall] } }] })); } endHandlers.get(requestId)?.(); return { ok: true, statusCode: 200, statusText: 'OK' }; }, }, }; const fetch = createBridgeFetchForSDK('deepseek-custom', { getOpenAIChatAssistantFields: () => assistantFields, }); await fetch('https://api.deepseek.com/chat/completions', { method: 'POST', body: JSON.stringify({ stream: true, messages: [{ role: 'user', content: 'inspect the host' }], }), }); await fetch('https://api.deepseek.com/chat/completions', { method: 'POST', body: JSON.stringify({ stream: true, messages: [ { role: 'user', content: 'inspect the host' }, { role: 'assistant', content: '', tool_calls: [toolCall] }, { role: 'tool', tool_call_id: 'call_1', content: '{"ok":true}' }, ], }), }); const followUpBody = sentBodies[1]; const messages = followUpBody.messages as Array>; assert.equal(messages[1].reasoning_content, 'need shell context'); }); test('does not duplicate reasoning_content when tool calls stream across chunks', async (t) => { const originalWindow = (globalThis as typeof globalThis & { window?: unknown }).window; t.after(() => { (globalThis as typeof globalThis & { window?: unknown }).window = originalWindow; }); const dataHandlers = new Map void>(); const endHandlers = new Map void>(); const sentBodies: Array> = []; const assistantFields: Array = []; (globalThis as typeof globalThis & { window?: unknown }).window = { netcatty: { aiFetch: async () => ({ ok: true, status: 200, data: '{}' }), aiChatCancel: async () => true, onAiStreamData: (requestId: string, cb: (data: string) => void) => { dataHandlers.set(requestId, cb); return () => dataHandlers.delete(requestId); }, onAiStreamEnd: (requestId: string, cb: () => void) => { endHandlers.set(requestId, cb); return () => endHandlers.delete(requestId); }, onAiStreamError: () => () => undefined, aiChatStream: async ( requestId: string, _url: string, _headers: Record, body: string, ) => { sentBodies.push(JSON.parse(body)); if (sentBodies.length === 1) { const emit = dataHandlers.get(requestId); assert.ok(emit, 'stream data handler should be registered before aiChatStream starts'); emit(JSON.stringify({ choices: [{ index: 0, delta: { reasoning_content: 'need shell context' } }] })); emit(JSON.stringify({ choices: [{ index: 0, delta: { tool_calls: [{ index: 0, id: 'call_1', type: 'function', function: { name: 'terminal_exec', arguments: '' }, }], }, }], })); emit(JSON.stringify({ choices: [{ index: 0, delta: { tool_calls: [{ index: 0, function: { arguments: '{}' }, }], }, }], })); } endHandlers.get(requestId)?.(); return { ok: true, statusCode: 200, statusText: 'OK' }; }, }, }; const fetch = createBridgeFetchForSDK('deepseek-custom', { getOpenAIChatAssistantFields: () => assistantFields, }); await fetch('https://api.deepseek.com/chat/completions', { method: 'POST', body: JSON.stringify({ stream: true, messages: [{ role: 'user', content: 'inspect the host' }], }), }); await fetch('https://api.deepseek.com/chat/completions', { method: 'POST', body: JSON.stringify({ stream: true, messages: [ { role: 'user', content: 'inspect the host' }, { role: 'assistant', content: '', tool_calls: [{ id: 'call_1', type: 'function', function: { name: 'terminal_exec', arguments: '{}' }, }], }, { role: 'tool', tool_call_id: 'call_1', content: '{"ok":true}' }, ], }), }); const followUpBody = sentBodies[1]; const messages = followUpBody.messages as Array>; assert.equal(messages[1].reasoning_content, 'need shell context'); }); test('keeps captured reasoning_content aligned across consecutive tool calls', async (t) => { const originalWindow = (globalThis as typeof globalThis & { window?: unknown }).window; t.after(() => { (globalThis as typeof globalThis & { window?: unknown }).window = originalWindow; }); const dataHandlers = new Map void>(); const endHandlers = new Map void>(); const sentBodies: Array> = []; const assistantFields: Array = []; const toolCall = (id: string) => ({ id, type: 'function', function: { name: 'terminal_exec', arguments: '{}' }, }); (globalThis as typeof globalThis & { window?: unknown }).window = { netcatty: { aiFetch: async () => ({ ok: true, status: 200, data: '{}' }), aiChatCancel: async () => true, onAiStreamData: (requestId: string, cb: (data: string) => void) => { dataHandlers.set(requestId, cb); return () => dataHandlers.delete(requestId); }, onAiStreamEnd: (requestId: string, cb: () => void) => { endHandlers.set(requestId, cb); return () => endHandlers.delete(requestId); }, onAiStreamError: () => () => undefined, aiChatStream: async ( requestId: string, _url: string, _headers: Record, body: string, ) => { sentBodies.push(JSON.parse(body)); const emit = dataHandlers.get(requestId); assert.ok(emit, 'stream data handler should be registered before aiChatStream starts'); if (sentBodies.length === 1) { emit(JSON.stringify({ choices: [{ index: 0, delta: { reasoning_content: 'first tool reasoning' } }] })); emit(JSON.stringify({ choices: [{ index: 0, delta: { tool_calls: [toolCall('call_1')] } }] })); } else if (sentBodies.length === 2) { emit(JSON.stringify({ choices: [{ index: 0, delta: { reasoning_content: 'second tool reasoning' } }] })); emit(JSON.stringify({ choices: [{ index: 0, delta: { tool_calls: [toolCall('call_2')] } }] })); } endHandlers.get(requestId)?.(); return { ok: true, statusCode: 200, statusText: 'OK' }; }, }, }; const fetch = createBridgeFetchForSDK('deepseek-custom', { getOpenAIChatAssistantFields: () => assistantFields, }); await fetch('https://api.deepseek.com/chat/completions', { method: 'POST', body: JSON.stringify({ stream: true, messages: [{ role: 'user', content: 'inspect the host' }], }), }); await fetch('https://api.deepseek.com/chat/completions', { method: 'POST', body: JSON.stringify({ stream: true, messages: [ { role: 'user', content: 'inspect the host' }, { role: 'assistant', content: '', tool_calls: [toolCall('call_1')] }, { role: 'tool', tool_call_id: 'call_1', content: '{"ok":true}' }, ], }), }); await fetch('https://api.deepseek.com/chat/completions', { method: 'POST', body: JSON.stringify({ stream: true, messages: [ { role: 'user', content: 'inspect the host' }, { role: 'assistant', content: '', tool_calls: [toolCall('call_1')] }, { role: 'tool', tool_call_id: 'call_1', content: '{"ok":true}' }, { role: 'assistant', content: '', tool_calls: [toolCall('call_2')] }, { role: 'tool', tool_call_id: 'call_2', content: '{"ok":true}' }, ], }), }); const secondRequestMessages = sentBodies[1].messages as Array>; const thirdRequestMessages = sentBodies[2].messages as Array>; assert.equal(secondRequestMessages[1].reasoning_content, 'first tool reasoning'); assert.equal(thirdRequestMessages[1].reasoning_content, 'first tool reasoning'); assert.equal(thirdRequestMessages[3].reasoning_content, 'second tool reasoning'); }); test('replays reasoning_content through the SDK tool loop', async (t) => { const originalWindow = (globalThis as typeof globalThis & { window?: unknown }).window; t.after(() => { (globalThis as typeof globalThis & { window?: unknown }).window = originalWindow; }); const dataHandlers = new Map void>(); const endHandlers = new Map void>(); const sentBodies: Array> = []; const assistantFields: Array = []; const toolCall = { index: 0, id: 'call_1', type: 'function', function: { name: 'terminal_exec', arguments: '{}' }, }; const emitChatChunk = (emit: (data: string) => void, delta: Record, finishReason?: string) => { emit(JSON.stringify({ id: 'chatcmpl-test', object: 'chat.completion.chunk', created: 1777600000, model: 'deepseek-v4-flash', choices: [{ index: 0, delta, finish_reason: finishReason ?? null }], })); }; (globalThis as typeof globalThis & { window?: unknown }).window = { netcatty: { aiFetch: async () => ({ ok: true, status: 200, data: '{}' }), aiChatCancel: async () => true, onAiStreamData: (requestId: string, cb: (data: string) => void) => { dataHandlers.set(requestId, cb); return () => dataHandlers.delete(requestId); }, onAiStreamEnd: (requestId: string, cb: () => void) => { endHandlers.set(requestId, cb); return () => endHandlers.delete(requestId); }, onAiStreamError: () => () => undefined, aiChatStream: async ( requestId: string, _url: string, _headers: Record, body: string, ) => { sentBodies.push(JSON.parse(body)); const requestNumber = sentBodies.length; setTimeout(() => { const emit = dataHandlers.get(requestId); assert.ok(emit, 'stream data handler should be registered before aiChatStream starts'); if (requestNumber === 1) { emitChatChunk(emit, { reasoning_content: 'need disk ' }); emitChatChunk(emit, { reasoning_content: 'context' }); emitChatChunk(emit, { tool_calls: [toolCall] }); emitChatChunk(emit, {}, 'tool_calls'); } else { emitChatChunk(emit, { reasoning_content: 'read result' }); emitChatChunk(emit, { content: 'disk usage is 81%' }); emitChatChunk(emit, {}, 'stop'); } endHandlers.get(requestId)?.(); }, 0); return { ok: true, statusCode: 200, statusText: 'OK' }; }, }, }; const model = createModelFromConfig( { id: 'deepseek-custom', providerId: 'custom', name: 'DeepSeek', apiKey: 'test-key', baseURL: 'https://api.deepseek.com', defaultModel: 'deepseek-v4-flash', enabled: true, }, { getOpenAIChatAssistantFields: () => assistantFields }, ); const result = streamText({ model, messages: [{ role: 'user', content: 'inspect disk' }], tools: { terminal_exec: tool({ inputSchema: z.object({}), execute: async () => ({ ok: true }), }), }, stopWhen: isStepCount(2), include: { rawChunks: true }, }); for await (const _chunk of result.stream) { // Drain the stream so the SDK completes the tool loop. } const followUpBody = sentBodies[1]; const messages = followUpBody.messages as Array>; assert.equal(messages[1].reasoning_content, 'need disk context'); }); test('continues OpenAI-compatible tool streams when the introductory tool chunk omits id', async (t) => { const originalWindow = (globalThis as typeof globalThis & { window?: unknown }).window; t.after(() => { (globalThis as typeof globalThis & { window?: unknown }).window = originalWindow; }); const dataHandlers = new Map void>(); const endHandlers = new Map void>(); const sentBodies: Array> = []; const emitChatChunk = (emit: (data: string) => void, delta: Record, finishReason?: string) => { emit(JSON.stringify({ id: 'chatcmpl-glm-test', object: 'chat.completion.chunk', created: 1777600000, model: 'glm-5.1', choices: [{ index: 0, delta, finish_reason: finishReason ?? null }], })); }; (globalThis as typeof globalThis & { window?: unknown }).window = { netcatty: { aiFetch: async () => ({ ok: true, status: 200, data: '{}' }), aiChatCancel: async () => true, onAiStreamData: (requestId: string, cb: (data: string) => void) => { dataHandlers.set(requestId, cb); return () => dataHandlers.delete(requestId); }, onAiStreamEnd: (requestId: string, cb: () => void) => { endHandlers.set(requestId, cb); return () => endHandlers.delete(requestId); }, onAiStreamError: () => () => undefined, aiChatStream: async ( requestId: string, _url: string, _headers: Record, body: string, ) => { sentBodies.push(JSON.parse(body)); const requestNumber = sentBodies.length; setTimeout(() => { const emit = dataHandlers.get(requestId); assert.ok(emit, 'stream data handler should be registered before aiChatStream starts'); if (requestNumber === 1) { emitChatChunk(emit, { tool_calls: [{ index: 0, type: 'function', function: { name: 'terminal_exec', arguments: '' }, }], }); emitChatChunk(emit, { tool_calls: [{ index: 0, function: { arguments: '{}' }, }], }); emitChatChunk(emit, {}, 'tool_calls'); } else { emitChatChunk(emit, { content: 'tool completed' }); emitChatChunk(emit, {}, 'stop'); } endHandlers.get(requestId)?.(); }, 0); return { ok: true, statusCode: 200, statusText: 'OK' }; }, }, }; const model = createModelFromConfig({ id: 'glm-custom', providerId: 'custom', name: 'GLM', apiKey: 'test-key', baseURL: 'https://tokenhub.tencentmaas.com/plan/v3', defaultModel: 'glm-5.1', enabled: true, }); const result = streamText({ model, messages: [{ role: 'user', content: 'inspect the host' }], tools: { terminal_exec: tool({ inputSchema: z.object({}), execute: async () => ({ ok: true }), }), }, stopWhen: isStepCount(2), }); let text = ''; for await (const chunk of result.stream) { if (chunk.type === 'text-delta') { text += chunk.text; } } assert.equal(text, 'tool completed'); const followUpMessages = sentBodies[1].messages as Array>; const assistantMessage = followUpMessages[1] as { tool_calls?: Array<{ id?: string }> }; const toolMessage = followUpMessages[2] as { tool_call_id?: string }; assert.ok(assistantMessage.tool_calls?.[0]?.id?.startsWith('call_netcatty_')); assert.equal(toolMessage.tool_call_id, assistantMessage.tool_calls?.[0]?.id); }); test('continues OpenAI-compatible streams when provider chunks omit the top-level id', async (t) => { const originalWindow = (globalThis as typeof globalThis & { window?: unknown }).window; t.after(() => { (globalThis as typeof globalThis & { window?: unknown }).window = originalWindow; }); const dataHandlers = new Map void>(); const endHandlers = new Map void>(); const sentBodies: Array> = []; const emitChatChunk = (emit: (data: string) => void, delta: Record, finishReason?: string) => { emit(JSON.stringify({ object: 'chat.completion.chunk', created: 1777600000, model: 'kimi-k2.6', choices: [{ index: 0, delta, finish_reason: finishReason ?? null }], })); }; (globalThis as typeof globalThis & { window?: unknown }).window = { netcatty: { aiFetch: async () => ({ ok: true, status: 200, data: '{}' }), aiChatCancel: async () => true, onAiStreamData: (requestId: string, cb: (data: string) => void) => { dataHandlers.set(requestId, cb); return () => dataHandlers.delete(requestId); }, onAiStreamEnd: (requestId: string, cb: () => void) => { endHandlers.set(requestId, cb); return () => endHandlers.delete(requestId); }, onAiStreamError: () => () => undefined, aiChatStream: async ( requestId: string, _url: string, _headers: Record, body: string, ) => { sentBodies.push(JSON.parse(body)); const requestNumber = sentBodies.length; setTimeout(() => { const emit = dataHandlers.get(requestId); assert.ok(emit, 'stream data handler should be registered before aiChatStream starts'); if (requestNumber === 1) { emitChatChunk(emit, { tool_calls: [{ index: 0, type: 'function', function: { name: 'terminal_exec', arguments: '' }, }], }); emitChatChunk(emit, { tool_calls: [{ index: 0, function: { arguments: '{}' }, }], }); emitChatChunk(emit, {}, 'tool_calls'); } else { emitChatChunk(emit, { content: 'tool completed' }); emitChatChunk(emit, {}, 'stop'); } endHandlers.get(requestId)?.(); }, 0); return { ok: true, statusCode: 200, statusText: 'OK' }; }, }, }; const model = createModelFromConfig({ id: 'kimi-custom', providerId: 'custom', name: 'Kimi', apiKey: 'test-key', baseURL: 'https://api.moonshot.cn/v1', defaultModel: 'kimi-k2.6', enabled: true, }); const result = streamText({ model, messages: [{ role: 'user', content: 'inspect the host' }], tools: { terminal_exec: tool({ inputSchema: z.object({}), execute: async () => ({ ok: true }), }), }, stopWhen: isStepCount(2), }); let text = ''; for await (const chunk of result.stream) { if (chunk.type === 'text-delta') { text += chunk.text; } } assert.equal(text, 'tool completed'); const followUpMessages = sentBodies[1].messages as Array>; const assistantMessage = followUpMessages[1] as { tool_calls?: Array<{ id?: string }> }; const toolMessage = followUpMessages[2] as { tool_call_id?: string }; assert.ok(assistantMessage.tool_calls?.[0]?.id?.startsWith('call_netcatty_')); assert.equal(toolMessage.tool_call_id, assistantMessage.tool_calls?.[0]?.id); }); test('continues DeepSeek-compatible tool streams when empty id, type, and name precede the real tool name', async (t) => { const originalWindow = (globalThis as typeof globalThis & { window?: unknown }).window; t.after(() => { (globalThis as typeof globalThis & { window?: unknown }).window = originalWindow; }); const dataHandlers = new Map void>(); const endHandlers = new Map void>(); const sentBodies: Array> = []; const emitChatChunk = (emit: (data: string) => void, delta: Record, finishReason?: string) => { emit(JSON.stringify({ id: 'chatcmpl-one-api-test', object: 'chat.completion.chunk', created: 1777600000, model: 'deepseek-chat', choices: [{ index: 0, delta, finish_reason: finishReason ?? null }], })); }; (globalThis as typeof globalThis & { window?: unknown }).window = { netcatty: { aiFetch: async () => ({ ok: true, status: 200, data: '{}' }), aiChatCancel: async () => true, onAiStreamData: (requestId: string, cb: (data: string) => void) => { dataHandlers.set(requestId, cb); return () => dataHandlers.delete(requestId); }, onAiStreamEnd: (requestId: string, cb: () => void) => { endHandlers.set(requestId, cb); return () => endHandlers.delete(requestId); }, onAiStreamError: () => () => undefined, aiChatStream: async ( requestId: string, _url: string, _headers: Record, body: string, ) => { sentBodies.push(JSON.parse(body)); const requestNumber = sentBodies.length; setTimeout(() => { const emit = dataHandlers.get(requestId); assert.ok(emit, 'stream data handler should be registered before aiChatStream starts'); if (requestNumber === 1) { emitChatChunk(emit, { tool_calls: [{ index: 0, id: '', type: '', function: { name: '', arguments: '' }, }], }); emitChatChunk(emit, { tool_calls: [{ index: 0, function: { name: 'terminal_exec', arguments: '{"command":"pwd"}' }, }], }); emitChatChunk(emit, {}, 'tool_calls'); } else { emitChatChunk(emit, { content: 'tool completed' }); emitChatChunk(emit, {}, 'stop'); } endHandlers.get(requestId)?.(); }, 0); return { ok: true, statusCode: 200, statusText: 'OK' }; }, }, }; const executedCommands: string[] = []; const model = createModelFromConfig({ id: 'deepseek-one-api', providerId: 'custom', name: 'DeepSeek One API', apiKey: 'test-key', baseURL: 'https://one-api.example/v1', defaultModel: 'deepseek-chat', enabled: true, }); const result = streamText({ model, messages: [{ role: 'user', content: 'inspect cwd' }], tools: { terminal_exec: tool({ inputSchema: z.object({ command: z.string() }), execute: async ({ command }) => { executedCommands.push(command); return { ok: true }; }, }), }, stopWhen: isStepCount(2), }); let text = ''; for await (const chunk of result.stream) { if (chunk.type === 'text-delta') { text += chunk.text; } } assert.deepEqual(executedCommands, ['pwd']); assert.equal(text, 'tool completed'); const followUpMessages = sentBodies[1].messages as Array>; const assistantMessage = followUpMessages[1] as { tool_calls?: Array<{ id?: string; type?: string; function?: { name?: string; arguments?: string } }>; }; const toolMessage = followUpMessages[2] as { tool_call_id?: string }; assert.ok(assistantMessage.tool_calls?.[0]?.id?.startsWith('call_netcatty_')); assert.equal(assistantMessage.tool_calls?.[0]?.type, 'function'); assert.equal(assistantMessage.tool_calls?.[0]?.function?.name, 'terminal_exec'); assert.equal(toolMessage.tool_call_id, assistantMessage.tool_calls?.[0]?.id); }); test('continues DeepSeek-compatible tool streams when later argument chunks keep empty id and type', async (t) => { const originalWindow = (globalThis as typeof globalThis & { window?: unknown }).window; t.after(() => { (globalThis as typeof globalThis & { window?: unknown }).window = originalWindow; }); const dataHandlers = new Map void>(); const endHandlers = new Map void>(); const sentBodies: Array> = []; const emitChatChunk = (emit: (data: string) => void, delta: Record, finishReason?: string) => { emit(JSON.stringify({ id: 'chatcmpl-one-api-later-empty-test', object: 'chat.completion.chunk', created: 1777600000, model: 'deepseek-chat', choices: [{ index: 0, delta, finish_reason: finishReason ?? null }], })); }; (globalThis as typeof globalThis & { window?: unknown }).window = { netcatty: { aiFetch: async () => ({ ok: true, status: 200, data: '{}' }), aiChatCancel: async () => true, onAiStreamData: (requestId: string, cb: (data: string) => void) => { dataHandlers.set(requestId, cb); return () => dataHandlers.delete(requestId); }, onAiStreamEnd: (requestId: string, cb: () => void) => { endHandlers.set(requestId, cb); return () => endHandlers.delete(requestId); }, onAiStreamError: () => () => undefined, aiChatStream: async ( requestId: string, _url: string, _headers: Record, body: string, ) => { sentBodies.push(JSON.parse(body)); const requestNumber = sentBodies.length; setTimeout(() => { const emit = dataHandlers.get(requestId); assert.ok(emit, 'stream data handler should be registered before aiChatStream starts'); if (requestNumber === 1) { emitChatChunk(emit, { tool_calls: [{ index: 0, id: '', type: '', function: { name: 'terminal_exec', arguments: '{"command":' }, }], }); emitChatChunk(emit, { tool_calls: [{ index: 0, id: '', type: '', function: { name: '', arguments: '"pwd"}' }, }], }); emitChatChunk(emit, {}, 'tool_calls'); } else { emitChatChunk(emit, { content: 'tool completed' }); emitChatChunk(emit, {}, 'stop'); } endHandlers.get(requestId)?.(); }, 0); return { ok: true, statusCode: 200, statusText: 'OK' }; }, }, }; const executedCommands: string[] = []; const model = createModelFromConfig({ id: 'deepseek-one-api', providerId: 'custom', name: 'DeepSeek One API', apiKey: 'test-key', baseURL: 'https://one-api.example/v1', defaultModel: 'deepseek-chat', enabled: true, }); const result = streamText({ model, messages: [{ role: 'user', content: 'inspect cwd' }], tools: { terminal_exec: tool({ inputSchema: z.object({ command: z.string() }), execute: async ({ command }) => { executedCommands.push(command); return { ok: true }; }, }), }, stopWhen: isStepCount(2), }); let text = ''; for await (const chunk of result.stream) { if (chunk.type === 'text-delta') { text += chunk.text; } } assert.deepEqual(executedCommands, ['pwd']); assert.equal(text, 'tool completed'); }); test('continues OpenAI-compatible tool streams when arguments arrive before the tool id and name', async (t) => { const originalWindow = (globalThis as typeof globalThis & { window?: unknown }).window; t.after(() => { (globalThis as typeof globalThis & { window?: unknown }).window = originalWindow; }); const dataHandlers = new Map void>(); const endHandlers = new Map void>(); const sentBodies: Array> = []; const emitChatChunk = (emit: (data: string) => void, delta: Record, finishReason?: string) => { emit(JSON.stringify({ id: 'chatcmpl-kimi-test', object: 'chat.completion.chunk', created: 1777600000, model: 'kimi-k2.6', choices: [{ index: 0, delta, finish_reason: finishReason ?? null }], })); }; (globalThis as typeof globalThis & { window?: unknown }).window = { netcatty: { aiFetch: async () => ({ ok: true, status: 200, data: '{}' }), aiChatCancel: async () => true, onAiStreamData: (requestId: string, cb: (data: string) => void) => { dataHandlers.set(requestId, cb); return () => dataHandlers.delete(requestId); }, onAiStreamEnd: (requestId: string, cb: () => void) => { endHandlers.set(requestId, cb); return () => endHandlers.delete(requestId); }, onAiStreamError: () => () => undefined, aiChatStream: async ( requestId: string, _url: string, _headers: Record, body: string, ) => { sentBodies.push(JSON.parse(body)); const requestNumber = sentBodies.length; setTimeout(() => { const emit = dataHandlers.get(requestId); assert.ok(emit, 'stream data handler should be registered before aiChatStream starts'); if (requestNumber === 1) { emitChatChunk(emit, { tool_calls: [{ index: 0, type: 'function', function: { arguments: '{"command":' }, }], }); emitChatChunk(emit, { tool_calls: [{ index: 0, type: 'function', function: { name: 'terminal_exec', arguments: '"which docker"}' }, }], }); emitChatChunk(emit, {}, 'tool_calls'); } else { emitChatChunk(emit, { content: 'tool completed' }); emitChatChunk(emit, {}, 'stop'); } endHandlers.get(requestId)?.(); }, 0); return { ok: true, statusCode: 200, statusText: 'OK' }; }, }, }; const executedCommands: string[] = []; const model = createModelFromConfig({ id: 'kimi-custom', providerId: 'custom', name: 'Kimi', apiKey: 'test-key', baseURL: 'https://api.moonshot.cn/v1', defaultModel: 'kimi-k2.6', enabled: true, }); const result = streamText({ model, messages: [{ role: 'user', content: 'inspect docker' }], tools: { terminal_exec: tool({ inputSchema: z.object({ command: z.string() }), execute: async ({ command }) => { executedCommands.push(command); return { ok: true }; }, }), }, stopWhen: isStepCount(2), }); let text = ''; for await (const chunk of result.stream) { if (chunk.type === 'text-delta') { text += chunk.text; } } assert.deepEqual(executedCommands, ['which docker']); assert.equal(text, 'tool completed'); const followUpMessages = sentBodies[1].messages as Array>; const assistantMessage = followUpMessages[1] as { tool_calls?: Array<{ id?: string; function?: { arguments?: string } }> }; assert.ok(assistantMessage.tool_calls?.[0]?.id?.startsWith('call_netcatty_')); assert.equal(assistantMessage.tool_calls?.[0]?.function?.arguments, '{"command":"which docker"}'); }); test('recovers tool streams when the first named chunk carries a non-standard tool call type', async (t) => { const originalWindow = (globalThis as typeof globalThis & { window?: unknown }).window; t.after(() => { (globalThis as typeof globalThis & { window?: unknown }).window = originalWindow; }); const dataHandlers = new Map void>(); const endHandlers = new Map void>(); const sentBodies: Array> = []; const emitChatChunk = (emit: (data: string) => void, delta: Record, finishReason?: string) => { emit(JSON.stringify({ id: 'chatcmpl-nonstandard-type-test', object: 'chat.completion.chunk', created: 1777600000, model: 'deepseek-chat', choices: [{ index: 0, delta, finish_reason: finishReason ?? null }], })); }; (globalThis as typeof globalThis & { window?: unknown }).window = { netcatty: { aiFetch: async () => ({ ok: true, status: 200, data: '{}' }), aiChatCancel: async () => true, onAiStreamData: (requestId: string, cb: (data: string) => void) => { dataHandlers.set(requestId, cb); return () => dataHandlers.delete(requestId); }, onAiStreamEnd: (requestId: string, cb: () => void) => { endHandlers.set(requestId, cb); return () => endHandlers.delete(requestId); }, onAiStreamError: () => () => undefined, aiChatStream: async ( requestId: string, _url: string, _headers: Record, body: string, ) => { sentBodies.push(JSON.parse(body)); const requestNumber = sentBodies.length; setTimeout(() => { const emit = dataHandlers.get(requestId); assert.ok(emit, 'stream data handler should be registered before aiChatStream starts'); if (requestNumber === 1) { emitChatChunk(emit, { tool_calls: [{ index: 0, id: 'call_1', type: 'tool_call', function: { name: 'terminal_exec', arguments: '{"co' }, }], }); emitChatChunk(emit, { tool_calls: [{ index: 0, id: '', type: '', function: { name: '', arguments: 'mmand":"pwd"}' }, }], }); emitChatChunk(emit, {}, 'tool_calls'); } else { emitChatChunk(emit, { content: 'tool completed' }); emitChatChunk(emit, {}, 'stop'); } endHandlers.get(requestId)?.(); }, 0); return { ok: true, statusCode: 200, statusText: 'OK' }; }, }, }; const executedCommands: string[] = []; const model = createModelFromConfig({ id: 'deepseek-one-api', providerId: 'custom', name: 'DeepSeek One API', apiKey: 'test-key', baseURL: 'https://one-api.example/v1', defaultModel: 'deepseek-chat', enabled: true, }); const result = streamText({ model, messages: [{ role: 'user', content: 'inspect cwd' }], tools: { terminal_exec: tool({ inputSchema: z.object({ command: z.string() }), execute: async ({ command }) => { executedCommands.push(command); return { ok: true }; }, }), }, stopWhen: isStepCount(2), }); let text = ''; for await (const chunk of result.stream) { if (chunk.type === 'text-delta') { text += chunk.text; } } assert.deepEqual(executedCommands, ['pwd']); assert.equal(text, 'tool completed'); const followUpMessages = sentBodies[1].messages as Array>; const assistantMessage = followUpMessages[1] as { tool_calls?: Array<{ id?: string; type?: string; function?: { name?: string; arguments?: string } }>; }; const toolMessage = followUpMessages[2] as { tool_call_id?: string }; assert.equal(assistantMessage.tool_calls?.[0]?.type, 'function'); assert.equal(assistantMessage.tool_calls?.[0]?.function?.name, 'terminal_exec'); assert.equal(toolMessage.tool_call_id, assistantMessage.tool_calls?.[0]?.id); }); test('re-injects the remembered tool name when the SDK missed the naming chunk', async (t) => { const originalWindow = (globalThis as typeof globalThis & { window?: unknown }).window; t.after(() => { (globalThis as typeof globalThis & { window?: unknown }).window = originalWindow; }); const dataHandlers = new Map void>(); const endHandlers = new Map void>(); const sentBodies: Array> = []; const emitRawChunk = (emit: (data: string) => void, choices: unknown[]) => { emit(JSON.stringify({ id: 'chatcmpl-choice-desync-test', object: 'chat.completion.chunk', created: 1777600000, model: 'deepseek-chat', choices, })); }; (globalThis as typeof globalThis & { window?: unknown }).window = { netcatty: { aiFetch: async () => ({ ok: true, status: 200, data: '{}' }), aiChatCancel: async () => true, onAiStreamData: (requestId: string, cb: (data: string) => void) => { dataHandlers.set(requestId, cb); return () => dataHandlers.delete(requestId); }, onAiStreamEnd: (requestId: string, cb: () => void) => { endHandlers.set(requestId, cb); return () => endHandlers.delete(requestId); }, onAiStreamError: () => () => undefined, aiChatStream: async ( requestId: string, _url: string, _headers: Record, body: string, ) => { sentBodies.push(JSON.parse(body)); const requestNumber = sentBodies.length; setTimeout(() => { const emit = dataHandlers.get(requestId); assert.ok(emit, 'stream data handler should be registered before aiChatStream starts'); if (requestNumber === 1) { emitRawChunk(emit, [ { index: 0, delta: {}, finish_reason: null }, { index: 1, delta: { tool_calls: [{ index: 0, id: 'call_1', type: 'function', function: { name: 'terminal_exec', arguments: '{"comm' }, }], }, finish_reason: null, }, ]); emitRawChunk(emit, [{ index: 1, delta: { tool_calls: [{ index: 0, function: { arguments: 'and":"pwd"}' }, }], }, finish_reason: null, }]); emitRawChunk(emit, [{ index: 0, delta: {}, finish_reason: 'tool_calls' }]); } else { emitRawChunk(emit, [{ index: 0, delta: { content: 'tool completed' }, finish_reason: null }]); emitRawChunk(emit, [{ index: 0, delta: {}, finish_reason: 'stop' }]); } endHandlers.get(requestId)?.(); }, 0); return { ok: true, statusCode: 200, statusText: 'OK' }; }, }, }; const executedCommands: string[] = []; const model = createModelFromConfig({ id: 'deepseek-one-api', providerId: 'custom', name: 'DeepSeek One API', apiKey: 'test-key', baseURL: 'https://one-api.example/v1', defaultModel: 'deepseek-chat', enabled: true, }); const result = streamText({ model, messages: [{ role: 'user', content: 'inspect cwd' }], tools: { terminal_exec: tool({ inputSchema: z.object({ command: z.string() }), execute: async ({ command }) => { executedCommands.push(command); return { ok: true }; }, }), }, stopWhen: isStepCount(2), }); const errorMessages: string[] = []; let text = ''; for await (const chunk of result.fullStream) { if (chunk.type === 'error') { const error = chunk.error; errorMessages.push(error instanceof Error ? error.message : String(error)); } if (chunk.type === 'text-delta') { text += chunk.text; } } assert.ok(!errorMessages.some((message) => /Expected 'function\.name'/.test(message))); assert.deepEqual(executedCommands, ['pwd']); assert.equal(text, 'tool completed'); });