@jsonstudio/llms 0.6.3379 → 0.6.3409

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 (38) hide show
  1. package/dist/conversion/compat/actions/claude-thinking-tools.d.ts +1 -14
  2. package/dist/conversion/compat/actions/claude-thinking-tools.js +3 -71
  3. package/dist/conversion/compat/actions/lmstudio-responses-fc-ids.d.ts +0 -8
  4. package/dist/conversion/compat/actions/lmstudio-responses-fc-ids.js +2 -57
  5. package/dist/conversion/compat/actions/normalize-tool-call-ids.d.ts +0 -9
  6. package/dist/conversion/compat/actions/normalize-tool-call-ids.js +6 -136
  7. package/dist/conversion/compat/actions/request-rules.js +2 -61
  8. package/dist/conversion/compat/actions/response-blacklist.d.ts +0 -4
  9. package/dist/conversion/compat/actions/response-blacklist.js +2 -77
  10. package/dist/conversion/compat/actions/response-normalize.js +2 -119
  11. package/dist/conversion/compat/actions/response-validate.js +2 -74
  12. package/dist/conversion/compat/actions/strip-orphan-function-calls-tag.js +2 -150
  13. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +24 -1
  14. package/dist/conversion/hub/pipeline/hub-pipeline.js +91 -0
  15. package/dist/conversion/shared/reasoning-tool-parser.js +7 -8
  16. package/dist/conversion/shared/responses-response-utils.js +3 -48
  17. package/dist/conversion/shared/responses-tool-utils.js +22 -126
  18. package/dist/conversion/shared/tool-call-id-manager.js +18 -21
  19. package/dist/native/router_hotpath_napi.node +0 -0
  20. package/dist/router/virtual-router/bootstrap/routing-config.d.ts +2 -1
  21. package/dist/router/virtual-router/bootstrap/routing-config.js +47 -2
  22. package/dist/router/virtual-router/bootstrap/web-search-config.js +25 -0
  23. package/dist/router/virtual-router/bootstrap.js +21 -16
  24. package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.d.ts +6 -0
  25. package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.js +171 -0
  26. package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +11 -0
  27. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.d.ts +5 -0
  28. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +137 -0
  29. package/dist/router/virtual-router/engine-selection/tier-load-balancing.d.ts +16 -0
  30. package/dist/router/virtual-router/engine-selection/tier-load-balancing.js +120 -0
  31. package/dist/router/virtual-router/engine-selection/tier-selection-quota-integration.d.ts +2 -0
  32. package/dist/router/virtual-router/engine-selection/tier-selection-quota-integration.js +44 -66
  33. package/dist/router/virtual-router/engine-selection/tier-selection-select.js +53 -84
  34. package/dist/router/virtual-router/types.d.ts +39 -0
  35. package/dist/servertool/handlers/web-search.js +26 -1
  36. package/dist/servertool/server-side-tools.js +11 -2
  37. package/dist/servertool/types.d.ts +4 -0
  38. package/package.json +1 -1
