@bxb1337/windsurf-fast-context 1.0.9 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/conversion/prompt-converter.test.js +7 -7
- package/dist/cjs/conversion/response-converter.js +25 -216
- package/dist/cjs/conversion/response-converter.test.js +32 -96
- package/dist/cjs/model/devstral-language-model.js +22 -29
- package/dist/cjs/model/devstral-language-model.test.js +94 -47
- package/dist/conversion/prompt-converter.d.ts +2 -2
- package/dist/conversion/response-converter.d.ts +2 -2
- package/dist/esm/conversion/prompt-converter.test.js +7 -7
- package/dist/esm/conversion/response-converter.js +25 -216
- package/dist/esm/conversion/response-converter.test.js +32 -96
- package/dist/esm/model/devstral-language-model.js +22 -29
- package/dist/esm/model/devstral-language-model.test.js +94 -47
- package/dist/model/devstral-language-model.d.ts +8 -5
- package/dist/types/index.d.ts +1 -1
- package/package.json +1 -1
|
@@ -133,25 +133,17 @@ async function collectStreamParts(stream) {
|
|
|
133
133
|
});
|
|
134
134
|
};
|
|
135
135
|
const model = new devstral_language_model_js_1.DevstralLanguageModel({ apiKey: 'test-api-key', fetch: fakeFetch, baseURL: 'https://windsurf.test' });
|
|
136
|
-
(0, vitest_1.expect)(model.specificationVersion).toBe('
|
|
136
|
+
(0, vitest_1.expect)(model.specificationVersion).toBe('v2');
|
|
137
137
|
(0, vitest_1.expect)(model.supportedUrls).toEqual({});
|
|
138
138
|
const result = await model.doGenerate({
|
|
139
139
|
prompt: [{ role: 'user', content: [{ type: 'text', text: 'Find auth logic.' }] }],
|
|
140
140
|
});
|
|
141
141
|
(0, vitest_1.expect)(result.content).toEqual([{ type: 'text', text: 'generated answer' }]);
|
|
142
|
-
(0, vitest_1.expect)(result.finishReason).
|
|
142
|
+
(0, vitest_1.expect)(result.finishReason).toBe('stop');
|
|
143
143
|
(0, vitest_1.expect)(result.usage).toEqual({
|
|
144
|
-
inputTokens:
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
cacheRead: undefined,
|
|
148
|
-
cacheWrite: undefined,
|
|
149
|
-
},
|
|
150
|
-
outputTokens: {
|
|
151
|
-
total: undefined,
|
|
152
|
-
text: undefined,
|
|
153
|
-
reasoning: undefined,
|
|
154
|
-
},
|
|
144
|
+
inputTokens: undefined,
|
|
145
|
+
outputTokens: undefined,
|
|
146
|
+
totalTokens: undefined,
|
|
155
147
|
});
|
|
156
148
|
(0, vitest_1.expect)(calls).toHaveLength(2);
|
|
157
149
|
(0, vitest_1.expect)(calls[0]?.url).toBe('https://windsurf.test/exa.auth_pb.AuthService/GetUserJwt');
|
|
@@ -171,7 +163,7 @@ async function collectStreamParts(stream) {
|
|
|
171
163
|
return new Response(Uint8Array.from(Buffer.from(jwt, 'utf8')), { status: 200 });
|
|
172
164
|
}
|
|
173
165
|
requestBodies.push(bufferFromBody(init?.body));
|
|
174
|
-
const payload = Buffer.from('[
|
|
166
|
+
const payload = Buffer.from('[{"type":"function","function":{"name":"searchRepo","parameters":{"query":"jwt manager"}}}]', 'utf8');
|
|
175
167
|
return new Response(Uint8Array.from((0, connect_frame_js_1.connectFrameEncode)(payload)), { status: 200 });
|
|
176
168
|
};
|
|
177
169
|
const model = new devstral_language_model_js_1.DevstralLanguageModel({ apiKey: 'tools-key', fetch: fakeFetch, baseURL: 'https://windsurf.test' });
|
|
@@ -200,7 +192,7 @@ async function collectStreamParts(stream) {
|
|
|
200
192
|
input: '{"query":"jwt manager"}',
|
|
201
193
|
},
|
|
202
194
|
]);
|
|
203
|
-
(0, vitest_1.expect)(result.finishReason).
|
|
195
|
+
(0, vitest_1.expect)(result.finishReason).toBe('tool-calls');
|
|
204
196
|
const strings = (0, protobuf_js_1.extractStrings)(decodeRequestPayload(requestBodies[0] ?? Buffer.alloc(0)));
|
|
205
197
|
const toolsPayload = extractToolsPayload(strings);
|
|
206
198
|
(0, vitest_1.expect)(toolsPayload).toBeDefined();
|
|
@@ -250,7 +242,7 @@ async function collectStreamParts(stream) {
|
|
|
250
242
|
},
|
|
251
243
|
},
|
|
252
244
|
{
|
|
253
|
-
type: 'provider',
|
|
245
|
+
type: 'provider-defined',
|
|
254
246
|
id: 'windsurf.restricted_exec',
|
|
255
247
|
name: 'restricted_exec',
|
|
256
248
|
args: { mode: 'read-only' },
|
|
@@ -283,6 +275,83 @@ async function collectStreamParts(stream) {
|
|
|
283
275
|
const strings = (0, protobuf_js_1.extractStrings)(decodeRequestPayload(requestBodies[0] ?? Buffer.alloc(0)));
|
|
284
276
|
(0, vitest_1.expect)(extractToolsPayload(strings)).toBeUndefined();
|
|
285
277
|
});
|
|
278
|
+
(0, vitest_1.it)('injects tool format instruction into request when tools are present', async () => {
|
|
279
|
+
const requestBodies = [];
|
|
280
|
+
const jwt = makeJwt(4_200_000_100, 'instruction');
|
|
281
|
+
const fakeFetch = async (input, init) => {
|
|
282
|
+
const url = String(input);
|
|
283
|
+
if (url.endsWith('/GetUserJwt')) {
|
|
284
|
+
return new Response(Uint8Array.from(Buffer.from(jwt, 'utf8')), { status: 200 });
|
|
285
|
+
}
|
|
286
|
+
requestBodies.push(bufferFromBody(init?.body));
|
|
287
|
+
return new Response(Uint8Array.from((0, connect_frame_js_1.connectFrameEncode)(Buffer.from('ok', 'utf8'))), { status: 200 });
|
|
288
|
+
};
|
|
289
|
+
const model = new devstral_language_model_js_1.DevstralLanguageModel({ apiKey: 'tools-key', fetch: fakeFetch, baseURL: 'https://windsurf.test' });
|
|
290
|
+
await model.doGenerate({
|
|
291
|
+
prompt: [{ role: 'user', content: [{ type: 'text', text: 'Search for auth.' }] }],
|
|
292
|
+
tools: [
|
|
293
|
+
{
|
|
294
|
+
type: 'function',
|
|
295
|
+
name: 'searchRepo',
|
|
296
|
+
description: 'Search repository files',
|
|
297
|
+
inputSchema: {
|
|
298
|
+
type: 'object',
|
|
299
|
+
properties: { query: { type: 'string' } },
|
|
300
|
+
required: ['query'],
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
});
|
|
305
|
+
const strings = (0, protobuf_js_1.extractStrings)(decodeRequestPayload(requestBodies[0] ?? Buffer.alloc(0)));
|
|
306
|
+
const combined = strings.join('\n');
|
|
307
|
+
(0, vitest_1.expect)(combined).toContain('When you need to call tools');
|
|
308
|
+
});
|
|
309
|
+
(0, vitest_1.it)('does not inject tool format instruction when no tools provided', async () => {
|
|
310
|
+
const requestBodies = [];
|
|
311
|
+
const jwt = makeJwt(4_200_000_101, 'no-instruction');
|
|
312
|
+
const fakeFetch = async (input, init) => {
|
|
313
|
+
const url = String(input);
|
|
314
|
+
if (url.endsWith('/GetUserJwt')) {
|
|
315
|
+
return new Response(Uint8Array.from(Buffer.from(jwt, 'utf8')), { status: 200 });
|
|
316
|
+
}
|
|
317
|
+
requestBodies.push(bufferFromBody(init?.body));
|
|
318
|
+
return new Response(Uint8Array.from((0, connect_frame_js_1.connectFrameEncode)(Buffer.from('ok', 'utf8'))), { status: 200 });
|
|
319
|
+
};
|
|
320
|
+
const model = new devstral_language_model_js_1.DevstralLanguageModel({ apiKey: 'tools-key', fetch: fakeFetch, baseURL: 'https://windsurf.test' });
|
|
321
|
+
await model.doGenerate({
|
|
322
|
+
prompt: [{ role: 'user', content: [{ type: 'text', text: 'Find auth logic.' }] }],
|
|
323
|
+
});
|
|
324
|
+
const strings = (0, protobuf_js_1.extractStrings)(decodeRequestPayload(requestBodies[0] ?? Buffer.alloc(0)));
|
|
325
|
+
const combined = strings.join('\n');
|
|
326
|
+
(0, vitest_1.expect)(combined).not.toContain('When you need to call tools');
|
|
327
|
+
});
|
|
328
|
+
(0, vitest_1.it)('does not inject tool format instruction when only provider-defined tools', async () => {
|
|
329
|
+
const requestBodies = [];
|
|
330
|
+
const jwt = makeJwt(4_200_000_102, 'provider-only');
|
|
331
|
+
const fakeFetch = async (input, init) => {
|
|
332
|
+
const url = String(input);
|
|
333
|
+
if (url.endsWith('/GetUserJwt')) {
|
|
334
|
+
return new Response(Uint8Array.from(Buffer.from(jwt, 'utf8')), { status: 200 });
|
|
335
|
+
}
|
|
336
|
+
requestBodies.push(bufferFromBody(init?.body));
|
|
337
|
+
return new Response(Uint8Array.from((0, connect_frame_js_1.connectFrameEncode)(Buffer.from('ok', 'utf8'))), { status: 200 });
|
|
338
|
+
};
|
|
339
|
+
const model = new devstral_language_model_js_1.DevstralLanguageModel({ apiKey: 'tools-key', fetch: fakeFetch, baseURL: 'https://windsurf.test' });
|
|
340
|
+
await model.doGenerate({
|
|
341
|
+
prompt: [{ role: 'user', content: [{ type: 'text', text: 'Use provider tools.' }] }],
|
|
342
|
+
tools: [
|
|
343
|
+
{
|
|
344
|
+
type: 'provider-defined',
|
|
345
|
+
id: 'some-provider-tool',
|
|
346
|
+
name: 'providerTool',
|
|
347
|
+
args: {},
|
|
348
|
+
},
|
|
349
|
+
],
|
|
350
|
+
});
|
|
351
|
+
const strings = (0, protobuf_js_1.extractStrings)(decodeRequestPayload(requestBodies[0] ?? Buffer.alloc(0)));
|
|
352
|
+
const combined = strings.join('\n');
|
|
353
|
+
(0, vitest_1.expect)(combined).not.toContain('When you need to call tools');
|
|
354
|
+
});
|
|
286
355
|
});
|
|
287
356
|
(0, vitest_1.describe)('DevstralLanguageModel doStream', () => {
|
|
288
357
|
(0, vitest_1.it)('stream-text resolves before full body arrives and emits parts incrementally', async () => {
|
|
@@ -370,22 +439,11 @@ async function collectStreamParts(stream) {
|
|
|
370
439
|
(0, vitest_1.expect)(parts[4]).toMatchObject({ type: 'text-delta', delta: 'world' });
|
|
371
440
|
(0, vitest_1.expect)(parts[6]).toEqual({
|
|
372
441
|
type: 'finish',
|
|
373
|
-
finishReason:
|
|
374
|
-
unified: 'stop',
|
|
375
|
-
raw: undefined,
|
|
376
|
-
},
|
|
442
|
+
finishReason: 'stop',
|
|
377
443
|
usage: {
|
|
378
|
-
inputTokens:
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
cacheRead: undefined,
|
|
382
|
-
cacheWrite: undefined,
|
|
383
|
-
},
|
|
384
|
-
outputTokens: {
|
|
385
|
-
total: undefined,
|
|
386
|
-
text: undefined,
|
|
387
|
-
reasoning: undefined,
|
|
388
|
-
},
|
|
444
|
+
inputTokens: undefined,
|
|
445
|
+
outputTokens: undefined,
|
|
446
|
+
totalTokens: undefined,
|
|
389
447
|
},
|
|
390
448
|
});
|
|
391
449
|
}
|
|
@@ -398,7 +456,7 @@ async function collectStreamParts(stream) {
|
|
|
398
456
|
});
|
|
399
457
|
(0, vitest_1.it)('stream-tool emits tool-input deltas before final tool-call and finish', async () => {
|
|
400
458
|
const jwt = makeJwt(4_300_000_001, 'stream-tool');
|
|
401
|
-
const toolPayload = Buffer.from('[
|
|
459
|
+
const toolPayload = Buffer.from('[{"type":"function","function":{"name":"searchRepo","parameters":{"query":"jwt manager"}}}]', 'utf8');
|
|
402
460
|
const frames = Buffer.concat([(0, connect_frame_js_1.connectFrameEncode)(toolPayload)]);
|
|
403
461
|
const fakeFetch = async (input) => {
|
|
404
462
|
const url = String(input);
|
|
@@ -448,22 +506,11 @@ async function collectStreamParts(stream) {
|
|
|
448
506
|
});
|
|
449
507
|
(0, vitest_1.expect)(parts[6]).toEqual({
|
|
450
508
|
type: 'finish',
|
|
451
|
-
finishReason:
|
|
452
|
-
unified: 'tool-calls',
|
|
453
|
-
raw: undefined,
|
|
454
|
-
},
|
|
509
|
+
finishReason: 'tool-calls',
|
|
455
510
|
usage: {
|
|
456
|
-
inputTokens:
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
cacheRead: undefined,
|
|
460
|
-
cacheWrite: undefined,
|
|
461
|
-
},
|
|
462
|
-
outputTokens: {
|
|
463
|
-
total: undefined,
|
|
464
|
-
text: undefined,
|
|
465
|
-
reasoning: undefined,
|
|
466
|
-
},
|
|
511
|
+
inputTokens: undefined,
|
|
512
|
+
outputTokens: undefined,
|
|
513
|
+
totalTokens: undefined,
|
|
467
514
|
},
|
|
468
515
|
});
|
|
469
516
|
});
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LanguageModelV2Prompt } from '@ai-sdk/provider';
|
|
2
2
|
import type { DevstralMessage } from '../types/index.js';
|
|
3
|
-
export declare function convertPrompt(prompt:
|
|
3
|
+
export declare function convertPrompt(prompt: LanguageModelV2Prompt): DevstralMessage[];
|
|
@@ -8,5 +8,5 @@ export interface ToolCallPart {
|
|
|
8
8
|
toolName: string;
|
|
9
9
|
input: unknown;
|
|
10
10
|
}
|
|
11
|
-
export type
|
|
12
|
-
export declare function convertResponse(buffer: Buffer):
|
|
11
|
+
export type LanguageModelV2Content = TextPart | ToolCallPart;
|
|
12
|
+
export declare function convertResponse(buffer: Buffer): LanguageModelV2Content[];
|
|
@@ -34,7 +34,7 @@ describe('convertPrompt', () => {
|
|
|
34
34
|
},
|
|
35
35
|
});
|
|
36
36
|
});
|
|
37
|
-
it('multi-turn preserves ordering across mixed roles (
|
|
37
|
+
it('multi-turn preserves ordering across mixed roles (V2 output shape)', () => {
|
|
38
38
|
const prompt = [
|
|
39
39
|
{ role: 'system', content: 'System instruction' },
|
|
40
40
|
{
|
|
@@ -159,7 +159,7 @@ describe('convertPrompt', () => {
|
|
|
159
159
|
},
|
|
160
160
|
]);
|
|
161
161
|
});
|
|
162
|
-
it('tool-result with
|
|
162
|
+
it('tool-result with error-text output includes denial reason', () => {
|
|
163
163
|
const prompt = [
|
|
164
164
|
{
|
|
165
165
|
role: 'tool',
|
|
@@ -168,7 +168,7 @@ describe('convertPrompt', () => {
|
|
|
168
168
|
type: 'tool-result',
|
|
169
169
|
toolCallId: 'call_denied',
|
|
170
170
|
toolName: 'dangerousAction',
|
|
171
|
-
output: { type: '
|
|
171
|
+
output: { type: 'error-text', value: 'User rejected tool execution' },
|
|
172
172
|
},
|
|
173
173
|
],
|
|
174
174
|
},
|
|
@@ -177,12 +177,12 @@ describe('convertPrompt', () => {
|
|
|
177
177
|
expect(result).toEqual([
|
|
178
178
|
{
|
|
179
179
|
role: 4,
|
|
180
|
-
content: '
|
|
180
|
+
content: 'User rejected tool execution',
|
|
181
181
|
metadata: { refCallId: 'call_denied' },
|
|
182
182
|
},
|
|
183
183
|
]);
|
|
184
184
|
});
|
|
185
|
-
it('tool-result with
|
|
185
|
+
it('tool-result with error-text output handles fallback denial text', () => {
|
|
186
186
|
const prompt = [
|
|
187
187
|
{
|
|
188
188
|
role: 'tool',
|
|
@@ -191,7 +191,7 @@ describe('convertPrompt', () => {
|
|
|
191
191
|
type: 'tool-result',
|
|
192
192
|
toolCallId: 'call_denied_no_reason',
|
|
193
193
|
toolName: 'someTool',
|
|
194
|
-
output: { type: 'execution
|
|
194
|
+
output: { type: 'error-text', value: 'Tool execution denied' },
|
|
195
195
|
},
|
|
196
196
|
],
|
|
197
197
|
},
|
|
@@ -200,7 +200,7 @@ describe('convertPrompt', () => {
|
|
|
200
200
|
expect(result).toEqual([
|
|
201
201
|
{
|
|
202
202
|
role: 4,
|
|
203
|
-
content: '
|
|
203
|
+
content: 'Tool execution denied',
|
|
204
204
|
metadata: { refCallId: 'call_denied_no_reason' },
|
|
205
205
|
},
|
|
206
206
|
]);
|
|
@@ -1,191 +1,37 @@
|
|
|
1
1
|
import { gunzipSync } from 'node:zlib';
|
|
2
2
|
import { extractStrings } from '../protocol/protobuf.js';
|
|
3
|
-
const TOOL_CALL_PREFIX = '[TOOL_CALLS]';
|
|
4
|
-
const ARGS_PREFIX = '[ARGS]';
|
|
5
3
|
const STOP_TOKEN = '</s>';
|
|
6
|
-
function
|
|
7
|
-
|
|
4
|
+
function parseStrictOpenAIToolCalls(responseText) {
|
|
5
|
+
const trimmed = responseText.trim();
|
|
6
|
+
if (!trimmed.startsWith('['))
|
|
8
7
|
return null;
|
|
8
|
+
try {
|
|
9
|
+
const parsed = JSON.parse(trimmed);
|
|
10
|
+
if (!Array.isArray(parsed) || parsed.length === 0)
|
|
11
|
+
return null;
|
|
12
|
+
const toolCalls = [];
|
|
13
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
14
|
+
const item = parsed[i];
|
|
15
|
+
if (item.type !== 'function' || !item.function?.name)
|
|
16
|
+
continue;
|
|
17
|
+
toolCalls.push({
|
|
18
|
+
type: 'tool-call',
|
|
19
|
+
toolCallId: `toolcall_${i + 1}`,
|
|
20
|
+
toolName: String(item.function.name),
|
|
21
|
+
input: item.function.parameters ?? {},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return toolCalls.length > 0 ? toolCalls : null;
|
|
9
25
|
}
|
|
10
|
-
|
|
11
|
-
if (!jsonPart.startsWith('{')) {
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
const toolCalls = [];
|
|
15
|
-
let cursor = 0;
|
|
16
|
-
while (cursor < jsonPart.length) {
|
|
17
|
-
if (jsonPart[cursor] !== '{') {
|
|
18
|
-
cursor++;
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
|
-
const endResult = parseBalancedEnd(jsonPart, cursor);
|
|
22
|
-
if (endResult == null) {
|
|
23
|
-
break;
|
|
24
|
-
}
|
|
25
|
-
const jsonStr = jsonPart.slice(cursor, endResult);
|
|
26
|
-
cursor = endResult;
|
|
27
|
-
if (jsonStr === '{}') {
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
try {
|
|
31
|
-
const parsed = JSON.parse(jsonStr);
|
|
32
|
-
if (parsed.type === 'function' && parsed.function) {
|
|
33
|
-
toolCalls.push(parsed);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
if (toolCalls.length === 0) {
|
|
26
|
+
catch {
|
|
41
27
|
return null;
|
|
42
28
|
}
|
|
43
|
-
return toolCalls.map((call, index) => {
|
|
44
|
-
const toolId = call.function.name;
|
|
45
|
-
const toolName = typeof toolId === 'number' ? mapToolIdToName(toolId) : String(toolId);
|
|
46
|
-
const args = call.function.parameters ?? {};
|
|
47
|
-
return {
|
|
48
|
-
type: 'tool-call',
|
|
49
|
-
toolCallId: `toolcall_${index + 1}`,
|
|
50
|
-
toolName,
|
|
51
|
-
input: args,
|
|
52
|
-
};
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
function mapToolIdToName(id) {
|
|
56
|
-
switch (id) {
|
|
57
|
-
case 1:
|
|
58
|
-
return 'read';
|
|
59
|
-
case 2:
|
|
60
|
-
return 'glob';
|
|
61
|
-
case 3:
|
|
62
|
-
return 'grep';
|
|
63
|
-
default:
|
|
64
|
-
return `tool_${id}`;
|
|
65
|
-
}
|
|
66
29
|
}
|
|
67
30
|
function pushText(parts, text) {
|
|
68
31
|
if (text.length > 0) {
|
|
69
32
|
parts.push({ type: 'text', text });
|
|
70
33
|
}
|
|
71
34
|
}
|
|
72
|
-
function parseStringEnd(value, startIndex) {
|
|
73
|
-
let index = startIndex + 1;
|
|
74
|
-
let escaping = false;
|
|
75
|
-
while (index < value.length) {
|
|
76
|
-
const char = value[index];
|
|
77
|
-
if (escaping) {
|
|
78
|
-
escaping = false;
|
|
79
|
-
}
|
|
80
|
-
else if (char === '\\') {
|
|
81
|
-
escaping = true;
|
|
82
|
-
}
|
|
83
|
-
else if (char === '"') {
|
|
84
|
-
return index + 1;
|
|
85
|
-
}
|
|
86
|
-
index += 1;
|
|
87
|
-
}
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
function parseBalancedEnd(value, startIndex) {
|
|
91
|
-
const stack = [value[startIndex] === '{' ? '}' : ']'];
|
|
92
|
-
let index = startIndex + 1;
|
|
93
|
-
let inString = false;
|
|
94
|
-
let escaping = false;
|
|
95
|
-
while (index < value.length) {
|
|
96
|
-
const char = value[index];
|
|
97
|
-
if (inString) {
|
|
98
|
-
if (escaping) {
|
|
99
|
-
escaping = false;
|
|
100
|
-
}
|
|
101
|
-
else if (char === '\\') {
|
|
102
|
-
escaping = true;
|
|
103
|
-
}
|
|
104
|
-
else if (char === '"') {
|
|
105
|
-
inString = false;
|
|
106
|
-
}
|
|
107
|
-
index += 1;
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
if (char === '"') {
|
|
111
|
-
inString = true;
|
|
112
|
-
index += 1;
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
if (char === '{') {
|
|
116
|
-
stack.push('}');
|
|
117
|
-
index += 1;
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
if (char === '[') {
|
|
121
|
-
stack.push(']');
|
|
122
|
-
index += 1;
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
if (char === '}' || char === ']') {
|
|
126
|
-
const expected = stack[stack.length - 1];
|
|
127
|
-
if (expected !== char) {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
stack.pop();
|
|
131
|
-
index += 1;
|
|
132
|
-
if (stack.length === 0) {
|
|
133
|
-
return index;
|
|
134
|
-
}
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
index += 1;
|
|
138
|
-
}
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
function parsePrimitiveEnd(value, startIndex) {
|
|
142
|
-
let index = startIndex;
|
|
143
|
-
while (index < value.length) {
|
|
144
|
-
const char = value[index];
|
|
145
|
-
if (char === ' ' || char === '\n' || char === '\r' || char === '\t') {
|
|
146
|
-
break;
|
|
147
|
-
}
|
|
148
|
-
index += 1;
|
|
149
|
-
}
|
|
150
|
-
return index;
|
|
151
|
-
}
|
|
152
|
-
function parseJsonValue(value, startIndex) {
|
|
153
|
-
let jsonStart = startIndex;
|
|
154
|
-
while (jsonStart < value.length) {
|
|
155
|
-
const char = value[jsonStart];
|
|
156
|
-
if (char !== ' ' && char !== '\n' && char !== '\r' && char !== '\t') {
|
|
157
|
-
break;
|
|
158
|
-
}
|
|
159
|
-
jsonStart += 1;
|
|
160
|
-
}
|
|
161
|
-
if (jsonStart >= value.length) {
|
|
162
|
-
return null;
|
|
163
|
-
}
|
|
164
|
-
const firstChar = value[jsonStart];
|
|
165
|
-
let endIndex;
|
|
166
|
-
if (firstChar === '{' || firstChar === '[') {
|
|
167
|
-
endIndex = parseBalancedEnd(value, jsonStart);
|
|
168
|
-
}
|
|
169
|
-
else if (firstChar === '"') {
|
|
170
|
-
endIndex = parseStringEnd(value, jsonStart);
|
|
171
|
-
}
|
|
172
|
-
else {
|
|
173
|
-
endIndex = parsePrimitiveEnd(value, jsonStart);
|
|
174
|
-
}
|
|
175
|
-
if (endIndex == null || endIndex <= jsonStart) {
|
|
176
|
-
return null;
|
|
177
|
-
}
|
|
178
|
-
const rawJson = value.slice(jsonStart, endIndex);
|
|
179
|
-
try {
|
|
180
|
-
return {
|
|
181
|
-
parsed: JSON.parse(rawJson),
|
|
182
|
-
endIndex,
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
catch {
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
35
|
function hasControlChars(value) {
|
|
190
36
|
for (const char of value) {
|
|
191
37
|
const code = char.charCodeAt(0);
|
|
@@ -213,10 +59,6 @@ function isLikelyMetadata(value) {
|
|
|
213
59
|
return /^[A-Za-z0-9._:-]{1,32}$/.test(value);
|
|
214
60
|
}
|
|
215
61
|
function pickBestExtractedText(values) {
|
|
216
|
-
const markerValues = values.filter((value) => value.includes(TOOL_CALL_PREFIX) || value.includes(ARGS_PREFIX));
|
|
217
|
-
if (markerValues.length > 0) {
|
|
218
|
-
return markerValues.join('');
|
|
219
|
-
}
|
|
220
62
|
const nonMetadata = values.filter((value) => !isLikelyMetadata(value));
|
|
221
63
|
const candidates = nonMetadata.length > 0 ? nonMetadata : values;
|
|
222
64
|
return candidates.reduce((best, current) => (current.length > best.length ? current : best), candidates[0] ?? '');
|
|
@@ -236,43 +78,10 @@ function decodeResponseText(buffer) {
|
|
|
236
78
|
export function convertResponse(buffer) {
|
|
237
79
|
let responseText = decodeResponseText(buffer);
|
|
238
80
|
responseText = responseText.replace(STOP_TOKEN, '');
|
|
239
|
-
const
|
|
240
|
-
if (
|
|
241
|
-
return
|
|
242
|
-
}
|
|
81
|
+
const strictToolCalls = parseStrictOpenAIToolCalls(responseText);
|
|
82
|
+
if (strictToolCalls)
|
|
83
|
+
return strictToolCalls;
|
|
243
84
|
const parts = [];
|
|
244
|
-
|
|
245
|
-
let toolCallCount = 0;
|
|
246
|
-
while (cursor < responseText.length) {
|
|
247
|
-
const markerStart = responseText.indexOf(TOOL_CALL_PREFIX, cursor);
|
|
248
|
-
if (markerStart === -1) {
|
|
249
|
-
pushText(parts, responseText.slice(cursor));
|
|
250
|
-
break;
|
|
251
|
-
}
|
|
252
|
-
pushText(parts, responseText.slice(cursor, markerStart));
|
|
253
|
-
const toolNameStart = markerStart + TOOL_CALL_PREFIX.length;
|
|
254
|
-
const argsStart = responseText.indexOf(ARGS_PREFIX, toolNameStart);
|
|
255
|
-
if (argsStart === -1) {
|
|
256
|
-
pushText(parts, responseText.slice(markerStart));
|
|
257
|
-
break;
|
|
258
|
-
}
|
|
259
|
-
const toolName = responseText.slice(toolNameStart, argsStart);
|
|
260
|
-
const parsedArgs = parseJsonValue(responseText, argsStart + ARGS_PREFIX.length);
|
|
261
|
-
if (parsedArgs == null) {
|
|
262
|
-
const nextMarker = responseText.indexOf(TOOL_CALL_PREFIX, markerStart + TOOL_CALL_PREFIX.length);
|
|
263
|
-
const malformedEnd = nextMarker === -1 ? responseText.length : nextMarker;
|
|
264
|
-
pushText(parts, responseText.slice(markerStart, malformedEnd));
|
|
265
|
-
cursor = malformedEnd;
|
|
266
|
-
continue;
|
|
267
|
-
}
|
|
268
|
-
toolCallCount += 1;
|
|
269
|
-
parts.push({
|
|
270
|
-
type: 'tool-call',
|
|
271
|
-
toolCallId: `toolcall_${toolCallCount}`,
|
|
272
|
-
toolName,
|
|
273
|
-
input: parsedArgs.parsed,
|
|
274
|
-
});
|
|
275
|
-
cursor = parsedArgs.endIndex;
|
|
276
|
-
}
|
|
85
|
+
pushText(parts, responseText);
|
|
277
86
|
return parts;
|
|
278
87
|
}
|