@bxb1337/windsurf-fast-context 1.0.7 → 1.0.8
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 +22 -7
- package/dist/cjs/conversion/prompt-converter.test.js +117 -2
- package/dist/cjs/conversion/response-converter.js +7 -30
- package/dist/cjs/conversion/response-converter.test.js +26 -67
- package/dist/cjs/model/devstral-language-model.js +35 -18
- package/dist/cjs/model/devstral-language-model.test.js +122 -17
- package/dist/conversion/prompt-converter.d.ts +1 -47
- package/dist/esm/conversion/prompt-converter.js +22 -7
- package/dist/esm/conversion/prompt-converter.test.js +117 -2
- package/dist/esm/conversion/response-converter.js +7 -30
- package/dist/esm/conversion/response-converter.test.js +26 -67
- package/dist/esm/model/devstral-language-model.js +35 -18
- package/dist/esm/model/devstral-language-model.test.js +122 -17
- package/dist/model/devstral-language-model.d.ts +2 -91
- package/package.json +1 -1
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.convertPrompt = convertPrompt;
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
function toolOutputToString(output) {
|
|
5
|
+
switch (output.type) {
|
|
6
|
+
case 'text':
|
|
7
|
+
case 'error-text':
|
|
8
|
+
return output.value;
|
|
9
|
+
case 'json':
|
|
10
|
+
case 'error-json':
|
|
11
|
+
return JSON.stringify(output.value);
|
|
12
|
+
case 'execution-denied': {
|
|
13
|
+
const obj = { type: 'execution-denied' };
|
|
14
|
+
if (output.reason !== undefined)
|
|
15
|
+
obj.reason = output.reason;
|
|
16
|
+
return JSON.stringify(obj);
|
|
17
|
+
}
|
|
18
|
+
case 'content':
|
|
19
|
+
return JSON.stringify(output.value);
|
|
20
|
+
default:
|
|
21
|
+
return JSON.stringify(output);
|
|
7
22
|
}
|
|
8
|
-
return JSON.stringify(value);
|
|
9
23
|
}
|
|
10
24
|
function convertPrompt(prompt) {
|
|
11
25
|
const messages = [];
|
|
@@ -42,13 +56,14 @@ function convertPrompt(prompt) {
|
|
|
42
56
|
}
|
|
43
57
|
continue;
|
|
44
58
|
}
|
|
45
|
-
// Tool result messages - use refCallId to reference the original tool call
|
|
46
59
|
for (const part of message.content) {
|
|
60
|
+
if (part.type !== 'tool-result')
|
|
61
|
+
continue;
|
|
47
62
|
messages.push({
|
|
48
63
|
role: 4,
|
|
49
|
-
content:
|
|
64
|
+
content: toolOutputToString(part.output),
|
|
50
65
|
metadata: {
|
|
51
|
-
refCallId: part.toolCallId,
|
|
66
|
+
refCallId: part.toolCallId,
|
|
52
67
|
},
|
|
53
68
|
});
|
|
54
69
|
}
|
|
@@ -36,7 +36,7 @@ const prompt_converter_js_1 = require("./prompt-converter.js");
|
|
|
36
36
|
},
|
|
37
37
|
});
|
|
38
38
|
});
|
|
39
|
-
(0, vitest_1.it)('multi-turn preserves ordering across mixed roles', () => {
|
|
39
|
+
(0, vitest_1.it)('multi-turn preserves ordering across mixed roles (V3 output shape)', () => {
|
|
40
40
|
const prompt = [
|
|
41
41
|
{ role: 'system', content: 'System instruction' },
|
|
42
42
|
{
|
|
@@ -60,7 +60,7 @@ const prompt_converter_js_1 = require("./prompt-converter.js");
|
|
|
60
60
|
type: 'tool-result',
|
|
61
61
|
toolCallId: 'call_2',
|
|
62
62
|
toolName: 'searchDocs',
|
|
63
|
-
|
|
63
|
+
output: { type: 'json', value: { hits: ['a.ts', 'b.ts'] } },
|
|
64
64
|
},
|
|
65
65
|
],
|
|
66
66
|
},
|
|
@@ -92,4 +92,119 @@ const prompt_converter_js_1 = require("./prompt-converter.js");
|
|
|
92
92
|
]);
|
|
93
93
|
(0, vitest_1.expect)(prompt).toEqual(snapshot);
|
|
94
94
|
});
|
|
95
|
+
(0, vitest_1.it)('tool-result with json output serializes value to JSON', () => {
|
|
96
|
+
const prompt = [
|
|
97
|
+
{
|
|
98
|
+
role: 'tool',
|
|
99
|
+
content: [
|
|
100
|
+
{
|
|
101
|
+
type: 'tool-result',
|
|
102
|
+
toolCallId: 'call_json',
|
|
103
|
+
toolName: 'searchTool',
|
|
104
|
+
output: { type: 'json', value: { files: ['x.ts', 'y.ts'], count: 2 } },
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
];
|
|
109
|
+
const result = (0, prompt_converter_js_1.convertPrompt)(prompt);
|
|
110
|
+
(0, vitest_1.expect)(result).toEqual([
|
|
111
|
+
{
|
|
112
|
+
role: 4,
|
|
113
|
+
content: '{"files":["x.ts","y.ts"],"count":2}',
|
|
114
|
+
metadata: { refCallId: 'call_json' },
|
|
115
|
+
},
|
|
116
|
+
]);
|
|
117
|
+
});
|
|
118
|
+
(0, vitest_1.it)('tool-result with text output uses string directly', () => {
|
|
119
|
+
const prompt = [
|
|
120
|
+
{
|
|
121
|
+
role: 'tool',
|
|
122
|
+
content: [
|
|
123
|
+
{
|
|
124
|
+
type: 'tool-result',
|
|
125
|
+
toolCallId: 'call_text',
|
|
126
|
+
toolName: 'readFile',
|
|
127
|
+
output: { type: 'text', value: 'Operation completed successfully' },
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
];
|
|
132
|
+
const result = (0, prompt_converter_js_1.convertPrompt)(prompt);
|
|
133
|
+
(0, vitest_1.expect)(result).toEqual([
|
|
134
|
+
{
|
|
135
|
+
role: 4,
|
|
136
|
+
content: 'Operation completed successfully',
|
|
137
|
+
metadata: { refCallId: 'call_text' },
|
|
138
|
+
},
|
|
139
|
+
]);
|
|
140
|
+
});
|
|
141
|
+
(0, vitest_1.it)('tool-result with error-text output serializes error message', () => {
|
|
142
|
+
const prompt = [
|
|
143
|
+
{
|
|
144
|
+
role: 'tool',
|
|
145
|
+
content: [
|
|
146
|
+
{
|
|
147
|
+
type: 'tool-result',
|
|
148
|
+
toolCallId: 'call_error',
|
|
149
|
+
toolName: 'executeCommand',
|
|
150
|
+
output: { type: 'error-text', value: 'Tool execution failed: timeout' },
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
];
|
|
155
|
+
const result = (0, prompt_converter_js_1.convertPrompt)(prompt);
|
|
156
|
+
(0, vitest_1.expect)(result).toEqual([
|
|
157
|
+
{
|
|
158
|
+
role: 4,
|
|
159
|
+
content: 'Tool execution failed: timeout',
|
|
160
|
+
metadata: { refCallId: 'call_error' },
|
|
161
|
+
},
|
|
162
|
+
]);
|
|
163
|
+
});
|
|
164
|
+
(0, vitest_1.it)('tool-result with execution-denied output includes reason', () => {
|
|
165
|
+
const prompt = [
|
|
166
|
+
{
|
|
167
|
+
role: 'tool',
|
|
168
|
+
content: [
|
|
169
|
+
{
|
|
170
|
+
type: 'tool-result',
|
|
171
|
+
toolCallId: 'call_denied',
|
|
172
|
+
toolName: 'dangerousAction',
|
|
173
|
+
output: { type: 'execution-denied', reason: 'User rejected tool execution' },
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
];
|
|
178
|
+
const result = (0, prompt_converter_js_1.convertPrompt)(prompt);
|
|
179
|
+
(0, vitest_1.expect)(result).toEqual([
|
|
180
|
+
{
|
|
181
|
+
role: 4,
|
|
182
|
+
content: '{"type":"execution-denied","reason":"User rejected tool execution"}',
|
|
183
|
+
metadata: { refCallId: 'call_denied' },
|
|
184
|
+
},
|
|
185
|
+
]);
|
|
186
|
+
});
|
|
187
|
+
(0, vitest_1.it)('tool-result with execution-denied output handles missing reason', () => {
|
|
188
|
+
const prompt = [
|
|
189
|
+
{
|
|
190
|
+
role: 'tool',
|
|
191
|
+
content: [
|
|
192
|
+
{
|
|
193
|
+
type: 'tool-result',
|
|
194
|
+
toolCallId: 'call_denied_no_reason',
|
|
195
|
+
toolName: 'someTool',
|
|
196
|
+
output: { type: 'execution-denied' },
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
];
|
|
201
|
+
const result = (0, prompt_converter_js_1.convertPrompt)(prompt);
|
|
202
|
+
(0, vitest_1.expect)(result).toEqual([
|
|
203
|
+
{
|
|
204
|
+
role: 4,
|
|
205
|
+
content: '{"type":"execution-denied"}',
|
|
206
|
+
metadata: { refCallId: 'call_denied_no_reason' },
|
|
207
|
+
},
|
|
208
|
+
]);
|
|
209
|
+
});
|
|
95
210
|
});
|
|
@@ -6,9 +6,6 @@ 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*(?:\{\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
9
|
function parseOpenAIToolCalls(responseText) {
|
|
13
10
|
if (!responseText.startsWith('TOOL_CALLS')) {
|
|
14
11
|
return null;
|
|
@@ -75,20 +72,6 @@ function pushText(parts, text) {
|
|
|
75
72
|
parts.push({ type: 'text', text });
|
|
76
73
|
}
|
|
77
74
|
}
|
|
78
|
-
function extractAnswerText(args) {
|
|
79
|
-
if (typeof args === 'string') {
|
|
80
|
-
return args;
|
|
81
|
-
}
|
|
82
|
-
if (args != null && typeof args === 'object') {
|
|
83
|
-
if ('answer' in args && typeof args.answer === 'string') {
|
|
84
|
-
return args.answer;
|
|
85
|
-
}
|
|
86
|
-
if ('text' in args && typeof args.text === 'string') {
|
|
87
|
-
return args.text;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return JSON.stringify(args);
|
|
91
|
-
}
|
|
92
75
|
function parseStringEnd(value, startIndex) {
|
|
93
76
|
let index = startIndex + 1;
|
|
94
77
|
let escaping = false;
|
|
@@ -255,7 +238,6 @@ function decodeResponseText(buffer) {
|
|
|
255
238
|
}
|
|
256
239
|
function convertResponse(buffer) {
|
|
257
240
|
let responseText = decodeResponseText(buffer);
|
|
258
|
-
responseText = responseText.replace(EMPTY_TOOL_CALLS_PATTERN, '');
|
|
259
241
|
responseText = responseText.replace(STOP_TOKEN, '');
|
|
260
242
|
const openaiToolCalls = parseOpenAIToolCalls(responseText);
|
|
261
243
|
if (openaiToolCalls) {
|
|
@@ -286,18 +268,13 @@ function convertResponse(buffer) {
|
|
|
286
268
|
cursor = malformedEnd;
|
|
287
269
|
continue;
|
|
288
270
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
toolCallId: `toolcall_${toolCallCount}`,
|
|
297
|
-
toolName,
|
|
298
|
-
input: parsedArgs.parsed,
|
|
299
|
-
});
|
|
300
|
-
}
|
|
271
|
+
toolCallCount += 1;
|
|
272
|
+
parts.push({
|
|
273
|
+
type: 'tool-call',
|
|
274
|
+
toolCallId: `toolcall_${toolCallCount}`,
|
|
275
|
+
toolName,
|
|
276
|
+
input: parsedArgs.parsed,
|
|
277
|
+
});
|
|
301
278
|
cursor = parsedArgs.endIndex;
|
|
302
279
|
}
|
|
303
280
|
return parts;
|
|
@@ -21,7 +21,12 @@ const response_converter_js_1 = require("./response-converter.js");
|
|
|
21
21
|
input: { query: 'prompt converter' },
|
|
22
22
|
},
|
|
23
23
|
{ type: 'text', text: ' between ' },
|
|
24
|
-
{
|
|
24
|
+
{
|
|
25
|
+
type: 'tool-call',
|
|
26
|
+
toolCallId: 'toolcall_2',
|
|
27
|
+
toolName: 'answer',
|
|
28
|
+
input: { answer: 'final answer' },
|
|
29
|
+
},
|
|
25
30
|
{ type: 'text', text: ' after' },
|
|
26
31
|
]);
|
|
27
32
|
});
|
|
@@ -48,7 +53,14 @@ const response_converter_js_1 = require("./response-converter.js");
|
|
|
48
53
|
payload.writeVarint(1, 150);
|
|
49
54
|
payload.writeString(2, '[TOOL_CALLS]answer[ARGS]{"answer":"final answer"}');
|
|
50
55
|
const result = (0, response_converter_js_1.convertResponse)(payload.toBuffer());
|
|
51
|
-
(0, vitest_1.expect)(result).toEqual([
|
|
56
|
+
(0, vitest_1.expect)(result).toEqual([
|
|
57
|
+
{
|
|
58
|
+
type: 'tool-call',
|
|
59
|
+
toolCallId: 'toolcall_1',
|
|
60
|
+
toolName: 'answer',
|
|
61
|
+
input: { answer: 'final answer' },
|
|
62
|
+
},
|
|
63
|
+
]);
|
|
52
64
|
});
|
|
53
65
|
(0, vitest_1.it)('protobuf payload - ignores metadata strings and keeps main text field', () => {
|
|
54
66
|
const payload = new protobuf_js_1.ProtobufEncoder();
|
|
@@ -62,76 +74,11 @@ const response_converter_js_1 = require("./response-converter.js");
|
|
|
62
74
|
const result = (0, response_converter_js_1.convertResponse)(compressed);
|
|
63
75
|
(0, vitest_1.expect)(result).toEqual([{ type: 'text', text: 'hello from gzip' }]);
|
|
64
76
|
});
|
|
65
|
-
(0, vitest_1.it)('strips empty TOOL_CALLS markers with stop token', () => {
|
|
66
|
-
const input = 'Hello world TOOL_CALLS0</s>{}';
|
|
67
|
-
const result = (0, response_converter_js_1.convertResponse)(Buffer.from(input, 'utf8'));
|
|
68
|
-
(0, vitest_1.expect)(result).toEqual([{ type: 'text', text: 'Hello world ' }]);
|
|
69
|
-
});
|
|
70
|
-
(0, vitest_1.it)('strips empty TOOL_CALLS markers without stop token', () => {
|
|
71
|
-
const input = 'Hello world TOOL_CALLS1{}';
|
|
72
|
-
const result = (0, response_converter_js_1.convertResponse)(Buffer.from(input, 'utf8'));
|
|
73
|
-
(0, vitest_1.expect)(result).toEqual([{ type: 'text', text: 'Hello world ' }]);
|
|
74
|
-
});
|
|
75
|
-
(0, vitest_1.it)('strips empty TOOL_CALLS markers with whitespace', () => {
|
|
76
|
-
const input = 'Text TOOL_CALLS2{ } more text';
|
|
77
|
-
const result = (0, response_converter_js_1.convertResponse)(Buffer.from(input, 'utf8'));
|
|
78
|
-
(0, vitest_1.expect)(result).toEqual([{ type: 'text', text: 'Text more text' }]);
|
|
79
|
-
});
|
|
80
77
|
(0, vitest_1.it)('strips standalone stop token', () => {
|
|
81
78
|
const input = 'Hello world</s>';
|
|
82
79
|
const result = (0, response_converter_js_1.convertResponse)(Buffer.from(input, 'utf8'));
|
|
83
80
|
(0, vitest_1.expect)(result).toEqual([{ type: 'text', text: 'Hello world' }]);
|
|
84
81
|
});
|
|
85
|
-
(0, vitest_1.it)('handles TOOL_CALLS with number prefix before stop token', () => {
|
|
86
|
-
const input = 'Text before TOOL_CALLS1</s>{} text after';
|
|
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 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
82
|
(0, vitest_1.it)('parses OpenAI-style TOOL_CALLS with numeric IDs', () => {
|
|
136
83
|
const input = 'TOOL_CALLS{"type":"function","function":{"name":3,"parameters":{"file_path":"/home/test","search_pattern":"binance"}}}{}';
|
|
137
84
|
const result = (0, response_converter_js_1.convertResponse)(Buffer.from(input, 'utf8'));
|
|
@@ -186,4 +133,16 @@ const response_converter_js_1 = require("./response-converter.js");
|
|
|
186
133
|
},
|
|
187
134
|
]);
|
|
188
135
|
});
|
|
136
|
+
(0, vitest_1.it)('handles OpenAI-style with string name "answer" - emits as tool-call', () => {
|
|
137
|
+
const input = 'TOOL_CALLS{"type":"function","function":{"name":"answer","parameters":{"answer":"final answer"}}}{}';
|
|
138
|
+
const result = (0, response_converter_js_1.convertResponse)(Buffer.from(input, 'utf8'));
|
|
139
|
+
(0, vitest_1.expect)(result).toEqual([
|
|
140
|
+
{
|
|
141
|
+
type: 'tool-call',
|
|
142
|
+
toolCallId: 'toolcall_1',
|
|
143
|
+
toolName: 'answer',
|
|
144
|
+
input: { answer: 'final answer' },
|
|
145
|
+
},
|
|
146
|
+
]);
|
|
147
|
+
});
|
|
189
148
|
});
|
|
@@ -59,9 +59,15 @@ class DevstralLanguageModel {
|
|
|
59
59
|
const responsePayloads = (0, connect_frame_js_1.connectFrameDecode)(responseFrame);
|
|
60
60
|
const payloads = responsePayloads.length > 0 ? responsePayloads : [responseFrame];
|
|
61
61
|
const content = payloads.flatMap((payload) => toV3Content((0, response_converter_js_1.convertResponse)(payload)));
|
|
62
|
+
const unified = content.some((part) => part.type === 'tool-call')
|
|
63
|
+
? 'tool-calls'
|
|
64
|
+
: 'stop';
|
|
62
65
|
return {
|
|
63
66
|
content,
|
|
64
|
-
finishReason:
|
|
67
|
+
finishReason: {
|
|
68
|
+
unified,
|
|
69
|
+
raw: undefined,
|
|
70
|
+
},
|
|
65
71
|
usage: emptyUsage(),
|
|
66
72
|
warnings: [],
|
|
67
73
|
};
|
|
@@ -89,6 +95,7 @@ class DevstralLanguageModel {
|
|
|
89
95
|
options.abortSignal?.addEventListener('abort', abortHandler, { once: true });
|
|
90
96
|
let textSegmentId = null;
|
|
91
97
|
let textSegmentCounter = 0;
|
|
98
|
+
let hasToolCalls = false;
|
|
92
99
|
let pending = Buffer.alloc(0);
|
|
93
100
|
const closeTextSegment = () => {
|
|
94
101
|
if (textSegmentId == null) {
|
|
@@ -138,6 +145,7 @@ class DevstralLanguageModel {
|
|
|
138
145
|
continue;
|
|
139
146
|
}
|
|
140
147
|
closeTextSegment();
|
|
148
|
+
hasToolCalls = true;
|
|
141
149
|
safeEnqueue(controller, {
|
|
142
150
|
type: 'tool-input-start',
|
|
143
151
|
id: part.toolCallId,
|
|
@@ -161,10 +169,13 @@ class DevstralLanguageModel {
|
|
|
161
169
|
return;
|
|
162
170
|
}
|
|
163
171
|
closeTextSegment();
|
|
172
|
+
const unified = hasToolCalls ? 'tool-calls' : 'stop';
|
|
164
173
|
safeEnqueue(controller, {
|
|
165
174
|
type: 'finish',
|
|
166
|
-
finishReason:
|
|
167
|
-
|
|
175
|
+
finishReason: {
|
|
176
|
+
unified,
|
|
177
|
+
raw: undefined,
|
|
178
|
+
},
|
|
168
179
|
usage: emptyUsage(),
|
|
169
180
|
});
|
|
170
181
|
safeClose(controller);
|
|
@@ -178,8 +189,10 @@ class DevstralLanguageModel {
|
|
|
178
189
|
});
|
|
179
190
|
safeEnqueue(controller, {
|
|
180
191
|
type: 'finish',
|
|
181
|
-
finishReason:
|
|
182
|
-
|
|
192
|
+
finishReason: {
|
|
193
|
+
unified: 'error',
|
|
194
|
+
raw: undefined,
|
|
195
|
+
},
|
|
183
196
|
usage: emptyUsage(),
|
|
184
197
|
});
|
|
185
198
|
}
|
|
@@ -253,31 +266,35 @@ function emptyUsage() {
|
|
|
253
266
|
}
|
|
254
267
|
function toV3Content(parts) {
|
|
255
268
|
return parts.map((part) => {
|
|
256
|
-
if (part.type
|
|
257
|
-
return
|
|
258
|
-
type: 'tool-call',
|
|
259
|
-
toolCallId: part.toolCallId,
|
|
260
|
-
toolName: part.toolName,
|
|
261
|
-
input: JSON.stringify(part.input),
|
|
262
|
-
};
|
|
269
|
+
if (part.type !== 'tool-call') {
|
|
270
|
+
return part;
|
|
263
271
|
}
|
|
264
|
-
|
|
272
|
+
const input = typeof part.input === 'string' ? part.input : JSON.stringify(part.input);
|
|
273
|
+
return {
|
|
274
|
+
type: 'tool-call',
|
|
275
|
+
toolCallId: part.toolCallId,
|
|
276
|
+
toolName: part.toolName,
|
|
277
|
+
input,
|
|
278
|
+
};
|
|
265
279
|
});
|
|
266
280
|
}
|
|
281
|
+
function isFunctionTool(tool) {
|
|
282
|
+
return tool.type === 'function';
|
|
283
|
+
}
|
|
267
284
|
function buildGenerateRequest(input) {
|
|
268
285
|
const request = new protobuf_js_1.ProtobufEncoder();
|
|
269
286
|
request.writeMessage(1, buildMetadata(input.apiKey, input.jwt));
|
|
270
287
|
for (const message of input.messages) {
|
|
271
288
|
request.writeMessage(2, buildMessage(message));
|
|
272
289
|
}
|
|
273
|
-
|
|
274
|
-
if (
|
|
275
|
-
const toolsArray =
|
|
290
|
+
const functionTools = input.tools?.filter(isFunctionTool) ?? [];
|
|
291
|
+
if (functionTools.length > 0) {
|
|
292
|
+
const toolsArray = functionTools.map((tool) => ({
|
|
276
293
|
type: 'function',
|
|
277
294
|
function: {
|
|
278
|
-
name,
|
|
295
|
+
name: tool.name,
|
|
279
296
|
description: tool.description ?? '',
|
|
280
|
-
parameters: tool.
|
|
297
|
+
parameters: tool.inputSchema,
|
|
281
298
|
},
|
|
282
299
|
}));
|
|
283
300
|
request.writeString(3, JSON.stringify(toolsArray));
|