@jsonstudio/llms 0.6.3409 → 0.6.3541

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.
Files changed (85) hide show
  1. package/dist/conversion/codecs/anthropic-openai-codec.d.ts +12 -3
  2. package/dist/conversion/codecs/anthropic-openai-codec.js +32 -92
  3. package/dist/conversion/codecs/gemini-openai-codec.d.ts +6 -5
  4. package/dist/conversion/codecs/gemini-openai-codec.js +48 -685
  5. package/dist/conversion/codecs/openai-openai-codec.d.ts +1 -1
  6. package/dist/conversion/codecs/openai-openai-codec.js +34 -100
  7. package/dist/conversion/codecs/responses-openai-codec.d.ts +1 -1
  8. package/dist/conversion/codecs/responses-openai-codec.js +47 -159
  9. package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.d.ts +2 -6
  10. package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.js +29 -245
  11. package/dist/conversion/compat/actions/anthropic-claude-code-user-id.d.ts +3 -0
  12. package/dist/conversion/compat/actions/anthropic-claude-code-user-id.js +30 -0
  13. package/dist/conversion/compat/actions/antigravity-thought-signature-prepare.js +21 -232
  14. package/dist/conversion/compat/actions/deepseek-web-request.js +41 -276
  15. package/dist/conversion/compat/actions/deepseek-web-response.js +117 -855
  16. package/dist/conversion/compat/actions/gemini-cli-request.d.ts +1 -1
  17. package/dist/conversion/compat/actions/gemini-cli-request.js +20 -613
  18. package/dist/conversion/compat/actions/gemini-web-search.d.ts +1 -15
  19. package/dist/conversion/compat/actions/gemini-web-search.js +22 -69
  20. package/dist/conversion/compat/actions/glm-tool-extraction.d.ts +3 -2
  21. package/dist/conversion/compat/actions/glm-tool-extraction.js +28 -257
  22. package/dist/conversion/compat/actions/iflow-tool-text-fallback.d.ts +0 -8
  23. package/dist/conversion/compat/actions/iflow-tool-text-fallback.js +24 -206
  24. package/dist/conversion/compat/actions/qwen-transform.d.ts +3 -2
  25. package/dist/conversion/compat/actions/qwen-transform.js +30 -271
  26. package/dist/conversion/compat/actions/tool-text-request-guidance.js +3 -173
  27. package/dist/conversion/compat/actions/universal-shape-filter.d.ts +6 -23
  28. package/dist/conversion/compat/actions/universal-shape-filter.js +4 -383
  29. package/dist/conversion/hub/pipeline/compat/native-adapter-context.js +1 -0
  30. package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.d.ts +1 -2
  31. package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +50 -104
  32. package/dist/conversion/pipeline/codecs/v2/openai-openai-pipeline.js +12 -10
  33. package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.d.ts +0 -2
  34. package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.js +46 -67
  35. package/dist/conversion/pipeline/codecs/v2/shared/openai-chat-helpers.js +15 -40
  36. package/dist/conversion/responses/responses-openai-bridge/response-payload.js +47 -348
  37. package/dist/conversion/responses/responses-openai-bridge.js +129 -611
  38. package/dist/conversion/shared/chat-output-normalizer.js +6 -0
  39. package/dist/conversion/shared/chat-request-filters.js +1 -1
  40. package/dist/conversion/shared/output-content-normalizer.js +10 -0
  41. package/dist/conversion/shared/responses-conversation-store.js +22 -135
  42. package/dist/conversion/shared/responses-output-builder.d.ts +0 -2
  43. package/dist/conversion/shared/responses-output-builder.js +28 -318
  44. package/dist/conversion/shared/responses-response-utils.js +35 -86
  45. package/dist/conversion/shared/streaming-text-extractor.d.ts +1 -2
  46. package/dist/conversion/shared/streaming-text-extractor.js +13 -14
  47. package/dist/native/router_hotpath_napi.node +0 -0
  48. package/dist/quota/quota-state.js +29 -7
  49. package/dist/quota/types.d.ts +1 -0
  50. package/dist/router/virtual-router/bootstrap/routing-config.js +11 -3
  51. package/dist/router/virtual-router/engine-legacy.d.ts +3 -3
  52. package/dist/router/virtual-router/engine-legacy.js +15 -7
  53. package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.d.ts +16 -0
  54. package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.js +434 -46
  55. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.d.ts +83 -0
  56. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js +295 -0
  57. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.d.ts +1 -0
  58. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.d.ts +7 -0
  59. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js +8 -1
  60. package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +383 -298
  61. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.d.ts +20 -0
  62. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +201 -0
  63. package/dist/router/virtual-router/engine-selection/native-virtual-router-routing-instructions-semantics.d.ts +1 -0
  64. package/dist/router/virtual-router/engine-selection/native-virtual-router-routing-instructions-semantics.js +37 -0
  65. package/dist/router/virtual-router/engine.js +0 -38
  66. package/dist/router/virtual-router/features.js +44 -3
  67. package/dist/router/virtual-router/routing-instructions/parse.d.ts +0 -12
  68. package/dist/router/virtual-router/routing-instructions/parse.js +9 -389
  69. package/dist/router/virtual-router/stop-message-state-sync.d.ts +3 -6
  70. package/dist/router/virtual-router/stop-message-state-sync.js +50 -21
  71. package/dist/servertool/handlers/followup-request-builder.js +12 -2
  72. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +1 -0
  73. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.js +26 -0
  74. package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +12 -2
  75. package/package.json +1 -1
  76. package/dist/router/virtual-router/engine-legacy/route-finalize.d.ts +0 -9
  77. package/dist/router/virtual-router/engine-legacy/route-finalize.js +0 -84
  78. package/dist/router/virtual-router/engine-legacy/route-selection.d.ts +0 -17
  79. package/dist/router/virtual-router/engine-legacy/route-selection.js +0 -205
  80. package/dist/router/virtual-router/engine-legacy/route-state-allowlist.d.ts +0 -3
  81. package/dist/router/virtual-router/engine-legacy/route-state-allowlist.js +0 -36
  82. package/dist/router/virtual-router/engine-legacy/route-state.d.ts +0 -12
  83. package/dist/router/virtual-router/engine-legacy/route-state.js +0 -386
  84. package/dist/router/virtual-router/engine-legacy/routing.d.ts +0 -8
  85. package/dist/router/virtual-router/engine-legacy/routing.js +0 -8
