@bxb1337/windsurf-fast-context 1.0.7 → 1.0.9
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 +43 -22
- package/dist/cjs/model/devstral-language-model.test.js +194 -19
- package/dist/cjs/protocol/connect-frame.js +10 -4
- package/dist/cjs/protocol/connect-frame.test.js +78 -5
- 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 +43 -22
- package/dist/esm/model/devstral-language-model.test.js +194 -19
- package/dist/esm/protocol/connect-frame.js +8 -3
- package/dist/esm/protocol/connect-frame.test.js +79 -6
- package/dist/model/devstral-language-model.d.ts +2 -91
- package/dist/protocol/connect-frame.d.ts +6 -1
- 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
|
});
|
|
@@ -56,12 +56,18 @@ class DevstralLanguageModel {
|
|
|
56
56
|
const requestFrame = (0, connect_frame_js_1.connectFrameEncode)(requestPayload);
|
|
57
57
|
const headers = createConnectHeaders(this.headers);
|
|
58
58
|
const responseFrame = await this.transport.postUnary(`${this.baseURL}${API_SERVICE_PATH}${DEVSTRAL_STREAM_PATH}`, requestFrame, headers);
|
|
59
|
-
const responsePayloads = (0, connect_frame_js_1.connectFrameDecode)(responseFrame);
|
|
59
|
+
const { payloads: 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) {
|
|
@@ -103,7 +110,7 @@ class DevstralLanguageModel {
|
|
|
103
110
|
try {
|
|
104
111
|
safeEnqueue(controller, { type: 'stream-start', warnings: [] });
|
|
105
112
|
safeEnqueue(controller, { type: 'response-metadata', modelId: this.modelId });
|
|
106
|
-
while (!isAborted(options.abortSignal)) {
|
|
113
|
+
outerLoop: while (!isAborted(options.abortSignal)) {
|
|
107
114
|
const next = await reader.read();
|
|
108
115
|
if (next.done) {
|
|
109
116
|
break;
|
|
@@ -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,
|
|
@@ -154,6 +162,9 @@ class DevstralLanguageModel {
|
|
|
154
162
|
});
|
|
155
163
|
safeEnqueue(controller, part);
|
|
156
164
|
}
|
|
165
|
+
if (frameResult.isEndStream) {
|
|
166
|
+
break outerLoop;
|
|
167
|
+
}
|
|
157
168
|
}
|
|
158
169
|
}
|
|
159
170
|
if (isAborted(options.abortSignal)) {
|
|
@@ -161,10 +172,13 @@ class DevstralLanguageModel {
|
|
|
161
172
|
return;
|
|
162
173
|
}
|
|
163
174
|
closeTextSegment();
|
|
175
|
+
const unified = hasToolCalls ? 'tool-calls' : 'stop';
|
|
164
176
|
safeEnqueue(controller, {
|
|
165
177
|
type: 'finish',
|
|
166
|
-
finishReason:
|
|
167
|
-
|
|
178
|
+
finishReason: {
|
|
179
|
+
unified,
|
|
180
|
+
raw: undefined,
|
|
181
|
+
},
|
|
168
182
|
usage: emptyUsage(),
|
|
169
183
|
});
|
|
170
184
|
safeClose(controller);
|
|
@@ -178,8 +192,10 @@ class DevstralLanguageModel {
|
|
|
178
192
|
});
|
|
179
193
|
safeEnqueue(controller, {
|
|
180
194
|
type: 'finish',
|
|
181
|
-
finishReason:
|
|
182
|
-
|
|
195
|
+
finishReason: {
|
|
196
|
+
unified: 'error',
|
|
197
|
+
raw: undefined,
|
|
198
|
+
},
|
|
183
199
|
usage: emptyUsage(),
|
|
184
200
|
});
|
|
185
201
|
}
|
|
@@ -206,10 +222,11 @@ function readNextConnectFrame(buffer) {
|
|
|
206
222
|
return null;
|
|
207
223
|
}
|
|
208
224
|
const frame = buffer.subarray(0, frameLength);
|
|
209
|
-
const
|
|
225
|
+
const { payloads, isEndStream } = (0, connect_frame_js_1.connectFrameDecode)(frame);
|
|
210
226
|
return {
|
|
211
|
-
payload:
|
|
227
|
+
payload: payloads[0] ?? Buffer.alloc(0),
|
|
212
228
|
rest: buffer.subarray(frameLength),
|
|
229
|
+
isEndStream,
|
|
213
230
|
};
|
|
214
231
|
}
|
|
215
232
|
function safeEnqueue(controller, part) {
|
|
@@ -253,31 +270,35 @@ function emptyUsage() {
|
|
|
253
270
|
}
|
|
254
271
|
function toV3Content(parts) {
|
|
255
272
|
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
|
-
};
|
|
273
|
+
if (part.type !== 'tool-call') {
|
|
274
|
+
return part;
|
|
263
275
|
}
|
|
264
|
-
|
|
276
|
+
const input = typeof part.input === 'string' ? part.input : JSON.stringify(part.input);
|
|
277
|
+
return {
|
|
278
|
+
type: 'tool-call',
|
|
279
|
+
toolCallId: part.toolCallId,
|
|
280
|
+
toolName: part.toolName,
|
|
281
|
+
input,
|
|
282
|
+
};
|
|
265
283
|
});
|
|
266
284
|
}
|
|
285
|
+
function isFunctionTool(tool) {
|
|
286
|
+
return tool.type === 'function';
|
|
287
|
+
}
|
|
267
288
|
function buildGenerateRequest(input) {
|
|
268
289
|
const request = new protobuf_js_1.ProtobufEncoder();
|
|
269
290
|
request.writeMessage(1, buildMetadata(input.apiKey, input.jwt));
|
|
270
291
|
for (const message of input.messages) {
|
|
271
292
|
request.writeMessage(2, buildMessage(message));
|
|
272
293
|
}
|
|
273
|
-
|
|
274
|
-
if (
|
|
275
|
-
const toolsArray =
|
|
294
|
+
const functionTools = input.tools?.filter(isFunctionTool) ?? [];
|
|
295
|
+
if (functionTools.length > 0) {
|
|
296
|
+
const toolsArray = functionTools.map((tool) => ({
|
|
276
297
|
type: 'function',
|
|
277
298
|
function: {
|
|
278
|
-
name,
|
|
299
|
+
name: tool.name,
|
|
279
300
|
description: tool.description ?? '',
|
|
280
|
-
parameters: tool.
|
|
301
|
+
parameters: tool.inputSchema,
|
|
281
302
|
},
|
|
282
303
|
}));
|
|
283
304
|
request.writeString(3, JSON.stringify(toolsArray));
|