@jsonstudio/llms 0.6.3238 → 0.6.3275
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conversion/bridge-actions.js +37 -322
- package/dist/conversion/bridge-instructions.js +12 -109
- package/dist/conversion/codecs/anthropic-openai-codec.js +1 -1
- package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.js +38 -0
- package/dist/conversion/compat/actions/deepseek-web-request.js +43 -110
- package/dist/conversion/compat/actions/deepseek-web-response.d.ts +3 -0
- package/dist/conversion/compat/actions/deepseek-web-response.js +150 -11
- package/dist/conversion/hub/response/response-runtime.d.ts +1 -0
- package/dist/conversion/hub/response/response-runtime.js +26 -0
- package/dist/conversion/shared/anthropic-message-utils.d.ts +3 -1
- package/dist/conversion/shared/anthropic-message-utils.js +23 -15
- package/dist/conversion/shared/openai-finalizer.d.ts +0 -3
- package/dist/conversion/shared/openai-finalizer.js +11 -169
- package/dist/conversion/shared/openai-message-normalize.js +11 -72
- package/dist/conversion/shared/tool-mapping.js +5 -0
- package/dist/native/router_hotpath_napi.node +0 -0
- package/dist/router/virtual-router/bootstrap/provider-normalization.js +11 -3
- package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.d.ts +20 -0
- package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js +71 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.d.ts +1 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.js +30 -0
- package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +6 -0
- package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.d.ts +2 -0
- package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +61 -0
- package/dist/router/virtual-router/engine.js +58 -1
- package/dist/router/virtual-router/types.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { applyToolTextRequestGuidance } from './tool-text-request-guidance.js';
|
|
1
2
|
const DEFAULT_OPTIONS = {
|
|
2
3
|
strictToolRequired: true,
|
|
3
|
-
|
|
4
|
+
toolProtocol: undefined
|
|
4
5
|
};
|
|
5
6
|
const SEARCH_ROUTE_PREFIXES = ['web_search', 'search'];
|
|
6
|
-
const TOOL_TEXT_GUIDANCE_MARKER = 'Tool-call output contract (STRICT)';
|
|
7
7
|
const readString = (value) => {
|
|
8
8
|
if (typeof value !== 'string') {
|
|
9
9
|
return undefined;
|
|
@@ -27,6 +27,31 @@ function readBoolean(input, fallback) {
|
|
|
27
27
|
}
|
|
28
28
|
return fallback;
|
|
29
29
|
}
|
|
30
|
+
function readOptionalBoolean(input) {
|
|
31
|
+
if (typeof input === 'boolean') {
|
|
32
|
+
return input;
|
|
33
|
+
}
|
|
34
|
+
if (typeof input === 'string') {
|
|
35
|
+
const normalized = input.trim().toLowerCase();
|
|
36
|
+
if (['true', '1', 'yes', 'on'].includes(normalized)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
if (['false', '0', 'no', 'off'].includes(normalized)) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
function readToolProtocol(input) {
|
|
46
|
+
if (typeof input !== 'string') {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
const normalized = input.trim().toLowerCase();
|
|
50
|
+
if (normalized === 'text' || normalized === 'native') {
|
|
51
|
+
return normalized;
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
30
55
|
function resolveOptions(adapterContext) {
|
|
31
56
|
const context = (adapterContext ?? {});
|
|
32
57
|
const deepseekNode = isRecord(context.deepseek)
|
|
@@ -37,9 +62,12 @@ function resolveOptions(adapterContext) {
|
|
|
37
62
|
if (!deepseekNode) {
|
|
38
63
|
return { ...DEFAULT_OPTIONS };
|
|
39
64
|
}
|
|
65
|
+
const legacyFallback = readOptionalBoolean(deepseekNode.textToolFallback);
|
|
66
|
+
const toolProtocol = readToolProtocol(deepseekNode.toolProtocol) ??
|
|
67
|
+
(legacyFallback !== undefined ? (legacyFallback ? 'text' : 'native') : undefined);
|
|
40
68
|
return {
|
|
41
69
|
strictToolRequired: readBoolean(deepseekNode.strictToolRequired, DEFAULT_OPTIONS.strictToolRequired),
|
|
42
|
-
|
|
70
|
+
toolProtocol
|
|
43
71
|
};
|
|
44
72
|
}
|
|
45
73
|
function normalizeContentToText(content) {
|
|
@@ -127,90 +155,6 @@ function normalizeToolCallsAsText(toolCallsRaw) {
|
|
|
127
155
|
return '';
|
|
128
156
|
}
|
|
129
157
|
}
|
|
130
|
-
function summarizeToolSchema(tool) {
|
|
131
|
-
const fn = isRecord(tool.function) ? tool.function : tool;
|
|
132
|
-
const name = readString(fn.name) ?? readString(tool.name) ?? 'unknown_tool';
|
|
133
|
-
const description = readString(fn.description) ?? 'No description';
|
|
134
|
-
const params = isRecord(fn.parameters) ? fn.parameters : undefined;
|
|
135
|
-
const properties = isRecord(params?.properties) ? params?.properties : undefined;
|
|
136
|
-
const required = Array.isArray(params?.required)
|
|
137
|
-
? params.required.map((item) => readString(item)).filter((item) => Boolean(item))
|
|
138
|
-
: [];
|
|
139
|
-
const lines = [`Tool: ${name}`, `Description: ${description}`];
|
|
140
|
-
if (properties) {
|
|
141
|
-
const fields = [];
|
|
142
|
-
for (const [propName, propValue] of Object.entries(properties)) {
|
|
143
|
-
const type = isRecord(propValue) ? (readString(propValue.type) ?? 'string') : 'string';
|
|
144
|
-
const marker = required.includes(propName) ? ' (required)' : '';
|
|
145
|
-
fields.push(` - ${propName}: ${type}${marker}`);
|
|
146
|
-
}
|
|
147
|
-
if (fields.length) {
|
|
148
|
-
lines.push('Parameters:', ...fields);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return lines.join('\n');
|
|
152
|
-
}
|
|
153
|
-
function buildToolFallbackInstruction(toolsRaw, requireToolCall) {
|
|
154
|
-
if (!Array.isArray(toolsRaw) || toolsRaw.length === 0) {
|
|
155
|
-
return '';
|
|
156
|
-
}
|
|
157
|
-
const schemas = toolsRaw
|
|
158
|
-
.filter((item) => isRecord(item))
|
|
159
|
-
.map((item) => summarizeToolSchema(item));
|
|
160
|
-
if (!schemas.length) {
|
|
161
|
-
return '';
|
|
162
|
-
}
|
|
163
|
-
return [
|
|
164
|
-
'You have access to these tools:',
|
|
165
|
-
'',
|
|
166
|
-
schemas.join('\n\n'),
|
|
167
|
-
'',
|
|
168
|
-
`${TOOL_TEXT_GUIDANCE_MARKER}:`,
|
|
169
|
-
'1) If you call a tool, your ENTIRE assistant output must be a single JSON object.',
|
|
170
|
-
'2) Use this exact top-level shape (and key names):',
|
|
171
|
-
'{"tool_calls":[{"name":"tool_name","input":{"arg":"value"}}]}',
|
|
172
|
-
'3) Use only `name` + `input` for each tool call. Do NOT emit `arguments`, `parameters`, or custom wrappers.',
|
|
173
|
-
'4) Do NOT include markdown fences, prose, progress logs, or shell transcript around the JSON.',
|
|
174
|
-
'5) Do NOT output pseudo tool results in text (forbidden examples: {"exec_command":...}, <function_results>...</function_results>).',
|
|
175
|
-
'6) If multiple tools are needed, append multiple entries in `tool_calls`.',
|
|
176
|
-
requireToolCall
|
|
177
|
-
? '7) tool_choice is required for this turn: return at least one tool call.'
|
|
178
|
-
: '7) If no tool is needed, plain text is allowed.',
|
|
179
|
-
'',
|
|
180
|
-
'Valid example:',
|
|
181
|
-
'{"tool_calls":[{"name":"exec_command","input":{"cmd":"pnpm -v","workdir":"/workspace"}}]}'
|
|
182
|
-
].join('\n');
|
|
183
|
-
}
|
|
184
|
-
function hasToolGuidanceMarker(messages) {
|
|
185
|
-
for (const item of messages) {
|
|
186
|
-
if (typeof item?.text !== 'string') {
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
if (item.text.includes(TOOL_TEXT_GUIDANCE_MARKER)) {
|
|
190
|
-
return true;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
return false;
|
|
194
|
-
}
|
|
195
|
-
function isToolChoiceRequired(root) {
|
|
196
|
-
const toolChoice = root.tool_choice;
|
|
197
|
-
if (typeof toolChoice === 'string') {
|
|
198
|
-
const normalized = toolChoice.trim().toLowerCase();
|
|
199
|
-
if (normalized === 'required') {
|
|
200
|
-
return true;
|
|
201
|
-
}
|
|
202
|
-
if (normalized === 'none' || normalized === 'auto') {
|
|
203
|
-
return false;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
if (isRecord(toolChoice)) {
|
|
207
|
-
const type = readString(toolChoice.type)?.toLowerCase();
|
|
208
|
-
if (type === 'function') {
|
|
209
|
-
return true;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
return false;
|
|
213
|
-
}
|
|
214
158
|
function toPromptMessages(root, options) {
|
|
215
159
|
const messagesRaw = Array.isArray(root.messages) ? root.messages : [];
|
|
216
160
|
const messages = [];
|
|
@@ -235,20 +179,6 @@ function toPromptMessages(root, options) {
|
|
|
235
179
|
const text = parts.filter(Boolean).join('\n').trim();
|
|
236
180
|
messages.push({ role, text });
|
|
237
181
|
}
|
|
238
|
-
if (options.textToolFallback) {
|
|
239
|
-
const instruction = hasToolGuidanceMarker(messages)
|
|
240
|
-
? ''
|
|
241
|
-
: buildToolFallbackInstruction(root.tools, isToolChoiceRequired(root));
|
|
242
|
-
if (instruction) {
|
|
243
|
-
const first = messages[0];
|
|
244
|
-
if (first && first.role === 'system') {
|
|
245
|
-
first.text = [first.text, instruction].filter(Boolean).join('\n\n');
|
|
246
|
-
}
|
|
247
|
-
else {
|
|
248
|
-
messages.unshift({ role: 'system', text: instruction });
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
182
|
return messages;
|
|
253
183
|
}
|
|
254
184
|
function mergeByRole(messages) {
|
|
@@ -337,27 +267,30 @@ export function applyDeepSeekWebRequestTransform(payload, adapterContext) {
|
|
|
337
267
|
}
|
|
338
268
|
const root = structuredClone(payload);
|
|
339
269
|
const options = resolveOptions(adapterContext);
|
|
340
|
-
const
|
|
270
|
+
const guidedRoot = options.toolProtocol === 'text'
|
|
271
|
+
? applyToolTextRequestGuidance(root, { enabled: true })
|
|
272
|
+
: root;
|
|
273
|
+
const model = resolveModel(guidedRoot, adapterContext);
|
|
341
274
|
const flags = resolveThinkingSearchFlags(model);
|
|
342
|
-
const forceSearch = shouldForceSearch(
|
|
343
|
-
const prompt = buildPromptFromMessages(toPromptMessages(
|
|
275
|
+
const forceSearch = shouldForceSearch(guidedRoot, adapterContext);
|
|
276
|
+
const prompt = buildPromptFromMessages(toPromptMessages(guidedRoot, options));
|
|
344
277
|
const next = {
|
|
345
|
-
...(readString(
|
|
346
|
-
parent_message_id: readString(
|
|
278
|
+
...(readString(guidedRoot.chat_session_id) ? { chat_session_id: readString(guidedRoot.chat_session_id) } : {}),
|
|
279
|
+
parent_message_id: readString(guidedRoot.parent_message_id) ?? null,
|
|
347
280
|
prompt,
|
|
348
|
-
ref_file_ids: Array.isArray(
|
|
281
|
+
ref_file_ids: Array.isArray(guidedRoot.ref_file_ids) ? guidedRoot.ref_file_ids : [],
|
|
349
282
|
thinking_enabled: flags.thinking,
|
|
350
283
|
search_enabled: forceSearch ? true : flags.search
|
|
351
284
|
};
|
|
352
|
-
if (
|
|
285
|
+
if (guidedRoot.stream === true) {
|
|
353
286
|
next.stream = true;
|
|
354
287
|
}
|
|
355
288
|
// Preserve a tiny runtime hint surface for response-side strict/fallback checks.
|
|
356
289
|
next.metadata = {
|
|
357
|
-
...(isRecord(
|
|
290
|
+
...(isRecord(guidedRoot.metadata) ? guidedRoot.metadata : {}),
|
|
358
291
|
deepseek: {
|
|
359
292
|
strictToolRequired: options.strictToolRequired,
|
|
360
|
-
|
|
293
|
+
...(options.toolProtocol ? { toolProtocol: options.toolProtocol } : {})
|
|
361
294
|
}
|
|
362
295
|
};
|
|
363
296
|
return next;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { AdapterContext } from '../../hub/types/chat-envelope.js';
|
|
2
2
|
import type { JsonObject } from '../../hub/types/json.js';
|
|
3
3
|
import { type TextMarkupNormalizeOptions } from '../../shared/text-markup-normalizer.js';
|
|
4
|
+
type DeepSeekToolProtocol = 'native' | 'text';
|
|
4
5
|
export interface DeepSeekWebResponseConfig {
|
|
5
6
|
strictToolRequired?: boolean;
|
|
6
7
|
textNormalizer?: TextMarkupNormalizeOptions;
|
|
8
|
+
toolProtocol?: DeepSeekToolProtocol;
|
|
7
9
|
}
|
|
8
10
|
export declare function applyDeepSeekWebResponseTransform(payload: JsonObject, adapterContext?: AdapterContext, config?: DeepSeekWebResponseConfig): JsonObject;
|
|
11
|
+
export {};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { validateToolCall } from '../../../tools/tool-registry.js';
|
|
2
2
|
import { encoding_for_model, get_encoding } from 'tiktoken';
|
|
3
|
+
import { normalizeFunctionCallId } from '../../bridge-id-utils.js';
|
|
3
4
|
const DEFAULT_OPTIONS = {
|
|
4
5
|
strictToolRequired: true,
|
|
5
|
-
textNormalizer: undefined
|
|
6
|
+
textNormalizer: undefined,
|
|
7
|
+
toolProtocol: undefined
|
|
6
8
|
};
|
|
7
9
|
const SHELL_LIKE_TOOL_NAMES = new Set(['exec_command', 'shell_command', 'shell', 'bash', 'terminal']);
|
|
8
10
|
const SHELL_TOOL_NAME_ALIASES = new Map([
|
|
@@ -29,6 +31,52 @@ const readString = (value) => {
|
|
|
29
31
|
const trimmed = value.trim();
|
|
30
32
|
return trimmed.length ? trimmed : undefined;
|
|
31
33
|
};
|
|
34
|
+
function flattenMessageContent(content) {
|
|
35
|
+
if (typeof content === 'string') {
|
|
36
|
+
return content.trim();
|
|
37
|
+
}
|
|
38
|
+
if (Array.isArray(content)) {
|
|
39
|
+
const parts = [];
|
|
40
|
+
for (const part of content) {
|
|
41
|
+
if (!isRecord(part)) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const text = readString(part.text) ?? readString(part.content) ?? readString(part.value);
|
|
45
|
+
if (text) {
|
|
46
|
+
parts.push(text);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return parts.join('\n').trim();
|
|
50
|
+
}
|
|
51
|
+
if (isRecord(content)) {
|
|
52
|
+
const direct = readString(content.text) ?? readString(content.content);
|
|
53
|
+
if (direct) {
|
|
54
|
+
return direct;
|
|
55
|
+
}
|
|
56
|
+
if (Array.isArray(content.content)) {
|
|
57
|
+
return flattenMessageContent(content.content);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return '';
|
|
61
|
+
}
|
|
62
|
+
function extractResponsesMessageText(item) {
|
|
63
|
+
const parts = [];
|
|
64
|
+
const content = Array.isArray(item.content) ? item.content : [];
|
|
65
|
+
for (const part of content) {
|
|
66
|
+
if (!isRecord(part)) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const text = readString(part.text) ?? readString(part.content) ?? readString(part.value);
|
|
70
|
+
if (text) {
|
|
71
|
+
parts.push(text);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const outputText = readString(item.output_text);
|
|
75
|
+
if (outputText) {
|
|
76
|
+
parts.push(outputText);
|
|
77
|
+
}
|
|
78
|
+
return parts.join('\n').trim();
|
|
79
|
+
}
|
|
32
80
|
function readNumber(value) {
|
|
33
81
|
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
34
82
|
return value;
|
|
@@ -75,6 +123,16 @@ function readOptionalBoolean(input) {
|
|
|
75
123
|
}
|
|
76
124
|
return undefined;
|
|
77
125
|
}
|
|
126
|
+
function readToolProtocol(input) {
|
|
127
|
+
if (typeof input !== 'string') {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
const normalized = input.trim().toLowerCase();
|
|
131
|
+
if (normalized === 'text' || normalized === 'native') {
|
|
132
|
+
return normalized;
|
|
133
|
+
}
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
78
136
|
function getTokenEncoder(modelHint) {
|
|
79
137
|
const normalized = typeof modelHint === 'string' ? modelHint.trim() : '';
|
|
80
138
|
if (normalized) {
|
|
@@ -116,19 +174,27 @@ function resolveOptions(adapterContext, config) {
|
|
|
116
174
|
? context.__rt.deepseek
|
|
117
175
|
: undefined;
|
|
118
176
|
if (!deepseekNode) {
|
|
177
|
+
const toolProtocolOverride = readToolProtocol(config?.toolProtocol);
|
|
119
178
|
return {
|
|
120
179
|
...DEFAULT_OPTIONS,
|
|
121
180
|
strictToolRequired: readOptionalBoolean(config?.strictToolRequired) ?? DEFAULT_OPTIONS.strictToolRequired,
|
|
122
|
-
textNormalizer: config?.textNormalizer
|
|
181
|
+
textNormalizer: config?.textNormalizer,
|
|
182
|
+
toolProtocol: toolProtocolOverride ?? DEFAULT_OPTIONS.toolProtocol
|
|
123
183
|
};
|
|
124
184
|
}
|
|
125
185
|
const strictOverride = readOptionalBoolean(config?.strictToolRequired);
|
|
186
|
+
const toolProtocolOverride = readToolProtocol(config?.toolProtocol);
|
|
187
|
+
const legacyFallback = readOptionalBoolean(deepseekNode.textToolFallback);
|
|
188
|
+
const toolProtocol = toolProtocolOverride ??
|
|
189
|
+
readToolProtocol(deepseekNode.toolProtocol) ??
|
|
190
|
+
(legacyFallback !== undefined ? (legacyFallback ? 'text' : 'native') : undefined);
|
|
126
191
|
return {
|
|
127
192
|
strictToolRequired: strictOverride ??
|
|
128
193
|
readBoolean(deepseekNode.strictToolRequired, DEFAULT_OPTIONS.strictToolRequired),
|
|
129
194
|
textNormalizer: config?.textNormalizer && typeof config.textNormalizer === 'object'
|
|
130
195
|
? config.textNormalizer
|
|
131
|
-
: undefined
|
|
196
|
+
: undefined,
|
|
197
|
+
toolProtocol
|
|
132
198
|
};
|
|
133
199
|
}
|
|
134
200
|
function resolveCapturedRequest(adapterContext) {
|
|
@@ -686,7 +752,7 @@ function applyUsageEstimate(root, choices, adapterContext, captured) {
|
|
|
686
752
|
function normalizeChoice(choice, options, allowedToolNames, callIdPrefix) {
|
|
687
753
|
const message = isRecord(choice.message) ? choice.message : undefined;
|
|
688
754
|
if (!message) {
|
|
689
|
-
return { hasNative: false,
|
|
755
|
+
return { hasNative: false, hasText: false, harvestedFunctionResults: false };
|
|
690
756
|
}
|
|
691
757
|
const native = normalizeMessageToolCalls(message, allowedToolNames, callIdPrefix);
|
|
692
758
|
if (native) {
|
|
@@ -694,14 +760,83 @@ function normalizeChoice(choice, options, allowedToolNames, callIdPrefix) {
|
|
|
694
760
|
if (!finish || finish === 'stop') {
|
|
695
761
|
choice.finish_reason = 'tool_calls';
|
|
696
762
|
}
|
|
697
|
-
return { hasNative: true,
|
|
763
|
+
return { hasNative: true, hasText: false, harvestedFunctionResults: false };
|
|
764
|
+
}
|
|
765
|
+
if (options.toolProtocol === 'text') {
|
|
766
|
+
const contentText = flattenMessageContent(message.content);
|
|
767
|
+
if (contentText && (contentText.startsWith('{') || contentText.startsWith('['))) {
|
|
768
|
+
message.tool_calls = contentText;
|
|
769
|
+
}
|
|
770
|
+
const text = normalizeMessageToolCalls(message, allowedToolNames, callIdPrefix);
|
|
771
|
+
if (text) {
|
|
772
|
+
const finish = readString(choice.finish_reason)?.toLowerCase();
|
|
773
|
+
if (!finish || finish === 'stop') {
|
|
774
|
+
choice.finish_reason = 'tool_calls';
|
|
775
|
+
}
|
|
776
|
+
return { hasNative: false, hasText: true, harvestedFunctionResults: false };
|
|
777
|
+
}
|
|
698
778
|
}
|
|
699
779
|
return {
|
|
700
780
|
hasNative: false,
|
|
701
|
-
|
|
781
|
+
hasText: false,
|
|
702
782
|
harvestedFunctionResults: false
|
|
703
783
|
};
|
|
704
784
|
}
|
|
785
|
+
function harvestResponsesTextToolCalls(root, options, allowedToolNames) {
|
|
786
|
+
if (options.toolProtocol !== 'text') {
|
|
787
|
+
return { harvested: false };
|
|
788
|
+
}
|
|
789
|
+
const output = Array.isArray(root.output) ? root.output : [];
|
|
790
|
+
if (!output.length) {
|
|
791
|
+
return { harvested: false };
|
|
792
|
+
}
|
|
793
|
+
let harvested = false;
|
|
794
|
+
const nextOutput = [];
|
|
795
|
+
let callIndex = 0;
|
|
796
|
+
for (const item of output) {
|
|
797
|
+
if (!isRecord(item)) {
|
|
798
|
+
nextOutput.push(item);
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
const type = readString(item.type)?.toLowerCase() ?? '';
|
|
802
|
+
if (type !== 'message') {
|
|
803
|
+
nextOutput.push(item);
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
const role = readString(item.role)?.toLowerCase() ?? 'assistant';
|
|
807
|
+
if (role !== 'assistant') {
|
|
808
|
+
nextOutput.push(item);
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
const text = extractResponsesMessageText(item);
|
|
812
|
+
if (!text || (!text.trim().startsWith('{') && !text.trim().startsWith('['))) {
|
|
813
|
+
nextOutput.push(item);
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
const normalizedCalls = normalizeToolCalls(text.trim(), allowedToolNames, 'deepseek_resp_call');
|
|
817
|
+
if (!normalizedCalls.length) {
|
|
818
|
+
nextOutput.push(item);
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
harvested = true;
|
|
822
|
+
for (const call of normalizedCalls) {
|
|
823
|
+
callIndex += 1;
|
|
824
|
+
const callId = readString(call.id) ?? `deepseek_resp_call_${callIndex}`;
|
|
825
|
+
const itemId = normalizeFunctionCallId({ callId, fallback: `fc_${callId}` });
|
|
826
|
+
nextOutput.push({
|
|
827
|
+
type: 'function_call',
|
|
828
|
+
id: itemId,
|
|
829
|
+
call_id: callId,
|
|
830
|
+
name: call.function.name,
|
|
831
|
+
arguments: call.function.arguments
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
if (harvested) {
|
|
836
|
+
root.output = nextOutput;
|
|
837
|
+
}
|
|
838
|
+
return { harvested };
|
|
839
|
+
}
|
|
705
840
|
function writeCompatState(root, state, source, harvestedFunctionResults) {
|
|
706
841
|
const metadata = isRecord(root.metadata) ? root.metadata : {};
|
|
707
842
|
metadata.deepseek = {
|
|
@@ -724,7 +859,7 @@ export function applyDeepSeekWebResponseTransform(payload, adapterContext, confi
|
|
|
724
859
|
const toolChoiceRequired = isToolChoiceRequired(captured);
|
|
725
860
|
const choices = Array.isArray(root.choices) ? root.choices : [];
|
|
726
861
|
let hasNative = false;
|
|
727
|
-
let
|
|
862
|
+
let hasText = false;
|
|
728
863
|
let harvestedFunctionResults = false;
|
|
729
864
|
for (const [index, choice] of choices.entries()) {
|
|
730
865
|
if (!isRecord(choice)) {
|
|
@@ -732,18 +867,22 @@ export function applyDeepSeekWebResponseTransform(payload, adapterContext, confi
|
|
|
732
867
|
}
|
|
733
868
|
const result = normalizeChoice(choice, options, allowedToolNames, `deepseek_call_${index + 1}`);
|
|
734
869
|
hasNative = hasNative || result.hasNative;
|
|
735
|
-
|
|
870
|
+
hasText = hasText || result.hasText;
|
|
736
871
|
harvestedFunctionResults = harvestedFunctionResults || result.harvestedFunctionResults;
|
|
737
872
|
}
|
|
873
|
+
if (!choices.length) {
|
|
874
|
+
const harvested = harvestResponsesTextToolCalls(root, options, allowedToolNames);
|
|
875
|
+
hasText = hasText || harvested.harvested;
|
|
876
|
+
}
|
|
738
877
|
const state = hasNative
|
|
739
878
|
? 'native_tool_calls'
|
|
740
|
-
:
|
|
879
|
+
: hasText
|
|
741
880
|
? 'text_tool_calls'
|
|
742
881
|
: 'no_tool_calls';
|
|
743
|
-
const source = hasNative ? 'native' :
|
|
882
|
+
const source = hasNative ? 'native' : hasText ? 'text' : 'none';
|
|
744
883
|
writeCompatState(root, state, source, harvestedFunctionResults);
|
|
745
884
|
applyUsageEstimate(root, choices, adapterContext, captured);
|
|
746
|
-
if (toolChoiceRequired && options.strictToolRequired && !hasNative && !
|
|
885
|
+
if (toolChoiceRequired && options.strictToolRequired && !hasNative && !hasText) {
|
|
747
886
|
const error = new Error('DeepSeek tool_choice=required but no valid tool call was produced');
|
|
748
887
|
error.code = 'DEEPSEEK_TOOL_REQUIRED_MISSING';
|
|
749
888
|
error.details = {
|
|
@@ -2,6 +2,7 @@ import type { JsonObject } from '../types/json.js';
|
|
|
2
2
|
type ToolAliasMap = Record<string, string>;
|
|
3
3
|
interface AnthropicResponseOptions {
|
|
4
4
|
aliasMap?: ToolAliasMap;
|
|
5
|
+
includeToolCallIds?: boolean;
|
|
5
6
|
}
|
|
6
7
|
export declare function buildOpenAIChatFromAnthropicMessage(payload: JsonObject, options?: AnthropicResponseOptions): JsonObject;
|
|
7
8
|
export declare function buildAnthropicResponseFromChat(chatResponse: JsonObject, options?: AnthropicResponseOptions): JsonObject;
|
|
@@ -221,8 +221,10 @@ export function buildOpenAIChatFromAnthropicMessage(payload, options) {
|
|
|
221
221
|
default: return 'stop';
|
|
222
222
|
}
|
|
223
223
|
};
|
|
224
|
+
const includeToolCallIds = options?.includeToolCallIds === true;
|
|
224
225
|
const canonicalToolCalls = toolCalls.map((tc) => ({
|
|
225
226
|
id: tc.id,
|
|
227
|
+
...(includeToolCallIds ? { call_id: tc.id, tool_call_id: tc.id } : {}),
|
|
226
228
|
type: 'function',
|
|
227
229
|
function: { name: tc.name, arguments: tc.args }
|
|
228
230
|
}));
|
|
@@ -237,11 +239,35 @@ export function buildOpenAIChatFromAnthropicMessage(payload, options) {
|
|
|
237
239
|
const key = deriveToolCallKey(inferred);
|
|
238
240
|
if (key && seen.has(key))
|
|
239
241
|
continue;
|
|
242
|
+
if (includeToolCallIds && typeof inferred.id === 'string') {
|
|
243
|
+
const inferredId = String(inferred.id);
|
|
244
|
+
if (!("call_id" in inferred))
|
|
245
|
+
inferred.call_id = inferredId;
|
|
246
|
+
if (!("tool_call_id" in inferred))
|
|
247
|
+
inferred.tool_call_id = inferredId;
|
|
248
|
+
}
|
|
240
249
|
canonicalToolCalls.push(inferred);
|
|
241
250
|
if (key)
|
|
242
251
|
seen.add(key);
|
|
243
252
|
}
|
|
244
253
|
}
|
|
254
|
+
for (const call of canonicalToolCalls) {
|
|
255
|
+
const cid = typeof call.id === 'string' ? String(call.id) : '';
|
|
256
|
+
if (includeToolCallIds) {
|
|
257
|
+
if (cid) {
|
|
258
|
+
if (!("call_id" in call))
|
|
259
|
+
call.call_id = cid;
|
|
260
|
+
if (!("tool_call_id" in call))
|
|
261
|
+
call.tool_call_id = cid;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
if ("call_id" in call)
|
|
266
|
+
delete call.call_id;
|
|
267
|
+
if ("tool_call_id" in call)
|
|
268
|
+
delete call.tool_call_id;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
245
271
|
const message = {
|
|
246
272
|
role: typeof payload.role === 'string' ? payload.role : 'assistant',
|
|
247
273
|
content: textParts.join('\n')
|
|
@@ -7,7 +7,9 @@ interface OpenAIChatPayload extends Unknown {
|
|
|
7
7
|
}
|
|
8
8
|
export declare function normalizeAnthropicToolName(value: unknown): string | undefined;
|
|
9
9
|
export declare function denormalizeAnthropicToolName(value: unknown): string | undefined;
|
|
10
|
-
export declare function buildOpenAIChatFromAnthropic(payload: unknown
|
|
10
|
+
export declare function buildOpenAIChatFromAnthropic(payload: unknown, options?: {
|
|
11
|
+
includeToolCallIds?: boolean;
|
|
12
|
+
}): OpenAIChatPayload;
|
|
11
13
|
export interface BuildAnthropicFromOpenAIOptions {
|
|
12
14
|
toolNameMap?: Record<string, string>;
|
|
13
15
|
requestId?: string;
|
|
@@ -17,23 +17,24 @@ function safeJson(v) {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
function stripOpenAIChatToolAliasFields(messages) {
|
|
20
|
+
// No-op: preserve tool_call_id/call_id for downstream consumers and regression parity.
|
|
21
|
+
void messages;
|
|
22
|
+
}
|
|
23
|
+
function stripToolCallIdFieldsFromAssistant(messages) {
|
|
20
24
|
for (const message of messages) {
|
|
21
|
-
if (!message || typeof message !== 'object')
|
|
25
|
+
if (!message || typeof message !== 'object')
|
|
22
26
|
continue;
|
|
23
|
-
}
|
|
24
27
|
const role = String(message.role || '').toLowerCase();
|
|
25
|
-
if (role
|
|
26
|
-
for (const call of message.tool_calls) {
|
|
27
|
-
if (!call || typeof call !== 'object') {
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
delete call.tool_call_id;
|
|
31
|
-
delete call.call_id;
|
|
32
|
-
}
|
|
28
|
+
if (role !== 'assistant')
|
|
33
29
|
continue;
|
|
34
|
-
|
|
35
|
-
if (
|
|
36
|
-
|
|
30
|
+
const calls = message.tool_calls;
|
|
31
|
+
if (!Array.isArray(calls))
|
|
32
|
+
continue;
|
|
33
|
+
for (const call of calls) {
|
|
34
|
+
if (!call || typeof call !== 'object')
|
|
35
|
+
continue;
|
|
36
|
+
delete call.call_id;
|
|
37
|
+
delete call.tool_call_id;
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
}
|
|
@@ -352,7 +353,7 @@ function invertAnthropicAliasMap(source) {
|
|
|
352
353
|
}
|
|
353
354
|
return Object.keys(inverted).length ? inverted : undefined;
|
|
354
355
|
}
|
|
355
|
-
export function buildOpenAIChatFromAnthropic(payload) {
|
|
356
|
+
export function buildOpenAIChatFromAnthropic(payload, options) {
|
|
356
357
|
const newMessages = [];
|
|
357
358
|
const body = isObject(payload) ? payload : {};
|
|
358
359
|
const canonicalAliasMap = coerceAliasRecord(buildAnthropicToolAliasMap(body.tools));
|
|
@@ -458,7 +459,13 @@ export function buildOpenAIChatFromAnthropic(payload) {
|
|
|
458
459
|
const input = block.input ?? {};
|
|
459
460
|
const args = safeJson(input);
|
|
460
461
|
const canonicalName = resolveToolName(name) || name;
|
|
461
|
-
|
|
462
|
+
const includeIds = options?.includeToolCallIds === true;
|
|
463
|
+
toolCalls.push({
|
|
464
|
+
id,
|
|
465
|
+
...(includeIds ? { call_id: id, tool_call_id: id } : {}),
|
|
466
|
+
type: 'function',
|
|
467
|
+
function: { name: canonicalName, arguments: args }
|
|
468
|
+
});
|
|
462
469
|
}
|
|
463
470
|
else if (t === 'tool_result') {
|
|
464
471
|
const callId = requireTrimmedString(block.tool_call_id ??
|
|
@@ -549,6 +556,7 @@ export function buildOpenAIChatFromAnthropic(payload) {
|
|
|
549
556
|
catch {
|
|
550
557
|
// ignore policy failures
|
|
551
558
|
}
|
|
559
|
+
stripToolCallIdFieldsFromAssistant(newMessages);
|
|
552
560
|
stripOpenAIChatToolAliasFields(newMessages);
|
|
553
561
|
return request;
|
|
554
562
|
}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
type ProcessingMode = 'streaming' | 'non-streaming' | 'auto';
|
|
2
1
|
export type ChatReasoningMode = 'keep' | 'drop' | 'append_to_content';
|
|
3
2
|
export interface FinalizeOptions {
|
|
4
3
|
requestId?: string;
|
|
5
4
|
endpoint?: string;
|
|
6
|
-
processingMode?: ProcessingMode;
|
|
7
5
|
stream?: boolean;
|
|
8
6
|
reasoningMode?: ChatReasoningMode;
|
|
9
7
|
}
|
|
10
8
|
export declare function finalizeOpenAIChatResponse(chatLike: unknown, opts?: FinalizeOptions): Promise<unknown>;
|
|
11
|
-
export {};
|