@@ -1,119 +1,18 @@
1
- import { validateToolCall } from '../../../tools/tool-registry.js';
2
- import { encoding_for_model, get_encoding } from 'tiktoken';
3
- import { normalizeFunctionCallId } from '../../bridge-id-utils.js';
4
- const DEFAULT_OPTIONS = {
5
- strictToolRequired: true,
6
- textNormalizer: undefined,
7
- toolProtocol: undefined
8
- };
9
- const SHELL_LIKE_TOOL_NAMES = new Set(['exec_command', 'shell_command', 'shell', 'bash', 'terminal']);
10
- const SHELL_TOOL_NAME_ALIASES = new Map([
11
- ['shell_command', 'exec_command'],
12
- ['shell', 'exec_command'],
13
- ['bash', 'exec_command'],
14
- ['terminal', 'exec_command']
15
- ]);
16
- const SHELL_ALLOWED_TOOL_CANDIDATES = [
17
- 'exec_command',
18
- 'shell_command',
19
- 'bash',
20
- 'shell',
21
- 'terminal'
22
- ];
23
- const DEFAULT_TOKEN_ENCODING = 'cl100k_base';
24
- const tokenEncoderCache = new Map();
25
- let defaultTokenEncoder = null;
1
+ import { buildNativeReqOutboundCompatAdapterContext } from '../../hub/pipeline/compat/native-adapter-context.js';
2
+ import { loadNativeRouterHotpathBindingForInternalUse } from '../../../router/virtual-router/engine-selection/native-router-hotpath.js';
3
+ import { isNativeDisabledByEnv, makeNativeRequiredError } from '../../../router/virtual-router/engine-selection/native-router-hotpath-policy.js';
4
+ import { providerErrorCenter } from '../../../router/virtual-router/error-center.js';
5
+ const CAPABILITY = 'runRespInboundStage3CompatJson';
6
+ const PROFILE = 'chat:deepseek-web';
7
+ const DEFAULT_PROVIDER_PROTOCOL = 'openai-chat';
8
+ const DEFAULT_ENTRY_ENDPOINT = '/v1/chat/completions';
26
9
  const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
