@jsonstudio/llms 0.6.938 → 0.6.1164

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 (131) hide show
  1. package/dist/conversion/hub/operation-table/operation-table-runner.d.ts +18 -0
  2. package/dist/conversion/hub/operation-table/operation-table-runner.js +158 -0
  3. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.d.ts +8 -0
  4. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +303 -0
  5. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.d.ts +8 -0
  6. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +413 -0
  7. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.d.ts +7 -0
  8. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +841 -0
  9. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.d.ts +21 -0
  10. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +535 -0
  11. package/dist/conversion/hub/ops/operations.d.ts +19 -0
  12. package/dist/conversion/hub/ops/operations.js +126 -0
  13. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +9 -0
  14. package/dist/conversion/hub/pipeline/hub-pipeline.js +533 -24
  15. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +6 -0
  16. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +6 -3
  17. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +11 -0
  18. package/dist/conversion/hub/policy/policy-engine.js +41 -9
  19. package/dist/conversion/hub/policy/protocol-spec.d.ts +25 -0
  20. package/dist/conversion/hub/policy/protocol-spec.js +73 -23
  21. package/dist/conversion/hub/process/chat-process.js +252 -41
  22. package/dist/conversion/hub/response/provider-response.js +175 -2
  23. package/dist/conversion/hub/response/response-runtime.js +1 -1
  24. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.d.ts +1 -8
  25. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +1 -365
  26. package/dist/conversion/hub/semantic-mappers/chat-mapper.d.ts +1 -8
  27. package/dist/conversion/hub/semantic-mappers/chat-mapper.js +1 -436
  28. package/dist/conversion/hub/semantic-mappers/gemini-mapper.d.ts +1 -7
  29. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +1 -894
  30. package/dist/conversion/hub/semantic-mappers/responses-mapper.d.ts +1 -21
  31. package/dist/conversion/hub/semantic-mappers/responses-mapper.js +1 -593
  32. package/dist/conversion/hub/tool-surface/tool-surface-engine.d.ts +18 -0
  33. package/dist/conversion/hub/tool-surface/tool-surface-engine.js +571 -0
  34. package/dist/conversion/responses/responses-openai-bridge.js +14 -2
  35. package/dist/conversion/shared/bridge-message-utils.js +2 -8
  36. package/dist/conversion/shared/bridge-policies.js +5 -105
  37. package/dist/conversion/shared/gemini-tool-utils.js +121 -4
  38. package/dist/conversion/shared/protocol-field-allowlists.d.ts +7 -0
  39. package/dist/conversion/shared/protocol-field-allowlists.js +145 -0
  40. package/dist/conversion/shared/reasoning-tool-normalizer.js +4 -2
  41. package/dist/conversion/shared/snapshot-hooks.js +166 -3
  42. package/dist/conversion/shared/text-markup-normalizer.d.ts +2 -0
  43. package/dist/conversion/shared/text-markup-normalizer.js +345 -9
  44. package/dist/conversion/shared/thought-signature-validator.d.ts +52 -0
  45. package/dist/conversion/shared/thought-signature-validator.js +170 -0
  46. package/dist/conversion/shared/tool-argument-repairer.d.ts +39 -0
  47. package/dist/conversion/shared/tool-argument-repairer.js +56 -0
  48. package/dist/conversion/shared/tool-call-id-manager.d.ts +113 -0
  49. package/dist/conversion/shared/tool-call-id-manager.js +231 -0
  50. package/dist/conversion/shared/tool-canonicalizer.js +2 -11
  51. package/dist/router/virtual-router/bootstrap.js +54 -5
  52. package/dist/router/virtual-router/engine-selection.js +132 -42
  53. package/dist/router/virtual-router/engine.d.ts +3 -0
  54. package/dist/router/virtual-router/engine.js +142 -33
  55. package/dist/router/virtual-router/health-weighted.d.ts +25 -0
  56. package/dist/router/virtual-router/health-weighted.js +63 -0
  57. package/dist/router/virtual-router/load-balancer.d.ts +2 -0
  58. package/dist/router/virtual-router/load-balancer.js +45 -16
  59. package/dist/router/virtual-router/routing-instructions.js +17 -1
  60. package/dist/router/virtual-router/sticky-session-store.js +136 -24
  61. package/dist/router/virtual-router/stop-message-file-resolver.d.ts +1 -0
  62. package/dist/router/virtual-router/stop-message-file-resolver.js +74 -0
  63. package/dist/router/virtual-router/stop-message-state-sync.d.ts +15 -0
  64. package/dist/router/virtual-router/stop-message-state-sync.js +57 -0
  65. package/dist/router/virtual-router/types.d.ts +70 -0
  66. package/dist/servertool/clock/config.d.ts +7 -0
  67. package/dist/servertool/clock/config.js +27 -0
  68. package/dist/servertool/clock/daemon.d.ts +3 -0
  69. package/dist/servertool/clock/daemon.js +79 -0
  70. package/dist/servertool/clock/io.d.ts +2 -0
  71. package/dist/servertool/clock/io.js +13 -0
  72. package/dist/servertool/clock/paths.d.ts +4 -0
  73. package/dist/servertool/clock/paths.js +25 -0
  74. package/dist/servertool/clock/session-store.d.ts +3 -0
  75. package/dist/servertool/clock/session-store.js +56 -0
  76. package/dist/servertool/clock/state.d.ts +5 -0
  77. package/dist/servertool/clock/state.js +62 -0
  78. package/dist/servertool/clock/task-store.d.ts +5 -0
  79. package/dist/servertool/clock/task-store.js +4 -0
  80. package/dist/servertool/clock/tasks.d.ts +17 -0
  81. package/dist/servertool/clock/tasks.js +221 -0
  82. package/dist/servertool/clock/types.d.ts +36 -0
  83. package/dist/servertool/clock/types.js +1 -0
  84. package/dist/servertool/engine.d.ts +2 -0
  85. package/dist/servertool/engine.js +164 -8
  86. package/dist/servertool/followup-shadow.d.ts +16 -0
  87. package/dist/servertool/followup-shadow.js +145 -0
  88. package/dist/servertool/handlers/apply-patch-guard.js +1 -265
  89. package/dist/servertool/handlers/clock-auto.d.ts +1 -0
  90. package/dist/servertool/handlers/clock-auto.js +160 -0
  91. package/dist/servertool/handlers/clock.d.ts +1 -0
  92. package/dist/servertool/handlers/clock.js +197 -0
  93. package/dist/servertool/handlers/exec-command-guard.js +7 -555
  94. package/dist/servertool/handlers/followup-request-builder.d.ts +15 -7
  95. package/dist/servertool/handlers/followup-request-builder.js +248 -28
  96. package/dist/servertool/handlers/gemini-empty-reply-continue.js +62 -169
  97. package/dist/servertool/handlers/iflow-model-error-retry.js +18 -28
  98. package/dist/servertool/handlers/recursive-detection-guard.d.ts +1 -0
  99. package/dist/servertool/handlers/recursive-detection-guard.js +333 -0
  100. package/dist/servertool/handlers/stop-message-auto.js +47 -175
  101. package/dist/servertool/handlers/vision.d.ts +7 -1
  102. package/dist/servertool/handlers/vision.js +61 -117
  103. package/dist/servertool/handlers/web-search.d.ts +7 -1
  104. package/dist/servertool/handlers/web-search.js +122 -105
  105. package/dist/servertool/reenter-backend.d.ts +23 -0
  106. package/dist/servertool/reenter-backend.js +18 -0
  107. package/dist/servertool/server-side-tools.d.ts +3 -2
  108. package/dist/servertool/server-side-tools.js +64 -10
  109. package/dist/servertool/types.d.ts +92 -3
  110. package/dist/sse/json-to-sse/event-generators/responses.js +3 -21
  111. package/dist/sse/shared/serializers/responses-event-serializer.d.ts +8 -0
  112. package/dist/sse/shared/serializers/responses-event-serializer.js +19 -0
  113. package/dist/sse/shared/writer.js +24 -7
  114. package/dist/tools/apply-patch/execution-capturer.js +3 -1
  115. package/dist/tools/apply-patch/json/parse-loose.d.ts +3 -0
  116. package/dist/tools/apply-patch/json/parse-loose.js +139 -0
  117. package/dist/tools/apply-patch/patch-text/context-diff.d.ts +1 -0
  118. package/dist/tools/apply-patch/patch-text/context-diff.js +173 -0
  119. package/dist/tools/apply-patch/patch-text/git-diff.d.ts +1 -0
  120. package/dist/tools/apply-patch/patch-text/git-diff.js +138 -0
  121. package/dist/tools/apply-patch/patch-text/looks-like-patch.d.ts +1 -0
  122. package/dist/tools/apply-patch/patch-text/looks-like-patch.js +13 -0
  123. package/dist/tools/apply-patch/patch-text/normalize.d.ts +3 -0
  124. package/dist/tools/apply-patch/patch-text/normalize.js +262 -0
  125. package/dist/tools/apply-patch/structured/coercion.d.ts +3 -0
  126. package/dist/tools/apply-patch/structured/coercion.js +82 -0
  127. package/dist/tools/apply-patch/validation/shared.d.ts +3 -0
  128. package/dist/tools/apply-patch/validation/shared.js +6 -0
  129. package/dist/tools/apply-patch/validator.d.ts +2 -2
  130. package/dist/tools/apply-patch/validator.js +6 -556
  131. package/package.json +1 -1
