@galaxy-yearn/codex-deepseek-gateway 0.1.6 → 0.2.0
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/README.md +58 -26
- package/bin/codex-deepseek-gateway.js +7 -0
- package/config/codex-model-catalog.json +24 -28
- package/config/codex-model-catalog.zh.json +126 -0
- package/config/frontend-design-guidance/en.md +33 -0
- package/config/frontend-design-guidance/zh.md +33 -0
- package/config/gateway.example.json +1 -0
- package/package.json +4 -1
- package/src/codex-launch.js +12 -3
- package/src/common.js +18 -3
- package/src/config.js +15 -2
- package/src/firecrawl.js +7 -16
- package/src/local-config.js +6 -2
- package/src/model-map.js +6 -20
- package/src/prompt-language.js +13 -0
- package/src/protocol.js +1319 -450
- package/src/server.js +510 -350
- package/src/session-store.js +228 -13
- package/src/tavily.js +2 -11
- package/src/upstream.js +14 -12
- package/src/web-search-emulator.js +12 -11
- package/state/sessions.example.json +72 -0
package/src/protocol.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import { generateId, isObject, normalizeRole, parseJsonObject, safeJsonParse, toText } from './common.js';
|
|
2
4
|
import {
|
|
3
5
|
DEFAULT_MODEL_ALIASES,
|
|
4
6
|
deepseekReasoningPayload,
|
|
@@ -33,12 +35,19 @@ const TOOL_OUTPUT_TYPES = new Set([
|
|
|
33
35
|
'image_generation_call_output',
|
|
34
36
|
]);
|
|
35
37
|
const CHAT_HISTORY_IGNORED_TOOL_ITEM_TYPES = new Set([
|
|
36
|
-
'web_search_call',
|
|
37
38
|
'web_search_call_output',
|
|
38
|
-
'tool_search_call',
|
|
39
|
-
'tool_search_output',
|
|
40
39
|
]);
|
|
41
40
|
const EMULATED_HOSTED_TOOL_TYPES = new Set(['web_search', 'web_search_preview']);
|
|
41
|
+
const BRIDGED_RESPONSES_TOOL_TYPES = new Set(['tool_search']);
|
|
42
|
+
const UNSUPPORTED_HOSTED_TOOL_TYPES = new Set([
|
|
43
|
+
'file_search',
|
|
44
|
+
'code_interpreter',
|
|
45
|
+
'image_generation',
|
|
46
|
+
'computer',
|
|
47
|
+
'computer_use',
|
|
48
|
+
'mcp',
|
|
49
|
+
'local_shell',
|
|
50
|
+
]);
|
|
42
51
|
const DEEPSEEK_TOOL_INSTRUCTIONS_MARKER = 'The tools in this request are real callable functions available now.';
|
|
43
52
|
const DEEPSEEK_TOOL_INSTRUCTIONS = [
|
|
44
53
|
DEEPSEEK_TOOL_INSTRUCTIONS_MARKER,
|
|
@@ -46,8 +55,30 @@ const DEEPSEEK_TOOL_INSTRUCTIONS = [
|
|
|
46
55
|
'Do not write pseudo XML, DSML, or JSON tool calls in assistant text.',
|
|
47
56
|
'Do not claim a listed function is unavailable because its name is unfamiliar.',
|
|
48
57
|
].join(' ');
|
|
49
|
-
const
|
|
50
|
-
const
|
|
58
|
+
export const INTERNAL_COMMENTARY_TOOL = 'commentary';
|
|
59
|
+
const DEEPSEEK_COMMENTARY_INSTRUCTIONS = 'The user cannot see your thinking; commentary is the only progress they see between tool batches. Include one commentary call, first, in every tool_calls batch, with one or two sentences on what you are doing. Never call it alone; never put the final answer in it — deliver the final answer as plain assistant text with no tool calls.';
|
|
60
|
+
const INTERNAL_COMMENTARY_TOOL_DEFINITION = {
|
|
61
|
+
type: 'function',
|
|
62
|
+
function: {
|
|
63
|
+
name: INTERNAL_COMMENTARY_TOOL,
|
|
64
|
+
description: "Post a short progress update the user sees immediately. The user cannot see your thinking; this is the only progress they see between tool batches. Include one commentary call, first, in every tool_calls batch, e.g. commentary(text: 'Config loader read; now checking how sessions persist.'). One or two sentences. Never call it alone; never put the final answer in it.",
|
|
65
|
+
parameters: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: {
|
|
68
|
+
text: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
description: 'One or two short sentences on what you are doing or just found.',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
required: ['text'],
|
|
74
|
+
additionalProperties: false,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
const CHAT_FUNCTION_NAME_MAX_CHARS = 64;
|
|
79
|
+
const TOOL_NAME_HASH_CHARS = 8;
|
|
80
|
+
const DEEPSEEK_TOOL_DESCRIPTION_CHARS = 900;
|
|
81
|
+
const DEEPSEEK_SCHEMA_DESCRIPTION_CHARS = 480;
|
|
51
82
|
const CODEX_REASONING_EFFORTS = ['low', 'medium', 'high', 'xhigh'];
|
|
52
83
|
const CODEX_SPAWN_AGENT_TOOL_NAME = 'multi_agent_v1__spawn_agent';
|
|
53
84
|
const CODEX_TOOL_SEARCH_TOOL_NAME = 'tool_search';
|
|
@@ -57,23 +88,27 @@ function jsonString(value) {
|
|
|
57
88
|
return JSON.stringify(value ?? {});
|
|
58
89
|
}
|
|
59
90
|
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
} catch {
|
|
67
|
-
return {};
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function sanitizeFunctionName(name, fallback = 'tool_call') {
|
|
72
|
-
const candidate = String(name || fallback)
|
|
91
|
+
function sanitizeFunctionName(name, fallback = 'tool_call', maxChars = CHAT_FUNCTION_NAME_MAX_CHARS) {
|
|
92
|
+
const fallbackName = String(fallback || 'tool_call')
|
|
93
|
+
.replace(/[^a-zA-Z0-9_-]/g, '_')
|
|
94
|
+
.replace(/^_+/, '')
|
|
95
|
+
.slice(0, maxChars) || 'tool_call';
|
|
96
|
+
const candidate = String(name || fallbackName)
|
|
73
97
|
.replace(/[^a-zA-Z0-9_-]/g, '_')
|
|
74
98
|
.replace(/^_+/, '')
|
|
75
|
-
.slice(0,
|
|
76
|
-
return candidate ||
|
|
99
|
+
.slice(0, maxChars);
|
|
100
|
+
return candidate || fallbackName;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function stableToolNameHash(parts) {
|
|
104
|
+
const hashInput = parts.map((part) => String(part ?? '')).join('\0');
|
|
105
|
+
return createHash('sha256').update(hashInput).digest('hex').slice(0, TOOL_NAME_HASH_CHARS);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function appendToolNameHash(name, hash, fallback = 'tool_call') {
|
|
109
|
+
const suffix = `__${hash}`;
|
|
110
|
+
const headChars = Math.max(1, CHAT_FUNCTION_NAME_MAX_CHARS - suffix.length);
|
|
111
|
+
return `${sanitizeFunctionName(name, fallback, headChars)}${suffix}`;
|
|
77
112
|
}
|
|
78
113
|
|
|
79
114
|
function toolNamespace(tool) {
|
|
@@ -88,22 +123,42 @@ function toolBaseName(tool, fallback = 'tool_call') {
|
|
|
88
123
|
}
|
|
89
124
|
|
|
90
125
|
function encodeToolName(namespace, name, fallback = 'tool_call') {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
126
|
+
const rawNamespace = String(namespace || '').trim();
|
|
127
|
+
const rawName = String(name || fallback);
|
|
128
|
+
const baseName = sanitizeFunctionName(rawName, fallback);
|
|
129
|
+
const encodedNamespace = rawNamespace ? sanitizeFunctionName(rawNamespace, 'namespace') : '';
|
|
130
|
+
const candidate = encodedNamespace ? `${encodedNamespace}__${baseName}` : baseName;
|
|
131
|
+
const encoded = sanitizeFunctionName(candidate, fallback);
|
|
132
|
+
const rawCandidate = rawNamespace ? `${rawNamespace}__${rawName}` : rawName;
|
|
133
|
+
if (encoded === rawCandidate && rawCandidate.length <= CHAT_FUNCTION_NAME_MAX_CHARS) {
|
|
134
|
+
return encoded;
|
|
135
|
+
}
|
|
136
|
+
return appendToolNameHash(candidate, stableToolNameHash([rawNamespace, rawName, fallback]), fallback);
|
|
95
137
|
}
|
|
96
138
|
|
|
97
139
|
function decodedToolName(name, toolNames) {
|
|
98
140
|
const value = String(name || '');
|
|
99
141
|
const known = toolNames?.get(value);
|
|
100
|
-
|
|
142
|
+
if (!known) return { name: value };
|
|
143
|
+
return omitUndefined({
|
|
144
|
+
namespace: known.namespace,
|
|
145
|
+
name: known.original_name || known.name,
|
|
146
|
+
});
|
|
101
147
|
}
|
|
102
148
|
|
|
103
149
|
function isCodexToolSearchTool(name) {
|
|
104
150
|
return String(name || '') === CODEX_TOOL_SEARCH_TOOL_NAME;
|
|
105
151
|
}
|
|
106
152
|
|
|
153
|
+
function toolChoiceDisablesTools(toolChoice) {
|
|
154
|
+
if (String(toolChoice || '').toLowerCase() === 'none') return true;
|
|
155
|
+
return isObject(toolChoice) && String(toolChoice.type || '').toLowerCase() === 'none';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function hasChatToolCalls(message) {
|
|
159
|
+
return Array.isArray(message?.tool_calls) && message.tool_calls.length > 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
107
162
|
function omitUndefined(value) {
|
|
108
163
|
if (!isObject(value)) return value;
|
|
109
164
|
const result = {};
|
|
@@ -120,10 +175,18 @@ function compactText(value) {
|
|
|
120
175
|
function shortenText(value, maxChars) {
|
|
121
176
|
const text = compactText(value);
|
|
122
177
|
if (!text || text.length <= maxChars) return text;
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
|
|
178
|
+
if (maxChars <= 16) return `${text.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
|
|
179
|
+
const marker = ' ... ';
|
|
180
|
+
const contentChars = maxChars - marker.length;
|
|
181
|
+
const headChars = Math.max(24, Math.floor(contentChars * 0.72));
|
|
182
|
+
const tailChars = Math.max(12, contentChars - headChars);
|
|
183
|
+
const headSlice = text.slice(0, headChars);
|
|
184
|
+
const headBreak = Math.max(headSlice.lastIndexOf('. '), headSlice.lastIndexOf('; '), headSlice.lastIndexOf(', '), headSlice.lastIndexOf(' '));
|
|
185
|
+
const head = headSlice.slice(0, headBreak >= Math.floor(headChars * 0.55) ? headBreak : headSlice.length).trimEnd();
|
|
186
|
+
const tailSlice = text.slice(-tailChars);
|
|
187
|
+
const tailBreak = Math.max(tailSlice.indexOf('. '), tailSlice.indexOf('; '), tailSlice.indexOf(', '), tailSlice.indexOf(' '));
|
|
188
|
+
const tail = tailSlice.slice(tailBreak >= 0 && tailBreak <= Math.floor(tailChars * 0.45) ? tailBreak + 1 : 0).trimStart();
|
|
189
|
+
return `${head}${marker}${tail}`.slice(0, maxChars);
|
|
127
190
|
}
|
|
128
191
|
|
|
129
192
|
function textPart(text) {
|
|
@@ -246,9 +309,29 @@ function chatToolCallFromResponseItem(item) {
|
|
|
246
309
|
};
|
|
247
310
|
}
|
|
248
311
|
|
|
312
|
+
const GATEWAY_ENCRYPTED_REASONING_PREFIX = 'dsgw1:';
|
|
313
|
+
|
|
314
|
+
function encodeGatewayReasoning(text) {
|
|
315
|
+
const value = String(text ?? '');
|
|
316
|
+
if (!value) return null;
|
|
317
|
+
return `${GATEWAY_ENCRYPTED_REASONING_PREFIX}${Buffer.from(value, 'utf8').toString('base64')}`;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function decodeGatewayReasoning(encryptedContent) {
|
|
321
|
+
if (typeof encryptedContent !== 'string') return '';
|
|
322
|
+
if (!encryptedContent.startsWith(GATEWAY_ENCRYPTED_REASONING_PREFIX)) return '';
|
|
323
|
+
try {
|
|
324
|
+
return Buffer.from(encryptedContent.slice(GATEWAY_ENCRYPTED_REASONING_PREFIX.length), 'base64').toString('utf8');
|
|
325
|
+
} catch {
|
|
326
|
+
return '';
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
249
330
|
function reasoningTextFromItem(item) {
|
|
250
331
|
if (!isObject(item)) return '';
|
|
251
332
|
if (typeof item.reasoning_content === 'string') return normalizeReasoningContent(item.reasoning_content);
|
|
333
|
+
const decoded = decodeGatewayReasoning(item.encrypted_content);
|
|
334
|
+
if (decoded) return normalizeReasoningContent(decoded);
|
|
252
335
|
const rawParts = [];
|
|
253
336
|
if (typeof item.text === 'string') rawParts.push(item.text);
|
|
254
337
|
if (typeof item.content === 'string') rawParts.push(item.content);
|
|
@@ -281,7 +364,7 @@ function normalizeReasoningDisplayText(text) {
|
|
|
281
364
|
.replace(/^[ \t]{0,3}(?:`{3,}|~{3,}).*$/, '')
|
|
282
365
|
.replace(/^[ \t]{0,3}#{1,6}[ \t]+/, '')
|
|
283
366
|
.replace(/^[ \t]{0,3}>[ \t]?/, '')
|
|
284
|
-
.replace(/^[ \t]*[-*+][ \t]+(?:\[[ xX]\][ \t]+)?/, '
|
|
367
|
+
.replace(/^[ \t]*[-*+][ \t]+(?:\[[ xX]\][ \t]+)?/, '• ')
|
|
285
368
|
.replace(/^[ \t]*(\d+)\.[ \t]+/, '$1) ')
|
|
286
369
|
.replace(/!\[([^\]]*)\]\([^)]+\)/g, '$1')
|
|
287
370
|
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
|
|
@@ -299,7 +382,36 @@ function reasoningDisplayText(text) {
|
|
|
299
382
|
return normalizeReasoningDisplayText(text);
|
|
300
383
|
}
|
|
301
384
|
|
|
385
|
+
function compactToolSearchOutputTool(tool) {
|
|
386
|
+
if (!isObject(tool)) return null;
|
|
387
|
+
const namespace = toolNamespace(tool);
|
|
388
|
+
const name = toolBaseName(tool, '');
|
|
389
|
+
if (!name) return null;
|
|
390
|
+
const schema = normalizeJsonSchemaObject(tool.function?.parameters ?? tool.parameters ?? tool.input_schema);
|
|
391
|
+
const properties = Object.keys(isObject(schema.properties) ? schema.properties : {});
|
|
392
|
+
const required = Array.isArray(schema.required) ? schema.required.map(String) : [];
|
|
393
|
+
const description = tool.function?.description ?? tool.description ?? '';
|
|
394
|
+
return omitUndefined({
|
|
395
|
+
name: encodeToolName(namespace, name),
|
|
396
|
+
description: description ? shortenText(description, 220) : undefined,
|
|
397
|
+
required: required.length ? required : undefined,
|
|
398
|
+
properties: properties.length ? properties : undefined,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
302
402
|
function toolOutputContent(item) {
|
|
403
|
+
if (item?.type === 'tool_search_output' && Array.isArray(item.tools)) {
|
|
404
|
+
const tools = expandTools(item.tools)
|
|
405
|
+
.map(compactToolSearchOutputTool)
|
|
406
|
+
.filter(Boolean);
|
|
407
|
+
return JSON.stringify(omitUndefined({
|
|
408
|
+
status: item.status || 'completed',
|
|
409
|
+
note: tools.length
|
|
410
|
+
? 'Discovered tools are loaded into the current tool list and can be called directly by these names.'
|
|
411
|
+
: undefined,
|
|
412
|
+
discovered_tools: tools,
|
|
413
|
+
}));
|
|
414
|
+
}
|
|
303
415
|
const output = item.output ?? item.content ?? item.result ?? item.error ?? '';
|
|
304
416
|
if (typeof output === 'string') return output;
|
|
305
417
|
if (Array.isArray(output)) return contentToChatContent(output);
|
|
@@ -330,46 +442,127 @@ function normalizeMessage(message) {
|
|
|
330
442
|
return normalized;
|
|
331
443
|
}
|
|
332
444
|
|
|
445
|
+
function assistantMessageIsEmpty(message) {
|
|
446
|
+
if (hasChatToolCalls(message)) return false;
|
|
447
|
+
const content = message?.content;
|
|
448
|
+
if (typeof content === 'string') return content.length === 0;
|
|
449
|
+
if (Array.isArray(content)) return content.length === 0;
|
|
450
|
+
return content == null;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function webSearchCallNote(item) {
|
|
454
|
+
const action = isObject(item?.action) ? item.action : {};
|
|
455
|
+
const actionType = action.type || 'search';
|
|
456
|
+
if (actionType === 'open_page') {
|
|
457
|
+
return action.url ? `Opened page ${action.url}.` : 'Opened a web page.';
|
|
458
|
+
}
|
|
459
|
+
if (actionType === 'find_in_page') {
|
|
460
|
+
const target = action.url || 'a web page';
|
|
461
|
+
return action.query ? `Searched within ${target} for "${action.query}".` : `Searched within ${target}.`;
|
|
462
|
+
}
|
|
463
|
+
const query = action.query ? `"${action.query}"` : 'the web';
|
|
464
|
+
const sources = (Array.isArray(action.sources) ? action.sources : [])
|
|
465
|
+
.filter(isObject)
|
|
466
|
+
.slice(0, 3)
|
|
467
|
+
.map((source) => (source.title && source.url ? `${source.title} <${source.url}>` : source.url || source.title))
|
|
468
|
+
.filter(Boolean)
|
|
469
|
+
.join('; ');
|
|
470
|
+
return `Searched the web for ${query}${sources ? ` (sources: ${sources})` : ''}.`;
|
|
471
|
+
}
|
|
472
|
+
|
|
333
473
|
function extractMessagesFromResponsesInput(input) {
|
|
334
474
|
if (Array.isArray(input)) {
|
|
335
475
|
const messages = [];
|
|
336
476
|
let pendingUserContent = [];
|
|
337
477
|
let pendingReasoningContent = '';
|
|
338
478
|
let pendingAssistantToolMessage = null;
|
|
479
|
+
let pendingAssistantMessage = null;
|
|
480
|
+
let pendingWebSearchNotes = [];
|
|
339
481
|
const flushPendingUserContent = () => {
|
|
340
482
|
if (!pendingUserContent.length) return;
|
|
341
483
|
messages.push({ role: 'user', content: contentToChatContent(pendingUserContent) });
|
|
342
484
|
pendingUserContent = [];
|
|
343
485
|
};
|
|
486
|
+
const flushPendingWebSearchNotes = () => {
|
|
487
|
+
if (!pendingWebSearchNotes.length) return;
|
|
488
|
+
messages.push({
|
|
489
|
+
role: 'assistant',
|
|
490
|
+
content: `[Earlier web activity] ${pendingWebSearchNotes.join(' ')}`,
|
|
491
|
+
});
|
|
492
|
+
pendingWebSearchNotes = [];
|
|
493
|
+
};
|
|
344
494
|
const flushPendingAssistantToolMessage = () => {
|
|
345
495
|
if (!pendingAssistantToolMessage) return;
|
|
346
496
|
messages.push(pendingAssistantToolMessage);
|
|
347
497
|
pendingAssistantToolMessage = null;
|
|
348
498
|
pendingReasoningContent = '';
|
|
349
499
|
};
|
|
500
|
+
const flushPendingAssistantMessage = () => {
|
|
501
|
+
if (!pendingAssistantMessage) return;
|
|
502
|
+
const message = pendingAssistantMessage;
|
|
503
|
+
pendingAssistantMessage = null;
|
|
504
|
+
if (assistantMessageIsEmpty(message)) {
|
|
505
|
+
if (message.reasoning_content && !pendingReasoningContent) {
|
|
506
|
+
pendingReasoningContent = message.reasoning_content;
|
|
507
|
+
}
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
messages.push(message);
|
|
511
|
+
};
|
|
512
|
+
const flushAssistantState = () => {
|
|
513
|
+
flushPendingAssistantToolMessage();
|
|
514
|
+
flushPendingAssistantMessage();
|
|
515
|
+
};
|
|
350
516
|
const ensurePendingAssistantToolMessage = () => {
|
|
351
517
|
if (!pendingAssistantToolMessage) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
tool_calls
|
|
356
|
-
|
|
357
|
-
|
|
518
|
+
if (pendingAssistantMessage) {
|
|
519
|
+
pendingAssistantToolMessage = pendingAssistantMessage;
|
|
520
|
+
pendingAssistantMessage = null;
|
|
521
|
+
if (!Array.isArray(pendingAssistantToolMessage.tool_calls)) {
|
|
522
|
+
pendingAssistantToolMessage.tool_calls = [];
|
|
523
|
+
}
|
|
524
|
+
} else {
|
|
525
|
+
pendingAssistantToolMessage = {
|
|
526
|
+
role: 'assistant',
|
|
527
|
+
content: '',
|
|
528
|
+
tool_calls: [],
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
if (!pendingAssistantToolMessage.reasoning_content && pendingReasoningContent) {
|
|
358
532
|
pendingAssistantToolMessage.reasoning_content = pendingReasoningContent;
|
|
359
533
|
}
|
|
360
534
|
}
|
|
361
535
|
return pendingAssistantToolMessage;
|
|
362
536
|
};
|
|
537
|
+
const pushRoleMessage = (item) => {
|
|
538
|
+
flushPendingUserContent();
|
|
539
|
+
flushPendingAssistantToolMessage();
|
|
540
|
+
flushPendingWebSearchNotes();
|
|
541
|
+
flushPendingAssistantMessage();
|
|
542
|
+
const normalized = normalizeMessage(item);
|
|
543
|
+
if (!normalized) return;
|
|
544
|
+
if (normalized.role === 'assistant' && !normalized.reasoning_content && pendingReasoningContent) {
|
|
545
|
+
normalized.reasoning_content = pendingReasoningContent;
|
|
546
|
+
pendingReasoningContent = '';
|
|
547
|
+
}
|
|
548
|
+
if (normalized.role === 'assistant' && !hasChatToolCalls(normalized)) {
|
|
549
|
+
pendingAssistantMessage = normalized;
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
messages.push(normalized);
|
|
553
|
+
};
|
|
363
554
|
|
|
364
555
|
for (const item of input) {
|
|
365
556
|
if (typeof item === 'string') {
|
|
366
|
-
|
|
557
|
+
flushAssistantState();
|
|
558
|
+
flushPendingWebSearchNotes();
|
|
367
559
|
pendingUserContent.push({ type: 'input_text', text: item });
|
|
368
560
|
continue;
|
|
369
561
|
}
|
|
370
562
|
if (!isObject(item)) continue;
|
|
371
563
|
if (isInputContentPart(item)) {
|
|
372
|
-
|
|
564
|
+
flushAssistantState();
|
|
565
|
+
flushPendingWebSearchNotes();
|
|
373
566
|
pendingUserContent.push(item);
|
|
374
567
|
continue;
|
|
375
568
|
}
|
|
@@ -378,45 +571,40 @@ function extractMessagesFromResponsesInput(input) {
|
|
|
378
571
|
continue;
|
|
379
572
|
}
|
|
380
573
|
if (item.type === 'message' && item.role) {
|
|
574
|
+
pushRoleMessage(item);
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
if (item.type === 'web_search_call') {
|
|
381
578
|
flushPendingUserContent();
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
if (normalized?.role === 'assistant' && !normalized.reasoning_content && pendingReasoningContent) {
|
|
385
|
-
normalized.reasoning_content = pendingReasoningContent;
|
|
386
|
-
pendingReasoningContent = '';
|
|
387
|
-
}
|
|
388
|
-
if (normalized) messages.push(normalized);
|
|
579
|
+
flushAssistantState();
|
|
580
|
+
pendingWebSearchNotes.push(webSearchCallNote(item));
|
|
389
581
|
continue;
|
|
390
582
|
}
|
|
391
583
|
if (CHAT_HISTORY_IGNORED_TOOL_ITEM_TYPES.has(item.type)) {
|
|
392
584
|
flushPendingUserContent();
|
|
393
|
-
|
|
585
|
+
flushAssistantState();
|
|
394
586
|
continue;
|
|
395
587
|
}
|
|
396
588
|
if (TOOL_OUTPUT_TYPES.has(item.type) || String(item.type || '').endsWith('_call_output')) {
|
|
397
589
|
flushPendingUserContent();
|
|
398
|
-
|
|
590
|
+
flushPendingWebSearchNotes();
|
|
591
|
+
flushAssistantState();
|
|
399
592
|
messages.push(responseToolOutputToMessage(item));
|
|
400
593
|
continue;
|
|
401
594
|
}
|
|
402
595
|
if (TOOL_CALL_TYPES.has(item.type) || String(item.type || '').endsWith('_call')) {
|
|
403
596
|
flushPendingUserContent();
|
|
597
|
+
flushPendingWebSearchNotes();
|
|
404
598
|
ensurePendingAssistantToolMessage().tool_calls.push(chatToolCallFromResponseItem(item));
|
|
405
599
|
continue;
|
|
406
600
|
}
|
|
407
601
|
if (item.role) {
|
|
408
|
-
|
|
409
|
-
flushPendingAssistantToolMessage();
|
|
410
|
-
const normalized = normalizeMessage(item);
|
|
411
|
-
if (normalized?.role === 'assistant' && !normalized.reasoning_content && pendingReasoningContent) {
|
|
412
|
-
normalized.reasoning_content = pendingReasoningContent;
|
|
413
|
-
pendingReasoningContent = '';
|
|
414
|
-
}
|
|
415
|
-
if (normalized) messages.push(normalized);
|
|
602
|
+
pushRoleMessage(item);
|
|
416
603
|
}
|
|
417
604
|
}
|
|
418
605
|
flushPendingUserContent();
|
|
419
|
-
|
|
606
|
+
flushAssistantState();
|
|
607
|
+
flushPendingWebSearchNotes();
|
|
420
608
|
return messages;
|
|
421
609
|
}
|
|
422
610
|
|
|
@@ -442,14 +630,147 @@ function extractMessagesFromResponsesInput(input) {
|
|
|
442
630
|
return [];
|
|
443
631
|
}
|
|
444
632
|
|
|
633
|
+
const CUSTOM_TOOL_INPUT_HINT = 'Pass the complete raw tool input as the "input" string argument. Do not wrap it in extra JSON, quotes, or markdown fences.';
|
|
634
|
+
const CUSTOM_TOOL_GRAMMAR_CHARS = 1600;
|
|
635
|
+
const CUSTOM_TOOL_DESCRIPTION_CHARS = 3600;
|
|
636
|
+
|
|
637
|
+
function truncateRawText(value, maxChars) {
|
|
638
|
+
const text = String(value ?? '');
|
|
639
|
+
if (text.length <= maxChars) return text;
|
|
640
|
+
return `${text.slice(0, Math.max(0, maxChars - 4))}\n...`;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function customToolShimParameters() {
|
|
644
|
+
return {
|
|
645
|
+
type: 'object',
|
|
646
|
+
properties: {
|
|
647
|
+
input: {
|
|
648
|
+
type: 'string',
|
|
649
|
+
description: 'Complete raw input text for this tool.',
|
|
650
|
+
},
|
|
651
|
+
},
|
|
652
|
+
required: ['input'],
|
|
653
|
+
additionalProperties: false,
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function customToolShim(tool) {
|
|
658
|
+
const format = isObject(tool.format) ? tool.format : {};
|
|
659
|
+
const baseName = toolBaseName(tool, 'custom_tool');
|
|
660
|
+
const chunks = [
|
|
661
|
+
compactText(tool.description) || `Freeform tool ${baseName}.`,
|
|
662
|
+
CUSTOM_TOOL_INPUT_HINT,
|
|
663
|
+
];
|
|
664
|
+
if (format.syntax) chunks.push(`Input syntax: ${format.syntax}.`);
|
|
665
|
+
if (typeof format.definition === 'string' && format.definition.trim()) {
|
|
666
|
+
chunks.push(`Input grammar:\n${truncateRawText(format.definition.trim(), CUSTOM_TOOL_GRAMMAR_CHARS)}`);
|
|
667
|
+
}
|
|
668
|
+
const parameters = customToolShimParameters();
|
|
669
|
+
const sourceSchema = isObject(tool.input_schema) ? tool.input_schema : isObject(tool.parameters) ? tool.parameters : null;
|
|
670
|
+
const sourceInputDescription = sourceSchema?.properties?.input?.description;
|
|
671
|
+
if (typeof sourceInputDescription === 'string' && sourceInputDescription) {
|
|
672
|
+
parameters.properties.input.description = sourceInputDescription;
|
|
673
|
+
}
|
|
674
|
+
return {
|
|
675
|
+
type: 'function',
|
|
676
|
+
gateway_custom_tool: true,
|
|
677
|
+
function: {
|
|
678
|
+
name: encodeToolName(toolNamespace(tool), baseName),
|
|
679
|
+
description: truncateRawText(chunks.join('\n'), CUSTOM_TOOL_DESCRIPTION_CHARS),
|
|
680
|
+
parameters,
|
|
681
|
+
},
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function buildCustomToolNames(tools) {
|
|
686
|
+
const names = new Set();
|
|
687
|
+
for (const tool of expandTools(tools)) {
|
|
688
|
+
if (!isObject(tool) || tool.type !== 'custom') continue;
|
|
689
|
+
const normalizedTool = normalizeTool(tool);
|
|
690
|
+
const fn = normalizedTool?.type === 'function' ? normalizedTool.function : null;
|
|
691
|
+
if (fn?.name) names.add(fn.name);
|
|
692
|
+
}
|
|
693
|
+
return names;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function customToolInputFromArguments(argumentsText) {
|
|
697
|
+
if (typeof argumentsText !== 'string' || !argumentsText) return '';
|
|
698
|
+
const parsed = safeJsonParse(argumentsText);
|
|
699
|
+
if (parsed.ok && typeof parsed.value === 'string') return parsed.value;
|
|
700
|
+
if (parsed.ok && isObject(parsed.value)) {
|
|
701
|
+
const input = parsed.value.input;
|
|
702
|
+
if (typeof input === 'string') return input;
|
|
703
|
+
if (input !== undefined) return jsonString(input);
|
|
704
|
+
return argumentsText;
|
|
705
|
+
}
|
|
706
|
+
return argumentsText;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function stripGatewayToolMarkers(tools) {
|
|
710
|
+
if (!Array.isArray(tools)) return tools;
|
|
711
|
+
return tools.map((tool) => {
|
|
712
|
+
if (!isObject(tool) || tool.gateway_custom_tool === undefined) return tool;
|
|
713
|
+
const { gateway_custom_tool, ...rest } = tool;
|
|
714
|
+
return rest;
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
const UNAVAILABLE_TOOL_GUIDANCE = 'Do not call this tool; if the task depends on it, tell the user it is unavailable.';
|
|
719
|
+
|
|
720
|
+
function unavailableHostedToolShim(tool, reason) {
|
|
721
|
+
const capability = String(tool.type || 'tool');
|
|
722
|
+
const detail = reason || `Unavailable capability: the client requested the hosted ${capability} tool, but this gateway cannot execute it.`;
|
|
723
|
+
return {
|
|
724
|
+
type: 'function',
|
|
725
|
+
function: {
|
|
726
|
+
name: encodeToolName(toolNamespace(tool), toolBaseName(tool, capability)),
|
|
727
|
+
description: `${detail} ${UNAVAILABLE_TOOL_GUIDANCE}`,
|
|
728
|
+
parameters: { type: 'object', properties: {}, additionalProperties: false },
|
|
729
|
+
},
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
export function unavailableWebSearchToolShims(tools) {
|
|
734
|
+
const shims = [];
|
|
735
|
+
const seen = new Set();
|
|
736
|
+
for (const tool of expandTools(tools)) {
|
|
737
|
+
if (!isObject(tool) || typeof tool.type !== 'string' || !EMULATED_HOSTED_TOOL_TYPES.has(tool.type)) continue;
|
|
738
|
+
const shim = unavailableHostedToolShim(
|
|
739
|
+
tool,
|
|
740
|
+
'Unavailable capability: web search was requested, but the gateway has no search provider configured.',
|
|
741
|
+
);
|
|
742
|
+
if (seen.has(shim.function.name)) continue;
|
|
743
|
+
seen.add(shim.function.name);
|
|
744
|
+
shims.push(shim);
|
|
745
|
+
}
|
|
746
|
+
return shims;
|
|
747
|
+
}
|
|
748
|
+
|
|
445
749
|
function normalizeTool(tool) {
|
|
446
750
|
if (!isObject(tool)) return tool;
|
|
447
751
|
if (tool.type === 'namespace') return null;
|
|
448
752
|
if (typeof tool.type === 'string' && EMULATED_HOSTED_TOOL_TYPES.has(tool.type)) {
|
|
449
753
|
return null;
|
|
450
754
|
}
|
|
755
|
+
if (typeof tool.type === 'string' && UNSUPPORTED_HOSTED_TOOL_TYPES.has(tool.type)) {
|
|
756
|
+
return unavailableHostedToolShim(tool);
|
|
757
|
+
}
|
|
758
|
+
if (tool.type === 'custom') {
|
|
759
|
+
return customToolShim(tool);
|
|
760
|
+
}
|
|
761
|
+
if (typeof tool.type === 'string' && BRIDGED_RESPONSES_TOOL_TYPES.has(tool.type)) {
|
|
762
|
+
return {
|
|
763
|
+
type: 'function',
|
|
764
|
+
function: omitUndefined({
|
|
765
|
+
name: encodeToolName(toolNamespace(tool), tool.type),
|
|
766
|
+
description: tool.description,
|
|
767
|
+
parameters: normalizeJsonSchemaObject(tool.parameters || tool.input_schema),
|
|
768
|
+
strict: tool.strict,
|
|
769
|
+
}),
|
|
770
|
+
};
|
|
771
|
+
}
|
|
451
772
|
if (tool.type === 'function' && isObject(tool.function)) {
|
|
452
|
-
const { namespace, ...fn } = tool.function;
|
|
773
|
+
const { namespace, defer_loading, ...fn } = tool.function;
|
|
453
774
|
return {
|
|
454
775
|
type: 'function',
|
|
455
776
|
function: omitUndefined({
|
|
@@ -461,7 +782,7 @@ function normalizeTool(tool) {
|
|
|
461
782
|
}),
|
|
462
783
|
};
|
|
463
784
|
}
|
|
464
|
-
if (tool.type === 'function'
|
|
785
|
+
if (tool.type === 'function') {
|
|
465
786
|
return {
|
|
466
787
|
type: 'function',
|
|
467
788
|
function: omitUndefined({
|
|
@@ -472,6 +793,17 @@ function normalizeTool(tool) {
|
|
|
472
793
|
}),
|
|
473
794
|
};
|
|
474
795
|
}
|
|
796
|
+
if (!tool.type && (tool.name || tool.parameters || tool.input_schema)) {
|
|
797
|
+
return {
|
|
798
|
+
type: 'function',
|
|
799
|
+
function: omitUndefined({
|
|
800
|
+
name: encodeToolName(toolNamespace(tool), toolBaseName(tool, 'tool_call')),
|
|
801
|
+
description: tool.description,
|
|
802
|
+
parameters: normalizeJsonSchemaObject(tool.parameters || tool.input_schema),
|
|
803
|
+
strict: tool.strict,
|
|
804
|
+
}),
|
|
805
|
+
};
|
|
806
|
+
}
|
|
475
807
|
return null;
|
|
476
808
|
}
|
|
477
809
|
|
|
@@ -518,8 +850,19 @@ function normalizeTools(tools) {
|
|
|
518
850
|
return normalized.length ? normalized : undefined;
|
|
519
851
|
}
|
|
520
852
|
|
|
853
|
+
function hasRunnableChatTools(normalized) {
|
|
854
|
+
return Array.isArray(applyAllowedTools(normalizeTools(normalized?.tools), normalized?.tool_choice));
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function toolSearchDiscoveryKey(tool) {
|
|
858
|
+
if (tool.type === 'namespace') {
|
|
859
|
+
return `namespace:${tool.name || tool.namespace || tool.server_label || ''}`;
|
|
860
|
+
}
|
|
861
|
+
return `${tool.type || 'function'}:${toolNamespace(tool)}:${toolBaseName(tool, '')}`;
|
|
862
|
+
}
|
|
863
|
+
|
|
521
864
|
function collectToolSearchOutputTools(input) {
|
|
522
|
-
const discovered =
|
|
865
|
+
const discovered = new Map();
|
|
523
866
|
const scan = (value) => {
|
|
524
867
|
if (Array.isArray(value)) {
|
|
525
868
|
for (const item of value) scan(item);
|
|
@@ -527,14 +870,16 @@ function collectToolSearchOutputTools(input) {
|
|
|
527
870
|
}
|
|
528
871
|
if (!isObject(value)) return;
|
|
529
872
|
if (value.type === 'tool_search_output' && Array.isArray(value.tools)) {
|
|
530
|
-
|
|
873
|
+
for (const tool of value.tools) {
|
|
874
|
+
if (isObject(tool)) discovered.set(toolSearchDiscoveryKey(tool), tool);
|
|
875
|
+
}
|
|
531
876
|
return;
|
|
532
877
|
}
|
|
533
878
|
if (Array.isArray(value.input)) scan(value.input);
|
|
534
879
|
if (Array.isArray(value.content)) scan(value.content);
|
|
535
880
|
};
|
|
536
881
|
scan(input);
|
|
537
|
-
return discovered;
|
|
882
|
+
return [...discovered.values()];
|
|
538
883
|
}
|
|
539
884
|
|
|
540
885
|
function mergeToolsWithToolSearchOutput(requestTools, input) {
|
|
@@ -572,6 +917,7 @@ function normalizeJsonSchemaObject(schema) {
|
|
|
572
917
|
}
|
|
573
918
|
|
|
574
919
|
function normalizeToolChoice(toolChoice) {
|
|
920
|
+
if (toolChoiceDisablesTools(toolChoice)) return 'none';
|
|
575
921
|
if (!isObject(toolChoice)) return toolChoice;
|
|
576
922
|
if (toolChoice.type === 'function' && toolChoice.name) {
|
|
577
923
|
return {
|
|
@@ -639,6 +985,7 @@ function allowedToolNames(toolChoice) {
|
|
|
639
985
|
}
|
|
640
986
|
|
|
641
987
|
function applyAllowedTools(tools, toolChoice) {
|
|
988
|
+
if (toolChoiceDisablesTools(toolChoice)) return undefined;
|
|
642
989
|
const allowed = allowedToolNames(toolChoice);
|
|
643
990
|
if (!allowed || !Array.isArray(tools)) return tools;
|
|
644
991
|
const filtered = tools.filter((tool) => {
|
|
@@ -710,7 +1057,7 @@ function convertItemToToolSearchCall(item) {
|
|
|
710
1057
|
return item;
|
|
711
1058
|
}
|
|
712
1059
|
|
|
713
|
-
function responseItemFromChatToolCall(toolCall, toolNames) {
|
|
1060
|
+
function responseItemFromChatToolCall(toolCall, toolNames, customToolNames) {
|
|
714
1061
|
const callId = toolCall.id || generateId('call');
|
|
715
1062
|
if (isCodexToolSearchTool(toolCall.function?.name)) {
|
|
716
1063
|
return {
|
|
@@ -723,6 +1070,16 @@ function responseItemFromChatToolCall(toolCall, toolNames) {
|
|
|
723
1070
|
};
|
|
724
1071
|
}
|
|
725
1072
|
const decoded = decodedToolName(toolCall.function?.name, toolNames);
|
|
1073
|
+
if (customToolNames?.has(toolCall.function?.name)) {
|
|
1074
|
+
return omitUndefined({
|
|
1075
|
+
type: 'custom_tool_call',
|
|
1076
|
+
id: callId,
|
|
1077
|
+
call_id: callId,
|
|
1078
|
+
name: decoded.name,
|
|
1079
|
+
input: customToolInputFromArguments(toolCall.function?.arguments || ''),
|
|
1080
|
+
status: 'completed',
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
726
1083
|
return omitUndefined({
|
|
727
1084
|
type: 'function_call',
|
|
728
1085
|
id: callId,
|
|
@@ -749,14 +1106,17 @@ function buildToolNames(tools) {
|
|
|
749
1106
|
const names = new Map();
|
|
750
1107
|
for (const tool of expandTools(tools)) {
|
|
751
1108
|
const namespace = toolNamespace(tool);
|
|
752
|
-
if (!namespace) continue;
|
|
753
1109
|
const normalizedTool = normalizeTool(tool);
|
|
754
1110
|
const fn = normalizedTool?.type === 'function' ? normalizedTool.function : null;
|
|
755
1111
|
if (!fn?.name) continue;
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
1112
|
+
const baseName = String(toolBaseName(tool, tool.type));
|
|
1113
|
+
const sanitizedBaseName = sanitizeFunctionName(baseName);
|
|
1114
|
+
if (!namespace && fn.name === baseName) continue;
|
|
1115
|
+
names.set(fn.name, omitUndefined({
|
|
1116
|
+
namespace: namespace || undefined,
|
|
1117
|
+
name: sanitizedBaseName,
|
|
1118
|
+
original_name: baseName,
|
|
1119
|
+
}));
|
|
760
1120
|
}
|
|
761
1121
|
return names;
|
|
762
1122
|
}
|
|
@@ -850,11 +1210,12 @@ function deepSeekSpawnAgentDescription() {
|
|
|
850
1210
|
}
|
|
851
1211
|
|
|
852
1212
|
function isCodexSpawnAgentTool(name) {
|
|
853
|
-
return name === CODEX_SPAWN_AGENT_TOOL_NAME
|
|
1213
|
+
return name === CODEX_SPAWN_AGENT_TOOL_NAME;
|
|
854
1214
|
}
|
|
855
1215
|
|
|
856
1216
|
function simplifyToolForDeepSeek(tool, config = {}) {
|
|
857
1217
|
if (tool?.type !== 'function' || !isObject(tool.function)) return tool;
|
|
1218
|
+
if (tool.gateway_custom_tool) return tool;
|
|
858
1219
|
const fn = tool.function;
|
|
859
1220
|
let parameters = simplifyJsonSchemaDescriptions(fn.parameters);
|
|
860
1221
|
if (isCodexSpawnAgentTool(fn.name)) {
|
|
@@ -882,21 +1243,36 @@ function hasDeepSeekToolInstructions(messages) {
|
|
|
882
1243
|
|
|
883
1244
|
function addDeepSeekToolInstructions(messages, tools) {
|
|
884
1245
|
if (!Array.isArray(tools) || !tools.length || hasDeepSeekToolInstructions(messages)) return messages;
|
|
1246
|
+
const instructions = requestToolsIncludeCommentary(tools)
|
|
1247
|
+
? `${DEEPSEEK_TOOL_INSTRUCTIONS} ${DEEPSEEK_COMMENTARY_INSTRUCTIONS}`
|
|
1248
|
+
: DEEPSEEK_TOOL_INSTRUCTIONS;
|
|
885
1249
|
if (!messages.length || messages[0]?.role !== 'system') {
|
|
886
|
-
return [{ role: 'system', content:
|
|
1250
|
+
return [{ role: 'system', content: instructions }, ...messages];
|
|
887
1251
|
}
|
|
888
1252
|
return [
|
|
889
1253
|
{
|
|
890
1254
|
...messages[0],
|
|
891
|
-
content: `${toText(messages[0].content)}\n\n${
|
|
1255
|
+
content: `${toText(messages[0].content)}\n\n${instructions}`,
|
|
892
1256
|
},
|
|
893
1257
|
...messages.slice(1),
|
|
894
1258
|
];
|
|
895
1259
|
}
|
|
896
1260
|
|
|
1261
|
+
function isDeepSeekBetaBaseUrl(baseUrl) {
|
|
1262
|
+
return /\/beta\/?$/.test(String(baseUrl || ''));
|
|
1263
|
+
}
|
|
1264
|
+
|
|
897
1265
|
function adaptToolsForProvider(tools, provider, config = {}) {
|
|
898
1266
|
if (provider !== 'deepseek' || !Array.isArray(tools)) return tools;
|
|
899
|
-
|
|
1267
|
+
const keepStrict = isDeepSeekBetaBaseUrl(config.upstreamBaseUrl);
|
|
1268
|
+
return tools.map((tool) => {
|
|
1269
|
+
const simplified = simplifyToolForDeepSeek(tool, config);
|
|
1270
|
+
if (!keepStrict && simplified?.type === 'function' && isObject(simplified.function) && simplified.function.strict !== undefined) {
|
|
1271
|
+
const { strict, ...fn } = simplified.function;
|
|
1272
|
+
return { ...simplified, function: fn };
|
|
1273
|
+
}
|
|
1274
|
+
return simplified;
|
|
1275
|
+
});
|
|
900
1276
|
}
|
|
901
1277
|
|
|
902
1278
|
function normalizeToolCallArguments(name, argumentsText, toolSchema) {
|
|
@@ -965,6 +1341,16 @@ function normalizeFunctionCallItemArguments(item, toolSchemas, config = {}) {
|
|
|
965
1341
|
item.arguments = normalizeCodexSpawnAgentArguments(encodedName, item.arguments, config);
|
|
966
1342
|
}
|
|
967
1343
|
|
|
1344
|
+
function functionCallItemNeedsArgumentNormalization(item, toolSchemas, config = {}) {
|
|
1345
|
+
if (!item || item.type !== 'function_call') return false;
|
|
1346
|
+
const encodedName = encodeToolName(item.namespace, item.name);
|
|
1347
|
+
return Boolean(toolSchemas?.has(encodedName) || isCodexSpawnAgentTool(encodedName));
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
function hasToolCallFunctionName(toolCall) {
|
|
1351
|
+
return typeof toolCall?.function?.name === 'string' && toolCall.function.name.length > 0;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
968
1354
|
function sanitizeToolCallsForChatCompletion(toolCalls) {
|
|
969
1355
|
return toolCalls.map((toolCall) => {
|
|
970
1356
|
if (!isObject(toolCall)) return toolCall;
|
|
@@ -983,11 +1369,51 @@ function sanitizeToolCallsForChatCompletion(toolCalls) {
|
|
|
983
1369
|
});
|
|
984
1370
|
}
|
|
985
1371
|
|
|
1372
|
+
function multimodalPlaceholder(kind, hint) {
|
|
1373
|
+
const cleanHint = compactText(hint);
|
|
1374
|
+
const shortHint = cleanHint && !cleanHint.startsWith('data:') ? shortenText(cleanHint, 120) : '';
|
|
1375
|
+
return `[${kind} omitted: DeepSeek accepts text input only${shortHint ? `; source: ${shortHint}` : ''}]`;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
function deepseekTextOnlyContent(content) {
|
|
1379
|
+
if (content == null) return '';
|
|
1380
|
+
if (typeof content === 'string') return content;
|
|
1381
|
+
const parts = Array.isArray(content) ? content : [content];
|
|
1382
|
+
const chunks = [];
|
|
1383
|
+
for (const part of parts) {
|
|
1384
|
+
if (typeof part === 'string') {
|
|
1385
|
+
chunks.push(part);
|
|
1386
|
+
continue;
|
|
1387
|
+
}
|
|
1388
|
+
if (!isObject(part)) continue;
|
|
1389
|
+
if (part.type === 'text') {
|
|
1390
|
+
chunks.push(String(part.text ?? ''));
|
|
1391
|
+
continue;
|
|
1392
|
+
}
|
|
1393
|
+
if (part.type === 'image_url') {
|
|
1394
|
+
chunks.push(multimodalPlaceholder('image', part.image_url?.url || part.image_url?.file_id));
|
|
1395
|
+
continue;
|
|
1396
|
+
}
|
|
1397
|
+
if (part.type === 'file') {
|
|
1398
|
+
chunks.push(multimodalPlaceholder('file', part.file?.filename || part.file?.file_url || part.file?.file_id));
|
|
1399
|
+
continue;
|
|
1400
|
+
}
|
|
1401
|
+
if (part.type === 'input_audio') {
|
|
1402
|
+
chunks.push(multimodalPlaceholder('audio', part.input_audio?.file_id));
|
|
1403
|
+
continue;
|
|
1404
|
+
}
|
|
1405
|
+
const text = toText(part);
|
|
1406
|
+
if (text) chunks.push(text);
|
|
1407
|
+
}
|
|
1408
|
+
return chunks.filter((chunk) => chunk !== '').join('\n');
|
|
1409
|
+
}
|
|
1410
|
+
|
|
986
1411
|
function sanitizeMessageForChatCompletion(message, provider = 'generic') {
|
|
987
1412
|
if (!isObject(message)) return message;
|
|
1413
|
+
const chatContent = contentToChatContent(message.content);
|
|
988
1414
|
const sanitized = {
|
|
989
1415
|
role: normalizeRole(message.role, provider),
|
|
990
|
-
content:
|
|
1416
|
+
content: provider === 'deepseek' ? deepseekTextOnlyContent(chatContent) : chatContent,
|
|
991
1417
|
};
|
|
992
1418
|
if (message.name !== undefined) sanitized.name = message.name;
|
|
993
1419
|
if (Array.isArray(message.tool_calls)) {
|
|
@@ -1000,11 +1426,93 @@ function sanitizeMessageForChatCompletion(message, provider = 'generic') {
|
|
|
1000
1426
|
return sanitized;
|
|
1001
1427
|
}
|
|
1002
1428
|
|
|
1429
|
+
function deepSeekInstructionBlock(message, includePriorityLabels) {
|
|
1430
|
+
const content = toText(message?.content).trim();
|
|
1431
|
+
if (!content) return '';
|
|
1432
|
+
if (!includePriorityLabels) return content;
|
|
1433
|
+
const label = message.role === 'developer'
|
|
1434
|
+
? 'Developer instructions (priority below system):'
|
|
1435
|
+
: 'System instructions (highest priority):';
|
|
1436
|
+
return `${label}\n${content}`;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
function sanitizeMessagesForChatCompletion(messages, provider = 'generic') {
|
|
1440
|
+
const source = Array.isArray(messages) ? messages : [];
|
|
1441
|
+
if (provider !== 'deepseek') {
|
|
1442
|
+
return source.map((message) => sanitizeMessageForChatCompletion(message, provider));
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
const result = [];
|
|
1446
|
+
let instructionBlock = [];
|
|
1447
|
+
const flushInstructionBlock = () => {
|
|
1448
|
+
if (!instructionBlock.length) return;
|
|
1449
|
+
const hasDeveloper = instructionBlock.some((message) => message?.role === 'developer');
|
|
1450
|
+
const content = instructionBlock
|
|
1451
|
+
.map((message) => deepSeekInstructionBlock(message, hasDeveloper))
|
|
1452
|
+
.filter(Boolean)
|
|
1453
|
+
.join('\n\n');
|
|
1454
|
+
if (content) result.push({ role: 'system', content });
|
|
1455
|
+
instructionBlock = [];
|
|
1456
|
+
};
|
|
1457
|
+
|
|
1458
|
+
for (const message of source) {
|
|
1459
|
+
if (message?.role === 'system' || message?.role === 'developer') {
|
|
1460
|
+
instructionBlock.push(message);
|
|
1461
|
+
continue;
|
|
1462
|
+
}
|
|
1463
|
+
flushInstructionBlock();
|
|
1464
|
+
result.push(sanitizeMessageForChatCompletion(message, provider));
|
|
1465
|
+
}
|
|
1466
|
+
flushInstructionBlock();
|
|
1467
|
+
return result;
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
const DEEPSEEK_DEFAULT_MAX_TOKENS = 65536;
|
|
1471
|
+
|
|
1472
|
+
function deepseekDefaultMaxTokens(config = {}) {
|
|
1473
|
+
const value = Number(config.upstreamMaxTokens);
|
|
1474
|
+
if (Number.isFinite(value) && value > 0) return Math.floor(value);
|
|
1475
|
+
return DEEPSEEK_DEFAULT_MAX_TOKENS;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
const DEEPSEEK_JSON_SCHEMA_INSTRUCTIONS_MARKER = 'Respond with a single valid json object';
|
|
1479
|
+
|
|
1480
|
+
function jsonSchemaFromResponseFormat(responseFormat) {
|
|
1481
|
+
if (!isObject(responseFormat)) return null;
|
|
1482
|
+
const container = isObject(responseFormat.json_schema) ? responseFormat.json_schema : responseFormat;
|
|
1483
|
+
return isObject(container.schema) ? container.schema : null;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
function addDeepSeekJsonSchemaInstructions(messages, responseFormat) {
|
|
1487
|
+
if (messages.some((message) => message?.role === 'system' && toText(message.content).includes(DEEPSEEK_JSON_SCHEMA_INSTRUCTIONS_MARKER))) {
|
|
1488
|
+
return messages;
|
|
1489
|
+
}
|
|
1490
|
+
const schema = jsonSchemaFromResponseFormat(responseFormat);
|
|
1491
|
+
const schemaText = schema ? truncateRawText(JSON.stringify(schema), 6000) : '';
|
|
1492
|
+
const instructions = [
|
|
1493
|
+
`${DEEPSEEK_JSON_SCHEMA_INSTRUCTIONS_MARKER} and nothing else: no prose, no markdown fences.`,
|
|
1494
|
+
schemaText
|
|
1495
|
+
? `The json object must conform to this JSON Schema:\n${schemaText}`
|
|
1496
|
+
: 'Use the json object shape requested in the conversation.',
|
|
1497
|
+
].join('\n');
|
|
1498
|
+
if (!messages.length || messages[0]?.role !== 'system') {
|
|
1499
|
+
return [{ role: 'system', content: instructions }, ...messages];
|
|
1500
|
+
}
|
|
1501
|
+
return [
|
|
1502
|
+
{
|
|
1503
|
+
...messages[0],
|
|
1504
|
+
content: `${toText(messages[0].content)}\n\n${instructions}`,
|
|
1505
|
+
},
|
|
1506
|
+
...messages.slice(1),
|
|
1507
|
+
];
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1003
1510
|
function patchDeepSeekThinkingHistory(messages, request) {
|
|
1004
1511
|
if (request.thinking?.type !== 'enabled' && !request.reasoning_effort) return messages;
|
|
1005
1512
|
return messages.map((message) => {
|
|
1006
1513
|
if (message?.role !== 'assistant') return message;
|
|
1007
1514
|
if (typeof message.reasoning_content === 'string') return message;
|
|
1515
|
+
if (!hasChatToolCalls(message)) return message;
|
|
1008
1516
|
return {
|
|
1009
1517
|
...message,
|
|
1010
1518
|
reasoning_content: '',
|
|
@@ -1020,8 +1528,12 @@ function normalizeOutputTextPart(text, annotations = []) {
|
|
|
1020
1528
|
};
|
|
1021
1529
|
}
|
|
1022
1530
|
|
|
1531
|
+
const REASONING_SUMMARY_HEADER = '**Reasoning**\n\n';
|
|
1532
|
+
|
|
1023
1533
|
function reasoningSummaryText(text) {
|
|
1024
|
-
|
|
1534
|
+
const display = reasoningDisplayText(text);
|
|
1535
|
+
if (!display) return '';
|
|
1536
|
+
return `${REASONING_SUMMARY_HEADER}${display}`;
|
|
1025
1537
|
}
|
|
1026
1538
|
|
|
1027
1539
|
function normalizeSummaryTextPart(text) {
|
|
@@ -1074,6 +1586,16 @@ function snapshotResponseItem(item) {
|
|
|
1074
1586
|
arguments: isObject(item.arguments) ? { ...item.arguments } : toolSearchArgumentsFromText(item.arguments),
|
|
1075
1587
|
});
|
|
1076
1588
|
}
|
|
1589
|
+
if (item.type === 'custom_tool_call') {
|
|
1590
|
+
return omitUndefined({
|
|
1591
|
+
type: item.type,
|
|
1592
|
+
id: item.id,
|
|
1593
|
+
call_id: item.call_id,
|
|
1594
|
+
status: item.status,
|
|
1595
|
+
name: item.name,
|
|
1596
|
+
input: typeof item.input === 'string' ? item.input : '',
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1077
1599
|
return {
|
|
1078
1600
|
...item,
|
|
1079
1601
|
content: Array.isArray(item.content) ? item.content.map(snapshotResponsePart) : item.content,
|
|
@@ -1204,7 +1726,9 @@ export function toChatCompletionsRequest(normalized, overrides = {}) {
|
|
|
1204
1726
|
|
|
1205
1727
|
export function assistantMessageFromResponseOutput(output) {
|
|
1206
1728
|
const messageItems = Array.isArray(output) ? output.filter((item) => item.type === 'message') : [];
|
|
1207
|
-
const functionItems = Array.isArray(output)
|
|
1729
|
+
const functionItems = Array.isArray(output)
|
|
1730
|
+
? output.filter((item) => item.type === 'function_call' || item.type === 'custom_tool_call' || item.type === 'tool_search_call')
|
|
1731
|
+
: [];
|
|
1208
1732
|
const reasoningItems = Array.isArray(output) ? output.filter((item) => item.type === 'reasoning') : [];
|
|
1209
1733
|
const contentParts = messageItems.map((item) => item.content || []).flat();
|
|
1210
1734
|
const chatParts = contentParts.map(contentPartToChatPart).filter(Boolean);
|
|
@@ -1217,14 +1741,7 @@ export function assistantMessageFromResponseOutput(output) {
|
|
|
1217
1741
|
role: 'assistant',
|
|
1218
1742
|
content,
|
|
1219
1743
|
};
|
|
1220
|
-
const toolCalls = functionItems.map(
|
|
1221
|
-
id: item.call_id || item.id,
|
|
1222
|
-
type: 'function',
|
|
1223
|
-
function: {
|
|
1224
|
-
name: encodeToolName(item.namespace, item.name),
|
|
1225
|
-
arguments: item.arguments || '',
|
|
1226
|
-
},
|
|
1227
|
-
}));
|
|
1744
|
+
const toolCalls = functionItems.map(chatToolCallFromResponseItem);
|
|
1228
1745
|
if (toolCalls.length) assistant.tool_calls = toolCalls;
|
|
1229
1746
|
const reasoningContent = reasoningItems
|
|
1230
1747
|
.map((item) => reasoningTextFromItem(item))
|
|
@@ -1245,15 +1762,64 @@ export function extractToolCallIdsFromMessages(messages) {
|
|
|
1245
1762
|
return ids;
|
|
1246
1763
|
}
|
|
1247
1764
|
|
|
1765
|
+
export function requestToolsIncludeCommentary(tools) {
|
|
1766
|
+
for (const tool of Array.isArray(tools) ? tools : []) {
|
|
1767
|
+
if (!isObject(tool)) continue;
|
|
1768
|
+
const name = isObject(tool.function) ? tool.function.name : tool.name;
|
|
1769
|
+
if (String(name || '') === INTERNAL_COMMENTARY_TOOL) return true;
|
|
1770
|
+
}
|
|
1771
|
+
return false;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
export function bridgedCommentaryToolCallsFromMessage(message, tools) {
|
|
1775
|
+
if (requestToolsIncludeCommentary(tools)) return [];
|
|
1776
|
+
const toolCalls = Array.isArray(message?.tool_calls) ? message.tool_calls : [];
|
|
1777
|
+
return toolCalls.filter(
|
|
1778
|
+
(toolCall) => toolCall?.type === 'function' && String(toolCall?.function?.name || '') === INTERNAL_COMMENTARY_TOOL,
|
|
1779
|
+
);
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
export function stripBridgedCommentaryFromCompletion(completion, tools) {
|
|
1783
|
+
if (requestToolsIncludeCommentary(tools)) return completion;
|
|
1784
|
+
const choice = Array.isArray(completion?.choices) ? completion.choices[0] : null;
|
|
1785
|
+
const message = choice?.message;
|
|
1786
|
+
if (!Array.isArray(message?.tool_calls)) return completion;
|
|
1787
|
+
const kept = message.tool_calls.filter(
|
|
1788
|
+
(toolCall) => !(toolCall?.type === 'function' && String(toolCall?.function?.name || '') === INTERNAL_COMMENTARY_TOOL),
|
|
1789
|
+
);
|
|
1790
|
+
if (kept.length === message.tool_calls.length) return completion;
|
|
1791
|
+
const nextMessage = { ...message };
|
|
1792
|
+
if (kept.length) nextMessage.tool_calls = kept;
|
|
1793
|
+
else delete nextMessage.tool_calls;
|
|
1794
|
+
return {
|
|
1795
|
+
...completion,
|
|
1796
|
+
choices: [{ ...choice, message: nextMessage }, ...completion.choices.slice(1)],
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
function commentaryTextFromArguments(argumentsText) {
|
|
1801
|
+
const args = parseJsonObject(argumentsText);
|
|
1802
|
+
const text = String(args.text ?? args.message ?? args.update ?? args.content ?? '').trim();
|
|
1803
|
+
if (text) return text;
|
|
1804
|
+
const raw = String(argumentsText ?? '').trim();
|
|
1805
|
+
if (!raw || raw.startsWith('{') || raw === 'null') return '';
|
|
1806
|
+
return raw;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
function addInternalCommentaryTool(tools, toolChoice) {
|
|
1810
|
+
if (!Array.isArray(tools) || !tools.length) return tools;
|
|
1811
|
+
if (toolChoiceDisablesTools(toolChoice)) return tools;
|
|
1812
|
+
if (requestToolsIncludeCommentary(tools)) return tools;
|
|
1813
|
+
return [...tools, JSON.parse(JSON.stringify(INTERNAL_COMMENTARY_TOOL_DEFINITION))];
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1248
1816
|
export function toProviderChatCompletionsRequest(chatRequest, config = {}) {
|
|
1249
1817
|
const provider = config.upstreamProvider || 'generic';
|
|
1250
1818
|
const modelAlias = resolveModelAlias(chatRequest.model, config);
|
|
1251
1819
|
const fallbackReasoning = !chatRequest.reasoning && config.codexReasoningEffort
|
|
1252
1820
|
? { effort: config.codexReasoningEffort }
|
|
1253
1821
|
: chatRequest.reasoning;
|
|
1254
|
-
const messages =
|
|
1255
|
-
? chatRequest.messages.map((message) => sanitizeMessageForChatCompletion(message, provider))
|
|
1256
|
-
: [];
|
|
1822
|
+
const messages = sanitizeMessagesForChatCompletion(chatRequest.messages, provider);
|
|
1257
1823
|
const request = {
|
|
1258
1824
|
model: modelAlias.upstreamModel || config.upstreamModel || chatRequest.model,
|
|
1259
1825
|
messages,
|
|
@@ -1282,7 +1848,7 @@ export function toProviderChatCompletionsRequest(chatRequest, config = {}) {
|
|
|
1282
1848
|
if (provider === 'deepseek') {
|
|
1283
1849
|
delete request.reasoning_effort;
|
|
1284
1850
|
delete request.parallel_tool_calls;
|
|
1285
|
-
request.tools = adaptToolsForProvider(request.tools, provider, config);
|
|
1851
|
+
request.tools = addInternalCommentaryTool(adaptToolsForProvider(request.tools, provider, config), chatRequest.tool_choice);
|
|
1286
1852
|
request.messages = addDeepSeekToolInstructions(request.messages, request.tools);
|
|
1287
1853
|
if (request.user !== undefined) {
|
|
1288
1854
|
request.user_id = request.user;
|
|
@@ -1293,15 +1859,23 @@ export function toProviderChatCompletionsRequest(chatRequest, config = {}) {
|
|
|
1293
1859
|
} else {
|
|
1294
1860
|
delete request.stream_options;
|
|
1295
1861
|
}
|
|
1862
|
+
if (request.max_tokens == null) {
|
|
1863
|
+
request.max_tokens = deepseekDefaultMaxTokens(config);
|
|
1864
|
+
}
|
|
1296
1865
|
if (request.response_format?.type === 'json_schema') {
|
|
1866
|
+
request.messages = addDeepSeekJsonSchemaInstructions(request.messages, request.response_format);
|
|
1297
1867
|
request.response_format = { type: 'json_object' };
|
|
1298
1868
|
}
|
|
1299
1869
|
Object.assign(request, deepseekReasoningPayload({ alias: modelAlias, reasoning: fallbackReasoning }));
|
|
1300
|
-
request.messages = patchDeepSeekThinkingHistory(request.messages, request);
|
|
1301
1870
|
}
|
|
1302
1871
|
|
|
1303
1872
|
Object.assign(request, modelAlias.extraBody);
|
|
1304
1873
|
|
|
1874
|
+
if (provider === 'deepseek') {
|
|
1875
|
+
request.messages = patchDeepSeekThinkingHistory(request.messages, request);
|
|
1876
|
+
}
|
|
1877
|
+
request.tools = stripGatewayToolMarkers(request.tools);
|
|
1878
|
+
|
|
1305
1879
|
for (const key of Object.keys(request)) {
|
|
1306
1880
|
if (request[key] === undefined) delete request[key];
|
|
1307
1881
|
}
|
|
@@ -1363,7 +1937,7 @@ function normalizeResponsesUsage(usage) {
|
|
|
1363
1937
|
return {
|
|
1364
1938
|
input_tokens: inputTokens,
|
|
1365
1939
|
input_tokens_details: usage.input_tokens_details ?? {
|
|
1366
|
-
cached_tokens: usage.prompt_tokens_details?.cached_tokens ?? 0,
|
|
1940
|
+
cached_tokens: usage.prompt_tokens_details?.cached_tokens ?? usage.prompt_cache_hit_tokens ?? 0,
|
|
1367
1941
|
},
|
|
1368
1942
|
output_tokens: outputTokens,
|
|
1369
1943
|
output_tokens_details: usage.output_tokens_details ?? {
|
|
@@ -1378,6 +1952,7 @@ function outputTextFromOutput(output) {
|
|
|
1378
1952
|
const chunks = [];
|
|
1379
1953
|
for (const item of output) {
|
|
1380
1954
|
if (item?.type !== 'message' || !Array.isArray(item.content)) continue;
|
|
1955
|
+
if (item.phase === 'commentary') continue;
|
|
1381
1956
|
for (const part of item.content) {
|
|
1382
1957
|
if (part?.type === 'output_text' && typeof part.text === 'string') {
|
|
1383
1958
|
chunks.push(part.text);
|
|
@@ -1413,29 +1988,174 @@ export function createResponseEnvelope({
|
|
|
1413
1988
|
});
|
|
1414
1989
|
}
|
|
1415
1990
|
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
const choice = Array.isArray(completion.choices) ? completion.choices[0] : null;
|
|
1419
|
-
const message = choice?.message || {};
|
|
1420
|
-
const content = chatContentToResponseOutputParts(message.content);
|
|
1421
|
-
const toolSchemas = buildToolSchemas(normalized?.tools);
|
|
1422
|
-
const toolNames = buildToolNames(normalized?.tools);
|
|
1423
|
-
const output = [];
|
|
1991
|
+
const PARALLEL_TOOL_WRAPPER_NAMES = new Set(['multi_tool_use.parallel', 'multi_tool_use_parallel']);
|
|
1992
|
+
const EMITTED_TOOL_NAME_PREFIX = 'functions.';
|
|
1424
1993
|
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
content,
|
|
1431
|
-
status: 'completed',
|
|
1432
|
-
phase: message.phase || 'final_answer',
|
|
1433
|
-
});
|
|
1434
|
-
}
|
|
1994
|
+
export function isParallelToolWrapperName(name) {
|
|
1995
|
+
const raw = String(name || '').toLowerCase();
|
|
1996
|
+
const stripped = raw.startsWith(EMITTED_TOOL_NAME_PREFIX) ? raw.slice(EMITTED_TOOL_NAME_PREFIX.length) : raw;
|
|
1997
|
+
return PARALLEL_TOOL_WRAPPER_NAMES.has(stripped);
|
|
1998
|
+
}
|
|
1435
1999
|
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
2000
|
+
function toolNameMatchKey(name) {
|
|
2001
|
+
return String(name || '').toLowerCase().replace(/\./g, '__');
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
export function resolveEmittedToolName(name, knownNames) {
|
|
2005
|
+
const raw = String(name || '');
|
|
2006
|
+
if (!raw || !knownNames) return name;
|
|
2007
|
+
const known = knownNames instanceof Set ? knownNames : new Set(knownNames);
|
|
2008
|
+
if (!known.size || known.has(raw)) return name;
|
|
2009
|
+
const candidates = [raw];
|
|
2010
|
+
if (raw.startsWith(EMITTED_TOOL_NAME_PREFIX)) candidates.push(raw.slice(EMITTED_TOOL_NAME_PREFIX.length));
|
|
2011
|
+
for (const candidate of candidates) {
|
|
2012
|
+
if (known.has(candidate)) return candidate;
|
|
2013
|
+
}
|
|
2014
|
+
const knownByKey = new Map();
|
|
2015
|
+
for (const knownName of known) {
|
|
2016
|
+
const key = toolNameMatchKey(knownName);
|
|
2017
|
+
if (!knownByKey.has(key)) knownByKey.set(key, knownName);
|
|
2018
|
+
}
|
|
2019
|
+
for (const candidate of candidates) {
|
|
2020
|
+
const match = knownByKey.get(toolNameMatchKey(candidate));
|
|
2021
|
+
if (match) return match;
|
|
2022
|
+
}
|
|
2023
|
+
return name;
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
export function chatToolNamesFromTools(tools) {
|
|
2027
|
+
const names = [];
|
|
2028
|
+
for (const tool of Array.isArray(tools) ? tools : []) {
|
|
2029
|
+
if (!isObject(tool)) continue;
|
|
2030
|
+
const name = isObject(tool.function) ? tool.function.name : tool.name;
|
|
2031
|
+
if (name) names.push(String(name));
|
|
2032
|
+
}
|
|
2033
|
+
return names;
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
export function resolveEmittedToolCallNamesInCompletion(completion, knownNames) {
|
|
2037
|
+
const choice = Array.isArray(completion?.choices) ? completion.choices[0] : null;
|
|
2038
|
+
const toolCalls = choice?.message?.tool_calls;
|
|
2039
|
+
if (!Array.isArray(toolCalls) || !toolCalls.length) return completion;
|
|
2040
|
+
let changed = false;
|
|
2041
|
+
const resolved = toolCalls.map((toolCall) => {
|
|
2042
|
+
const name = toolCall?.function?.name;
|
|
2043
|
+
if (!name) return toolCall;
|
|
2044
|
+
const resolvedName = resolveEmittedToolName(name, knownNames);
|
|
2045
|
+
if (resolvedName === name) return toolCall;
|
|
2046
|
+
changed = true;
|
|
2047
|
+
return { ...toolCall, function: { ...toolCall.function, name: resolvedName } };
|
|
2048
|
+
});
|
|
2049
|
+
if (!changed) return completion;
|
|
2050
|
+
return {
|
|
2051
|
+
...completion,
|
|
2052
|
+
choices: completion.choices.map((entry, index) => (index === 0
|
|
2053
|
+
? { ...entry, message: { ...entry.message, tool_calls: resolved } }
|
|
2054
|
+
: entry)),
|
|
2055
|
+
};
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
function parallelToolUsesFromArguments(argumentsText) {
|
|
2059
|
+
const parsed = safeJsonParse(String(argumentsText || ''));
|
|
2060
|
+
if (!parsed.ok || !isObject(parsed.value)) return null;
|
|
2061
|
+
const uses = Array.isArray(parsed.value.tool_uses) ? parsed.value.tool_uses : null;
|
|
2062
|
+
if (!uses || !uses.length) return null;
|
|
2063
|
+
const calls = [];
|
|
2064
|
+
for (const use of uses) {
|
|
2065
|
+
if (!isObject(use)) return null;
|
|
2066
|
+
const name = String(use.recipient_name || use.name || '').replace(/^functions\./, '');
|
|
2067
|
+
if (!name) return null;
|
|
2068
|
+
const parameters = use.parameters ?? use.arguments ?? {};
|
|
2069
|
+
calls.push({
|
|
2070
|
+
name,
|
|
2071
|
+
arguments: typeof parameters === 'string' ? parameters : JSON.stringify(parameters),
|
|
2072
|
+
});
|
|
2073
|
+
}
|
|
2074
|
+
return calls;
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
export function expandParallelToolCalls(toolCalls) {
|
|
2078
|
+
if (!Array.isArray(toolCalls)) return toolCalls;
|
|
2079
|
+
if (!toolCalls.some((toolCall) => isParallelToolWrapperName(toolCall?.function?.name))) return toolCalls;
|
|
2080
|
+
const expanded = [];
|
|
2081
|
+
for (const toolCall of toolCalls) {
|
|
2082
|
+
const uses = isParallelToolWrapperName(toolCall?.function?.name)
|
|
2083
|
+
? parallelToolUsesFromArguments(toolCall.function?.arguments)
|
|
2084
|
+
: null;
|
|
2085
|
+
if (!uses) {
|
|
2086
|
+
expanded.push(toolCall);
|
|
2087
|
+
continue;
|
|
2088
|
+
}
|
|
2089
|
+
for (const use of uses) {
|
|
2090
|
+
expanded.push({
|
|
2091
|
+
id: generateId('call'),
|
|
2092
|
+
type: 'function',
|
|
2093
|
+
function: { name: use.name, arguments: use.arguments },
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
return expanded;
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
export function expandParallelToolCallsInCompletion(completion) {
|
|
2101
|
+
const choice = Array.isArray(completion?.choices) ? completion.choices[0] : null;
|
|
2102
|
+
const toolCalls = choice?.message?.tool_calls;
|
|
2103
|
+
if (!Array.isArray(toolCalls)) return completion;
|
|
2104
|
+
const expanded = expandParallelToolCalls(toolCalls);
|
|
2105
|
+
if (expanded === toolCalls) return completion;
|
|
2106
|
+
return {
|
|
2107
|
+
...completion,
|
|
2108
|
+
choices: completion.choices.map((entry, index) => (index === 0
|
|
2109
|
+
? { ...entry, message: { ...entry.message, tool_calls: expanded } }
|
|
2110
|
+
: entry)),
|
|
2111
|
+
};
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
export function convertChatCompletionToResponses({ completion: rawCompletion, model, previousResponseId, normalized, responseId = generateId('resp'), config = {} }) {
|
|
2115
|
+
const toolSchemas = buildToolSchemas(normalized?.tools);
|
|
2116
|
+
const knownToolNames = new Set([...toolSchemas.keys(), INTERNAL_COMMENTARY_TOOL]);
|
|
2117
|
+
const completion = resolveEmittedToolCallNamesInCompletion(
|
|
2118
|
+
expandParallelToolCallsInCompletion(rawCompletion),
|
|
2119
|
+
knownToolNames,
|
|
2120
|
+
);
|
|
2121
|
+
const createdAt = completion.created || Date.now() / 1000;
|
|
2122
|
+
const choice = Array.isArray(completion.choices) ? completion.choices[0] : null;
|
|
2123
|
+
const message = choice?.message || {};
|
|
2124
|
+
const content = chatContentToResponseOutputParts(message.content);
|
|
2125
|
+
const messageHasToolCalls = hasChatToolCalls(message);
|
|
2126
|
+
const toolNames = buildToolNames(normalized?.tools);
|
|
2127
|
+
const customToolNames = buildCustomToolNames(normalized?.tools);
|
|
2128
|
+
const output = [];
|
|
2129
|
+
|
|
2130
|
+
if (content.length) {
|
|
2131
|
+
output.push({
|
|
2132
|
+
type: 'message',
|
|
2133
|
+
id: generateId('msg'),
|
|
2134
|
+
role: 'assistant',
|
|
2135
|
+
content,
|
|
2136
|
+
status: 'completed',
|
|
2137
|
+
phase: message.phase || (messageHasToolCalls ? 'commentary' : 'final_answer'),
|
|
2138
|
+
});
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
if (messageHasToolCalls) {
|
|
2142
|
+
const bridgedCommentary = !requestToolsIncludeCommentary(normalized?.tools);
|
|
2143
|
+
for (const toolCall of message.tool_calls) {
|
|
2144
|
+
if (bridgedCommentary && toolCall?.type === 'function' && String(toolCall?.function?.name || '') === INTERNAL_COMMENTARY_TOOL) {
|
|
2145
|
+
const text = commentaryTextFromArguments(toolCall.function?.arguments);
|
|
2146
|
+
if (text) {
|
|
2147
|
+
output.push({
|
|
2148
|
+
type: 'message',
|
|
2149
|
+
id: generateId('msg'),
|
|
2150
|
+
role: 'assistant',
|
|
2151
|
+
content: [normalizeOutputTextPart(text)],
|
|
2152
|
+
status: 'completed',
|
|
2153
|
+
phase: 'commentary',
|
|
2154
|
+
});
|
|
2155
|
+
}
|
|
2156
|
+
continue;
|
|
2157
|
+
}
|
|
2158
|
+
const item = responseItemFromChatToolCall(toolCall, toolNames, customToolNames);
|
|
1439
2159
|
normalizeFunctionCallItemArguments(item, toolSchemas, config);
|
|
1440
2160
|
output.push(item);
|
|
1441
2161
|
}
|
|
@@ -1449,7 +2169,7 @@ export function convertChatCompletionToResponses({ completion, model, previousRe
|
|
|
1449
2169
|
summary: [normalizeSummaryTextPart(reasoningSummaryText(reasoningText))],
|
|
1450
2170
|
content: [],
|
|
1451
2171
|
reasoning_content: reasoningText,
|
|
1452
|
-
encrypted_content:
|
|
2172
|
+
encrypted_content: encodeGatewayReasoning(reasoningText),
|
|
1453
2173
|
status: 'completed',
|
|
1454
2174
|
});
|
|
1455
2175
|
}
|
|
@@ -1475,9 +2195,10 @@ export class ResponsesStreamMapper {
|
|
|
1475
2195
|
createdAt = Date.now() / 1000,
|
|
1476
2196
|
previousResponseId = null,
|
|
1477
2197
|
normalized,
|
|
1478
|
-
bufferOutputUntilDone = false,
|
|
1479
2198
|
emitReasoningSummary = true,
|
|
1480
2199
|
emitReasoningText = false,
|
|
2200
|
+
holdToolItemEvents = false,
|
|
2201
|
+
knownToolNames,
|
|
1481
2202
|
config = {},
|
|
1482
2203
|
} = {}) {
|
|
1483
2204
|
this.responseId = responseId;
|
|
@@ -1485,9 +2206,10 @@ export class ResponsesStreamMapper {
|
|
|
1485
2206
|
this.createdAt = createdAt;
|
|
1486
2207
|
this.previousResponseId = previousResponseId;
|
|
1487
2208
|
this.normalized = normalized;
|
|
1488
|
-
this.bufferOutputUntilDone = Boolean(bufferOutputUntilDone);
|
|
1489
2209
|
this.emitReasoningSummary = Boolean(emitReasoningSummary);
|
|
1490
2210
|
this.emitReasoningText = Boolean(emitReasoningText);
|
|
2211
|
+
this.holdToolItemEvents = Boolean(holdToolItemEvents);
|
|
2212
|
+
this.roundStartIndex = 0;
|
|
1491
2213
|
this.sequenceNumber = 0;
|
|
1492
2214
|
this.output = [];
|
|
1493
2215
|
this.messageItem = null;
|
|
@@ -1495,19 +2217,33 @@ export class ResponsesStreamMapper {
|
|
|
1495
2217
|
this.toolItems = new Map();
|
|
1496
2218
|
this.text = '';
|
|
1497
2219
|
this.reasoningText = '';
|
|
1498
|
-
this.streamReasoningLive = true;
|
|
1499
2220
|
this.reasoningItemDone = false;
|
|
1500
2221
|
this.finishReason = null;
|
|
1501
2222
|
this.usage = null;
|
|
2223
|
+
this.pendingFinishReason = null;
|
|
2224
|
+
this.pendingUsage = null;
|
|
1502
2225
|
this.completedAt = null;
|
|
1503
2226
|
this.finalized = false;
|
|
2227
|
+
this.messageItemAdded = false;
|
|
1504
2228
|
this.messageContentAdded = false;
|
|
2229
|
+
this.messageItemClosed = false;
|
|
2230
|
+
this.holdVisibleText = false;
|
|
1505
2231
|
this.reasoningContentAdded = false;
|
|
1506
2232
|
this.reasoningSummaryAdded = false;
|
|
1507
2233
|
this.reasoningItemAdded = false;
|
|
1508
2234
|
this.streamedReasoningSummaryText = '';
|
|
1509
2235
|
this.toolSchemas = buildToolSchemas(normalized?.tools);
|
|
2236
|
+
this.knownToolNames = new Set([
|
|
2237
|
+
...(Array.isArray(knownToolNames) || knownToolNames instanceof Set ? knownToolNames : []),
|
|
2238
|
+
...this.toolSchemas.keys(),
|
|
2239
|
+
INTERNAL_COMMENTARY_TOOL,
|
|
2240
|
+
]);
|
|
1510
2241
|
this.toolNames = buildToolNames(normalized?.tools);
|
|
2242
|
+
this.customToolNames = buildCustomToolNames(normalized?.tools);
|
|
2243
|
+
this.bridgedCommentaryTool = !requestToolsIncludeCommentary(normalized?.tools);
|
|
2244
|
+
this.toolItemsAdded = new Set();
|
|
2245
|
+
this.toolItemsBufferedArguments = new Set();
|
|
2246
|
+
this.toolItemsStreamedArgumentLengths = new Map();
|
|
1511
2247
|
this.config = config;
|
|
1512
2248
|
}
|
|
1513
2249
|
|
|
@@ -1548,27 +2284,92 @@ export class ResponsesStreamMapper {
|
|
|
1548
2284
|
}
|
|
1549
2285
|
|
|
1550
2286
|
ensureMessageItem() {
|
|
2287
|
+
this.ensureMessageItemState();
|
|
2288
|
+
if (!this.messageItemAdded) {
|
|
2289
|
+
this.messageItemAdded = true;
|
|
2290
|
+
return {
|
|
2291
|
+
type: 'response.output_item.added',
|
|
2292
|
+
sequence_number: this.nextSequence(),
|
|
2293
|
+
output_index: this.output.length - 1,
|
|
2294
|
+
item: snapshotResponseItem(this.messageItem),
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2297
|
+
return null;
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
ensureMessageItemState() {
|
|
1551
2301
|
if (!this.messageItem) {
|
|
1552
2302
|
this.messageItem = {
|
|
1553
2303
|
id: generateId('msg'),
|
|
1554
2304
|
type: 'message',
|
|
1555
2305
|
status: 'in_progress',
|
|
1556
2306
|
role: 'assistant',
|
|
1557
|
-
phase: 'final_answer',
|
|
2307
|
+
phase: this.messageStartsAsCommentary() ? 'commentary' : 'final_answer',
|
|
1558
2308
|
content: [],
|
|
1559
2309
|
};
|
|
1560
2310
|
this.output.push(this.messageItem);
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
messageStartsAsCommentary() {
|
|
2315
|
+
return this.toolItems.size > 0 || (this.config.upstreamProvider === 'deepseek' && hasRunnableChatTools(this.normalized));
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
finalizeMessagePhase() {
|
|
2319
|
+
if (!this.messageItem || this.toolItems.size > 0) return;
|
|
2320
|
+
this.messageItem.phase = 'final_answer';
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
closeMessageItemAsCommentary(status = 'completed') {
|
|
2324
|
+
if (!this.messageItem || this.messageItemClosed) return [];
|
|
2325
|
+
this.messageItem.phase = 'commentary';
|
|
2326
|
+
if (!this.messageItemAdded) return [];
|
|
2327
|
+
this.messageItemClosed = true;
|
|
2328
|
+
this.messageItem.status = status;
|
|
2329
|
+
this.messageItem.content[0].text = this.text;
|
|
2330
|
+
const outputIndex = this.output.indexOf(this.messageItem);
|
|
2331
|
+
return [
|
|
2332
|
+
{
|
|
2333
|
+
type: 'response.output_text.done',
|
|
2334
|
+
sequence_number: this.nextSequence(),
|
|
2335
|
+
output_index: outputIndex,
|
|
2336
|
+
content_index: 0,
|
|
2337
|
+
item_id: this.messageItem.id,
|
|
2338
|
+
text: this.text,
|
|
2339
|
+
logprobs: [],
|
|
2340
|
+
},
|
|
2341
|
+
{
|
|
2342
|
+
type: 'response.content_part.done',
|
|
2343
|
+
sequence_number: this.nextSequence(),
|
|
2344
|
+
output_index: outputIndex,
|
|
2345
|
+
content_index: 0,
|
|
2346
|
+
item_id: this.messageItem.id,
|
|
2347
|
+
part: snapshotResponsePart(this.messageItem.content[0]),
|
|
2348
|
+
},
|
|
2349
|
+
{
|
|
2350
|
+
type: 'response.output_item.done',
|
|
2351
|
+
sequence_number: this.nextSequence(),
|
|
2352
|
+
output_index: outputIndex,
|
|
2353
|
+
item: snapshotResponseItem(this.messageItem),
|
|
2354
|
+
},
|
|
2355
|
+
];
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
ensureReasoningItem() {
|
|
2359
|
+
this.ensureReasoningItemState();
|
|
2360
|
+
if (!this.reasoningItemAdded) {
|
|
2361
|
+
this.reasoningItemAdded = true;
|
|
1561
2362
|
return {
|
|
1562
2363
|
type: 'response.output_item.added',
|
|
1563
2364
|
sequence_number: this.nextSequence(),
|
|
1564
|
-
output_index: this.output.
|
|
1565
|
-
item: snapshotResponseItem(this.
|
|
2365
|
+
output_index: this.output.indexOf(this.reasoningItem),
|
|
2366
|
+
item: snapshotResponseItem(this.reasoningItem),
|
|
1566
2367
|
};
|
|
1567
2368
|
}
|
|
1568
2369
|
return null;
|
|
1569
2370
|
}
|
|
1570
2371
|
|
|
1571
|
-
|
|
2372
|
+
ensureReasoningItemState() {
|
|
1572
2373
|
if (!this.reasoningItem) {
|
|
1573
2374
|
this.reasoningItem = {
|
|
1574
2375
|
id: generateId('rs'),
|
|
@@ -1580,16 +2381,6 @@ export class ResponsesStreamMapper {
|
|
|
1580
2381
|
};
|
|
1581
2382
|
this.output.push(this.reasoningItem);
|
|
1582
2383
|
}
|
|
1583
|
-
if (!this.reasoningItemAdded) {
|
|
1584
|
-
this.reasoningItemAdded = true;
|
|
1585
|
-
return {
|
|
1586
|
-
type: 'response.output_item.added',
|
|
1587
|
-
sequence_number: this.nextSequence(),
|
|
1588
|
-
output_index: this.output.indexOf(this.reasoningItem),
|
|
1589
|
-
item: snapshotResponseItem(this.reasoningItem),
|
|
1590
|
-
};
|
|
1591
|
-
}
|
|
1592
|
-
return null;
|
|
1593
2384
|
}
|
|
1594
2385
|
|
|
1595
2386
|
ensureReasoningContentPart(events) {
|
|
@@ -1619,7 +2410,13 @@ export class ResponsesStreamMapper {
|
|
|
1619
2410
|
else delete this.reasoningItem.reasoning_content;
|
|
1620
2411
|
}
|
|
1621
2412
|
|
|
1622
|
-
|
|
2413
|
+
reasoningSummarySourceText({ final = false } = {}) {
|
|
2414
|
+
if (final) return this.reasoningText;
|
|
2415
|
+
const boundary = this.reasoningText.lastIndexOf('\n');
|
|
2416
|
+
return boundary < 0 ? '' : this.reasoningText.slice(0, boundary + 1);
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
appendReasoningSummaryDelta(events, { final = false } = {}) {
|
|
1623
2420
|
if (!this.emitReasoningSummary || !this.reasoningItem) return;
|
|
1624
2421
|
if (!this.reasoningSummaryAdded) {
|
|
1625
2422
|
this.reasoningSummaryAdded = true;
|
|
@@ -1633,14 +2430,17 @@ export class ResponsesStreamMapper {
|
|
|
1633
2430
|
part: snapshotResponsePart(this.reasoningItem.summary[0]),
|
|
1634
2431
|
});
|
|
1635
2432
|
}
|
|
1636
|
-
const nextSummaryText = reasoningSummaryText(this.
|
|
1637
|
-
this.reasoningItem.summary[0].text = nextSummaryText;
|
|
2433
|
+
const nextSummaryText = reasoningSummaryText(this.reasoningSummarySourceText({ final }));
|
|
2434
|
+
if (final) this.reasoningItem.summary[0].text = nextSummaryText;
|
|
2435
|
+
if (nextSummaryText === this.streamedReasoningSummaryText) return;
|
|
1638
2436
|
if (!nextSummaryText.startsWith(this.streamedReasoningSummaryText)) {
|
|
2437
|
+
if (!final) return;
|
|
1639
2438
|
this.streamedReasoningSummaryText = nextSummaryText;
|
|
1640
2439
|
return;
|
|
1641
2440
|
}
|
|
1642
2441
|
const deltaText = nextSummaryText.slice(this.streamedReasoningSummaryText.length);
|
|
1643
2442
|
this.streamedReasoningSummaryText = nextSummaryText;
|
|
2443
|
+
this.reasoningItem.summary[0].text = nextSummaryText;
|
|
1644
2444
|
for (const delta of splitBufferedReasoningDeltas(deltaText)) {
|
|
1645
2445
|
events.push({
|
|
1646
2446
|
type: 'response.reasoning_summary_text.delta',
|
|
@@ -1653,28 +2453,20 @@ export class ResponsesStreamMapper {
|
|
|
1653
2453
|
}
|
|
1654
2454
|
}
|
|
1655
2455
|
|
|
2456
|
+
holdVisibleTextUntilDone() {
|
|
2457
|
+
this.holdVisibleText = true;
|
|
2458
|
+
}
|
|
2459
|
+
|
|
1656
2460
|
textDelta(delta) {
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
status: 'in_progress',
|
|
1663
|
-
role: 'assistant',
|
|
1664
|
-
phase: 'final_answer',
|
|
1665
|
-
content: [normalizeOutputTextPart('')],
|
|
1666
|
-
};
|
|
1667
|
-
this.output.push(this.messageItem);
|
|
1668
|
-
}
|
|
2461
|
+
const events = [...this.closeReasoningItem('completed')];
|
|
2462
|
+
if (this.messageItemClosed) return events;
|
|
2463
|
+
if (!this.messageItemAdded && (this.holdVisibleText || this.toolItems.size > 0)) {
|
|
2464
|
+
this.ensureMessageItemState();
|
|
2465
|
+
if (!this.messageItem.content.length) this.messageItem.content.push(normalizeOutputTextPart(''));
|
|
1669
2466
|
this.text += delta;
|
|
1670
2467
|
this.messageItem.content[0].text = this.text;
|
|
1671
|
-
return
|
|
1672
|
-
}
|
|
1673
|
-
const events = [];
|
|
1674
|
-
if (this.streamReasoningLive) {
|
|
1675
|
-
events.push(...this.closeReasoningItem('completed'));
|
|
2468
|
+
return events;
|
|
1676
2469
|
}
|
|
1677
|
-
this.streamReasoningLive = false;
|
|
1678
2470
|
const added = this.ensureMessageItem();
|
|
1679
2471
|
if (added) events.push(added);
|
|
1680
2472
|
if (!this.messageContentAdded) {
|
|
@@ -1705,15 +2497,11 @@ export class ResponsesStreamMapper {
|
|
|
1705
2497
|
|
|
1706
2498
|
reasoningDelta(delta) {
|
|
1707
2499
|
this.reasoningText += delta;
|
|
1708
|
-
if (this.
|
|
1709
|
-
const events = [];
|
|
1710
|
-
const added = this.ensureReasoningItem();
|
|
1711
|
-
if (added) events.push(added);
|
|
2500
|
+
if (this.reasoningItemDone) {
|
|
1712
2501
|
this.syncReasoningItemContent();
|
|
1713
|
-
|
|
1714
|
-
return events;
|
|
2502
|
+
return [];
|
|
1715
2503
|
}
|
|
1716
|
-
if (
|
|
2504
|
+
if (this.messageItem || this.toolItems.size > 0) {
|
|
1717
2505
|
if (!this.reasoningItem) {
|
|
1718
2506
|
this.reasoningItem = {
|
|
1719
2507
|
id: generateId('rs'),
|
|
@@ -1748,89 +2536,222 @@ export class ResponsesStreamMapper {
|
|
|
1748
2536
|
return events;
|
|
1749
2537
|
}
|
|
1750
2538
|
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
execution: 'client',
|
|
1764
|
-
arguments: '',
|
|
1765
|
-
}
|
|
1766
|
-
: {
|
|
1767
|
-
id: callId,
|
|
1768
|
-
type: 'function_call',
|
|
1769
|
-
status: 'in_progress',
|
|
1770
|
-
call_id: callId,
|
|
1771
|
-
...decodedToolName(toolCall.function?.name, this.toolNames),
|
|
1772
|
-
arguments: '',
|
|
1773
|
-
};
|
|
1774
|
-
this.toolItems.set(index, item);
|
|
1775
|
-
this.output.push(item);
|
|
1776
|
-
}
|
|
1777
|
-
if (isCodexToolSearchTool(toolCall.function?.name)) {
|
|
1778
|
-
item = convertItemToToolSearchCall(item);
|
|
1779
|
-
} else if (toolCall.function?.name && item.type !== 'tool_search_call') {
|
|
1780
|
-
Object.assign(item, decodedToolName(toolCall.function.name, this.toolNames));
|
|
1781
|
-
}
|
|
1782
|
-
item.arguments += toolCall.function?.arguments || '';
|
|
1783
|
-
return [];
|
|
2539
|
+
createToolItem(toolCall) {
|
|
2540
|
+
const callId = toolCall.id || generateId('call');
|
|
2541
|
+
const name = toolCall.function?.name;
|
|
2542
|
+
if (isCodexToolSearchTool(name)) {
|
|
2543
|
+
return {
|
|
2544
|
+
id: generateId('tsc'),
|
|
2545
|
+
type: 'tool_search_call',
|
|
2546
|
+
status: 'in_progress',
|
|
2547
|
+
call_id: callId,
|
|
2548
|
+
execution: 'client',
|
|
2549
|
+
arguments: '',
|
|
2550
|
+
};
|
|
1784
2551
|
}
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
2552
|
+
if (name && this.customToolNames.has(name)) {
|
|
2553
|
+
return {
|
|
2554
|
+
id: callId,
|
|
2555
|
+
type: 'custom_tool_call',
|
|
2556
|
+
status: 'in_progress',
|
|
2557
|
+
call_id: callId,
|
|
2558
|
+
...decodedToolName(name, this.toolNames),
|
|
2559
|
+
input: '',
|
|
2560
|
+
arguments: '',
|
|
2561
|
+
};
|
|
2562
|
+
}
|
|
2563
|
+
return {
|
|
2564
|
+
id: callId,
|
|
2565
|
+
type: 'function_call',
|
|
2566
|
+
status: 'in_progress',
|
|
2567
|
+
call_id: callId,
|
|
2568
|
+
...decodedToolName(name, this.toolNames),
|
|
2569
|
+
arguments: '',
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
|
|
2573
|
+
applyToolCallName(item, name) {
|
|
2574
|
+
if (!name || item.type === 'tool_search_call') return;
|
|
2575
|
+
Object.assign(item, decodedToolName(name, this.toolNames));
|
|
2576
|
+
if (item.type === 'function_call' && this.customToolNames.has(name)) {
|
|
2577
|
+
item.type = 'custom_tool_call';
|
|
2578
|
+
if (typeof item.input !== 'string') item.input = '';
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
functionDelta(rawToolCall) {
|
|
2583
|
+
let toolCall = rawToolCall;
|
|
2584
|
+
const emittedName = toolCall.function?.name;
|
|
2585
|
+
if (emittedName) {
|
|
2586
|
+
const resolvedName = resolveEmittedToolName(emittedName, this.knownToolNames);
|
|
2587
|
+
if (resolvedName !== emittedName) {
|
|
2588
|
+
toolCall = { ...toolCall, function: { ...toolCall.function, name: resolvedName } };
|
|
2589
|
+
}
|
|
1788
2590
|
}
|
|
1789
|
-
|
|
2591
|
+
const events = [...this.closeReasoningItem('completed')];
|
|
2592
|
+
events.push(...this.closeMessageItemAsCommentary('completed'));
|
|
1790
2593
|
const index = toolCall.index ?? 0;
|
|
1791
2594
|
let item = this.toolItems.get(index);
|
|
1792
2595
|
if (!item) {
|
|
1793
|
-
|
|
1794
|
-
item = isCodexToolSearchTool(toolCall.function?.name)
|
|
1795
|
-
? {
|
|
1796
|
-
id: generateId('tsc'),
|
|
1797
|
-
type: 'tool_search_call',
|
|
1798
|
-
status: 'in_progress',
|
|
1799
|
-
call_id: callId,
|
|
1800
|
-
execution: 'client',
|
|
1801
|
-
arguments: '',
|
|
1802
|
-
}
|
|
1803
|
-
: {
|
|
1804
|
-
id: callId,
|
|
1805
|
-
type: 'function_call',
|
|
1806
|
-
status: 'in_progress',
|
|
1807
|
-
call_id: callId,
|
|
1808
|
-
...decodedToolName(toolCall.function?.name, this.toolNames),
|
|
1809
|
-
arguments: '',
|
|
1810
|
-
};
|
|
2596
|
+
item = this.createToolItem(toolCall);
|
|
1811
2597
|
this.toolItems.set(index, item);
|
|
1812
2598
|
this.output.push(item);
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
}
|
|
2599
|
+
if (this.holdToolItemEvents || isParallelToolWrapperName(toolCall.function?.name)) {
|
|
2600
|
+
this.toolItemsBufferedArguments.add(index);
|
|
2601
|
+
}
|
|
2602
|
+
if (!this.holdToolItemEvents && hasToolCallFunctionName(toolCall) && !isParallelToolWrapperName(toolCall.function?.name) && !this.isBridgedCommentaryToolItem(item)) {
|
|
2603
|
+
events.push(...this.addToolItemEvents(index));
|
|
2604
|
+
}
|
|
1819
2605
|
}
|
|
1820
2606
|
if (isCodexToolSearchTool(toolCall.function?.name)) {
|
|
2607
|
+
const wasAdded = this.toolItemsAdded.has(index);
|
|
1821
2608
|
item = convertItemToToolSearchCall(item);
|
|
1822
|
-
|
|
1823
|
-
|
|
2609
|
+
this.toolItems.set(index, item);
|
|
2610
|
+
if (!wasAdded && !this.holdToolItemEvents && hasToolCallFunctionName(toolCall)) {
|
|
2611
|
+
events.push(...this.addToolItemEvents(index));
|
|
2612
|
+
}
|
|
2613
|
+
} else if (toolCall.function?.name) {
|
|
2614
|
+
this.applyToolCallName(item, toolCall.function.name);
|
|
2615
|
+
if (!this.holdToolItemEvents && !this.toolItemsAdded.has(index) && !isParallelToolWrapperName(item.name) && !this.isBridgedCommentaryToolItem(item)) {
|
|
2616
|
+
events.push(...this.addToolItemEvents(index));
|
|
2617
|
+
}
|
|
1824
2618
|
}
|
|
1825
|
-
|
|
1826
|
-
item.
|
|
1827
|
-
|
|
2619
|
+
item.arguments += toolCall.function?.arguments || '';
|
|
2620
|
+
if (functionCallItemNeedsArgumentNormalization(item, this.toolSchemas, this.config)) {
|
|
2621
|
+
this.toolItemsBufferedArguments.add(index);
|
|
2622
|
+
}
|
|
2623
|
+
const streamedLength = this.toolItemsStreamedArgumentLengths.get(index) || 0;
|
|
2624
|
+
const argumentDelta = item.arguments.slice(streamedLength);
|
|
2625
|
+
if (
|
|
2626
|
+
argumentDelta &&
|
|
2627
|
+
item.type === 'function_call' &&
|
|
2628
|
+
this.toolItemsAdded.has(index) &&
|
|
2629
|
+
!this.toolItemsBufferedArguments.has(index)
|
|
2630
|
+
) {
|
|
2631
|
+
this.toolItemsStreamedArgumentLengths.set(index, item.arguments.length);
|
|
1828
2632
|
events.push({
|
|
1829
2633
|
type: 'response.function_call_arguments.delta',
|
|
1830
2634
|
sequence_number: this.nextSequence(),
|
|
1831
2635
|
output_index: this.output.indexOf(item),
|
|
1832
2636
|
item_id: item.id,
|
|
1833
|
-
delta,
|
|
2637
|
+
delta: argumentDelta,
|
|
2638
|
+
});
|
|
2639
|
+
}
|
|
2640
|
+
return events;
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
addToolItemEvents(index) {
|
|
2644
|
+
if (this.toolItemsAdded.has(index)) return [];
|
|
2645
|
+
const item = this.toolItems.get(index);
|
|
2646
|
+
if (!item) return [];
|
|
2647
|
+
this.toolItemsAdded.add(index);
|
|
2648
|
+
return [{
|
|
2649
|
+
type: 'response.output_item.added',
|
|
2650
|
+
sequence_number: this.nextSequence(),
|
|
2651
|
+
output_index: this.output.indexOf(item),
|
|
2652
|
+
item: snapshotResponseItem(item),
|
|
2653
|
+
}];
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
expandParallelToolItems() {
|
|
2657
|
+
for (const [index, item] of [...this.toolItems.entries()]) {
|
|
2658
|
+
if (item.type !== 'function_call' || !isParallelToolWrapperName(item.name)) continue;
|
|
2659
|
+
if (this.toolItemsAdded.has(index)) continue;
|
|
2660
|
+
const uses = parallelToolUsesFromArguments(item.arguments);
|
|
2661
|
+
if (!uses) continue;
|
|
2662
|
+
const at = this.output.indexOf(item);
|
|
2663
|
+
this.toolItems.delete(index);
|
|
2664
|
+
this.toolItemsBufferedArguments.delete(index);
|
|
2665
|
+
this.toolItemsStreamedArgumentLengths.delete(index);
|
|
2666
|
+
const expandedItems = uses.map((use, position) => {
|
|
2667
|
+
const key = `${index}:${position}`;
|
|
2668
|
+
const expanded = this.createToolItem({
|
|
2669
|
+
id: generateId('call'),
|
|
2670
|
+
function: { name: resolveEmittedToolName(use.name, this.knownToolNames) },
|
|
2671
|
+
});
|
|
2672
|
+
expanded.arguments = use.arguments;
|
|
2673
|
+
this.toolItems.set(key, expanded);
|
|
2674
|
+
this.toolItemsBufferedArguments.add(key);
|
|
2675
|
+
return expanded;
|
|
2676
|
+
});
|
|
2677
|
+
if (at >= 0) this.output.splice(at, 1, ...expandedItems);
|
|
2678
|
+
else this.output.push(...expandedItems);
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
isBridgedCommentaryToolItem(item) {
|
|
2683
|
+
return Boolean(this.bridgedCommentaryTool) && item?.type === 'function_call' && item?.name === INTERNAL_COMMENTARY_TOOL;
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
convertBridgedCommentaryItems() {
|
|
2687
|
+
const events = [];
|
|
2688
|
+
for (const [index, item] of [...this.toolItems.entries()]) {
|
|
2689
|
+
if (!this.isBridgedCommentaryToolItem(item) || this.toolItemsAdded.has(index)) continue;
|
|
2690
|
+
this.toolItems.delete(index);
|
|
2691
|
+
this.toolItemsBufferedArguments.delete(index);
|
|
2692
|
+
this.toolItemsStreamedArgumentLengths.delete(index);
|
|
2693
|
+
const at = this.output.indexOf(item);
|
|
2694
|
+
const text = commentaryTextFromArguments(item.arguments);
|
|
2695
|
+
if (!text) {
|
|
2696
|
+
if (at >= 0) this.output.splice(at, 1);
|
|
2697
|
+
continue;
|
|
2698
|
+
}
|
|
2699
|
+
const messageItem = {
|
|
2700
|
+
type: 'message',
|
|
2701
|
+
id: generateId('msg'),
|
|
2702
|
+
role: 'assistant',
|
|
2703
|
+
status: 'completed',
|
|
2704
|
+
phase: 'commentary',
|
|
2705
|
+
content: [normalizeOutputTextPart(text)],
|
|
2706
|
+
};
|
|
2707
|
+
if (at >= 0) this.output.splice(at, 1, messageItem);
|
|
2708
|
+
else this.output.push(messageItem);
|
|
2709
|
+
const outputIndex = this.output.indexOf(messageItem);
|
|
2710
|
+
events.push({
|
|
2711
|
+
type: 'response.output_item.added',
|
|
2712
|
+
sequence_number: this.nextSequence(),
|
|
2713
|
+
output_index: outputIndex,
|
|
2714
|
+
item: snapshotResponseItem({ ...messageItem, status: 'in_progress', content: [] }),
|
|
2715
|
+
});
|
|
2716
|
+
events.push({
|
|
2717
|
+
type: 'response.content_part.added',
|
|
2718
|
+
sequence_number: this.nextSequence(),
|
|
2719
|
+
output_index: outputIndex,
|
|
2720
|
+
content_index: 0,
|
|
2721
|
+
item_id: messageItem.id,
|
|
2722
|
+
part: snapshotResponsePart(normalizeOutputTextPart('')),
|
|
2723
|
+
});
|
|
2724
|
+
events.push({
|
|
2725
|
+
type: 'response.output_text.delta',
|
|
2726
|
+
sequence_number: this.nextSequence(),
|
|
2727
|
+
output_index: outputIndex,
|
|
2728
|
+
content_index: 0,
|
|
2729
|
+
item_id: messageItem.id,
|
|
2730
|
+
delta: text,
|
|
2731
|
+
logprobs: [],
|
|
2732
|
+
});
|
|
2733
|
+
events.push({
|
|
2734
|
+
type: 'response.output_text.done',
|
|
2735
|
+
sequence_number: this.nextSequence(),
|
|
2736
|
+
output_index: outputIndex,
|
|
2737
|
+
content_index: 0,
|
|
2738
|
+
item_id: messageItem.id,
|
|
2739
|
+
text,
|
|
2740
|
+
logprobs: [],
|
|
2741
|
+
});
|
|
2742
|
+
events.push({
|
|
2743
|
+
type: 'response.content_part.done',
|
|
2744
|
+
sequence_number: this.nextSequence(),
|
|
2745
|
+
output_index: outputIndex,
|
|
2746
|
+
content_index: 0,
|
|
2747
|
+
item_id: messageItem.id,
|
|
2748
|
+
part: snapshotResponsePart(messageItem.content[0]),
|
|
2749
|
+
});
|
|
2750
|
+
events.push({
|
|
2751
|
+
type: 'response.output_item.done',
|
|
2752
|
+
sequence_number: this.nextSequence(),
|
|
2753
|
+
output_index: outputIndex,
|
|
2754
|
+
item: snapshotResponseItem(messageItem),
|
|
1834
2755
|
});
|
|
1835
2756
|
}
|
|
1836
2757
|
return events;
|
|
@@ -1839,33 +2760,24 @@ export class ResponsesStreamMapper {
|
|
|
1839
2760
|
finalize(finishReason = 'stop', usage = null) {
|
|
1840
2761
|
if (this.finalized) return [];
|
|
1841
2762
|
this.finalized = true;
|
|
2763
|
+
this.expandParallelToolItems();
|
|
1842
2764
|
const status = finishReason === 'length' ? 'incomplete' : 'completed';
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
this.
|
|
1852
|
-
|
|
1853
|
-
if (this.messageItem) {
|
|
1854
|
-
this.messageItem.status = status;
|
|
1855
|
-
if (!this.messageItem.content.length) {
|
|
1856
|
-
this.messageItem.content.push(normalizeOutputTextPart(''));
|
|
1857
|
-
}
|
|
2765
|
+
this.finishReason = finishReason;
|
|
2766
|
+
this.usage = normalizeResponsesUsage(usage);
|
|
2767
|
+
this.completedAt = Date.now() / 1000;
|
|
2768
|
+
const events = [];
|
|
2769
|
+
if (this.messageItem) {
|
|
2770
|
+
this.messageItem.status = status;
|
|
2771
|
+
const outputIndex = this.output.indexOf(this.messageItem);
|
|
2772
|
+
if (!this.messageItemClosed) {
|
|
2773
|
+
this.finalizeMessagePhase();
|
|
2774
|
+
if (!this.messageItem.content.length) this.messageItem.content.push(normalizeOutputTextPart(''));
|
|
1858
2775
|
this.messageItem.content[0].text = this.text;
|
|
1859
2776
|
}
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
}
|
|
1865
|
-
events.push(...this.closeReasoningItem(status));
|
|
1866
|
-
|
|
1867
|
-
if (this.messageItem) {
|
|
1868
|
-
const outputIndex = this.output.indexOf(this.messageItem);
|
|
2777
|
+
if (!this.messageItemAdded) {
|
|
2778
|
+
this.messageItemAdded = true;
|
|
2779
|
+
this.messageContentAdded = true;
|
|
2780
|
+
this.messageItemClosed = true;
|
|
1869
2781
|
events.push({
|
|
1870
2782
|
type: 'response.output_item.added',
|
|
1871
2783
|
sequence_number: this.nextSequence(),
|
|
@@ -1895,36 +2807,22 @@ export class ResponsesStreamMapper {
|
|
|
1895
2807
|
logprobs: [],
|
|
1896
2808
|
});
|
|
1897
2809
|
}
|
|
1898
|
-
events.push(
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
content_index: 0,
|
|
1903
|
-
item_id: this.messageItem.id,
|
|
1904
|
-
text: this.text,
|
|
1905
|
-
logprobs: [],
|
|
1906
|
-
});
|
|
1907
|
-
events.push({
|
|
1908
|
-
type: 'response.content_part.done',
|
|
1909
|
-
sequence_number: this.nextSequence(),
|
|
1910
|
-
output_index: outputIndex,
|
|
1911
|
-
content_index: 0,
|
|
1912
|
-
item_id: this.messageItem.id,
|
|
1913
|
-
part: snapshotResponsePart(this.messageItem.content[0]),
|
|
1914
|
-
});
|
|
1915
|
-
events.push({
|
|
1916
|
-
type: 'response.output_item.done',
|
|
1917
|
-
sequence_number: this.nextSequence(),
|
|
1918
|
-
output_index: outputIndex,
|
|
1919
|
-
item: snapshotResponseItem(this.messageItem),
|
|
1920
|
-
});
|
|
2810
|
+
events.push(...this.messageDoneEvents(outputIndex));
|
|
2811
|
+
} else if (!this.messageItemClosed) {
|
|
2812
|
+
this.messageItemClosed = true;
|
|
2813
|
+
events.push(...this.messageDoneEvents(outputIndex));
|
|
1921
2814
|
}
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
2815
|
+
}
|
|
2816
|
+
events.push(...this.closeReasoningItem(status));
|
|
2817
|
+
events.push(...this.convertBridgedCommentaryItems());
|
|
2818
|
+
for (const [index, item] of this.toolItems.entries()) {
|
|
2819
|
+
item.status = status;
|
|
2820
|
+
if (item.type === 'tool_search_call') finalizeToolSearchCallItemArguments(item);
|
|
2821
|
+
else if (item.type === 'custom_tool_call') item.input = customToolInputFromArguments(item.arguments);
|
|
2822
|
+
else normalizeFunctionCallItemArguments(item, this.toolSchemas, this.config);
|
|
2823
|
+
const outputIndex = this.output.indexOf(item);
|
|
2824
|
+
if (!this.toolItemsAdded.has(index)) {
|
|
2825
|
+
this.toolItemsAdded.add(index);
|
|
1928
2826
|
events.push({
|
|
1929
2827
|
type: 'response.output_item.added',
|
|
1930
2828
|
sequence_number: this.nextSequence(),
|
|
@@ -1932,121 +2830,31 @@ export class ResponsesStreamMapper {
|
|
|
1932
2830
|
item: snapshotResponseItem(item.type === 'tool_search_call' ? {
|
|
1933
2831
|
...item,
|
|
1934
2832
|
status: 'in_progress',
|
|
2833
|
+
} : item.type === 'custom_tool_call' ? {
|
|
2834
|
+
...item,
|
|
2835
|
+
status: 'in_progress',
|
|
2836
|
+
input: '',
|
|
1935
2837
|
} : {
|
|
1936
2838
|
...item,
|
|
1937
2839
|
status: 'in_progress',
|
|
1938
2840
|
arguments: '',
|
|
1939
2841
|
}),
|
|
1940
2842
|
});
|
|
1941
|
-
if (item.type === 'function_call' && item.arguments) {
|
|
1942
|
-
events.push({
|
|
1943
|
-
type: 'response.function_call_arguments.delta',
|
|
1944
|
-
sequence_number: this.nextSequence(),
|
|
1945
|
-
output_index: outputIndex,
|
|
1946
|
-
item_id: item.id,
|
|
1947
|
-
delta: item.arguments,
|
|
1948
|
-
});
|
|
1949
|
-
}
|
|
1950
|
-
if (item.type === 'function_call') {
|
|
1951
|
-
events.push({
|
|
1952
|
-
type: 'response.function_call_arguments.done',
|
|
1953
|
-
sequence_number: this.nextSequence(),
|
|
1954
|
-
output_index: outputIndex,
|
|
1955
|
-
item_id: item.id,
|
|
1956
|
-
arguments: item.arguments,
|
|
1957
|
-
});
|
|
1958
|
-
}
|
|
1959
|
-
events.push({
|
|
1960
|
-
type: 'response.output_item.done',
|
|
1961
|
-
sequence_number: this.nextSequence(),
|
|
1962
|
-
output_index: outputIndex,
|
|
1963
|
-
item: snapshotResponseItem(item),
|
|
1964
|
-
});
|
|
1965
|
-
}
|
|
1966
|
-
|
|
1967
|
-
const response = createBaseResponse({
|
|
1968
|
-
id: this.responseId,
|
|
1969
|
-
model: this.model,
|
|
1970
|
-
createdAt: this.createdAt,
|
|
1971
|
-
status,
|
|
1972
|
-
output: this.output,
|
|
1973
|
-
previousResponseId: this.previousResponseId,
|
|
1974
|
-
usage: normalizeResponsesUsage(usage),
|
|
1975
|
-
normalized: this.normalized,
|
|
1976
|
-
completedAt: Date.now() / 1000,
|
|
1977
|
-
incompleteReason: finishReason === 'length' ? 'max_output_tokens' : null,
|
|
1978
|
-
});
|
|
1979
|
-
events.push({
|
|
1980
|
-
type: status === 'incomplete' ? 'response.incomplete' : 'response.completed',
|
|
1981
|
-
sequence_number: this.nextSequence(),
|
|
1982
|
-
response,
|
|
1983
|
-
});
|
|
1984
|
-
return events;
|
|
1985
|
-
}
|
|
1986
|
-
|
|
1987
|
-
const events = [];
|
|
1988
|
-
this.finishReason = finishReason;
|
|
1989
|
-
this.usage = normalizeResponsesUsage(usage);
|
|
1990
|
-
this.completedAt = Date.now() / 1000;
|
|
1991
|
-
if (this.reasoningItem) {
|
|
1992
|
-
this.reasoningItem.status = finishReason === 'length' ? 'incomplete' : 'completed';
|
|
1993
|
-
if (this.emitReasoningSummary && this.reasoningText && !this.reasoningSummaryAdded) {
|
|
1994
|
-
this.reasoningSummaryAdded = true;
|
|
1995
|
-
this.reasoningItem.summary.push(normalizeSummaryTextPart(reasoningSummaryText(this.reasoningText)));
|
|
1996
2843
|
}
|
|
1997
|
-
this.
|
|
1998
|
-
if (this.reasoningItem.summary[0]) {
|
|
1999
|
-
this.reasoningItem.summary[0].text = reasoningSummaryText(this.reasoningText);
|
|
2000
|
-
}
|
|
2001
|
-
}
|
|
2002
|
-
if (this.messageItem) {
|
|
2003
|
-
this.messageItem.status = finishReason === 'length' ? 'incomplete' : 'completed';
|
|
2004
|
-
this.messageItem.content[0].text = this.text;
|
|
2005
|
-
if (!this.messageContentAdded) {
|
|
2006
|
-
this.messageContentAdded = true;
|
|
2844
|
+
if (item.type === 'function_call' && this.toolItemsBufferedArguments.has(index) && item.arguments) {
|
|
2007
2845
|
events.push({
|
|
2008
|
-
type: 'response.
|
|
2846
|
+
type: 'response.function_call_arguments.delta',
|
|
2009
2847
|
sequence_number: this.nextSequence(),
|
|
2010
|
-
output_index:
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
part: snapshotResponsePart(this.messageItem.content[0]),
|
|
2848
|
+
output_index: outputIndex,
|
|
2849
|
+
item_id: item.id,
|
|
2850
|
+
delta: item.arguments,
|
|
2014
2851
|
});
|
|
2015
2852
|
}
|
|
2016
|
-
events.push({
|
|
2017
|
-
type: 'response.output_text.done',
|
|
2018
|
-
sequence_number: this.nextSequence(),
|
|
2019
|
-
output_index: this.output.indexOf(this.messageItem),
|
|
2020
|
-
content_index: 0,
|
|
2021
|
-
item_id: this.messageItem.id,
|
|
2022
|
-
text: this.text,
|
|
2023
|
-
logprobs: [],
|
|
2024
|
-
});
|
|
2025
|
-
events.push({
|
|
2026
|
-
type: 'response.content_part.done',
|
|
2027
|
-
sequence_number: this.nextSequence(),
|
|
2028
|
-
output_index: this.output.indexOf(this.messageItem),
|
|
2029
|
-
content_index: 0,
|
|
2030
|
-
item_id: this.messageItem.id,
|
|
2031
|
-
part: snapshotResponsePart(this.messageItem.content[0]),
|
|
2032
|
-
});
|
|
2033
|
-
events.push({
|
|
2034
|
-
type: 'response.output_item.done',
|
|
2035
|
-
sequence_number: this.nextSequence(),
|
|
2036
|
-
output_index: this.output.indexOf(this.messageItem),
|
|
2037
|
-
item: snapshotResponseItem(this.messageItem),
|
|
2038
|
-
});
|
|
2039
|
-
}
|
|
2040
|
-
events.push(...this.closeReasoningItem(finishReason === 'length' ? 'incomplete' : 'completed'));
|
|
2041
|
-
for (const item of this.toolItems.values()) {
|
|
2042
|
-
item.status = finishReason === 'length' ? 'incomplete' : 'completed';
|
|
2043
|
-
if (item.type === 'tool_search_call') finalizeToolSearchCallItemArguments(item);
|
|
2044
|
-
else normalizeFunctionCallItemArguments(item, this.toolSchemas, this.config);
|
|
2045
2853
|
if (item.type === 'function_call') {
|
|
2046
2854
|
events.push({
|
|
2047
2855
|
type: 'response.function_call_arguments.done',
|
|
2048
2856
|
sequence_number: this.nextSequence(),
|
|
2049
|
-
output_index:
|
|
2857
|
+
output_index: outputIndex,
|
|
2050
2858
|
item_id: item.id,
|
|
2051
2859
|
arguments: item.arguments,
|
|
2052
2860
|
});
|
|
@@ -2054,7 +2862,7 @@ export class ResponsesStreamMapper {
|
|
|
2054
2862
|
events.push({
|
|
2055
2863
|
type: 'response.output_item.done',
|
|
2056
2864
|
sequence_number: this.nextSequence(),
|
|
2057
|
-
output_index:
|
|
2865
|
+
output_index: outputIndex,
|
|
2058
2866
|
item: snapshotResponseItem(item),
|
|
2059
2867
|
});
|
|
2060
2868
|
}
|
|
@@ -2078,93 +2886,130 @@ export class ResponsesStreamMapper {
|
|
|
2078
2886
|
return events;
|
|
2079
2887
|
}
|
|
2080
2888
|
|
|
2081
|
-
|
|
2082
|
-
return
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
flushBufferedReasoning(status = 'completed') {
|
|
2086
|
-
if (!this.reasoningItem || this.reasoningItemDone) return [];
|
|
2087
|
-
const events = [];
|
|
2088
|
-
const outputIndex = this.output.indexOf(this.reasoningItem);
|
|
2089
|
-
const summaryText = reasoningSummaryText(this.reasoningText);
|
|
2090
|
-
if (!this.reasoningItemAdded) {
|
|
2091
|
-
this.reasoningItemAdded = true;
|
|
2092
|
-
events.push({
|
|
2093
|
-
type: 'response.output_item.added',
|
|
2889
|
+
messageDoneEvents(outputIndex) {
|
|
2890
|
+
return [
|
|
2891
|
+
{
|
|
2892
|
+
type: 'response.output_text.done',
|
|
2094
2893
|
sequence_number: this.nextSequence(),
|
|
2095
2894
|
output_index: outputIndex,
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
}
|
|
2104
|
-
if (this.emitReasoningText) {
|
|
2105
|
-
this.reasoningContentAdded = true;
|
|
2106
|
-
events.push({
|
|
2107
|
-
type: 'response.content_part.added',
|
|
2895
|
+
content_index: 0,
|
|
2896
|
+
item_id: this.messageItem.id,
|
|
2897
|
+
text: this.messageItem.content[0].text,
|
|
2898
|
+
logprobs: [],
|
|
2899
|
+
},
|
|
2900
|
+
{
|
|
2901
|
+
type: 'response.content_part.done',
|
|
2108
2902
|
sequence_number: this.nextSequence(),
|
|
2109
2903
|
output_index: outputIndex,
|
|
2110
2904
|
content_index: 0,
|
|
2111
|
-
item_id: this.
|
|
2112
|
-
part: snapshotResponsePart(
|
|
2113
|
-
}
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
events.push({
|
|
2117
|
-
type: 'response.reasoning_text.delta',
|
|
2118
|
-
sequence_number: this.nextSequence(),
|
|
2119
|
-
output_index: outputIndex,
|
|
2120
|
-
item_id: this.reasoningItem.id,
|
|
2121
|
-
content_index: 0,
|
|
2122
|
-
delta,
|
|
2123
|
-
});
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
}
|
|
2127
|
-
if (this.emitReasoningSummary && !this.reasoningSummaryAdded) {
|
|
2128
|
-
this.reasoningSummaryAdded = true;
|
|
2129
|
-
this.reasoningItem.summary = [normalizeSummaryTextPart(summaryText)];
|
|
2130
|
-
events.push({
|
|
2131
|
-
type: 'response.reasoning_summary_part.added',
|
|
2905
|
+
item_id: this.messageItem.id,
|
|
2906
|
+
part: snapshotResponsePart(this.messageItem.content[0]),
|
|
2907
|
+
},
|
|
2908
|
+
{
|
|
2909
|
+
type: 'response.output_item.done',
|
|
2132
2910
|
sequence_number: this.nextSequence(),
|
|
2133
2911
|
output_index: outputIndex,
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2912
|
+
item: snapshotResponseItem(this.messageItem),
|
|
2913
|
+
},
|
|
2914
|
+
];
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2917
|
+
assistantMessage() {
|
|
2918
|
+
return assistantMessageFromResponseOutput(this.output);
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2921
|
+
markRoundStart() {
|
|
2922
|
+
this.roundStartIndex = this.output.length;
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2925
|
+
roundAssistantMessage() {
|
|
2926
|
+
return assistantMessageFromResponseOutput(this.output.slice(this.roundStartIndex));
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
removeToolItems(predicate = () => true) {
|
|
2930
|
+
for (const [index, item] of [...this.toolItems.entries()]) {
|
|
2931
|
+
if (!predicate(item)) continue;
|
|
2932
|
+
if (this.toolItemsAdded.has(index)) continue;
|
|
2933
|
+
this.toolItems.delete(index);
|
|
2934
|
+
this.toolItemsBufferedArguments.delete(index);
|
|
2935
|
+
this.toolItemsStreamedArgumentLengths.delete(index);
|
|
2936
|
+
const at = this.output.indexOf(item);
|
|
2937
|
+
if (at >= 0) this.output.splice(at, 1);
|
|
2151
2938
|
}
|
|
2152
|
-
|
|
2153
|
-
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
beginNextRound() {
|
|
2942
|
+
const events = [...this.closeReasoningItem('completed'), ...this.closeMessageItemAsCommentary('completed')];
|
|
2943
|
+
if (this.reasoningItem && !this.reasoningItemAdded) {
|
|
2944
|
+
const at = this.output.indexOf(this.reasoningItem);
|
|
2945
|
+
if (at >= 0) this.output.splice(at, 1);
|
|
2946
|
+
}
|
|
2947
|
+
if (this.messageItem && !this.messageItemAdded) {
|
|
2948
|
+
const at = this.output.indexOf(this.messageItem);
|
|
2949
|
+
if (at >= 0) this.output.splice(at, 1);
|
|
2950
|
+
}
|
|
2951
|
+
for (const [index, item] of this.toolItems.entries()) {
|
|
2952
|
+
if (this.toolItemsAdded.has(index)) continue;
|
|
2953
|
+
const at = this.output.indexOf(item);
|
|
2954
|
+
if (at >= 0) this.output.splice(at, 1);
|
|
2955
|
+
}
|
|
2956
|
+
this.messageItem = null;
|
|
2957
|
+
this.reasoningItem = null;
|
|
2958
|
+
this.toolItems = new Map();
|
|
2959
|
+
this.toolItemsAdded = new Set();
|
|
2960
|
+
this.toolItemsBufferedArguments = new Set();
|
|
2961
|
+
this.toolItemsStreamedArgumentLengths = new Map();
|
|
2962
|
+
this.text = '';
|
|
2963
|
+
this.reasoningText = '';
|
|
2964
|
+
this.reasoningItemDone = false;
|
|
2965
|
+
this.messageItemAdded = false;
|
|
2966
|
+
this.messageContentAdded = false;
|
|
2967
|
+
this.messageItemClosed = false;
|
|
2968
|
+
this.holdVisibleText = false;
|
|
2969
|
+
this.reasoningContentAdded = false;
|
|
2970
|
+
this.reasoningSummaryAdded = false;
|
|
2971
|
+
this.reasoningItemAdded = false;
|
|
2972
|
+
this.streamedReasoningSummaryText = '';
|
|
2973
|
+
this.pendingFinishReason = null;
|
|
2974
|
+
this.pendingUsage = null;
|
|
2154
2975
|
return events;
|
|
2155
2976
|
}
|
|
2156
2977
|
|
|
2978
|
+
replaceBufferedAssistantText(text) {
|
|
2979
|
+
if (this.messageItemAdded || this.messageContentAdded) return null;
|
|
2980
|
+
if (!this.messageItem) return this.textDelta(String(text ?? ''));
|
|
2981
|
+
this.text = String(text ?? '');
|
|
2982
|
+
if (this.messageItem.content.length) this.messageItem.content[0].text = this.text;
|
|
2983
|
+
return [];
|
|
2984
|
+
}
|
|
2985
|
+
|
|
2157
2986
|
closeReasoningItem(status = 'completed') {
|
|
2158
2987
|
if (!this.reasoningItem || this.reasoningItemDone) return [];
|
|
2159
2988
|
this.reasoningItemDone = true;
|
|
2160
2989
|
this.reasoningItem.status = status;
|
|
2161
2990
|
this.syncReasoningItemContent();
|
|
2162
|
-
|
|
2163
|
-
if (this.emitReasoningSummary && this.reasoningText && !this.reasoningSummaryAdded) {
|
|
2164
|
-
this.reasoningSummaryAdded = true;
|
|
2165
|
-
this.reasoningItem.summary.push(normalizeSummaryTextPart(summaryText));
|
|
2166
|
-
}
|
|
2991
|
+
this.reasoningItem.encrypted_content = encodeGatewayReasoning(normalizeReasoningContent(this.reasoningText));
|
|
2167
2992
|
const events = [];
|
|
2993
|
+
if (!this.reasoningItemAdded) {
|
|
2994
|
+
this.reasoningItemAdded = true;
|
|
2995
|
+
const addedItem = {
|
|
2996
|
+
...this.reasoningItem,
|
|
2997
|
+
status: 'in_progress',
|
|
2998
|
+
summary: [],
|
|
2999
|
+
content: this.emitReasoningText ? [normalizeReasoningTextPart('')] : [],
|
|
3000
|
+
encrypted_content: null,
|
|
3001
|
+
};
|
|
3002
|
+
delete addedItem.reasoning_content;
|
|
3003
|
+
events.push({
|
|
3004
|
+
type: 'response.output_item.added',
|
|
3005
|
+
sequence_number: this.nextSequence(),
|
|
3006
|
+
output_index: this.output.indexOf(this.reasoningItem),
|
|
3007
|
+
item: snapshotResponseItem(addedItem),
|
|
3008
|
+
});
|
|
3009
|
+
}
|
|
3010
|
+
if (this.emitReasoningSummary && this.reasoningText) {
|
|
3011
|
+
this.appendReasoningSummaryDelta(events, { final: true });
|
|
3012
|
+
}
|
|
2168
3013
|
if (this.reasoningSummaryAdded) {
|
|
2169
3014
|
for (const [summaryIndex, part] of this.reasoningItem.summary.entries()) {
|
|
2170
3015
|
events.push({
|
|
@@ -2212,11 +3057,35 @@ export class ResponsesStreamMapper {
|
|
|
2212
3057
|
return events;
|
|
2213
3058
|
}
|
|
2214
3059
|
|
|
3060
|
+
streamFailed(message) {
|
|
3061
|
+
if (this.finalized) return [];
|
|
3062
|
+
this.finalized = true;
|
|
3063
|
+
this.completedAt = Date.now() / 1000;
|
|
3064
|
+
const response = this.response('failed');
|
|
3065
|
+
response.error = { code: 'upstream_error', message: String(message || 'upstream stream failed') };
|
|
3066
|
+
return [{
|
|
3067
|
+
type: 'response.failed',
|
|
3068
|
+
sequence_number: this.nextSequence(),
|
|
3069
|
+
response,
|
|
3070
|
+
}];
|
|
3071
|
+
}
|
|
3072
|
+
|
|
2215
3073
|
mapChatEvent(event) {
|
|
2216
3074
|
if (!event) return [];
|
|
2217
|
-
if (event.done)
|
|
2218
|
-
|
|
3075
|
+
if (event.done) {
|
|
3076
|
+
if (this.pendingFinishReason || !event.eof) {
|
|
3077
|
+
return this.finalize(this.pendingFinishReason || 'stop', this.pendingUsage);
|
|
3078
|
+
}
|
|
3079
|
+
return this.streamFailed('upstream stream ended before completion');
|
|
3080
|
+
}
|
|
3081
|
+
let payload = event.data;
|
|
3082
|
+
if (typeof payload === 'string') {
|
|
3083
|
+
const parsed = safeJsonParse(payload);
|
|
3084
|
+
if (!parsed.ok) return [];
|
|
3085
|
+
payload = parsed.value;
|
|
3086
|
+
}
|
|
2219
3087
|
if (!isObject(payload)) return [];
|
|
3088
|
+
if (isObject(payload.usage)) this.pendingUsage = payload.usage;
|
|
2220
3089
|
const choice = Array.isArray(payload.choices) ? payload.choices[0] : null;
|
|
2221
3090
|
if (!choice) return [];
|
|
2222
3091
|
const delta = choice.delta || choice.message || {};
|
|
@@ -2236,7 +3105,7 @@ export class ResponsesStreamMapper {
|
|
|
2236
3105
|
}
|
|
2237
3106
|
}
|
|
2238
3107
|
if (choice.finish_reason) {
|
|
2239
|
-
|
|
3108
|
+
this.pendingFinishReason = choice.finish_reason;
|
|
2240
3109
|
}
|
|
2241
3110
|
return events;
|
|
2242
3111
|
}
|