@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.
- package/dist/conversion/hub/operation-table/operation-table-runner.d.ts +18 -0
- package/dist/conversion/hub/operation-table/operation-table-runner.js +158 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.d.ts +8 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +303 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.d.ts +8 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +413 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.d.ts +7 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +841 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.d.ts +21 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +535 -0
- package/dist/conversion/hub/ops/operations.d.ts +19 -0
- package/dist/conversion/hub/ops/operations.js +126 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +9 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +533 -24
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +6 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +6 -3
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +11 -0
- package/dist/conversion/hub/policy/policy-engine.js +41 -9
- package/dist/conversion/hub/policy/protocol-spec.d.ts +25 -0
- package/dist/conversion/hub/policy/protocol-spec.js +73 -23
- package/dist/conversion/hub/process/chat-process.js +252 -41
- package/dist/conversion/hub/response/provider-response.js +175 -2
- package/dist/conversion/hub/response/response-runtime.js +1 -1
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.d.ts +1 -8
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +1 -365
- package/dist/conversion/hub/semantic-mappers/chat-mapper.d.ts +1 -8
- package/dist/conversion/hub/semantic-mappers/chat-mapper.js +1 -436
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.d.ts +1 -7
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +1 -894
- package/dist/conversion/hub/semantic-mappers/responses-mapper.d.ts +1 -21
- package/dist/conversion/hub/semantic-mappers/responses-mapper.js +1 -593
- package/dist/conversion/hub/tool-surface/tool-surface-engine.d.ts +18 -0
- package/dist/conversion/hub/tool-surface/tool-surface-engine.js +571 -0
- package/dist/conversion/responses/responses-openai-bridge.js +14 -2
- package/dist/conversion/shared/bridge-message-utils.js +2 -8
- package/dist/conversion/shared/bridge-policies.js +5 -105
- package/dist/conversion/shared/gemini-tool-utils.js +121 -4
- package/dist/conversion/shared/protocol-field-allowlists.d.ts +7 -0
- package/dist/conversion/shared/protocol-field-allowlists.js +145 -0
- package/dist/conversion/shared/reasoning-tool-normalizer.js +4 -2
- package/dist/conversion/shared/snapshot-hooks.js +166 -3
- package/dist/conversion/shared/text-markup-normalizer.d.ts +2 -0
- package/dist/conversion/shared/text-markup-normalizer.js +345 -9
- package/dist/conversion/shared/thought-signature-validator.d.ts +52 -0
- package/dist/conversion/shared/thought-signature-validator.js +170 -0
- package/dist/conversion/shared/tool-argument-repairer.d.ts +39 -0
- package/dist/conversion/shared/tool-argument-repairer.js +56 -0
- package/dist/conversion/shared/tool-call-id-manager.d.ts +113 -0
- package/dist/conversion/shared/tool-call-id-manager.js +231 -0
- package/dist/conversion/shared/tool-canonicalizer.js +2 -11
- package/dist/router/virtual-router/bootstrap.js +54 -5
- package/dist/router/virtual-router/engine-selection.js +132 -42
- package/dist/router/virtual-router/engine.d.ts +3 -0
- package/dist/router/virtual-router/engine.js +142 -33
- package/dist/router/virtual-router/health-weighted.d.ts +25 -0
- package/dist/router/virtual-router/health-weighted.js +63 -0
- package/dist/router/virtual-router/load-balancer.d.ts +2 -0
- package/dist/router/virtual-router/load-balancer.js +45 -16
- package/dist/router/virtual-router/routing-instructions.js +17 -1
- package/dist/router/virtual-router/sticky-session-store.js +136 -24
- package/dist/router/virtual-router/stop-message-file-resolver.d.ts +1 -0
- package/dist/router/virtual-router/stop-message-file-resolver.js +74 -0
- package/dist/router/virtual-router/stop-message-state-sync.d.ts +15 -0
- package/dist/router/virtual-router/stop-message-state-sync.js +57 -0
- package/dist/router/virtual-router/types.d.ts +70 -0
- package/dist/servertool/clock/config.d.ts +7 -0
- package/dist/servertool/clock/config.js +27 -0
- package/dist/servertool/clock/daemon.d.ts +3 -0
- package/dist/servertool/clock/daemon.js +79 -0
- package/dist/servertool/clock/io.d.ts +2 -0
- package/dist/servertool/clock/io.js +13 -0
- package/dist/servertool/clock/paths.d.ts +4 -0
- package/dist/servertool/clock/paths.js +25 -0
- package/dist/servertool/clock/session-store.d.ts +3 -0
- package/dist/servertool/clock/session-store.js +56 -0
- package/dist/servertool/clock/state.d.ts +5 -0
- package/dist/servertool/clock/state.js +62 -0
- package/dist/servertool/clock/task-store.d.ts +5 -0
- package/dist/servertool/clock/task-store.js +4 -0
- package/dist/servertool/clock/tasks.d.ts +17 -0
- package/dist/servertool/clock/tasks.js +221 -0
- package/dist/servertool/clock/types.d.ts +36 -0
- package/dist/servertool/clock/types.js +1 -0
- package/dist/servertool/engine.d.ts +2 -0
- package/dist/servertool/engine.js +164 -8
- package/dist/servertool/followup-shadow.d.ts +16 -0
- package/dist/servertool/followup-shadow.js +145 -0
- package/dist/servertool/handlers/apply-patch-guard.js +1 -265
- package/dist/servertool/handlers/clock-auto.d.ts +1 -0
- package/dist/servertool/handlers/clock-auto.js +160 -0
- package/dist/servertool/handlers/clock.d.ts +1 -0
- package/dist/servertool/handlers/clock.js +197 -0
- package/dist/servertool/handlers/exec-command-guard.js +7 -555
- package/dist/servertool/handlers/followup-request-builder.d.ts +15 -7
- package/dist/servertool/handlers/followup-request-builder.js +248 -28
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +62 -169
- package/dist/servertool/handlers/iflow-model-error-retry.js +18 -28
- package/dist/servertool/handlers/recursive-detection-guard.d.ts +1 -0
- package/dist/servertool/handlers/recursive-detection-guard.js +333 -0
- package/dist/servertool/handlers/stop-message-auto.js +47 -175
- package/dist/servertool/handlers/vision.d.ts +7 -1
- package/dist/servertool/handlers/vision.js +61 -117
- package/dist/servertool/handlers/web-search.d.ts +7 -1
- package/dist/servertool/handlers/web-search.js +122 -105
- package/dist/servertool/reenter-backend.d.ts +23 -0
- package/dist/servertool/reenter-backend.js +18 -0
- package/dist/servertool/server-side-tools.d.ts +3 -2
- package/dist/servertool/server-side-tools.js +64 -10
- package/dist/servertool/types.d.ts +92 -3
- package/dist/sse/json-to-sse/event-generators/responses.js +3 -21
- package/dist/sse/shared/serializers/responses-event-serializer.d.ts +8 -0
- package/dist/sse/shared/serializers/responses-event-serializer.js +19 -0
- package/dist/sse/shared/writer.js +24 -7
- package/dist/tools/apply-patch/execution-capturer.js +3 -1
- package/dist/tools/apply-patch/json/parse-loose.d.ts +3 -0
- package/dist/tools/apply-patch/json/parse-loose.js +139 -0
- package/dist/tools/apply-patch/patch-text/context-diff.d.ts +1 -0
- package/dist/tools/apply-patch/patch-text/context-diff.js +173 -0
- package/dist/tools/apply-patch/patch-text/git-diff.d.ts +1 -0
- package/dist/tools/apply-patch/patch-text/git-diff.js +138 -0
- package/dist/tools/apply-patch/patch-text/looks-like-patch.d.ts +1 -0
- package/dist/tools/apply-patch/patch-text/looks-like-patch.js +13 -0
- package/dist/tools/apply-patch/patch-text/normalize.d.ts +3 -0
- package/dist/tools/apply-patch/patch-text/normalize.js +262 -0
- package/dist/tools/apply-patch/structured/coercion.d.ts +3 -0
- package/dist/tools/apply-patch/structured/coercion.js +82 -0
- package/dist/tools/apply-patch/validation/shared.d.ts +3 -0
- package/dist/tools/apply-patch/validation/shared.js +6 -0
- package/dist/tools/apply-patch/validator.d.ts +2 -2
- package/dist/tools/apply-patch/validator.js +6 -556
- 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
|
-
|
|
368
|
-
|
|
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
|
-
|
|
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;
|