@@ -0,0 +1,571 @@
1
+ import { isJsonObject, jsonClone } from '../types/json.js';
2
+ import { mapBridgeToolsToChat, mapChatToolsToBridge } from '../../shared/tool-mapping.js';
3
+ import { buildGeminiToolsFromBridge, prepareGeminiToolsForBridge } from '../../shared/gemini-tool-utils.js';
4
+ import { mapAnthropicToolsToChat, mapChatToolsToAnthropicTools } from '../../shared/anthropic-message-utils.js';
5
+ import { convertBridgeInputToChatMessages, convertMessagesToBridgeInput } from '../../shared/bridge-message-utils.js';
6
+ import { resolveHubProtocolSpec } from '../policy/protocol-spec.js';
7
+ function clampSampleRate(value) {
8
+ const num = typeof value === 'number' && Number.isFinite(value) ? value : 1;
9
+ if (num <= 0)
10
+ return 0;
11
+ if (num >= 1)
12
+ return 1;
13
+ return num;
14
+ }
15
+ function fnv1a32(input) {
16
+ let hash = 0x811c9dc5;
17
+ for (let i = 0; i < input.length; i++) {
18
+ hash ^= input.charCodeAt(i);
19
+ hash = (hash * 0x01000193) >>> 0;
20
+ }
21
+ return hash >>> 0;
22
+ }
23
+ function shouldSample(config, requestId) {
24
+ if (!config)
25
+ return false;
26
+ if (config.mode === 'off')
27
+ return false;
28
+ const rate = clampSampleRate(config.sampleRate);
29
+ if (rate <= 0)
30
+ return false;
31
+ if (rate >= 1)
32
+ return true;
33
+ const key = typeof requestId === 'string' && requestId.trim().length ? requestId.trim() : 'no_request_id';
34
+ const bucket = fnv1a32(key) / 0xffffffff;
35
+ return bucket < rate;
36
+ }
37
+ function stableStringify(value) {
38
+ const normalize = (node) => {
39
+ if (node === null || typeof node === 'string' || typeof node === 'number' || typeof node === 'boolean') {
40
+ return node;
41
+ }
42
+ if (Array.isArray(node)) {
43
+ return node.map((entry) => normalize(entry));
44
+ }
45
+ if (isJsonObject(node)) {
46
+ const out = {};
47
+ const keys = Object.keys(node).sort();
48
+ for (const key of keys) {
49
+ out[key] = normalize(node[key]);
50
+ }
51
+ return out;
52
+ }
53
+ return node;
54
+ };
55
+ try {
56
+ return JSON.stringify(normalize(value));
57
+ }
58
+ catch {
59
+ try {
60
+ return JSON.stringify(value);
61
+ }
62
+ catch {
63
+ return String(value);
64
+ }
65
+ }
66
+ }
67
+ function extractToolName(tool) {
68
+ if (!tool || typeof tool !== 'object' || Array.isArray(tool)) {
69
+ return undefined;
70
+ }
71
+ const obj = tool;
72
+ const fnNode = obj.function && typeof obj.function === 'object' && !Array.isArray(obj.function)
73
+ ? obj.function
74
+ : undefined;
75
+ const nameCandidate = fnNode?.name ?? obj.name;
76
+ if (typeof nameCandidate === 'string' && nameCandidate.trim().length) {
77
+ return nameCandidate.trim();
78
+ }
79
+ const typeRaw = typeof obj.type === 'string' ? obj.type.trim().toLowerCase() : '';
80
+ if (typeRaw === 'web_search' || typeRaw.startsWith('web_search')) {
81
+ return 'web_search';
82
+ }
83
+ return undefined;
84
+ }
85
+ function extractToolSchema(tool) {
86
+ const name = extractToolName(tool);
87
+ if (!name)
88
+ return undefined;
89
+ if (!tool || typeof tool !== 'object' || Array.isArray(tool)) {
90
+ return { name };
91
+ }
92
+ const obj = tool;
93
+ const fnNode = obj.function && typeof obj.function === 'object' && !Array.isArray(obj.function)
94
+ ? obj.function
95
+ : undefined;
96
+ const description = typeof fnNode?.description === 'string'
97
+ ? fnNode.description
98
+ : typeof obj.description === 'string'
99
+ ? obj.description
100
+ : undefined;
101
+ const parameters = fnNode && Object.prototype.hasOwnProperty.call(fnNode, 'parameters')
102
+ ? fnNode.parameters
103
+ : Object.prototype.hasOwnProperty.call(obj, 'parameters')
104
+ ? obj.parameters
105
+ : Object.prototype.hasOwnProperty.call(obj, 'input_schema')
106
+ ? obj.input_schema
107
+ : undefined;
108
+ return {
109
+ name,
110
+ ...(description !== undefined ? { description } : {}),
111
+ ...(parameters !== undefined ? { parameters } : {})
112
+ };
113
+ }
114
+ function parseToolsForDiff(raw) {
115
+ const map = new Map();
116
+ if (!Array.isArray(raw)) {
117
+ return map;
118
+ }
119
+ for (const entry of raw) {
120
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
121
+ continue;
122
+ }
123
+ const obj = entry;
124
+ const declarations = Array.isArray(obj.functionDeclarations) ? obj.functionDeclarations : undefined;
125
+ if (declarations && declarations.length) {
126
+ for (const decl of declarations) {
127
+ const schema = extractToolSchema({ type: 'function', function: decl });
128
+ if (schema && !map.has(schema.name)) {
129
+ map.set(schema.name, schema);
130
+ }
131
+ }
132
+ continue;
133
+ }
134
+ const schema = extractToolSchema(entry);
135
+ if (schema && !map.has(schema.name)) {
136
+ map.set(schema.name, schema);
137
+ }
138
+ }
139
+ return map;
140
+ }
141
+ function computeToolSchemaDiff(baselineTools, candidateTools) {
142
+ const baseline = parseToolsForDiff(baselineTools);
143
+ const candidate = parseToolsForDiff(candidateTools);
144
+ const names = Array.from(new Set([...baseline.keys(), ...candidate.keys()])).sort();
145
+ let diffCount = 0;
146
+ const diffHead = [];
147
+ const pushHead = (entry) => {
148
+ if (diffHead.length < 10) {
149
+ diffHead.push(entry);
150
+ }
151
+ };
152
+ for (const name of names) {
153
+ const a = baseline.get(name);
154
+ const b = candidate.get(name);
155
+ if (!a && b) {
156
+ diffCount += 1;
157
+ pushHead({ name, kind: 'missing_in_baseline' });
158
+ continue;
159
+ }
160
+ if (a && !b) {
161
+ diffCount += 1;
162
+ pushHead({ name, kind: 'missing_in_candidate' });
163
+ continue;
164
+ }
165
+ if (!a || !b) {
166
+ continue;
167
+ }
168
+ const aSig = stableStringify({ description: a.description, parameters: a.parameters });
169
+ const bSig = stableStringify({ description: b.description, parameters: b.parameters });
170
+ if (aSig !== bSig) {
171
+ diffCount += 1;
172
+ pushHead({
173
+ name,
174
+ kind: 'schema_mismatch',
175
+ baseline: jsonClone(a),
176
+ candidate: jsonClone(b)
177
+ });
178
+ }
179
+ }
180
+ return { diffCount, diffHead };
181
+ }
182
+ function extractToolHistoryFromChatMessages(messages) {
183
+ const out = { toolCalls: [], toolResults: [] };
184
+ if (!Array.isArray(messages))
185
+ return out;
186
+ for (const msg of messages) {
187
+ if (!msg || typeof msg !== 'object' || Array.isArray(msg))
188
+ continue;
189
+ const record = msg;
190
+ const role = typeof record.role === 'string' ? record.role.trim().toLowerCase() : '';
191
+ if (role === 'assistant') {
192
+ const toolCalls = Array.isArray(record.tool_calls) ? record.tool_calls : [];
193
+ for (const tc of toolCalls) {
194
+ if (!tc || typeof tc !== 'object')
195
+ continue;
196
+ const id = typeof tc.id === 'string' && tc.id.trim().length ? String(tc.id) : undefined;
197
+ const fn = tc.function;
198
+ const name = typeof fn?.name === 'string' ? String(fn.name).trim() : '';
199
+ const args = fn?.arguments;
200
+ const argsType = typeof args === 'string' ? 'string' : args && typeof args === 'object' && !Array.isArray(args) ? 'object' : 'other';
201
+ const argsLen = typeof args === 'string' ? args.length : undefined;
202
+ if (name) {
203
+ out.toolCalls.push({ id, name, argsType, argsLen });
204
+ }
205
+ }
206
+ }
207
+ if (role === 'tool') {
208
+ const toolCallId = typeof record.tool_call_id === 'string'
209
+ ? String(record.tool_call_id).trim()
210
+ : typeof record.call_id === 'string'
211
+ ? String(record.call_id).trim()
212
+ : undefined;
213
+ const content = record.content;
214
+ const contentLen = typeof content === 'string' ? content.length : undefined;
215
+ out.toolResults.push({ toolCallId: toolCallId || undefined, contentLen });
216
+ }
217
+ }
218
+ return out;
219
+ }
220
+ function extractToolHistoryFromResponsesInputItems(input) {
221
+ const out = { toolCalls: [], toolResults: [] };
222
+ if (!Array.isArray(input))
223
+ return out;
224
+ for (const item of input) {
225
+ if (!item || typeof item !== 'object' || Array.isArray(item))
226
+ continue;
227
+ const rec = item;
228
+ const type = typeof rec.type === 'string' ? rec.type.trim().toLowerCase() : '';
229
+ if (type === 'function_call') {
230
+ const id = typeof rec.call_id === 'string' && String(rec.call_id).trim().length
231
+ ? String(rec.call_id).trim()
232
+ : typeof rec.id === 'string' && rec.id.trim().length
233
+ ? rec.id.trim()
234
+ : undefined;
235
+ const name = typeof rec.name === 'string' ? rec.name.trim() : '';
236
+ const args = rec.arguments;
237
+ const argsType = typeof args === 'string' ? 'string' : args && typeof args === 'object' && !Array.isArray(args) ? 'object' : 'other';
238
+ const argsLen = typeof args === 'string' ? args.length : undefined;
239
+ if (name) {
240
+ out.toolCalls.push({ id, name, argsType, argsLen });
241
+ }
242
+ continue;
243
+ }
244
+ if (type === 'function_call_output') {
245
+ const toolCallId = typeof rec.call_id === 'string' && String(rec.call_id).trim().length
246
+ ? String(rec.call_id).trim()
247
+ : typeof rec.tool_call_id === 'string' && String(rec.tool_call_id).trim().length
248
+ ? String(rec.tool_call_id).trim()
249
+ : undefined;
250
+ const output = rec.output;
251
+ const contentLen = typeof output === 'string' ? output.length : undefined;
252
+ out.toolResults.push({ toolCallId: toolCallId || undefined, contentLen });
253
+ continue;
254
+ }
255
+ }
256
+ return out;
257
+ }
258
+ function computeToolHistoryDiff(baseline, candidate) {
259
+ let diffCount = 0;
260
+ const diffs = [];
261
+ const push = (entry) => {
262
+ diffCount += 1;
263
+ if (diffs.length < 10)
264
+ diffs.push(entry);
265
+ };
266
+ const compareSeq = (a, b, kind) => {
267
+ const max = Math.max(a.length, b.length);
268
+ for (let i = 0; i < max; i += 1) {
269
+ const ai = a[i];
270
+ const bi = b[i];
271
+ if (ai === undefined && bi !== undefined) {
272
+ push({ kind, index: i, type: 'missing_in_baseline', candidate: bi });
273
+ continue;
274
+ }
275
+ if (ai !== undefined && bi === undefined) {
276
+ push({ kind, index: i, type: 'missing_in_candidate', baseline: ai });
277
+ continue;
278
+ }
279
+ const aSig = stableStringify(ai);
280
+ const bSig = stableStringify(bi);
281
+ if (aSig !== bSig) {
282
+ push({ kind, index: i, type: 'mismatch', baseline: ai, candidate: bi });
283
+ }
284
+ }
285
+ };
286
+ compareSeq(baseline.toolCalls, candidate.toolCalls, 'tool_calls');
287
+ compareSeq(baseline.toolResults, candidate.toolResults, 'tool_results');
288
+ return { diffCount, diffHead: diffs };
289
+ }
290
+ function looksLikeOpenAITools(raw) {
291
+ if (!Array.isArray(raw))
292
+ return false;
293
+ return raw.some((entry) => {
294
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry))
295
+ return false;
296
+ const obj = entry;
297
+ return typeof obj.type === 'string' || (obj.function && typeof obj.function === 'object');
298
+ });
299
+ }
300
+ function looksLikeGeminiTools(raw) {
301
+ if (!Array.isArray(raw))
302
+ return false;
303
+ return raw.some((entry) => {
304
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry))
305
+ return false;
306
+ const obj = entry;
307
+ return Array.isArray(obj.functionDeclarations);
308
+ });
309
+ }
310
+ function looksLikeAnthropicTools(raw) {
311
+ if (!Array.isArray(raw))
312
+ return false;
313
+ return raw.some((entry) => {
314
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry))
315
+ return false;
316
+ const obj = entry;
317
+ return typeof obj.name === 'string' && Object.prototype.hasOwnProperty.call(obj, 'input_schema');
318
+ });
319
+ }
320
+ function detectToolFormat(raw) {
321
+ if (!Array.isArray(raw))
322
+ return 'unknown';
323
+ if (looksLikeGeminiTools(raw))
324
+ return 'gemini';
325
+ if (looksLikeAnthropicTools(raw))
326
+ return 'anthropic';
327
+ if (looksLikeOpenAITools(raw))
328
+ return 'openai';
329
+ return 'unknown';
330
+ }
331
+ function convertToolDefinitions(args) {
332
+ if (!Array.isArray(args.tools)) {
333
+ return undefined;
334
+ }
335
+ const from = args.from;
336
+ const to = args.to;
337
+ const raw = args.tools;
338
+ if (from === to) {
339
+ return raw;
340
+ }
341
+ // All conversions pass through OpenAI-format as the canonical bridge.
342
+ const toOpenAI = (sourceFormat, input) => {
343
+ if (!Array.isArray(input))
344
+ return undefined;
345
+ if (sourceFormat === 'openai') {
346
+ return input;
347
+ }
348
+ if (sourceFormat === 'gemini') {
349
+ if (!looksLikeGeminiTools(input))
350
+ return undefined;
351
+ const bridgeDefs = prepareGeminiToolsForBridge(input);
352
+ return mapBridgeToolsToChat(bridgeDefs);
353
+ }
354
+ if (sourceFormat === 'anthropic') {
355
+ if (!looksLikeAnthropicTools(input))
356
+ return undefined;
357
+ return mapAnthropicToolsToChat(input);
358
+ }
359
+ return undefined;
360
+ };
361
+ const fromOpenAI = (targetFormat, input) => {
362
+ if (!Array.isArray(input))
363
+ return undefined;
364
+ if (targetFormat === 'openai') {
365
+ return input;
366
+ }
367
+ if (targetFormat === 'gemini') {
368
+ if (!looksLikeOpenAITools(input))
369
+ return undefined;
370
+ const bridgeDefs = mapChatToolsToBridge(input);
371
+ return buildGeminiToolsFromBridge(bridgeDefs);
372
+ }
373
+ if (targetFormat === 'anthropic') {
374
+ if (!looksLikeOpenAITools(input))
375
+ return undefined;
376
+ return mapChatToolsToAnthropicTools(input);
377
+ }
378
+ return undefined;
379
+ };
380
+ const openaiTools = toOpenAI(from, raw);
381
+ if (!openaiTools) {
382
+ return undefined;
383
+ }
384
+ const converted = fromOpenAI(to, openaiTools);
385
+ if (!converted) {
386
+ return undefined;
387
+ }
388
+ return converted;
389
+ }
390
+ function resolveExpectedHistoryCarrier(providerProtocol) {
391
+ try {
392
+ const spec = resolveHubProtocolSpec(providerProtocol);
393
+ return spec.toolSurface.expectedHistoryCarrier ?? null;
394
+ }
395
+ catch {
396
+ return null;
397
+ }
398
+ }
399
+ function buildCandidateTools(args) {
400
+ const rawTools = args.tools;
401
+ if (!Array.isArray(rawTools)) {
402
+ return undefined;
403
+ }
404
+ const spec = resolveHubProtocolSpec(args.providerProtocol);
405
+ const expected = spec.toolSurface.expectedToolFormat;
406
+ const detected = detectToolFormat(rawTools);
407
+ if (detected === 'unknown' || detected === expected) {
408
+ return undefined;
409
+ }
410
+ const candidateTools = convertToolDefinitions({
411
+ from: detected,
412
+ to: expected,
413
+ tools: rawTools
414
+ });
415
+ if (candidateTools === undefined) {
416
+ return undefined;
417
+ }
418
+ return {
419
+ candidateTools,
420
+ reason: `${detected}_tools_on_${expected}_protocol`
421
+ };
422
+ }
423
+ export function applyProviderOutboundToolSurface(args) {
424
+ const config = args.config;
425
+ if (!config || config.mode === 'off') {
426
+ return args.payload;
427
+ }
428
+ if (!shouldSample(config, args.requestId)) {
429
+ return args.payload;
430
+ }
431
+ const payload = args.payload;
432
+ const tools = payload.tools;
433
+ const candidate = buildCandidateTools({ providerProtocol: args.providerProtocol, tools });
434
+ if (!candidate) {
435
+ // Still allow history/tool-call surface observation even when tool definitions don't need conversion.
436
+ }
437
+ const stageBase = `hub_toolsurface.${config.mode}.provider_outbound`;
438
+ const toolSchemaCandidate = candidate?.candidateTools;
439
+ const schemaDiff = candidate ? computeToolSchemaDiff(tools, toolSchemaCandidate) : { diffCount: 0, diffHead: [] };
440
+ const expectedHistoryCarrier = resolveExpectedHistoryCarrier(args.providerProtocol);
441
+ let historyReason = undefined;
442
+ let historyBaseline = undefined;
443
+ let historyCandidate = undefined;
444
+ let historyDiff = { diffCount: 0, diffHead: [] };
445
+ // Phase 2 (shadow): detect tool call/result history carriers drifting between
446
+ // Chat messages and Responses input shapes.
447
+ try {
448
+ if (expectedHistoryCarrier === 'input') {
449
+ const messages = payload.messages;
450
+ const input = payload.input;
451
+ if (Array.isArray(messages) && !Array.isArray(input)) {
452
+ const bridge = convertMessagesToBridgeInput({
453
+ messages: messages,
454
+ tools: Array.isArray(tools) ? tools : undefined
455
+ });
456
+ historyReason = 'openai_chat_messages_on_responses_protocol';
457
+ historyBaseline = extractToolHistoryFromChatMessages(messages);
458
+ historyCandidate = extractToolHistoryFromResponsesInputItems(bridge.input);
459
+ historyDiff = computeToolHistoryDiff(historyBaseline, historyCandidate);
460
+ }
461
+ }
462
+ else if (expectedHistoryCarrier === 'messages') {
463
+ const messages = payload.messages;
464
+ const input = payload.input;
465
+ if (!Array.isArray(messages) && Array.isArray(input)) {
466
+ historyReason = 'responses_input_on_openai_chat_protocol';
467
+ historyBaseline = extractToolHistoryFromResponsesInputItems(input);
468
+ historyCandidate = extractToolHistoryFromChatMessages(convertBridgeInputToChatMessages({
469
+ input: input,
470
+ tools: Array.isArray(tools) ? tools : undefined
471
+ }));
472
+ historyDiff = computeToolHistoryDiff(historyBaseline, historyCandidate);
473
+ }
474
+ }
475
+ }
476
+ catch {
477
+ // best-effort only
478
+ }
479
+ const totalDiffCount = (schemaDiff.diffCount || 0) + (historyDiff.diffCount || 0);
480
+ if (totalDiffCount > 0) {
481
+ const diffHeadMerged = [];
482
+ for (const entry of schemaDiff.diffHead) {
483
+ if (diffHeadMerged.length >= 10)
484
+ break;
485
+ diffHeadMerged.push({ area: 'tool_definitions', ...entry });
486
+ }
487
+ for (const entry of historyDiff.diffHead) {
488
+ if (diffHeadMerged.length >= 10)
489
+ break;
490
+ diffHeadMerged.push({ area: 'tool_history', ...entry });
491
+ }
492
+ args.stageRecorder?.record(stageBase, {
493
+ kind: 'provider_outbound',
494
+ providerProtocol: args.providerProtocol,
495
+ reason: candidate?.reason ?? historyReason,
496
+ diffCount: totalDiffCount,
497
+ diffHead: diffHeadMerged,
498
+ ...(schemaDiff.diffCount > 0
499
+ ? {
500
+ definitionDiffCount: schemaDiff.diffCount,
501
+ definitionDiffHead: schemaDiff.diffHead,
502
+ ...(tools !== undefined ? { baselineTools: jsonClone(tools) } : {}),
503
+ ...(toolSchemaCandidate !== undefined ? { candidateTools: jsonClone(toolSchemaCandidate) } : {})
504
+ }
505
+ : {}),
506
+ ...(historyDiff.diffCount > 0
507
+ ? {
508
+ historyDiffCount: historyDiff.diffCount,
509
+ historyDiffHead: historyDiff.diffHead,
510
+ historyReason,
511
+ historyBaseline,
512
+ historyCandidate
513
+ }
514
+ : {})
515
+ });
516
+ }
517
+ if (config.mode === 'enforce') {
518
+ let next = null;
519
+ const ensureClone = () => {
520
+ if (!next) {
521
+ next = jsonClone(payload);
522
+ }
523
+ return next;
524
+ };
525
+ if (candidate && candidate.candidateTools !== undefined) {
526
+ ensureClone().tools = candidate.candidateTools;
527
+ }
528
+ // Best-effort: normalize history carrier when we can reconstruct a canonical representation.
529
+ try {
530
+ if (expectedHistoryCarrier === 'input') {
531
+ const messages = payload.messages;
532
+ const input = payload.input;
533
+ if (Array.isArray(messages) && !Array.isArray(input)) {
534
+ const bridge = convertMessagesToBridgeInput({
535
+ messages: messages,
536
+ tools: Array.isArray(tools) ? tools : undefined
537
+ });
538
+ ensureClone().input = bridge.input;
539
+ try {
540
+ delete ensureClone().messages;
541
+ }
542
+ catch {
543
+ ensureClone().messages = undefined;
544
+ }
545
+ }
546
+ }
547
+ else if (expectedHistoryCarrier === 'messages') {
548
+ const messages = payload.messages;
549
+ const input = payload.input;
550
+ if (!Array.isArray(messages) && Array.isArray(input)) {
551
+ const convertedMessages = convertBridgeInputToChatMessages({
552
+ input: input,
553
+ tools: Array.isArray(tools) ? tools : undefined
554
+ });
555
+ ensureClone().messages = convertedMessages;
556
+ try {
557
+ delete ensureClone().input;
558
+ }
559
+ catch {
560
+ ensureClone().input = undefined;
561
+ }
562
+ }
563
+ }
564
+ }
565
+ catch {
566
+ // best-effort only
567
+ }
568
+ return next ?? payload;
569
+ }
570
+ return payload;
571
+ }
@@ -13,6 +13,17 @@ import { buildResponsesOutputFromChat } from '../shared/responses-output-builder
13
13
  function isObject(v) {
14
14
  return !!v && typeof v === 'object' && !Array.isArray(v);
15
15
  }