27
- const readString = (value) => {
28
- if (typeof value !== 'string') {
29
- return undefined;
30
- }
31
- const trimmed = value.trim();
32
- return trimmed.length ? trimmed : undefined;
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
- }
80
- function readNumber(value) {
81
- if (typeof value === 'number' && Number.isFinite(value)) {
10
+ const readBoolean = (value) => {
11
+ if (typeof value === 'boolean') {
82
12
  return value;
83
13
  }
84
14
  if (typeof value === 'string') {
85
- const trimmed = value.trim();
86
- if (!trimmed.length) {
87
- return undefined;
88
- }
89
- const parsed = Number(trimmed);
90
- if (Number.isFinite(parsed)) {
91
- return parsed;
92
- }
93
- }
94
- return undefined;
95
- }
96
- function readBoolean(input, fallback) {
97
- if (typeof input === 'boolean') {
98
- return input;
99
- }
100
- if (typeof input === 'string') {
101
- const normalized = input.trim().toLowerCase();
102
- if (['true', '1', 'yes', 'on'].includes(normalized)) {
103
- return true;
104
- }
105
- if (['false', '0', 'no', 'off'].includes(normalized)) {
106
- return false;
107
- }
108
- }
109
- return fallback;
110
- }
111
- function readOptionalBoolean(input) {
112
- if (typeof input === 'boolean') {
113
- return input;
114
- }
115
- if (typeof input === 'string') {
116
- const normalized = input.trim().toLowerCase();
15
+ const normalized = value.trim().toLowerCase();
117
16
  if (['true', '1', 'yes', 'on'].includes(normalized)) {
118
17
  return true;
119
18
  }
@@ -122,776 +21,139 @@ function readOptionalBoolean(input) {
122
21
  }
123
22
  }
124
23
  return undefined;
125
- }
126
- function readToolProtocol(input) {
127
- if (typeof input !== 'string') {
24
+ };
25
+ const readToolProtocol = (value) => {
26
+ if (typeof value !== 'string') {
128
27
  return undefined;
129
28
  }
130
- const normalized = input.trim().toLowerCase();
131
- if (normalized === 'text' || normalized === 'native') {
132
- return normalized;
133
- }
134
- return undefined;
135
- }
136
- function getTokenEncoder(modelHint) {
137
- const normalized = typeof modelHint === 'string' ? modelHint.trim() : '';
138
- if (normalized) {
139
- const cached = tokenEncoderCache.get(normalized);
140
- if (cached) {
141
- return cached;
142
- }
143
- try {
144
- const encoder = encoding_for_model(normalized);
145
- tokenEncoderCache.set(normalized, encoder);
146
- return encoder;
147
- }
148
- catch {
149
- // fall through to default encoder
29
+ const normalized = value.trim().toLowerCase();
30
+ return normalized === 'native' || normalized === 'text' ? normalized : undefined;
31
+ };
32
+ function buildRuntimeMetadata(adapterContext, payload, details) {
33
+ const contextRecord = adapterContext && typeof adapterContext === 'object'
34
+ ? adapterContext
35
+ : undefined;
36
+ const runtime = {};
37
+ const assignString = (key, value) => {
38
+ if (typeof value === 'string' && value.trim()) {
39
+ runtime[key] = value.trim();
150
40
  }
151
- }
152
- if (!defaultTokenEncoder) {
153
- defaultTokenEncoder = get_encoding(DEFAULT_TOKEN_ENCODING);
154
- }
155
- return defaultTokenEncoder;
156
- }
157
- function countTextTokens(text, modelHint) {
158
- const trimmed = text.trim();
159
- if (!trimmed.length) {
160
- return 0;
161
- }
162
- try {
163
- return getTokenEncoder(modelHint).encode(trimmed).length;
164
- }
165
- catch {
166
- return 0;
167
- }
41
+ };
42
+ assignString('requestId', contextRecord?.requestId);
43
+ assignString('providerProtocol', contextRecord?.providerProtocol);
44
+ assignString('providerId', contextRecord?.providerId);
45
+ assignString('providerKey', contextRecord?.providerKey);
46
+ assignString('runtimeKey', contextRecord?.runtimeKey);
47
+ assignString('routeName', contextRecord?.routeId);
48
+ assignString('pipelineId', PROFILE);
49
+ if (payload && typeof payload === 'object') {
50
+ assignString('target', payload.model);
51
+ }
52
+ if (details && Object.keys(details).length > 0) {
53
+ runtime.details = details;
54
+ }
55
+ return runtime;
56
+ }
57
+ function emitCompatError(error, adapterContext, payload, details) {
58
+ providerErrorCenter.emit({
59
+ code: 'DEEPSEEK_WEB_COMPAT_ERROR',
60
+ message: error.message,
61
+ stage: 'compat:deepseek-web-response',
62
+ runtime: buildRuntimeMetadata(adapterContext, payload, details),
63
+ details: {
64
+ compatibilityProfile: PROFILE,
65
+ ...(details ?? {})
66
+ }
67
+ });
68
+ throw error;
168
69
  }
169
- function resolveOptions(adapterContext, config) {
170
- const context = (adapterContext ?? {});
171
- const deepseekNode = isRecord(context.deepseek)
172
- ? context.deepseek
173
- : isRecord(context.__rt) && isRecord(context.__rt.deepseek)
174
- ? context.__rt.deepseek
175
- : undefined;
176
- if (!deepseekNode) {
177
- const toolProtocolOverride = readToolProtocol(config?.toolProtocol);
178
- return {
179
- ...DEFAULT_OPTIONS,
180
- strictToolRequired: readOptionalBoolean(config?.strictToolRequired) ?? DEFAULT_OPTIONS.strictToolRequired,
181
- textNormalizer: config?.textNormalizer,
182
- toolProtocol: toolProtocolOverride ?? DEFAULT_OPTIONS.toolProtocol
183
- };
184
- }
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);
70
+ function resolveDeepseekNode(adapterContext, config) {
71
+ const nativeContext = buildNativeReqOutboundCompatAdapterContext(adapterContext);
72
+ const baseNode = isRecord(nativeContext.deepseek) ? nativeContext.deepseek : {};
73
+ const configProtocol = readToolProtocol(config?.toolProtocol);
74
+ const baseProtocol = readToolProtocol(baseNode.toolProtocol);
75
+ const baseFallback = readBoolean(baseNode.textToolFallback);
76
+ const protocol = configProtocol ??
77
+ baseProtocol ??
78
+ (baseFallback === undefined ? undefined : baseFallback ? 'text' : 'native');
191
79
  return {
192
- strictToolRequired: strictOverride ??
193
- readBoolean(deepseekNode.strictToolRequired, DEFAULT_OPTIONS.strictToolRequired),
194
- textNormalizer: config?.textNormalizer && typeof config.textNormalizer === 'object'
195
- ? config.textNormalizer
196
- : undefined,
197
- toolProtocol
80
+ ...baseNode,
81
+ strictToolRequired: config?.strictToolRequired ?? readBoolean(baseNode.strictToolRequired) ?? true,
82
+ textToolFallback: protocol ? protocol === 'text' : baseFallback ?? true,
83
+ ...(protocol ? { toolProtocol: protocol } : {})
198
84
  };
199
85
  }
200
- function resolveCapturedRequest(adapterContext) {
201
- const context = (adapterContext ?? {});
202
- if (isRecord(context.capturedChatRequest)) {
203
- return context.capturedChatRequest;
204
- }
205
- return undefined;
206
- }
207
- function resolveEstimatedInputTokens(adapterContext) {
208
- const context = (adapterContext ?? {});
209
- const candidates = [
210
- context.estimatedInputTokens,
211
- context.estimated_tokens,
212
- context.estimatedTokens
213
- ];
214
- for (const candidate of candidates) {
215
- const value = readNumber(candidate);
216
- if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
217
- return Math.max(1, Math.round(value));
218
- }
219
- }
220
- return undefined;
221
- }
222
- function resolveModelHint(root, captured, adapterContext) {
223
- return (readString(root.model) ??
224
- readString(captured?.model) ??
225
- readString(adapterContext?.modelId) ??
226
- readString(adapterContext?.clientModelId) ??
227
- readString(adapterContext?.originalModelId));
228
- }
229
- function looksLikeKnownProviderResponseShape(value) {
230
- if (!isRecord(value))
231
- return false;
232
- if (Array.isArray(value.choices))
233
- return true;
234
- if (Array.isArray(value.output) || value.object === 'response')
235
- return true;
236
- if (typeof value.type === 'string' && String(value.type).toLowerCase() === 'message' && Array.isArray(value.content)) {
237
- return true;
238
- }
239
- if (Array.isArray(value.candidates))
240
- return true;
241
- return false;
242
- }
243
- function tryUnwrapKnownShape(value, depth) {
244
- if (depth < 0) {
245
- return null;
246
- }
247
- if (looksLikeKnownProviderResponseShape(value)) {
248
- return value;
249
- }
250
- if (!isRecord(value)) {
251
- return null;
252
- }
253
- for (const key of ['data', 'body', 'response', 'payload', 'result', 'biz_data']) {
254
- const nested = value[key];
255
- const unwrapped = tryUnwrapKnownShape(nested, depth - 1);
256
- if (unwrapped) {
257
- return unwrapped;
258
- }
259
- }
260
- return null;
261
- }
262
- function isLikelyRateLimitMessage(message) {
263
- return /(rate.?limit|quota|too many|exceed|exceeded|limit reached|message count)/i.test(message);
264
- }
265
- function throwDeepSeekBusinessEnvelopeError(root, adapterContext) {
266
- const dataNode = isRecord(root.data) ? root.data : undefined;
267
- const upstreamCode = readNumber(root.code);
268
- const bizCode = readNumber(dataNode?.biz_code);
269
- const bizMsg = readString(dataNode?.biz_msg);
270
- const topMsg = readString(root.msg);
271
- const message = bizMsg ?? topMsg ?? 'DeepSeek returned a non-chat business envelope';
272
- const error = new Error(`[deepseek-web] upstream business error: ${message}`);
273
- error.code = 'DEEPSEEK_BIZ_ERROR';
274
- if ((typeof bizCode === 'number' && bizCode === 3) || isLikelyRateLimitMessage(message)) {
275
- error.statusCode = 429;
276
- }
277
- error.details = {
278
- requestId: readString(root.request_id) ??
279
- readString(adapterContext?.requestId),
280
- upstreamCode,
281
- bizCode,
282
- bizMsg,
283
- upstreamMsg: topMsg,
284
- payloadKeys: Object.keys(root).slice(0, 20),
285
- phase: 'chat_process.resp.stage3.compat'
86
+ function buildCompatInput(payload, adapterContext, config) {
87
+ const nativeContext = buildNativeReqOutboundCompatAdapterContext(adapterContext);
88
+ const normalizedContext = {
89
+ ...nativeContext,
90
+ compatibilityProfile: PROFILE,
91
+ providerProtocol: nativeContext.providerProtocol ?? adapterContext?.providerProtocol ?? DEFAULT_PROVIDER_PROTOCOL,
92
+ entryEndpoint: nativeContext.entryEndpoint ?? adapterContext?.entryEndpoint ?? DEFAULT_ENTRY_ENDPOINT,
93
+ deepseek: resolveDeepseekNode(adapterContext, config)
94
+ };
95
+ return {
96
+ payload,
97
+ adapterContext: normalizedContext,
98
+ explicitProfile: PROFILE
286
99
  };
287
- throw error;
288
- }
289
- function normalizeDeepSeekBusinessEnvelope(root, adapterContext) {
290
- if (looksLikeKnownProviderResponseShape(root)) {
291
- return root;
292
- }
293
- const hasEnvelopeKeys = Object.prototype.hasOwnProperty.call(root, 'code')
294
- && Object.prototype.hasOwnProperty.call(root, 'data');
295
- if (!hasEnvelopeKeys) {
296
- return root;
297
- }
298
- const unwrapped = tryUnwrapKnownShape(root.data, 6);
299
- if (unwrapped) {
300
- return unwrapped;
301
- }
302
- const dataNode = isRecord(root.data) ? root.data : undefined;
303
- const upstreamCode = readNumber(root.code);
304
- const bizCode = readNumber(dataNode?.biz_code);
305
- const bizMsg = readString(dataNode?.biz_msg);
306
- const topMsg = readString(root.msg);
307
- if ((typeof upstreamCode === 'number' && upstreamCode !== 0) ||
308
- (typeof bizCode === 'number' && bizCode !== 0) ||
309
- Boolean(bizMsg) ||
310
- Boolean(topMsg)) {
311
- throwDeepSeekBusinessEnvelopeError(root, adapterContext);
312
- }
313
- return root;
314
- }
315
- function resolveRequestedToolNames(captured) {
316
- const names = new Set();
317
- if (!captured) {
318
- return names;
319
- }
320
- const toolsRaw = Array.isArray(captured.tools) ? captured.tools : [];
321
- for (const tool of toolsRaw) {
322
- if (!isRecord(tool)) {
323
- continue;
324
- }
325
- const fnNode = isRecord(tool.function) ? tool.function : undefined;
326
- const name = readString(fnNode?.name) ?? readString(tool.name);
327
- if (name) {
328
- names.add(name);
329
- }
330
- }
331
- const toolChoice = captured.tool_choice;
332
- if (isRecord(toolChoice)) {
333
- const type = readString(toolChoice.type)?.toLowerCase();
334
- if (type === 'function') {
335
- const fnNode = isRecord(toolChoice.function)
336
- ? toolChoice.function
337
- : undefined;
338
- const forcedName = readString(fnNode?.name) ?? readString(toolChoice.name);
339
- if (forcedName) {
340
- names.add(forcedName);
341
- }
342
- }
343
- }
344
- return names;
345
- }
346
- function isToolChoiceRequired(captured) {
347
- if (!captured) {
348
- return false;
349
- }
350
- const toolChoice = captured.tool_choice;
351
- if (typeof toolChoice === 'string') {
352
- const normalized = toolChoice.trim().toLowerCase();
353
- if (normalized === 'required') {
354
- return true;
355
- }
356
- if (normalized === 'none' || normalized === 'auto') {
357
- return false;
358
- }
359
- }
360
- if (isRecord(toolChoice)) {
361
- const type = readString(toolChoice.type)?.toLowerCase();
362
- if (type === 'function') {
363
- return true;
364
- }
365
- }
366
- return false;
367
- }
368
- function tryParseJsonText(value) {
369
- try {
370
- return JSON.parse(value);
371
- }
372
- catch {
373
- // Best-effort repair: some upstream payloads place raw newlines/tabs inside JSON strings.
374
- let out = '';
375
- let changed = false;
376
- let inString = false;
377
- let escaped = false;
378
- for (let i = 0; i < value.length; i += 1) {
379
- const ch = value[i] ?? '';
380
- if (inString) {
381
- if (escaped) {
382
- out += ch;
383
- escaped = false;
384
- continue;
385
- }
386
- if (ch === '\\') {
387
- out += ch;
388
- escaped = true;
389
- continue;
390
- }
391
- if (ch === '"') {
392
- out += ch;
393
- inString = false;
394
- continue;
395
- }
396
- if (ch === '\n') {
397
- out += '\\n';
398
- changed = true;
399
- continue;
400
- }
401
- if (ch === '\r') {
402
- out += '\\n';
403
- changed = true;
404
- continue;
405
- }
406
- if (ch === '\t') {
407
- out += '\\t';
408
- changed = true;
409
- continue;
410
- }
411
- const code = ch.charCodeAt(0);
412
- if (code >= 0 && code < 0x20) {
413
- out += `\\u${code.toString(16).padStart(4, '0')}`;
414
- changed = true;
415
- continue;
416
- }
417
- out += ch;
418
- continue;
419
- }
420
- if (ch === '"') {
421
- inString = true;
422
- }
423
- out += ch;
424
- }
425
- if (!changed) {
426
- return null;
427
- }
428
- try {
429
- return JSON.parse(out);
430
- }
431
- catch {
432
- return null;
433
- }
434
- }
435
100
  }
436
- function normalizeToolArguments(value, toolName) {
437
- if (typeof value === 'string') {
438
- const trimmed = value.trim();
439
- if (!trimmed.length) {
440
- return JSON.stringify({});
441
- }
442
- const parsed = tryParseJsonText(trimmed);
443
- if (parsed !== null) {
444
- return JSON.stringify(parsed);
445
- }
446
- if (toolName) {
447
- const validation = validateToolCall(toolName, trimmed);
448
- if (validation.ok && typeof validation.normalizedArgs === 'string' && validation.normalizedArgs.trim().length > 0) {
449
- return validation.normalizedArgs;
450
- }
451
- }
452
- return null;
453
- }
454
- if (value === undefined || value === null) {
455
- const empty = JSON.stringify({});
456
- if (!toolName) {
457
- return empty;
458
- }
459
- const validation = validateToolCall(toolName, empty);
460
- if (validation.ok && typeof validation.normalizedArgs === 'string' && validation.normalizedArgs.trim().length > 0) {
461
- return validation.normalizedArgs;
462
- }
463
- return empty;
464
- }
101
+ function parseCompatOutput(raw) {
102
+ let parsed;
465
103
  try {
466
- const serialized = JSON.stringify(value);
467
- if (!toolName) {
468
- return serialized;
469
- }
470
- const validation = validateToolCall(toolName, serialized);
471
- if (validation.ok && typeof validation.normalizedArgs === 'string' && validation.normalizedArgs.trim().length > 0) {
472
- return validation.normalizedArgs;
473
- }
474
- return serialized;
104
+ parsed = JSON.parse(raw);
475
105
  }
476
106
  catch {
477
- return null;
478
- }
479
- }
480
- function hasAllowedToolName(allowed, candidate) {
481
- if (allowed.has(candidate)) {
482
- return true;
483
- }
484
- const lowered = candidate.toLowerCase();
485
- for (const name of allowed) {
486
- if (name.toLowerCase() === lowered) {
487
- return true;
488
- }
489
- }
490
- return false;
491
- }
492
- function findAllowedToolNameCaseInsensitive(allowed, candidate) {
493
- if (allowed.has(candidate)) {
494
- return candidate;
495
- }
496
- const lowered = candidate.toLowerCase();
497
- for (const name of allowed) {
498
- if (name.toLowerCase() === lowered) {
499
- return name;
500
- }
501
- }
502
- return undefined;
503
- }
504
- function resolveAllowedShellToolName(allowedToolNames) {
505
- for (const candidate of SHELL_ALLOWED_TOOL_CANDIDATES) {
506
- const resolved = findAllowedToolNameCaseInsensitive(allowedToolNames, candidate);
507
- if (resolved) {
508
- return resolved;
509
- }
510
- }
511
- return undefined;
512
- }
513
- function resolveToolNameForCompat(rawName, allowedToolNames) {
514
- const normalizedRawName = rawName.toLowerCase();
515
- const alias = SHELL_TOOL_NAME_ALIASES.get(normalizedRawName);
516
- const shellLike = SHELL_LIKE_TOOL_NAMES.has(normalizedRawName) || alias === 'exec_command';
517
- if (allowedToolNames.size > 0 && shellLike) {
518
- const allowedShell = resolveAllowedShellToolName(allowedToolNames);
519
- if (allowedShell) {
520
- return allowedShell;
521
- }
107
+ throw makeNativeRequiredError(CAPABILITY, 'invalid payload');
522
108
  }
523
- if (!alias) {
524
- return findAllowedToolNameCaseInsensitive(allowedToolNames, rawName) ?? rawName;
109
+ if (!isRecord(parsed) || !isRecord(parsed.payload) || typeof parsed.nativeApplied !== 'boolean') {
110
+ throw makeNativeRequiredError(CAPABILITY, 'invalid payload');
525
111
  }
526
- if (allowedToolNames.size === 0) {
527
- return alias;
528
- }
529
- const allowedAlias = findAllowedToolNameCaseInsensitive(allowedToolNames, alias);
530
- if (allowedAlias) {
531
- return allowedAlias;
532
- }
533
- const allowedRaw = findAllowedToolNameCaseInsensitive(allowedToolNames, rawName);
534
- if (allowedRaw) {
535
- return allowedRaw;
536
- }
537
- return alias;
112
+ return parsed;
538
113
  }
539
- function readCommandFromShellArgs(args) {
540
- const input = isRecord(args.input) ? args.input : undefined;
541
- const direct = readString(args.cmd) ??
542
- readString(args.command) ??
543
- readString(args.script) ??
544
- (() => {
545
- const arr = Array.isArray(args.command) ? args.command : Array.isArray(args.cmd) ? args.cmd : undefined;
546
- if (!arr)
547
- return undefined;
548
- const tokens = arr.map((item) => (item == null ? '' : String(item).trim())).filter((item) => item.length > 0);
549
- return tokens.length ? tokens.join(' ') : undefined;
550
- })();
551
- if (direct) {
552
- return direct;
114
+ function callDeepSeekWebResponseCompat(input, adapterContext) {
115
+ if (isNativeDisabledByEnv()) {
116
+ emitCompatError(makeNativeRequiredError(CAPABILITY, 'native disabled'), adapterContext, input.payload, {
117
+ reason: 'native disabled'
118
+ });
553
119
  }
554
- if (!input) {
555
- return undefined;
556
- }
557
- return (readString(input.cmd) ??
558
- readString(input.command) ??
559
- readString(input.script) ??
560
- (() => {
561
- const arr = Array.isArray(input.command) ? input.command : Array.isArray(input.cmd) ? input.cmd : undefined;
562
- if (!arr)
563
- return undefined;
564
- const tokens = arr.map((item) => (item == null ? '' : String(item).trim())).filter((item) => item.length > 0);
565
- return tokens.length ? tokens.join(' ') : undefined;
566
- })());
567
- }
568
- function readWorkdirFromShellArgs(args) {
569
- const input = isRecord(args.input) ? args.input : undefined;
570
- return (readString(args.workdir) ??
571
- readString(args.cwd) ??
572
- readString(args.workDir) ??
573
- readString(input?.workdir) ??
574
- readString(input?.cwd));
575
- }
576
- function normalizeShellLikeArguments(argsString, argsSource) {
577
- const parsed = tryParseJsonText(argsString);
578
- const args = isRecord(parsed) ? { ...parsed } : isRecord(argsSource) ? { ...argsSource } : {};
579
- const cmd = readCommandFromShellArgs(args);
580
- if (!cmd) {
581
- return argsString;
582
- }
583
- const workdir = readWorkdirFromShellArgs(args);
584
- const next = {
585
- ...args,
586
- cmd,
587
- command: cmd
588
- };
589
- if (workdir) {
590
- next.workdir = workdir;
591
- }
592
- if (Object.prototype.hasOwnProperty.call(next, 'toon')) {
593
- delete next.toon;
120
+ const binding = loadNativeRouterHotpathBindingForInternalUse();
121
+ const fn = binding?.[CAPABILITY];
122
+ if (typeof fn !== 'function') {
123
+ emitCompatError(makeNativeRequiredError(CAPABILITY), adapterContext, input.payload, {
124
+ reason: 'missing native export'
125
+ });
594
126
  }
127
+ let inputJson;
595
128
  try {
596
- return JSON.stringify(next);
129
+ inputJson = JSON.stringify(input);
597
130
  }
598
131
  catch {
599
- return JSON.stringify({ cmd, command: cmd, ...(workdir ? { workdir } : {}) });
600
- }
601
- }
602
- function coerceToolCallsValue(toolCallsRaw) {
603
- if (Array.isArray(toolCallsRaw)) {
604
- return toolCallsRaw;
605
- }
606
- if (typeof toolCallsRaw !== 'string') {
607
- return [];
608
- }
609
- const parsed = tryParseJsonText(toolCallsRaw.trim());
610
- if (Array.isArray(parsed)) {
611
- return parsed;
612
- }
613
- if (isRecord(parsed) && Array.isArray(parsed.tool_calls)) {
614
- return parsed.tool_calls;
615
- }
616
- return [];
617
- }
618
- function normalizeToolCallEntry(entry, allowedToolNames, callIdPrefix, index) {
619
- const fn = isRecord(entry.function) ? entry.function : undefined;
620
- const rawName = readString(entry.name) ?? readString(fn?.name);
621
- if (!rawName) {
622
- return null;
623
- }
624
- const name = resolveToolNameForCompat(rawName, allowedToolNames);
625
- const argsSource = entry.input !== undefined
626
- ? entry.input
627
- : entry.arguments !== undefined
628
- ? entry.arguments
629
- : fn?.arguments;
630
- const normalizedArgs = normalizeToolArguments(argsSource, name);
631
- if (!normalizedArgs) {
632
- return null;
132
+ emitCompatError(makeNativeRequiredError(CAPABILITY, 'json stringify failed'), adapterContext, input.payload, {
133
+ reason: 'json stringify failed'
134
+ });
633
135
  }
634
- const finalArgs = SHELL_LIKE_TOOL_NAMES.has(name.toLowerCase())
635
- ? normalizeShellLikeArguments(normalizedArgs, argsSource)
636
- : normalizedArgs;
637
- const id = readString(entry.id) ??
638
- readString(entry.call_id) ??
639
- `${callIdPrefix}_${index + 1}`;
640
- return {
641
- id,
642
- type: 'function',
643
- function: {
644
- name,
645
- arguments: finalArgs
646
- }
647
- };
648
- }
649
- function normalizeToolCalls(toolCallsRaw, allowedToolNames, callIdPrefix) {
650
- const toolCalls = coerceToolCallsValue(toolCallsRaw);
651
- const normalized = [];
652
- for (const [index, entry] of toolCalls.entries()) {
653
- if (!isRecord(entry)) {
654
- continue;
655
- }
656
- const call = normalizeToolCallEntry(entry, allowedToolNames, callIdPrefix, index);
657
- if (call) {
658
- normalized.push(call);
659
- }
660
- }
661
- return normalized;
662
- }
663
- function normalizeMessageToolCalls(message, allowedToolNames, callIdPrefix) {
664
- const nextCalls = normalizeToolCalls(message.tool_calls, allowedToolNames, callIdPrefix);
665
- if (!nextCalls.length) {
666
- delete message.tool_calls;
667
- return false;
668
- }
669
- message.tool_calls = nextCalls;
670
- message.content = null;
671
- return true;
672
- }
673
- function normalizeUsageSnapshot(raw) {
674
- if (!isRecord(raw)) {
675
- return {};
676
- }
677
- const prompt = readNumber(raw.prompt_tokens) ?? readNumber(raw.input_tokens);
678
- const completion = readNumber(raw.completion_tokens) ?? readNumber(raw.output_tokens);
679
- const total = readNumber(raw.total_tokens);
680
- return {
681
- ...(typeof prompt === 'number' ? { prompt: Math.max(0, Math.round(prompt)) } : {}),
682
- ...(typeof completion === 'number' ? { completion: Math.max(0, Math.round(completion)) } : {}),
683
- ...(typeof total === 'number' ? { total: Math.max(0, Math.round(total)) } : {})
684
- };
685
- }
686
- function estimateCompletionTokensFromChoices(choices, modelHint) {
687
- const segments = [];
688
- for (const choice of choices) {
689
- if (!isRecord(choice)) {
690
- continue;
691
- }
692
- const message = isRecord(choice.message) ? choice.message : undefined;
693
- if (!message) {
694
- continue;
695
- }
696
- const content = message.content;
697
- if (typeof content === 'string' && content.trim().length) {
698
- segments.push(content);
699
- }
700
- else if (Array.isArray(content)) {
701
- for (const part of content) {
702
- if (!isRecord(part)) {
703
- continue;
704
- }
705
- const text = readString(part.text);
706
- if (text) {
707
- segments.push(text);
708
- }
709
- }
710
- }
711
- if (Array.isArray(message.tool_calls) && message.tool_calls.length) {
712
- try {
713
- segments.push(JSON.stringify(message.tool_calls));
714
- }
715
- catch {
716
- // ignore serialization failure
717
- }
718
- }
719
- }
720
- if (!segments.length) {
721
- return 0;
722
- }
723
- const joined = segments.join('\n');
724
- return countTextTokens(joined, modelHint);
725
- }
726
- function applyUsageEstimate(root, choices, adapterContext, captured) {
727
- const current = normalizeUsageSnapshot(root.usage);
728
- const prompt = current.prompt ?? resolveEstimatedInputTokens(adapterContext);
729
- const completion = current.completion ??
730
- estimateCompletionTokensFromChoices(choices, resolveModelHint(root, captured, adapterContext));
731
- const total = current.total ??
732
- (typeof prompt === 'number' && typeof completion === 'number' ? prompt + completion : undefined);
733
- if (typeof prompt !== 'number' &&
734
- typeof completion !== 'number' &&
735
- typeof total !== 'number') {
736
- return;
737
- }
738
- const usage = isRecord(root.usage) ? { ...root.usage } : {};
739
- if (typeof prompt === 'number') {
740
- usage.prompt_tokens = prompt;
741
- usage.input_tokens = prompt;
742
- }
743
- if (typeof completion === 'number') {
744
- usage.completion_tokens = completion;
745
- usage.output_tokens = completion;
746
- }
747
- if (typeof total === 'number') {
748
- usage.total_tokens = total;
749
- }
750
- root.usage = usage;
751
- }
752
- function normalizeChoice(choice, options, allowedToolNames, callIdPrefix) {
753
- const message = isRecord(choice.message) ? choice.message : undefined;
754
- if (!message) {
755
- return { hasNative: false, hasText: false, harvestedFunctionResults: false };
756
- }
757
- const native = normalizeMessageToolCalls(message, allowedToolNames, callIdPrefix);
758
- if (native) {
759
- const finish = readString(choice.finish_reason)?.toLowerCase();
760
- if (!finish || finish === 'stop') {
761
- choice.finish_reason = 'tool_calls';
762
- }
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
- }
778
- }
779
- return {
780
- hasNative: false,
781
- hasText: false,
782
- harvestedFunctionResults: false
783
- };
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
136
+ try {
137
+ const raw = fn(inputJson);
138
+ if (typeof raw !== 'string' || !raw) {
139
+ emitCompatError(makeNativeRequiredError(CAPABILITY, 'empty result'), adapterContext, input.payload, {
140
+ reason: 'empty result'
832
141
  });
833
142
  }
143
+ return parseCompatOutput(raw);
834
144
  }
835
- if (harvested) {
836
- root.output = nextOutput;
145
+ catch (error) {
146
+ const compatError = error instanceof Error ? error : new Error(String(error));
147
+ emitCompatError(compatError, adapterContext, input.payload, {
148
+ reason: 'native compat execution failed'
149
+ });
837
150
  }
838
- return { harvested };
839
- }
840
- function writeCompatState(root, state, source, harvestedFunctionResults) {
841
- const metadata = isRecord(root.metadata) ? root.metadata : {};
842
- metadata.deepseek = {
843
- ...(isRecord(metadata.deepseek) ? metadata.deepseek : {}),
844
- toolCallState: state,
845
- toolCallSource: source,
846
- ...(harvestedFunctionResults ? { functionResultsTextHarvested: true } : {})
847
- };
848
- root.metadata = metadata;
849
151
  }
850
152
  export function applyDeepSeekWebResponseTransform(payload, adapterContext, config) {
851
153
  if (!payload || typeof payload !== 'object') {
852
- return payload;
853
- }
854
- const cloned = structuredClone(payload);
855
- const root = normalizeDeepSeekBusinessEnvelope(cloned, adapterContext);
856
- const options = resolveOptions(adapterContext, config);
857
- const captured = resolveCapturedRequest(adapterContext);
858
- const allowedToolNames = resolveRequestedToolNames(captured);
859
- const toolChoiceRequired = isToolChoiceRequired(captured);
860
- const choices = Array.isArray(root.choices) ? root.choices : [];
861
- let hasNative = false;
862
- let hasText = false;
863
- let harvestedFunctionResults = false;
864
- for (const [index, choice] of choices.entries()) {
865
- if (!isRecord(choice)) {
866
- continue;
867
- }
868
- const result = normalizeChoice(choice, options, allowedToolNames, `deepseek_call_${index + 1}`);
869
- hasNative = hasNative || result.hasNative;
870
- hasText = hasText || result.hasText;
871
- harvestedFunctionResults = harvestedFunctionResults || result.harvestedFunctionResults;
872
- }
873
- if (!choices.length) {
874
- const harvested = harvestResponsesTextToolCalls(root, options, allowedToolNames);
875
- hasText = hasText || harvested.harvested;
876
- }
877
- const state = hasNative
878
- ? 'native_tool_calls'
879
- : hasText
880
- ? 'text_tool_calls'
881
- : 'no_tool_calls';
882
- const source = hasNative ? 'native' : hasText ? 'text' : 'none';
883
- writeCompatState(root, state, source, harvestedFunctionResults);
884
- applyUsageEstimate(root, choices, adapterContext, captured);
885
- if (toolChoiceRequired && options.strictToolRequired && !hasNative && !hasText) {
886
- const error = new Error('DeepSeek tool_choice=required but no valid tool call was produced');
887
- error.code = 'DEEPSEEK_TOOL_REQUIRED_MISSING';
888
- error.details = {
889
- requestId: readString(adapterContext?.requestId),
890
- phase: 'chat_process.resp.stage3.compat',
891
- state,
892
- strictToolRequired: options.strictToolRequired
893
- };
894
- throw error;
154
+ emitCompatError(new Error('[deepseek-web] invalid compat payload: expected object'), adapterContext, payload, {
155
+ reason: 'payload is not an object'
156
+ });
895
157
  }
896
- return root;
158
+ return callDeepSeekWebResponseCompat(buildCompatInput(payload, adapterContext, config), adapterContext).payload;
897
159
  }