@bxb1337/windsurf-fast-context 1.0.4 → 1.0.6
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.js +1 -1
- package/dist/cjs/conversion/prompt-converter.test.js +2 -2
- package/dist/cjs/conversion/response-converter.js +69 -2
- package/dist/cjs/conversion/response-converter.test.js +102 -3
- package/dist/cjs/model/devstral-language-model.js +5 -6
- package/dist/cjs/model/devstral-language-model.test.js +2 -4
- package/dist/conversion/prompt-converter.d.ts +1 -1
- package/dist/conversion/response-converter.d.ts +1 -1
- package/dist/esm/conversion/prompt-converter.js +1 -1
- package/dist/esm/conversion/prompt-converter.test.js +2 -2
- package/dist/esm/conversion/response-converter.js +69 -2
- package/dist/esm/conversion/response-converter.test.js +102 -3
- package/dist/esm/model/devstral-language-model.js +5 -6
- package/dist/esm/model/devstral-language-model.test.js +2 -4
- package/dist/model/devstral-language-model.d.ts +4 -5
- package/package.json +14 -11
|
@@ -18,7 +18,7 @@ const prompt_converter_js_1 = require("./prompt-converter.js");
|
|
|
18
18
|
type: 'tool-call',
|
|
19
19
|
toolCallId: 'call_1',
|
|
20
20
|
toolName: 'searchDocs',
|
|
21
|
-
|
|
21
|
+
input: { query: 'prompt converter', topK: 3 },
|
|
22
22
|
},
|
|
23
23
|
],
|
|
24
24
|
},
|
|
@@ -50,7 +50,7 @@ const prompt_converter_js_1 = require("./prompt-converter.js");
|
|
|
50
50
|
role: 'assistant',
|
|
51
51
|
content: [
|
|
52
52
|
{ type: 'text', text: 'I will call a tool now.' },
|
|
53
|
-
{ type: 'tool-call', toolCallId: 'call_2', toolName: 'searchDocs',
|
|
53
|
+
{ type: 'tool-call', toolCallId: 'call_2', toolName: 'searchDocs', input: { q: 'usage examples' } },
|
|
54
54
|
],
|
|
55
55
|
},
|
|
56
56
|
{
|
|
@@ -6,7 +6,70 @@ const protobuf_js_1 = require("../protocol/protobuf.js");
|
|
|
6
6
|
const TOOL_CALL_PREFIX = '[TOOL_CALLS]';
|
|
7
7
|
const ARGS_PREFIX = '[ARGS]';
|
|
8
8
|
const STOP_TOKEN = '</s>';
|
|
9
|
-
const EMPTY_TOOL_CALLS_PATTERN = /TOOL_CALLS\d*(?:<\/s>)?\s
|
|
9
|
+
const EMPTY_TOOL_CALLS_PATTERN = /TOOL_CALLS\d*(?:<\/s>)?\s*(?:\{\s*\}\s*)+/g;
|
|
10
|
+
// OpenAI-style TOOL_CALLS format: TOOL_CALLS{"type":"function","function":{"name":3,...}}{}...
|
|
11
|
+
const OPENAI_TOOL_CALLS_PATTERN = /^TOOL_CALLS(\{[\s\S]*\}\s*)+\s*$/;
|
|
12
|
+
function parseOpenAIToolCalls(responseText) {
|
|
13
|
+
if (!responseText.startsWith('TOOL_CALLS')) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const jsonPart = responseText.slice('TOOL_CALLS'.length);
|
|
17
|
+
if (!jsonPart.startsWith('{')) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const toolCalls = [];
|
|
21
|
+
let cursor = 0;
|
|
22
|
+
while (cursor < jsonPart.length) {
|
|
23
|
+
if (jsonPart[cursor] !== '{') {
|
|
24
|
+
cursor++;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const endResult = parseBalancedEnd(jsonPart, cursor);
|
|
28
|
+
if (endResult == null) {
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
const jsonStr = jsonPart.slice(cursor, endResult);
|
|
32
|
+
cursor = endResult;
|
|
33
|
+
if (jsonStr === '{}') {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(jsonStr);
|
|
38
|
+
if (parsed.type === 'function' && parsed.function) {
|
|
39
|
+
toolCalls.push(parsed);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (toolCalls.length === 0) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return toolCalls.map((call, index) => {
|
|
50
|
+
const toolId = call.function.name;
|
|
51
|
+
const toolName = typeof toolId === 'number' ? mapToolIdToName(toolId) : String(toolId);
|
|
52
|
+
const args = call.function.parameters ?? {};
|
|
53
|
+
return {
|
|
54
|
+
type: 'tool-call',
|
|
55
|
+
toolCallId: `toolcall_${index + 1}`,
|
|
56
|
+
toolName,
|
|
57
|
+
input: args,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function mapToolIdToName(id) {
|
|
62
|
+
switch (id) {
|
|
63
|
+
case 1:
|
|
64
|
+
return 'read';
|
|
65
|
+
case 2:
|
|
66
|
+
return 'glob';
|
|
67
|
+
case 3:
|
|
68
|
+
return 'grep';
|
|
69
|
+
default:
|
|
70
|
+
return `tool_${id}`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
10
73
|
function pushText(parts, text) {
|
|
11
74
|
if (text.length > 0) {
|
|
12
75
|
parts.push({ type: 'text', text });
|
|
@@ -194,6 +257,10 @@ function convertResponse(buffer) {
|
|
|
194
257
|
let responseText = decodeResponseText(buffer);
|
|
195
258
|
responseText = responseText.replace(EMPTY_TOOL_CALLS_PATTERN, '');
|
|
196
259
|
responseText = responseText.replace(STOP_TOKEN, '');
|
|
260
|
+
const openaiToolCalls = parseOpenAIToolCalls(responseText);
|
|
261
|
+
if (openaiToolCalls) {
|
|
262
|
+
return openaiToolCalls;
|
|
263
|
+
}
|
|
197
264
|
const parts = [];
|
|
198
265
|
let cursor = 0;
|
|
199
266
|
let toolCallCount = 0;
|
|
@@ -228,7 +295,7 @@ function convertResponse(buffer) {
|
|
|
228
295
|
type: 'tool-call',
|
|
229
296
|
toolCallId: `toolcall_${toolCallCount}`,
|
|
230
297
|
toolName,
|
|
231
|
-
|
|
298
|
+
input: parsedArgs.parsed,
|
|
232
299
|
});
|
|
233
300
|
}
|
|
234
301
|
cursor = parsedArgs.endIndex;
|
|
@@ -18,7 +18,7 @@ const response_converter_js_1 = require("./response-converter.js");
|
|
|
18
18
|
type: 'tool-call',
|
|
19
19
|
toolCallId: 'toolcall_1',
|
|
20
20
|
toolName: 'searchDocs',
|
|
21
|
-
|
|
21
|
+
input: { query: 'prompt converter' },
|
|
22
22
|
},
|
|
23
23
|
{ type: 'text', text: ' between ' },
|
|
24
24
|
{ type: 'text', text: 'final answer' },
|
|
@@ -75,7 +75,7 @@ const response_converter_js_1 = require("./response-converter.js");
|
|
|
75
75
|
(0, vitest_1.it)('strips empty TOOL_CALLS markers with whitespace', () => {
|
|
76
76
|
const input = 'Text TOOL_CALLS2{ } more text';
|
|
77
77
|
const result = (0, response_converter_js_1.convertResponse)(Buffer.from(input, 'utf8'));
|
|
78
|
-
(0, vitest_1.expect)(result).toEqual([{ type: 'text', text: 'Text
|
|
78
|
+
(0, vitest_1.expect)(result).toEqual([{ type: 'text', text: 'Text more text' }]);
|
|
79
79
|
});
|
|
80
80
|
(0, vitest_1.it)('strips standalone stop token', () => {
|
|
81
81
|
const input = 'Hello world</s>';
|
|
@@ -85,6 +85,105 @@ const response_converter_js_1 = require("./response-converter.js");
|
|
|
85
85
|
(0, vitest_1.it)('handles TOOL_CALLS with number prefix before stop token', () => {
|
|
86
86
|
const input = 'Text before TOOL_CALLS1</s>{} text after';
|
|
87
87
|
const result = (0, response_converter_js_1.convertResponse)(Buffer.from(input, 'utf8'));
|
|
88
|
-
(0, vitest_1.expect)(result).toEqual([{ type: 'text', text: 'Text before
|
|
88
|
+
(0, vitest_1.expect)(result).toEqual([{ type: 'text', text: 'Text before text after' }]);
|
|
89
|
+
});
|
|
90
|
+
(0, vitest_1.it)('strips TOOL_CALLS with double empty braces', () => {
|
|
91
|
+
const input = 'Hello world TOOL_CALLS1{}{}';
|
|
92
|
+
const result = (0, response_converter_js_1.convertResponse)(Buffer.from(input, 'utf8'));
|
|
93
|
+
(0, vitest_1.expect)(result).toEqual([{ type: 'text', text: 'Hello world ' }]);
|
|
94
|
+
});
|
|
95
|
+
(0, vitest_1.it)('strips TOOL_CALLS with triple empty braces', () => {
|
|
96
|
+
const input = 'Response TOOL_CALLS2{}{}{} end';
|
|
97
|
+
const result = (0, response_converter_js_1.convertResponse)(Buffer.from(input, 'utf8'));
|
|
98
|
+
(0, vitest_1.expect)(result).toEqual([{ type: 'text', text: 'Response end' }]);
|
|
99
|
+
});
|
|
100
|
+
(0, vitest_1.it)('strips TOOL_CALLS with stop token and multiple braces', () => {
|
|
101
|
+
const input = 'Text TOOL_CALLS0</s>{}{} more text';
|
|
102
|
+
const result = (0, response_converter_js_1.convertResponse)(Buffer.from(input, 'utf8'));
|
|
103
|
+
(0, vitest_1.expect)(result).toEqual([{ type: 'text', text: 'Text more text' }]);
|
|
104
|
+
});
|
|
105
|
+
(0, vitest_1.it)('handles empty marker followed by real tool call', () => {
|
|
106
|
+
const input = 'TOOL_CALLS0{} [TOOL_CALLS]searchDocs[ARGS]{"query":"test"}';
|
|
107
|
+
const result = (0, response_converter_js_1.convertResponse)(Buffer.from(input, 'utf8'));
|
|
108
|
+
(0, vitest_1.expect)(result).toEqual([
|
|
109
|
+
{
|
|
110
|
+
type: 'tool-call',
|
|
111
|
+
toolCallId: 'toolcall_1',
|
|
112
|
+
toolName: 'searchDocs',
|
|
113
|
+
input: { query: 'test' },
|
|
114
|
+
},
|
|
115
|
+
]);
|
|
116
|
+
});
|
|
117
|
+
(0, vitest_1.it)('handles real tool call followed by empty marker', () => {
|
|
118
|
+
const input = '[TOOL_CALLS]searchDocs[ARGS]{"query":"test"} TOOL_CALLS1{} done';
|
|
119
|
+
const result = (0, response_converter_js_1.convertResponse)(Buffer.from(input, 'utf8'));
|
|
120
|
+
(0, vitest_1.expect)(result).toEqual([
|
|
121
|
+
{
|
|
122
|
+
type: 'tool-call',
|
|
123
|
+
toolCallId: 'toolcall_1',
|
|
124
|
+
toolName: 'searchDocs',
|
|
125
|
+
input: { query: 'test' },
|
|
126
|
+
},
|
|
127
|
+
{ type: 'text', text: ' done' },
|
|
128
|
+
]);
|
|
129
|
+
});
|
|
130
|
+
(0, vitest_1.it)('handles empty marker adjacent to real tool call', () => {
|
|
131
|
+
const input = 'TOOL_CALLS0{}[TOOL_CALLS]answer[ARGS]{"answer":"result"}';
|
|
132
|
+
const result = (0, response_converter_js_1.convertResponse)(Buffer.from(input, 'utf8'));
|
|
133
|
+
(0, vitest_1.expect)(result).toEqual([{ type: 'text', text: 'result' }]);
|
|
134
|
+
});
|
|
135
|
+
(0, vitest_1.it)('parses OpenAI-style TOOL_CALLS with numeric IDs', () => {
|
|
136
|
+
const input = 'TOOL_CALLS{"type":"function","function":{"name":3,"parameters":{"file_path":"/home/test","search_pattern":"binance"}}}{}';
|
|
137
|
+
const result = (0, response_converter_js_1.convertResponse)(Buffer.from(input, 'utf8'));
|
|
138
|
+
(0, vitest_1.expect)(result).toEqual([
|
|
139
|
+
{
|
|
140
|
+
type: 'tool-call',
|
|
141
|
+
toolCallId: 'toolcall_1',
|
|
142
|
+
toolName: 'grep',
|
|
143
|
+
input: { file_path: '/home/test', search_pattern: 'binance' },
|
|
144
|
+
},
|
|
145
|
+
]);
|
|
146
|
+
});
|
|
147
|
+
(0, vitest_1.it)('parses multiple OpenAI-style tool calls', () => {
|
|
148
|
+
const input = 'TOOL_CALLS{"type":"function","function":{"name":1,"parameters":{"file_path":"/home/test"}}}, {"type":"function","function":{"name":2,"parameters":{"pattern":"*.ts"}}}{}';
|
|
149
|
+
const result = (0, response_converter_js_1.convertResponse)(Buffer.from(input, 'utf8'));
|
|
150
|
+
(0, vitest_1.expect)(result).toEqual([
|
|
151
|
+
{
|
|
152
|
+
type: 'tool-call',
|
|
153
|
+
toolCallId: 'toolcall_1',
|
|
154
|
+
toolName: 'read',
|
|
155
|
+
input: { file_path: '/home/test' },
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
type: 'tool-call',
|
|
159
|
+
toolCallId: 'toolcall_2',
|
|
160
|
+
toolName: 'glob',
|
|
161
|
+
input: { pattern: '*.ts' },
|
|
162
|
+
},
|
|
163
|
+
]);
|
|
164
|
+
});
|
|
165
|
+
(0, vitest_1.it)('maps unknown tool IDs to tool_N format', () => {
|
|
166
|
+
const input = 'TOOL_CALLS{"type":"function","function":{"name":99,"parameters":{"arg":"value"}}}{}';
|
|
167
|
+
const result = (0, response_converter_js_1.convertResponse)(Buffer.from(input, 'utf8'));
|
|
168
|
+
(0, vitest_1.expect)(result).toEqual([
|
|
169
|
+
{
|
|
170
|
+
type: 'tool-call',
|
|
171
|
+
toolCallId: 'toolcall_1',
|
|
172
|
+
toolName: 'tool_99',
|
|
173
|
+
input: { arg: 'value' },
|
|
174
|
+
},
|
|
175
|
+
]);
|
|
176
|
+
});
|
|
177
|
+
(0, vitest_1.it)('handles OpenAI-style with string tool names', () => {
|
|
178
|
+
const input = 'TOOL_CALLS{"type":"function","function":{"name":"custom_tool","parameters":{"key":"value"}}}{}';
|
|
179
|
+
const result = (0, response_converter_js_1.convertResponse)(Buffer.from(input, 'utf8'));
|
|
180
|
+
(0, vitest_1.expect)(result).toEqual([
|
|
181
|
+
{
|
|
182
|
+
type: 'tool-call',
|
|
183
|
+
toolCallId: 'toolcall_1',
|
|
184
|
+
toolName: 'custom_tool',
|
|
185
|
+
input: { key: 'value' },
|
|
186
|
+
},
|
|
187
|
+
]);
|
|
89
188
|
});
|
|
90
189
|
});
|
|
@@ -140,17 +140,17 @@ class DevstralLanguageModel {
|
|
|
140
140
|
closeTextSegment();
|
|
141
141
|
safeEnqueue(controller, {
|
|
142
142
|
type: 'tool-input-start',
|
|
143
|
-
|
|
143
|
+
id: part.toolCallId,
|
|
144
144
|
toolName: part.toolName,
|
|
145
145
|
});
|
|
146
146
|
safeEnqueue(controller, {
|
|
147
147
|
type: 'tool-input-delta',
|
|
148
|
-
|
|
149
|
-
delta: part.
|
|
148
|
+
id: part.toolCallId,
|
|
149
|
+
delta: part.input,
|
|
150
150
|
});
|
|
151
151
|
safeEnqueue(controller, {
|
|
152
152
|
type: 'tool-input-end',
|
|
153
|
-
|
|
153
|
+
id: part.toolCallId,
|
|
154
154
|
});
|
|
155
155
|
safeEnqueue(controller, part);
|
|
156
156
|
}
|
|
@@ -256,10 +256,9 @@ function toV3Content(parts) {
|
|
|
256
256
|
if (part.type === 'tool-call') {
|
|
257
257
|
return {
|
|
258
258
|
type: 'tool-call',
|
|
259
|
-
toolCallType: 'function',
|
|
260
259
|
toolCallId: part.toolCallId,
|
|
261
260
|
toolName: part.toolName,
|
|
262
|
-
|
|
261
|
+
input: JSON.stringify(part.input),
|
|
263
262
|
};
|
|
264
263
|
}
|
|
265
264
|
return part;
|
|
@@ -190,10 +190,9 @@ async function collectStreamParts(stream) {
|
|
|
190
190
|
(0, vitest_1.expect)(result.content).toEqual([
|
|
191
191
|
{
|
|
192
192
|
type: 'tool-call',
|
|
193
|
-
toolCallType: 'function',
|
|
194
193
|
toolCallId: 'toolcall_1',
|
|
195
194
|
toolName: 'searchRepo',
|
|
196
|
-
|
|
195
|
+
input: '{"query":"jwt manager"}',
|
|
197
196
|
},
|
|
198
197
|
]);
|
|
199
198
|
const strings = (0, protobuf_js_1.extractStrings)(decodeRequestPayload(requestBodies[0] ?? Buffer.alloc(0)));
|
|
@@ -358,10 +357,9 @@ async function collectStreamParts(stream) {
|
|
|
358
357
|
(0, vitest_1.expect)(parts[3]).toMatchObject({ type: 'tool-input-delta', delta: '{"query":"jwt manager"}' });
|
|
359
358
|
(0, vitest_1.expect)(parts[5]).toEqual({
|
|
360
359
|
type: 'tool-call',
|
|
361
|
-
toolCallType: 'function',
|
|
362
360
|
toolCallId: 'toolcall_1',
|
|
363
361
|
toolName: 'searchRepo',
|
|
364
|
-
|
|
362
|
+
input: '{"query":"jwt manager"}',
|
|
365
363
|
});
|
|
366
364
|
});
|
|
367
365
|
(0, vitest_1.it)('abort stops stream mid-response', async () => {
|
|
@@ -6,7 +6,7 @@ export interface ToolCallPart {
|
|
|
6
6
|
type: 'tool-call';
|
|
7
7
|
toolCallId: string;
|
|
8
8
|
toolName: string;
|
|
9
|
-
|
|
9
|
+
input: unknown;
|
|
10
10
|
}
|
|
11
11
|
export type LanguageModelV3Content = TextPart | ToolCallPart;
|
|
12
12
|
export declare function convertResponse(buffer: Buffer): LanguageModelV3Content[];
|
|
@@ -16,7 +16,7 @@ describe('convertPrompt', () => {
|
|
|
16
16
|
type: 'tool-call',
|
|
17
17
|
toolCallId: 'call_1',
|
|
18
18
|
toolName: 'searchDocs',
|
|
19
|
-
|
|
19
|
+
input: { query: 'prompt converter', topK: 3 },
|
|
20
20
|
},
|
|
21
21
|
],
|
|
22
22
|
},
|
|
@@ -48,7 +48,7 @@ describe('convertPrompt', () => {
|
|
|
48
48
|
role: 'assistant',
|
|
49
49
|
content: [
|
|
50
50
|
{ type: 'text', text: 'I will call a tool now.' },
|
|
51
|
-
{ type: 'tool-call', toolCallId: 'call_2', toolName: 'searchDocs',
|
|
51
|
+
{ type: 'tool-call', toolCallId: 'call_2', toolName: 'searchDocs', input: { q: 'usage examples' } },
|
|
52
52
|
],
|
|
53
53
|
},
|
|
54
54
|
{
|
|
@@ -3,7 +3,70 @@ import { extractStrings } from '../protocol/protobuf.js';
|
|
|
3
3
|
const TOOL_CALL_PREFIX = '[TOOL_CALLS]';
|
|
4
4
|
const ARGS_PREFIX = '[ARGS]';
|
|
5
5
|
const STOP_TOKEN = '</s>';
|
|
6
|
-
const EMPTY_TOOL_CALLS_PATTERN = /TOOL_CALLS\d*(?:<\/s>)?\s
|
|
6
|
+
const EMPTY_TOOL_CALLS_PATTERN = /TOOL_CALLS\d*(?:<\/s>)?\s*(?:\{\s*\}\s*)+/g;
|
|
7
|
+
// OpenAI-style TOOL_CALLS format: TOOL_CALLS{"type":"function","function":{"name":3,...}}{}...
|
|
8
|
+
const OPENAI_TOOL_CALLS_PATTERN = /^TOOL_CALLS(\{[\s\S]*\}\s*)+\s*$/;
|
|
9
|
+
function parseOpenAIToolCalls(responseText) {
|
|
10
|
+
if (!responseText.startsWith('TOOL_CALLS')) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const jsonPart = responseText.slice('TOOL_CALLS'.length);
|
|
14
|
+
if (!jsonPart.startsWith('{')) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const toolCalls = [];
|
|
18
|
+
let cursor = 0;
|
|
19
|
+
while (cursor < jsonPart.length) {
|
|
20
|
+
if (jsonPart[cursor] !== '{') {
|
|
21
|
+
cursor++;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const endResult = parseBalancedEnd(jsonPart, cursor);
|
|
25
|
+
if (endResult == null) {
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
const jsonStr = jsonPart.slice(cursor, endResult);
|
|
29
|
+
cursor = endResult;
|
|
30
|
+
if (jsonStr === '{}') {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(jsonStr);
|
|
35
|
+
if (parsed.type === 'function' && parsed.function) {
|
|
36
|
+
toolCalls.push(parsed);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (toolCalls.length === 0) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return toolCalls.map((call, index) => {
|
|
47
|
+
const toolId = call.function.name;
|
|
48
|
+
const toolName = typeof toolId === 'number' ? mapToolIdToName(toolId) : String(toolId);
|
|
49
|
+
const args = call.function.parameters ?? {};
|
|
50
|
+
return {
|
|
51
|
+
type: 'tool-call',
|
|
52
|
+
toolCallId: `toolcall_${index + 1}`,
|
|
53
|
+
toolName,
|
|
54
|
+
input: args,
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function mapToolIdToName(id) {
|
|
59
|
+
switch (id) {
|
|
60
|
+
case 1:
|
|
61
|
+
return 'read';
|
|
62
|
+
case 2:
|
|
63
|
+
return 'glob';
|
|
64
|
+
case 3:
|
|
65
|
+
return 'grep';
|
|
66
|
+
default:
|
|
67
|
+
return `tool_${id}`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
7
70
|
function pushText(parts, text) {
|
|
8
71
|
if (text.length > 0) {
|
|
9
72
|
parts.push({ type: 'text', text });
|
|
@@ -191,6 +254,10 @@ export function convertResponse(buffer) {
|
|
|
191
254
|
let responseText = decodeResponseText(buffer);
|
|
192
255
|
responseText = responseText.replace(EMPTY_TOOL_CALLS_PATTERN, '');
|
|
193
256
|
responseText = responseText.replace(STOP_TOKEN, '');
|
|
257
|
+
const openaiToolCalls = parseOpenAIToolCalls(responseText);
|
|
258
|
+
if (openaiToolCalls) {
|
|
259
|
+
return openaiToolCalls;
|
|
260
|
+
}
|
|
194
261
|
const parts = [];
|
|
195
262
|
let cursor = 0;
|
|
196
263
|
let toolCallCount = 0;
|
|
@@ -225,7 +292,7 @@ export function convertResponse(buffer) {
|
|
|
225
292
|
type: 'tool-call',
|
|
226
293
|
toolCallId: `toolcall_${toolCallCount}`,
|
|
227
294
|
toolName,
|
|
228
|
-
|
|
295
|
+
input: parsedArgs.parsed,
|
|
229
296
|
});
|
|
230
297
|
}
|
|
231
298
|
cursor = parsedArgs.endIndex;
|
|
@@ -16,7 +16,7 @@ describe('convertResponse', () => {
|
|
|
16
16
|
type: 'tool-call',
|
|
17
17
|
toolCallId: 'toolcall_1',
|
|
18
18
|
toolName: 'searchDocs',
|
|
19
|
-
|
|
19
|
+
input: { query: 'prompt converter' },
|
|
20
20
|
},
|
|
21
21
|
{ type: 'text', text: ' between ' },
|
|
22
22
|
{ type: 'text', text: 'final answer' },
|
|
@@ -73,7 +73,7 @@ describe('convertResponse', () => {
|
|
|
73
73
|
it('strips empty TOOL_CALLS markers with whitespace', () => {
|
|
74
74
|
const input = 'Text TOOL_CALLS2{ } more text';
|
|
75
75
|
const result = convertResponse(Buffer.from(input, 'utf8'));
|
|
76
|
-
expect(result).toEqual([{ type: 'text', text: 'Text
|
|
76
|
+
expect(result).toEqual([{ type: 'text', text: 'Text more text' }]);
|
|
77
77
|
});
|
|
78
78
|
it('strips standalone stop token', () => {
|
|
79
79
|
const input = 'Hello world</s>';
|
|
@@ -83,6 +83,105 @@ describe('convertResponse', () => {
|
|
|
83
83
|
it('handles TOOL_CALLS with number prefix before stop token', () => {
|
|
84
84
|
const input = 'Text before TOOL_CALLS1</s>{} text after';
|
|
85
85
|
const result = convertResponse(Buffer.from(input, 'utf8'));
|
|
86
|
-
expect(result).toEqual([{ type: 'text', text: 'Text before
|
|
86
|
+
expect(result).toEqual([{ type: 'text', text: 'Text before text after' }]);
|
|
87
|
+
});
|
|
88
|
+
it('strips TOOL_CALLS with double empty braces', () => {
|
|
89
|
+
const input = 'Hello world TOOL_CALLS1{}{}';
|
|
90
|
+
const result = convertResponse(Buffer.from(input, 'utf8'));
|
|
91
|
+
expect(result).toEqual([{ type: 'text', text: 'Hello world ' }]);
|
|
92
|
+
});
|
|
93
|
+
it('strips TOOL_CALLS with triple empty braces', () => {
|
|
94
|
+
const input = 'Response TOOL_CALLS2{}{}{} end';
|
|
95
|
+
const result = convertResponse(Buffer.from(input, 'utf8'));
|
|
96
|
+
expect(result).toEqual([{ type: 'text', text: 'Response end' }]);
|
|
97
|
+
});
|
|
98
|
+
it('strips TOOL_CALLS with stop token and multiple braces', () => {
|
|
99
|
+
const input = 'Text TOOL_CALLS0</s>{}{} more text';
|
|
100
|
+
const result = convertResponse(Buffer.from(input, 'utf8'));
|
|
101
|
+
expect(result).toEqual([{ type: 'text', text: 'Text more text' }]);
|
|
102
|
+
});
|
|
103
|
+
it('handles empty marker followed by real tool call', () => {
|
|
104
|
+
const input = 'TOOL_CALLS0{} [TOOL_CALLS]searchDocs[ARGS]{"query":"test"}';
|
|
105
|
+
const result = convertResponse(Buffer.from(input, 'utf8'));
|
|
106
|
+
expect(result).toEqual([
|
|
107
|
+
{
|
|
108
|
+
type: 'tool-call',
|
|
109
|
+
toolCallId: 'toolcall_1',
|
|
110
|
+
toolName: 'searchDocs',
|
|
111
|
+
input: { query: 'test' },
|
|
112
|
+
},
|
|
113
|
+
]);
|
|
114
|
+
});
|
|
115
|
+
it('handles real tool call followed by empty marker', () => {
|
|
116
|
+
const input = '[TOOL_CALLS]searchDocs[ARGS]{"query":"test"} TOOL_CALLS1{} done';
|
|
117
|
+
const result = convertResponse(Buffer.from(input, 'utf8'));
|
|
118
|
+
expect(result).toEqual([
|
|
119
|
+
{
|
|
120
|
+
type: 'tool-call',
|
|
121
|
+
toolCallId: 'toolcall_1',
|
|
122
|
+
toolName: 'searchDocs',
|
|
123
|
+
input: { query: 'test' },
|
|
124
|
+
},
|
|
125
|
+
{ type: 'text', text: ' done' },
|
|
126
|
+
]);
|
|
127
|
+
});
|
|
128
|
+
it('handles empty marker adjacent to real tool call', () => {
|
|
129
|
+
const input = 'TOOL_CALLS0{}[TOOL_CALLS]answer[ARGS]{"answer":"result"}';
|
|
130
|
+
const result = convertResponse(Buffer.from(input, 'utf8'));
|
|
131
|
+
expect(result).toEqual([{ type: 'text', text: 'result' }]);
|
|
132
|
+
});
|
|
133
|
+
it('parses OpenAI-style TOOL_CALLS with numeric IDs', () => {
|
|
134
|
+
const input = 'TOOL_CALLS{"type":"function","function":{"name":3,"parameters":{"file_path":"/home/test","search_pattern":"binance"}}}{}';
|
|
135
|
+
const result = convertResponse(Buffer.from(input, 'utf8'));
|
|
136
|
+
expect(result).toEqual([
|
|
137
|
+
{
|
|
138
|
+
type: 'tool-call',
|
|
139
|
+
toolCallId: 'toolcall_1',
|
|
140
|
+
toolName: 'grep',
|
|
141
|
+
input: { file_path: '/home/test', search_pattern: 'binance' },
|
|
142
|
+
},
|
|
143
|
+
]);
|
|
144
|
+
});
|
|
145
|
+
it('parses multiple OpenAI-style tool calls', () => {
|
|
146
|
+
const input = 'TOOL_CALLS{"type":"function","function":{"name":1,"parameters":{"file_path":"/home/test"}}}, {"type":"function","function":{"name":2,"parameters":{"pattern":"*.ts"}}}{}';
|
|
147
|
+
const result = convertResponse(Buffer.from(input, 'utf8'));
|
|
148
|
+
expect(result).toEqual([
|
|
149
|
+
{
|
|
150
|
+
type: 'tool-call',
|
|
151
|
+
toolCallId: 'toolcall_1',
|
|
152
|
+
toolName: 'read',
|
|
153
|
+
input: { file_path: '/home/test' },
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
type: 'tool-call',
|
|
157
|
+
toolCallId: 'toolcall_2',
|
|
158
|
+
toolName: 'glob',
|
|
159
|
+
input: { pattern: '*.ts' },
|
|
160
|
+
},
|
|
161
|
+
]);
|
|
162
|
+
});
|
|
163
|
+
it('maps unknown tool IDs to tool_N format', () => {
|
|
164
|
+
const input = 'TOOL_CALLS{"type":"function","function":{"name":99,"parameters":{"arg":"value"}}}{}';
|
|
165
|
+
const result = convertResponse(Buffer.from(input, 'utf8'));
|
|
166
|
+
expect(result).toEqual([
|
|
167
|
+
{
|
|
168
|
+
type: 'tool-call',
|
|
169
|
+
toolCallId: 'toolcall_1',
|
|
170
|
+
toolName: 'tool_99',
|
|
171
|
+
input: { arg: 'value' },
|
|
172
|
+
},
|
|
173
|
+
]);
|
|
174
|
+
});
|
|
175
|
+
it('handles OpenAI-style with string tool names', () => {
|
|
176
|
+
const input = 'TOOL_CALLS{"type":"function","function":{"name":"custom_tool","parameters":{"key":"value"}}}{}';
|
|
177
|
+
const result = convertResponse(Buffer.from(input, 'utf8'));
|
|
178
|
+
expect(result).toEqual([
|
|
179
|
+
{
|
|
180
|
+
type: 'tool-call',
|
|
181
|
+
toolCallId: 'toolcall_1',
|
|
182
|
+
toolName: 'custom_tool',
|
|
183
|
+
input: { key: 'value' },
|
|
184
|
+
},
|
|
185
|
+
]);
|
|
87
186
|
});
|
|
88
187
|
});
|
|
@@ -137,17 +137,17 @@ export class DevstralLanguageModel {
|
|
|
137
137
|
closeTextSegment();
|
|
138
138
|
safeEnqueue(controller, {
|
|
139
139
|
type: 'tool-input-start',
|
|
140
|
-
|
|
140
|
+
id: part.toolCallId,
|
|
141
141
|
toolName: part.toolName,
|
|
142
142
|
});
|
|
143
143
|
safeEnqueue(controller, {
|
|
144
144
|
type: 'tool-input-delta',
|
|
145
|
-
|
|
146
|
-
delta: part.
|
|
145
|
+
id: part.toolCallId,
|
|
146
|
+
delta: part.input,
|
|
147
147
|
});
|
|
148
148
|
safeEnqueue(controller, {
|
|
149
149
|
type: 'tool-input-end',
|
|
150
|
-
|
|
150
|
+
id: part.toolCallId,
|
|
151
151
|
});
|
|
152
152
|
safeEnqueue(controller, part);
|
|
153
153
|
}
|
|
@@ -252,10 +252,9 @@ function toV3Content(parts) {
|
|
|
252
252
|
if (part.type === 'tool-call') {
|
|
253
253
|
return {
|
|
254
254
|
type: 'tool-call',
|
|
255
|
-
toolCallType: 'function',
|
|
256
255
|
toolCallId: part.toolCallId,
|
|
257
256
|
toolName: part.toolName,
|
|
258
|
-
|
|
257
|
+
input: JSON.stringify(part.input),
|
|
259
258
|
};
|
|
260
259
|
}
|
|
261
260
|
return part;
|
|
@@ -188,10 +188,9 @@ describe('DevstralLanguageModel doGenerate', () => {
|
|
|
188
188
|
expect(result.content).toEqual([
|
|
189
189
|
{
|
|
190
190
|
type: 'tool-call',
|
|
191
|
-
toolCallType: 'function',
|
|
192
191
|
toolCallId: 'toolcall_1',
|
|
193
192
|
toolName: 'searchRepo',
|
|
194
|
-
|
|
193
|
+
input: '{"query":"jwt manager"}',
|
|
195
194
|
},
|
|
196
195
|
]);
|
|
197
196
|
const strings = extractStrings(decodeRequestPayload(requestBodies[0] ?? Buffer.alloc(0)));
|
|
@@ -356,10 +355,9 @@ describe('DevstralLanguageModel doStream', () => {
|
|
|
356
355
|
expect(parts[3]).toMatchObject({ type: 'tool-input-delta', delta: '{"query":"jwt manager"}' });
|
|
357
356
|
expect(parts[5]).toEqual({
|
|
358
357
|
type: 'tool-call',
|
|
359
|
-
toolCallType: 'function',
|
|
360
358
|
toolCallId: 'toolcall_1',
|
|
361
359
|
toolName: 'searchRepo',
|
|
362
|
-
|
|
360
|
+
input: '{"query":"jwt manager"}',
|
|
363
361
|
});
|
|
364
362
|
});
|
|
365
363
|
it('abort stops stream mid-response', async () => {
|
|
@@ -34,10 +34,9 @@ export interface LanguageModelV3StreamResult {
|
|
|
34
34
|
}
|
|
35
35
|
interface LanguageModelV3ToolCallContent {
|
|
36
36
|
type: 'tool-call';
|
|
37
|
-
toolCallType: 'function';
|
|
38
37
|
toolCallId: string;
|
|
39
38
|
toolName: string;
|
|
40
|
-
|
|
39
|
+
input: string;
|
|
41
40
|
}
|
|
42
41
|
type GenerateContentPart = {
|
|
43
42
|
type: 'text';
|
|
@@ -61,15 +60,15 @@ type LanguageModelV3StreamPart = {
|
|
|
61
60
|
id: string;
|
|
62
61
|
} | {
|
|
63
62
|
type: 'tool-input-start';
|
|
64
|
-
|
|
63
|
+
id: string;
|
|
65
64
|
toolName: string;
|
|
66
65
|
} | {
|
|
67
66
|
type: 'tool-input-delta';
|
|
68
|
-
|
|
67
|
+
id: string;
|
|
69
68
|
delta: string;
|
|
70
69
|
} | {
|
|
71
70
|
type: 'tool-input-end';
|
|
72
|
-
|
|
71
|
+
id: string;
|
|
73
72
|
} | LanguageModelV3ToolCallContent | {
|
|
74
73
|
type: 'error';
|
|
75
74
|
error: unknown;
|
package/package.json
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bxb1337/windsurf-fast-context",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "AI SDK V3 provider for Windsurf's Devstral code search API",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "vitest --run",
|
|
8
|
+
"build:types": "tsc -p tsconfig.types.json",
|
|
9
|
+
"build:esm": "tsc -p tsconfig.json",
|
|
10
|
+
"build:cjs": "tsc -p tsconfig.cjs.json",
|
|
11
|
+
"build": "pnpm -s run build:types && pnpm -s run build:esm && pnpm -s run build:cjs && node ./scripts/postbuild.js",
|
|
12
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
13
|
+
},
|
|
6
14
|
"keywords": [
|
|
7
15
|
"windsurf",
|
|
8
16
|
"devstral",
|
|
@@ -17,6 +25,9 @@
|
|
|
17
25
|
"type": "git",
|
|
18
26
|
"url": "https://github.com/bxb1337/windsurf-fast-context"
|
|
19
27
|
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@ai-sdk/provider": "^3.0.0"
|
|
30
|
+
},
|
|
20
31
|
"devDependencies": {
|
|
21
32
|
"ai": "^6.0.116",
|
|
22
33
|
"typescript": "^5.0.0",
|
|
@@ -39,13 +50,5 @@
|
|
|
39
50
|
"dist",
|
|
40
51
|
"README.md",
|
|
41
52
|
"LICENSE"
|
|
42
|
-
]
|
|
43
|
-
|
|
44
|
-
"test": "vitest --run",
|
|
45
|
-
"build:types": "tsc -p tsconfig.types.json",
|
|
46
|
-
"build:esm": "tsc -p tsconfig.json",
|
|
47
|
-
"build:cjs": "tsc -p tsconfig.cjs.json",
|
|
48
|
-
"build": "pnpm -s run build:types && pnpm -s run build:esm && pnpm -s run build:cjs && node ./scripts/postbuild.js",
|
|
49
|
-
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
50
|
-
}
|
|
51
|
-
}
|
|
53
|
+
]
|
|
54
|
+
}
|