16
+ function filterBridgeInputForUpstream(input) {
17
+ // Upstream `/v1/responses` create only accepts a subset of input item types.
18
+ // In particular, `type:"reasoning"` entries are output-only artifacts (often
19
+ // captured from previous responses) and OpenAI rejects them with schema errors
20
+ // like `input[N].content: array too long (max 0)`.
21
+ return (Array.isArray(input) ? input : []).filter((item) => {
22
+ if (!item || typeof item !== 'object')
23
+ return false;
24
+ return item.type !== 'reasoning';
25
+ });
26
+ }
16
27
  // normalizeTools unified in ../shared/args-mapping.ts
17
28
  // --- Structured self-repair helpers for tool failures (Responses path) ---
18
29
  // use shared isImagePath
@@ -364,8 +375,9 @@ export function buildResponsesRequestFromChat(payload, ctx, extras) {
364
375
  }
365
376
  }
366
377
  // 不追加 metadata,以便 roundtrip 与原始 payload 对齐;系统提示直接写入 instructions。
367
- if (input.length) {
368
- out.input = input;
378
+ const upstreamInput = filterBridgeInputForUpstream(input);
379
+ if (upstreamInput.length) {
380
+ out.input = upstreamInput;
369
381
  }
370
382
  const streamFromChat = typeof chat.stream === 'boolean' ? chat.stream : undefined;
371
383
  const streamFromParameters = chat?.parameters && typeof chat.parameters?.stream === 'boolean'
@@ -1,5 +1,6 @@
1
1
  import { normalizeFunctionCallId, normalizeFunctionCallOutputId } from './bridge-id-utils.js';
2
2
  import { normalizeChatMessageContent } from './chat-output-normalizer.js';
3
+ import { repairToolArguments } from './tool-argument-repairer.js';
3
4
  function ensureAssistantToolCallIdentity(call, fallbackId) {
4
5
  const resolved = (typeof call.id === 'string' && call.id.trim().length ? call.id.trim() : undefined) ??
5
6
  (typeof call.tool_call_id === 'string' && call.tool_call_id.trim().length
@@ -24,14 +25,7 @@ export function coerceBridgeRole(role) {
24
25
  return 'user';
25
26
  }
26
27
  export function serializeToolArguments(argsStringOrObj, _functionName, _tools) {
27
- if (typeof argsStringOrObj === 'string')
28
- return argsStringOrObj;
29
- try {
30
- return JSON.stringify(argsStringOrObj ?? {});
31
- }
32
- catch {
33
- return String(argsStringOrObj);
34
- }
28
+ return repairToolArguments(argsStringOrObj);
35
29
  }
36
30
  export function serializeToolOutput(entry) {
37
31
  const out = entry?.output;