@@ -1,76 +1,4 @@
1
- const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
1
+ import { validateResponsePayloadWithNative } from '../../../router/virtual-router/engine-selection/native-compat-action-semantics.js';
2
2
  export function validateResponsePayload(payload, _config) {
3
- const errors = [];
4
- if (!payload.id || typeof payload.id !== 'string') {
5
- errors.push('响应缺少有效的id字段');
6
- }
7
- if (!payload.created || typeof payload.created !== 'number') {
8
- errors.push('响应缺少有效的created字段');
9
- }
10
- if (!payload.model || typeof payload.model !== 'string') {
11
- errors.push('响应缺少有效的model字段');
12
- }
13
- if (!Array.isArray(payload.choices) || payload.choices.length === 0) {
14
- errors.push('choices数组不能为空');
15
- }
16
- else {
17
- payload.choices.forEach((choice, idx) => {
18
- if (!isRecord(choice)) {
19
- errors.push(`choices[${idx}]必须是对象`);
20
- return;
21
- }
22
- if (!choice.message || typeof choice.message !== 'object') {
23
- errors.push(`choices[${idx}].message字段必须是对象`);
24
- }
25
- else if (Array.isArray(choice.message.tool_calls)) {
26
- choice.message.tool_calls.forEach((toolCall, tIdx) => {
27
- if (!isRecord(toolCall)) {
28
- errors.push(`choices[${idx}].message.tool_calls[${tIdx}]必须是对象`);
29
- return;
30
- }
31
- if (!toolCall.function || typeof toolCall.function !== 'object') {
32
- errors.push(`choices[${idx}].message.tool_calls[${tIdx}].function字段必须是对象`);
33
- return;
34
- }
35
- const fn = toolCall.function;
36
- if (typeof fn.name !== 'string') {
37
- errors.push(`choices[${idx}].message.tool_calls[${tIdx}].function.name字段必须是字符串`);
38
- }
39
- if (typeof fn.arguments !== 'string') {
40
- errors.push(`choices[${idx}].message.tool_calls[${tIdx}].function.arguments字段必须是字符串`);
41
- }
42
- else {
43
- try {
44
- JSON.parse(fn.arguments);
45
- }
46
- catch {
47
- errors.push(`choices[${idx}].message.tool_calls[${tIdx}].function.arguments必须是有效JSON`);
48
- }
49
- }
50
- });
51
- }
52
- });
53
- }
54
- if (payload.usage && typeof payload.usage === 'object') {
55
- const usage = payload.usage;
56
- const promptTokens = usage.prompt_tokens;
57
- const completionTokens = usage.completion_tokens;
58
- const totalTokens = usage.total_tokens;
59
- if (!isNonNegativeNumber(promptTokens) ||
60
- !isNonNegativeNumber(completionTokens) ||
61
- !isNonNegativeNumber(totalTokens)) {
62
- errors.push('usage字段的token必须是非负数');
63
- }
64
- else if (promptTokens + completionTokens !== totalTokens) {
65
- errors.push('usage.total_tokens 应等于 prompt_tokens 与 completion_tokens 之和');
66
- }
67
- }
68
- if (errors.length) {
69
- const error = new Error(`GLM响应校验失败:\n${errors.join('\n')}`);
70
- error.code = 'compat_response_validation_failed';
71
- throw error;
72
- }
73
- }
74
- function isNonNegativeNumber(value) {
75
- return typeof value === 'number' && Number.isFinite(value) && value >= 0;
3
+ validateResponsePayloadWithNative(payload);
76
4
  }
@@ -1,152 +1,4 @@
1
- function isRecord(value) {
2
- return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
3
- }
4
- const ORPHAN_TAG_RE = /^\s*(?:[•*+-]\s*)?(?:<\/?\s*function_calls\s*\/?\s*>|<\/\s*(?:parameter|function|tool_call)\s*>)\s*$/i;
5
- function stripOrphanTagLines(text) {
6
- const raw = String(text ?? '');
7
- if (!raw)
8
- return { text: raw, changed: false };
9
- const lines = raw.split(/\r?\n/);
10
- const kept = [];
11
- let changed = false;
12
- for (const line of lines) {
13
- if (ORPHAN_TAG_RE.test(line)) {
14
- changed = true;
15
- continue;
16
- }
17
- kept.push(line);
18
- }
19
- return { text: kept.join('\n'), changed };
20
- }
21
- function stripInMessageInPlace(message) {
22
- let changed = false;
23
- const content = message.content;
24
- if (typeof content === 'string') {
25
- const res = stripOrphanTagLines(content);
26
- if (res.changed) {
27
- message.content = res.text;
28
- changed = true;
29
- }
30
- }
31
- else if (Array.isArray(content)) {
32
- const next = content.map((part) => {
33
- if (typeof part === 'string') {
34
- const res = stripOrphanTagLines(part);
35
- if (res.changed)
36
- changed = true;
37
- return res.text;
38
- }
39
- if (!isRecord(part))
40
- return part;
41
- const p = { ...part };
42
- if (typeof p.text === 'string') {
43
- const res = stripOrphanTagLines(p.text);
44
- if (res.changed) {
45
- p.text = res.text;
46
- changed = true;
47
- }
48
- }
49
- if (typeof p.content === 'string') {
50
- const res = stripOrphanTagLines(p.content);
51
- if (res.changed) {
52
- p.content = res.text;
53
- changed = true;
54
- }
55
- }
56
- return p;
57
- });
58
- if (changed) {
59
- message.content = next;
60
- }
61
- }
62
- for (const key of ['reasoning', 'thinking', 'reasoning_content']) {
63
- const raw = message[key];
64
- if (typeof raw === 'string' && raw.trim().length) {
65
- const res = stripOrphanTagLines(raw);
66
- if (res.changed) {
67
- message[key] = res.text;
68
- changed = true;
69
- }
70
- }
71
- }
72
- return changed;
73
- }
74
- function stripInChatPayloadInPlace(root) {
75
- const choices = Array.isArray(root.choices) ? root.choices : [];
76
- if (!choices.length)
77
- return false;
78
- let changed = false;
79
- for (const choice of choices) {
80
- if (!isRecord(choice))
81
- continue;
82
- const message = choice.message;
83
- if (!isRecord(message))
84
- continue;
85
- const role = typeof message.role === 'string' ? String(message.role).trim().toLowerCase() : 'assistant';
86
- if (role !== 'assistant')
87
- continue;
88
- if (stripInMessageInPlace(message)) {
89
- changed = true;
90
- choice.message = message;
91
- }
92
- }
93
- return changed;
94
- }
95
- function stripInResponsesPayloadInPlace(root) {
96
- const output = Array.isArray(root.output) ? root.output : [];
97
- if (!output.length)
98
- return false;
99
- let changed = false;
100
- for (const item of output) {
101
- if (!isRecord(item))
102
- continue;
103
- const type = typeof item.type === 'string' ? String(item.type).trim().toLowerCase() : '';
104
- if (type !== 'message')
105
- continue;
106
- const role = typeof item.role === 'string' ? String(item.role).trim().toLowerCase() : 'assistant';
107
- if (role !== 'assistant')
108
- continue;
109
- const content = Array.isArray(item.content) ? item.content : [];
110
- if (content.length) {
111
- const next = content.map((part) => {
112
- if (!isRecord(part))
113
- return part;
114
- const p = { ...part };
115
- for (const key of ['text', 'content', 'value']) {
116
- if (typeof p[key] === 'string' && String(p[key]).trim().length) {
117
- const res = stripOrphanTagLines(String(p[key]));
118
- if (res.changed) {
119
- p[key] = res.text;
120
- changed = true;
121
- }
122
- }
123
- }
124
- return p;
125
- });
126
- if (changed) {
127
- item.content = next;
128
- }
129
- }
130
- if (typeof item.output_text === 'string' && String(item.output_text).trim().length) {
131
- const res = stripOrphanTagLines(String(item.output_text));
132
- if (res.changed) {
133
- item.output_text = res.text;
134
- changed = true;
135
- }
136
- }
137
- }
138
- return changed;
139
- }
1
+ import { stripOrphanFunctionCallsTagWithNative } from '../../../router/virtual-router/engine-selection/native-chat-process-governance-semantics.js';
140
2
  export function stripOrphanFunctionCallsTag(payload) {
141
- try {
142
- if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
143
- return payload;
144
- }
145
- const root = structuredClone(payload);
146
- const changed = stripInChatPayloadInPlace(root) || stripInResponsesPayloadInPlace(root);
147
- return (changed ? root : payload);
148
- }
149
- catch {
150
- return payload;
151
- }
3
+ return stripOrphanFunctionCallsTagWithNative(payload);
152
4
  }
@@ -179,6 +179,20 @@ function buildAnthropicThinkingFromReasoning(reasoning) {
179
179
  }
180
180
  return { type: 'enabled', budget_tokens: 4096 };
181
181
  }
182
+ function normalizeContextToken(value) {
183
+ return typeof value === 'string' ? value.trim().toLowerCase() : '';
184
+ }
185
+ function isArkCodingPlanContext(ctx) {
186
+ if (!ctx || typeof ctx !== 'object') {
187
+ return false;
188
+ }
189
+ const candidates = [
190
+ normalizeContextToken(ctx.providerId),
191
+ normalizeContextToken(ctx.providerKey),
192
+ normalizeContextToken(ctx.runtimeKey)
193
+ ];
194
+ return candidates.some((candidate) => candidate === 'ark-coding-plan' || candidate.startsWith('ark-coding-plan.'));
195
+ }
182
196
  function cloneAnthropicSystemBlocks(value) {
183
197
  if (value === undefined || value === null) {
184
198
  return undefined;
@@ -395,10 +409,19 @@ export class AnthropicSemanticMapper {
395
409
  if (baseRequest.max_output_tokens && !baseRequest.max_tokens) {
396
410
  baseRequest.max_tokens = baseRequest.max_output_tokens;
397
411
  }
398
- const mappedThinking = buildAnthropicThinkingFromReasoning(trimmedParameters?.reasoning);
412
+ const rawReasoning = trimmedParameters?.reasoning;
413
+ const mappedThinking = buildAnthropicThinkingFromReasoning(rawReasoning);
399
414
  if (mappedThinking && baseRequest.thinking === undefined) {
400
415
  baseRequest.thinking = mappedThinking;
401
416
  }
417
+ if (baseRequest.thinking === undefined &&
418
+ rawReasoning === undefined &&
419
+ isArkCodingPlanContext(ctx)) {
420
+ baseRequest.thinking = {
421
+ type: 'enabled',
422
+ budget_tokens: mapReasoningEffortToAnthropicBudget('high')
423
+ };
424
+ }
402
425
  if (responsesOrigin && trimmedParameters && Object.prototype.hasOwnProperty.call(trimmedParameters, 'reasoning')) {
403
426
  appendLossyFieldAudit(chat, {
404
427
  field: 'reasoning',
@@ -69,6 +69,95 @@ function propagateAdapterContextMetadataFields(adapterContext, metadata, keys) {
69
69
  function resolveStopMessageRouterMetadata(metadata) {
70
70
  return resolveStopMessageRouterMetadataWithNative(metadata);
71
71
  }
72
+ function isSearchRouteId(routeId) {
73
+ const normalized = typeof routeId === 'string' ? routeId.trim().toLowerCase() : '';
74
+ return normalized.startsWith('web_search') || normalized.startsWith('search');
75
+ }
76
+ function isCanonicalWebSearchToolDefinition(tool) {
77
+ if (!tool || typeof tool !== 'object' || Array.isArray(tool)) {
78
+ return false;
79
+ }
80
+ const row = tool;
81
+ const rawType = typeof row.type === 'string' ? row.type.trim().toLowerCase() : '';
82
+ if (rawType === 'web_search_20250305' || rawType === 'web_search') {
83
+ return true;
84
+ }
85
+ const fnNode = row.function && typeof row.function === 'object' && !Array.isArray(row.function)
86
+ ? row.function
87
+ : undefined;
88
+ const name = typeof fnNode?.name === 'string'
89
+ ? fnNode.name.trim().toLowerCase()
90
+ : typeof row.name === 'string'
91
+ ? row.name.trim().toLowerCase()
92
+ : '';
93
+ return name === 'web_search' || name === 'websearch' || name === 'web-search';
94
+ }
95
+ function maybeApplyDirectBuiltinWebSearchTool(providerPayload, adapterContext, providerProtocol) {
96
+ if (providerProtocol !== 'anthropic-messages') {
97
+ return providerPayload;
98
+ }
99
+ if (!isSearchRouteId(adapterContext.routeId)) {
100
+ return providerPayload;
101
+ }
102
+ const modelId = typeof providerPayload.model === 'string' ? providerPayload.model.trim() : '';
103
+ if (!modelId) {
104
+ return providerPayload;
105
+ }
106
+ const rt = readRuntimeMetadata(adapterContext);
107
+ const webSearch = rt && typeof rt.webSearch === 'object' && rt.webSearch && !Array.isArray(rt.webSearch)
108
+ ? rt.webSearch
109
+ : undefined;
110
+ const enginesRaw = Array.isArray(webSearch?.engines) ? webSearch?.engines : [];
111
+ const matchedEngine = enginesRaw.find((entry) => {
112
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
113
+ return false;
114
+ }
115
+ const row = entry;
116
+ const executionMode = typeof row.executionMode === 'string' ? row.executionMode.trim().toLowerCase() : '';
117
+ if (executionMode !== 'direct') {
118
+ return false;
119
+ }
120
+ const directActivation = typeof row.directActivation === 'string' ? row.directActivation.trim().toLowerCase() : 'route';
121
+ if (directActivation !== 'builtin') {
122
+ return false;
123
+ }
124
+ const configuredModelId = typeof row.modelId === 'string' ? row.modelId.trim() : '';
125
+ if (configuredModelId && configuredModelId === modelId) {
126
+ return true;
127
+ }
128
+ const providerKey = typeof row.providerKey === 'string' ? row.providerKey.trim() : '';
129
+ return providerKey.endsWith(`.${modelId}`);
130
+ });
131
+ if (!matchedEngine) {
132
+ return providerPayload;
133
+ }
134
+ const rawMaxUses = typeof matchedEngine.maxUses === 'number' ? matchedEngine.maxUses : Number(matchedEngine.maxUses);
135
+ const maxUses = Number.isFinite(rawMaxUses) && rawMaxUses > 0 ? Math.floor(rawMaxUses) : 2;
136
+ const builtinTool = {
137
+ type: 'web_search_20250305',
138
+ name: 'web_search',
139
+ max_uses: maxUses
140
+ };
141
+ const tools = Array.isArray(providerPayload.tools) ? providerPayload.tools : [];
142
+ let replaced = false;
143
+ const nextTools = [];
144
+ for (const tool of tools) {
145
+ if (!replaced && isCanonicalWebSearchToolDefinition(tool)) {
146
+ nextTools.push(builtinTool);
147
+ replaced = true;
148
+ continue;
149
+ }
150
+ if (isCanonicalWebSearchToolDefinition(tool)) {
151
+ continue;
152
+ }
153
+ nextTools.push(tool);
154
+ }
155
+ if (!replaced) {
156
+ nextTools.unshift(builtinTool);
157
+ }
158
+ providerPayload.tools = nextTools;
159
+ return providerPayload;
160
+ }
72
161
  function extractHubShadowCompareConfig(metadata) {
73
162
  const parsed = resolveHubShadowCompareConfigWithNative(metadata);
74
163
  if (!parsed) {
@@ -558,6 +647,7 @@ export class HubPipeline {
558
647
  stageRecorder: outboundRecorder,
559
648
  requestId: normalized.id
560
649
  });
650
+ providerPayload = maybeApplyDirectBuiltinWebSearchTool(providerPayload, outboundAdapterContext, outboundProtocol);
561
651
  recordHubPolicyObservation({
562
652
  policy: effectivePolicy,
563
653
  providerProtocol: outboundProtocol,
@@ -1003,6 +1093,7 @@ export class HubPipeline {
1003
1093
  stageRecorder: outboundRecorder,
1004
1094
  requestId: normalized.id
1005
1095
  });
1096
+ providerPayload = maybeApplyDirectBuiltinWebSearchTool(providerPayload, outboundAdapterContext, outboundProtocol);
1006
1097
  recordHubPolicyObservation({
1007
1098
  policy: effectivePolicy,
1008
1099
  providerProtocol: outboundProtocol,
@@ -1,12 +1,11 @@
1
1
  import { extractToolCallsFromReasoningTextWithNative } from '../../router/virtual-router/engine-selection/native-shared-conversion-semantics.js';
2
- export function extractToolCallsFromReasoningText(text, options) {
3
- if (typeof text !== 'string' || !text.trim()) {
4
- return { cleanedText: typeof text === 'string' ? text : '', toolCalls: [] };
2
+ function assertReasoningToolParserNativeAvailable() {
3
+ if (typeof extractToolCallsFromReasoningTextWithNative !== 'function') {
4
+ throw new Error('[reasoning-tool-parser] native bindings unavailable');
5
5
  }
6
+ }
7
+ export function extractToolCallsFromReasoningText(text, options) {
8
+ assertReasoningToolParserNativeAvailable();
6
9
  const idPrefix = options?.idPrefix ?? 'reasoning';
7
- const output = extractToolCallsFromReasoningTextWithNative(text, idPrefix);
8
- return {
9
- cleanedText: output.cleanedText,
10
- toolCalls: output.toolCalls
11
- };
10
+ return extractToolCallsFromReasoningTextWithNative(String(text ?? ''), idPrefix);
12
11
  }
@@ -2,7 +2,7 @@ import { extractOutputSegments } from './output-content-normalizer.js';
2
2
  import { createBridgeActionState, runBridgeActionPipeline } from '../bridge-actions.js';
3
3
  import { resolveBridgePolicy, resolvePolicyActions } from '../bridge-policies.js';
4
4
  import { registerResponsesPayloadSnapshot, registerResponsesPassthrough } from './responses-reasoning-registry.js';
5
- import { normalizeFunctionCallIdWithNative, sanitizeReasoningTaggedTextWithNative } from '../../router/virtual-router/engine-selection/native-shared-conversion-semantics.js';
5
+ import { collectToolCallsFromResponsesWithNative, normalizeFunctionCallIdWithNative, resolveFinishReasonWithNative, sanitizeReasoningTaggedTextWithNative } from '../../router/virtual-router/engine-selection/native-shared-conversion-semantics.js';
6
6
  import { sanitizeResponsesFunctionNameWithNative } from '../../router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js';
7
7
  function selectCallId(entry) {
8
8
  const candidates = [
@@ -53,55 +53,10 @@ function normalizeToolCall(entry, fallbackPrefix) {
53
53
  };
54
54
  }
55
55
  export function collectToolCallsFromResponses(response) {
56
- const collected = [];
57
- const seenIds = new Set();
58
- const pushCall = (call, source) => {
59
- if (!call)
60
- return;
61
- const key = typeof call.id === 'string' ? call.id : `${source}_${collected.length}`;
62
- if (key && seenIds.has(key))
63
- return;
64
- if (key)
65
- seenIds.add(key);
66
- collected.push(call);
67
- };
68
- const required = response?.required_action?.submit_tool_outputs?.tool_calls;
69
- if (Array.isArray(required)) {
70
- for (const call of required) {
71
- pushCall(normalizeToolCall(call, 'req_call'), 'req_call');
72
- }
73
- }
74
- const outputItems = Array.isArray(response.output) ? response.output : [];
75
- for (const item of outputItems) {
76
- if (!item || typeof item !== 'object')
77
- continue;
78
- const type = typeof item.type === 'string' ? String(item.type).toLowerCase() : '';
79
- if (type !== 'function_call')
80
- continue;
81
- pushCall(normalizeToolCall(item, 'output_call'), 'output_call');
82
- }
83
- return collected;
56
+ return collectToolCallsFromResponsesWithNative(response);
84
57
  }
85
58
  export function resolveFinishReason(response, toolCalls) {
86
- const meta = response.metadata && typeof response.metadata === 'object'
87
- ? response.metadata
88
- : undefined;
89
- if (meta && typeof meta.finish_reason === 'string') {
90
- return meta.finish_reason;
91
- }
92
- if (toolCalls.length > 0) {
93
- return 'tool_calls';
94
- }
95
- const status = typeof response.status === 'string' ? response.status.toLowerCase() : '';
96
- if (status === 'requires_action')
97
- return 'tool_calls';
98
- if (status === 'in_progress' || status === 'streaming')
99
- return 'length';
100
- if (status === 'cancelled')
101
- return 'cancelled';
102
- if (status === 'failed')
103
- return 'error';
104
- return 'stop';
59
+ return resolveFinishReasonWithNative(response, toolCalls);
105
60
  }
106
61
  function unwrapResponsesResponse(payload) {
107
62
  if (!payload || typeof payload !== 'object')
@@ -1,14 +1,23 @@
1
- import { createToolCallIdTransformerWithNative, normalizeFunctionCallIdWithNative, normalizeFunctionCallOutputIdWithNative, normalizeResponsesCallIdWithNative } from '../../router/virtual-router/engine-selection/native-shared-conversion-semantics.js';
1
+ import { createToolCallIdTransformerWithNative, normalizeResponsesToolCallIdsWithNative, normalizeFunctionCallIdWithNative, normalizeFunctionCallOutputIdWithNative, normalizeResponsesCallIdWithNative, resolveToolCallIdStyleWithNative, stripInternalToolingMetadataWithNative } from '../../router/virtual-router/engine-selection/native-shared-conversion-semantics.js';
2
2
  import { sanitizeResponsesFunctionNameWithNative } from '../../router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js';
3
3
  function assertResponsesToolUtilsNativeAvailable() {
4
4
  if (typeof createToolCallIdTransformerWithNative !== 'function' ||
5
+ typeof normalizeResponsesToolCallIdsWithNative !== 'function' ||
5
6
  typeof normalizeFunctionCallIdWithNative !== 'function' ||
6
7
  typeof normalizeFunctionCallOutputIdWithNative !== 'function' ||
7
8
  typeof normalizeResponsesCallIdWithNative !== 'function' ||
9
+ typeof resolveToolCallIdStyleWithNative !== 'function' ||
10
+ typeof stripInternalToolingMetadataWithNative !== 'function' ||
8
11
  typeof sanitizeResponsesFunctionNameWithNative !== 'function') {
9
12
  throw new Error('[responses-tool-utils] native bindings unavailable');
10
13
  }
11
14
  }
15
+ function replaceMutableRecord(target, next) {
16
+ for (const key of Object.keys(target)) {
17
+ delete target[key];
18
+ }
19
+ Object.assign(target, next);
20
+ }
12
21
  export function createToolCallIdTransformer(style) {
13
22
  assertResponsesToolUtilsNativeAvailable();
14
23
  if (style !== 'fc') {
@@ -42,141 +51,28 @@ function transformCounter(state, prefix) {
42
51
  state.__counter = next;
43
52
  return `${prefix}_${next}`;
44
53
  }
45
- function isStableToolCallId(raw) {
46
- return /^((fc|call)_[A-Za-z0-9_-]+)$/i.test(raw);
47
- }
48
54
  export function normalizeResponsesToolCallIds(payload) {
49
55
  assertResponsesToolUtilsNativeAvailable();
50
- if (!payload || typeof payload !== 'object') {
56
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
51
57
  return;
52
58
  }
53
- let counter = 0;
54
- const aliasMap = new Map();
55
- const nextFallback = (prefix) => `${prefix}_${++counter}`;
56
- const normalizeCallId = (raw, fallbackPrefix) => {
57
- const trimmed = typeof raw === 'string' ? raw.trim() : '';
58
- if (trimmed) {
59
- const cached = aliasMap.get(trimmed);
60
- if (cached)
61
- return cached;
62
- }
63
- const normalized = trimmed && isStableToolCallId(trimmed)
64
- ? trimmed
65
- : normalizeFunctionCallIdWithNative({
66
- callId: trimmed || undefined,
67
- fallback: nextFallback(fallbackPrefix)
68
- });
69
- if (trimmed) {
70
- aliasMap.set(trimmed, normalized);
71
- }
72
- return normalized;
73
- };
74
- const output = Array.isArray(payload.output) ? payload.output : [];
75
- for (const item of output) {
76
- if (!item || typeof item !== 'object')
77
- continue;
78
- const type = typeof item.type === 'string' ? String(item.type).toLowerCase() : '';
79
- if (type === 'function_call') {
80
- const normalizedCallId = normalizeCallId(item.call_id ?? item.tool_call_id ?? item.id, 'fc_call');
81
- item.call_id = normalizedCallId;
82
- if (item.tool_call_id !== undefined) {
83
- item.tool_call_id = normalizedCallId;
84
- }
85
- const rawOutputId = typeof item.id === 'string' ? item.id : undefined;
86
- item.id = normalizeFunctionCallOutputIdWithNative({
87
- callId: normalizedCallId,
88
- fallback: rawOutputId ?? nextFallback('fc')
89
- });
90
- continue;
91
- }
92
- if (type === 'function_call_output' || type === 'tool_result' || type === 'tool_message') {
93
- const normalizedCallId = normalizeCallId(item.call_id ?? item.tool_call_id ?? item.id, 'fc_call');
94
- item.call_id = normalizedCallId;
95
- if (item.tool_call_id !== undefined) {
96
- item.tool_call_id = normalizedCallId;
97
- }
98
- const rawOutputId = typeof item.id === 'string' ? item.id : undefined;
99
- item.id = normalizeFunctionCallOutputIdWithNative({
100
- callId: normalizedCallId,
101
- fallback: rawOutputId ?? nextFallback('fc')
102
- });
103
- continue;
104
- }
105
- if (Array.isArray(item.tool_calls)) {
106
- for (const call of item.tool_calls) {
107
- if (!call || typeof call !== 'object')
108
- continue;
109
- const normalizedCallId = normalizeCallId(call.id ?? call.tool_call_id ?? call.call_id, 'fc_call');
110
- call.id = normalizedCallId;
111
- if (call.tool_call_id !== undefined) {
112
- call.tool_call_id = normalizedCallId;
113
- }
114
- if (call.call_id !== undefined) {
115
- call.call_id = normalizedCallId;
116
- }
117
- }
118
- }
119
- }
120
- const submitCalls = payload?.required_action?.submit_tool_outputs?.tool_calls;
121
- if (Array.isArray(submitCalls)) {
122
- for (const call of submitCalls) {
123
- if (!call || typeof call !== 'object')
124
- continue;
125
- const normalizedCallId = normalizeCallId(call.tool_call_id ?? call.id ?? call.call_id, 'fc_call');
126
- call.tool_call_id = normalizedCallId;
127
- call.id = normalizedCallId;
128
- if (call.call_id !== undefined) {
129
- call.call_id = normalizedCallId;
130
- }
131
- }
59
+ const normalized = normalizeResponsesToolCallIdsWithNative(payload);
60
+ if (normalized && typeof normalized === 'object' && !Array.isArray(normalized)) {
61
+ replaceMutableRecord(payload, normalized);
132
62
  }
133
63
  }
134
64
  export function resolveToolCallIdStyle(metadata) {
135
- if (!metadata)
136
- return 'fc';
137
- const raw = metadata.toolCallIdStyle;
138
- if (typeof raw === 'string') {
139
- const lowered = raw.trim().toLowerCase();
140
- if (lowered === 'fc') {
141
- return 'fc';
142
- }
143
- if (lowered === 'preserve') {
144
- return 'preserve';
145
- }
146
- }
147
- return 'fc';
65
+ assertResponsesToolUtilsNativeAvailable();
66
+ const style = resolveToolCallIdStyleWithNative(metadata ?? null);
67
+ return style === 'preserve' ? 'preserve' : 'fc';
148
68
  }
149
- const RAW_SYSTEM_SENTINEL = '__rcc_raw_system';
150
69
  export function stripInternalToolingMetadata(metadata) {
151
- if (!metadata || typeof metadata !== 'object')
70
+ assertResponsesToolUtilsNativeAvailable();
71
+ if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata))
152
72
  return;
153
- const record = metadata;
154
- if ('toolCallIdStyle' in record) {
155
- delete record.toolCallIdStyle;
156
- }
157
- if (RAW_SYSTEM_SENTINEL in record) {
158
- delete record[RAW_SYSTEM_SENTINEL];
159
- }
160
- if (record.extraFields && typeof record.extraFields === 'object') {
161
- prunePrivateExtraFields(record.extraFields);
162
- if (!Object.keys(record.extraFields).length) {
163
- delete record.extraFields;
164
- }
165
- }
166
- }
167
- function prunePrivateExtraFields(target) {
168
- for (const key of Object.keys(target)) {
169
- const value = target[key];
170
- if (typeof key === 'string' && key.startsWith('__rcc_')) {
171
- delete target[key];
172
- continue;
173
- }
174
- if (value && typeof value === 'object') {
175
- prunePrivateExtraFields(value);
176
- if (!Object.keys(value).length) {
177
- delete target[key];
178
- }
179
- }
73
+ const normalized = stripInternalToolingMetadataWithNative(metadata);
74
+ if (normalized && typeof normalized === 'object' && !Array.isArray(normalized)) {
75
+ replaceMutableRecord(metadata, normalized);
180
76
  }
181
77
  }
182
78
  export function sanitizeResponsesFunctionName(rawName) {