@jsonstudio/llms 0.6.3275 → 0.6.3379
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conversion/bridge-message-utils.d.ts +4 -4
- package/dist/conversion/bridge-message-utils.js +28 -538
- package/dist/conversion/compat/profiles/responses-crs.json +15 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +16 -5
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +1 -6
- package/dist/conversion/hub/response/response-runtime.js +14 -6
- package/dist/conversion/responses/responses-openai-bridge/response-payload.js +11 -11
- package/dist/conversion/shared/anthropic-message-utils.js +2 -12
- package/dist/conversion/shared/chat-request-filters.js +2 -61
- package/dist/conversion/shared/reasoning-mapping.js +3 -0
- package/dist/conversion/shared/reasoning-normalizer.d.ts +1 -0
- package/dist/conversion/shared/reasoning-normalizer.js +35 -388
- package/dist/conversion/shared/reasoning-tool-normalizer.js +8 -15
- package/dist/conversion/shared/reasoning-utils.js +13 -35
- package/dist/conversion/shared/responses-tool-utils.d.ts +1 -1
- package/dist/conversion/shared/responses-tool-utils.js +63 -65
- package/dist/conversion/shared/streaming-text-extractor.d.ts +0 -5
- package/dist/conversion/shared/streaming-text-extractor.js +18 -111
- package/dist/conversion/shared/text-markup-normalizer/normalize.d.ts +1 -1
- package/dist/conversion/shared/text-markup-normalizer/normalize.js +3 -91
- package/dist/conversion/shared/thought-signature-validator.js +19 -133
- package/dist/conversion/shared/tool-argument-repairer.js +16 -19
- package/dist/conversion/shared/tool-call-id-manager.d.ts +1 -5
- package/dist/conversion/shared/tool-call-id-manager.js +74 -98
- package/dist/conversion/shared/tool-harvester.js +19 -382
- package/dist/conversion/shared/tool-mapping.d.ts +2 -3
- package/dist/conversion/shared/tool-mapping.js +28 -184
- package/dist/conversion/shared/tooling.js +20 -151
- package/dist/filters/special/response-tool-arguments-stringify.js +9 -1
- package/dist/guidance/index.js +2 -2
- package/dist/native/router_hotpath_napi.node +0 -0
- package/dist/router/virtual-router/engine-legacy/helpers.js +1 -1
- package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.d.ts +39 -0
- package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js +196 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.d.ts +1 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.js +27 -0
- package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +34 -0
- package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.d.ts +65 -1
- package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +836 -35
- package/dist/router/virtual-router/engine.js +3 -2
- package/dist/router/virtual-router/routing-instructions/parse.js +30 -3
- package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +28 -3
- package/dist/sse/types/anthropic-types.d.ts +3 -1
- package/dist/tools/apply-patch/args-normalizer/extract-patch.js +2 -2
- package/dist/tools/apply-patch/patch-text/looks-like-patch.js +3 -6
- package/dist/tools/apply-patch/patch-text/normalize.js +14 -3
- package/dist/tools/tool-registry.js +12 -0
- package/package.json +6 -1
|
@@ -1,386 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// - Normalizes arguments (single JSON string), sets finish_reason when applicable
|
|
6
|
-
import { isStructuredApplyPatchPayload } from '../../tools/apply-patch-structured.js';
|
|
7
|
-
import { repairArgumentsToStringWithNative } from '../../router/virtual-router/engine-selection/native-shared-conversion-semantics.js';
|
|
8
|
-
function isObject(v) {
|
|
9
|
-
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
10
|
-
}
|
|
11
|
-
// Adjacent dedupe state (per request)
|
|
12
|
-
const dedupeState = new Map();
|
|
13
|
-
function hashString(s) {
|
|
14
|
-
try {
|
|
15
|
-
return require('crypto').createHash('sha1').update(s).digest('hex');
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
return String(s.length);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
function toJsonString(v) {
|
|
22
|
-
return repairArgumentsToStringWithNative(v);
|
|
23
|
-
}
|
|
24
|
-
function genId(ctx, i = 0) {
|
|
25
|
-
const p = ctx?.idPrefix || 'call';
|
|
26
|
-
return `${p}_${Date.now()}_${(Math.random().toString(36).slice(2, 8))}_${i}`;
|
|
27
|
-
}
|
|
28
|
-
function chunkString(input, size) {
|
|
29
|
-
try {
|
|
30
|
-
const s = String(input ?? '');
|
|
31
|
-
if (!s)
|
|
32
|
-
return [];
|
|
33
|
-
const out = [];
|
|
34
|
-
for (let i = 0; i < s.length; i += size)
|
|
35
|
-
out.push(s.slice(i, i + size));
|
|
36
|
-
return out;
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
return [String(input ?? '')];
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
function extractFromTextual(content, ctx) {
|
|
43
|
-
const events = [];
|
|
44
|
-
if (typeof content !== 'string' || !content)
|
|
45
|
-
return events;
|
|
46
|
-
// 1) structured apply_patch payload
|
|
47
|
-
const structuredPayloads = extractStructuredApplyPatchPayloads(content);
|
|
48
|
-
if (structuredPayloads.length) {
|
|
49
|
-
let idx = 0;
|
|
50
|
-
for (const payload of structuredPayloads) {
|
|
51
|
-
const id = genId(ctx, idx);
|
|
52
|
-
const argStr = toJsonString(payload);
|
|
53
|
-
events.push({ tool_calls: [{ index: idx, id, type: 'function', function: { name: 'apply_patch' } }] });
|
|
54
|
-
const parts = chunkString(argStr, Math.max(32, Math.min(1024, ctx?.chunkSize || 256)));
|
|
55
|
-
for (const d of parts) {
|
|
56
|
-
events.push({ tool_calls: [{ index: idx, id, type: 'function', function: { arguments: d } }] });
|
|
57
|
-
}
|
|
58
|
-
idx += 1;
|
|
59
|
-
}
|
|
60
|
-
return events;
|
|
61
|
-
}
|
|
62
|
-
// 2) <function=execute>
|
|
63
|
-
const execRe = /<function=execute>[\s\S]*?<parameter=command>([\s\S]*?)<\/parameter>[\s\S]*?<\/function=execute>/i;
|
|
64
|
-
const mExec = execRe.exec(content);
|
|
65
|
-
if (mExec && mExec[1]) {
|
|
66
|
-
const cmdRaw = mExec[1].trim();
|
|
67
|
-
const argv = splitCommand(cmdRaw);
|
|
68
|
-
const id = genId(ctx, 0);
|
|
69
|
-
const argStr = toJsonString({ command: argv });
|
|
70
|
-
events.push({ tool_calls: [{ index: 0, id, type: 'function', function: { name: 'shell' } }] });
|
|
71
|
-
const parts = chunkString(argStr, Math.max(32, Math.min(1024, ctx?.chunkSize || 256)));
|
|
72
|
-
for (const d of parts) {
|
|
73
|
-
events.push({ tool_calls: [{ index: 0, id, type: 'function', function: { arguments: d } }] });
|
|
74
|
-
}
|
|
75
|
-
return events;
|
|
76
|
-
}
|
|
77
|
-
// 3) <invoke name="tool"> wrapper with <parameter name="key">value</parameter>
|
|
78
|
-
try {
|
|
79
|
-
const invokeRe = /<invoke\s+name="([^">]+)"[^>]*>([\s\S]*?)<\/invoke>/gi;
|
|
80
|
-
let mInvoke;
|
|
81
|
-
const invokeEvents = [];
|
|
82
|
-
let idx = 0;
|
|
83
|
-
while ((mInvoke = invokeRe.exec(content)) !== null) {
|
|
84
|
-
const toolName = (mInvoke[1] || '').trim();
|
|
85
|
-
const inner = mInvoke[2] || '';
|
|
86
|
-
if (!toolName || !inner)
|
|
87
|
-
continue;
|
|
88
|
-
const paramRe = /<parameter\s+name="([^">]+)"[^>]*>([\s\S]*?)<\/parameter>/gi;
|
|
89
|
-
const argsObj = {};
|
|
90
|
-
let mParam;
|
|
91
|
-
while ((mParam = paramRe.exec(inner)) !== null) {
|
|
92
|
-
const key = String((mParam[1] || '').trim());
|
|
93
|
-
const raw = (mParam[2] || '').trim();
|
|
94
|
-
if (!key)
|
|
95
|
-
continue;
|
|
96
|
-
let value = raw;
|
|
97
|
-
try {
|
|
98
|
-
value = JSON.parse(raw);
|
|
99
|
-
}
|
|
100
|
-
catch { /* keep string */ }
|
|
101
|
-
argsObj[key] = value;
|
|
102
|
-
}
|
|
103
|
-
const id = genId(ctx, idx);
|
|
104
|
-
invokeEvents.push({ tool_calls: [{ index: idx, id, type: 'function', function: { name: toolName } }] });
|
|
105
|
-
const argStr = toJsonString(argsObj);
|
|
106
|
-
const parts = chunkString(argStr, Math.max(32, Math.min(1024, ctx?.chunkSize || 256)));
|
|
107
|
-
for (const d of parts) {
|
|
108
|
-
invokeEvents.push({ tool_calls: [{ index: idx, id, type: 'function', function: { arguments: d } }] });
|
|
109
|
-
}
|
|
110
|
-
idx += 1;
|
|
111
|
-
}
|
|
112
|
-
if (invokeEvents.length) {
|
|
113
|
-
return invokeEvents;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
catch { /* ignore textual invoke parse errors */ }
|
|
117
|
-
// 4) rcc.tool.v1 JSON envelope (removed)
|
|
118
|
-
// 5) Generic <tool_call> textual block with <arg_key>/<arg_value>
|
|
119
|
-
try {
|
|
120
|
-
// Explicit wrapper form
|
|
121
|
-
const blockRe = /<tool_call[^>]*>[\s\S]*?<\/tool_call>/gi;
|
|
122
|
-
const blocks = content.match(blockRe) || [];
|
|
123
|
-
let matched = false;
|
|
124
|
-
for (const b of blocks) {
|
|
125
|
-
const nameTag = /<tool_name>([\s\S]*?)<\/tool_name>/i.exec(b);
|
|
126
|
-
let nm = nameTag && nameTag[1] ? String(nameTag[1]).trim() : '';
|
|
127
|
-
if (!nm) {
|
|
128
|
-
if (/<arg_key>\s*command\s*<\/arg_key>/i.test(b))
|
|
129
|
-
nm = 'shell';
|
|
130
|
-
else if (/\bapply_patch\b/i.test(b))
|
|
131
|
-
nm = 'apply_patch';
|
|
132
|
-
else if (/\bupdate_plan\b/i.test(b))
|
|
133
|
-
nm = 'update_plan';
|
|
134
|
-
else if (/\bview_image\b/i.test(b))
|
|
135
|
-
nm = 'view_image';
|
|
136
|
-
}
|
|
137
|
-
const pairRe = /<arg_key>([\s\S]*?)<\/arg_key>\s*<arg_value>([\s\S]*?)<\/arg_value>/gi;
|
|
138
|
-
const argsObj = {};
|
|
139
|
-
let m;
|
|
140
|
-
while ((m = pairRe.exec(b)) !== null) {
|
|
141
|
-
const k = String((m[1] || '').trim());
|
|
142
|
-
const vRaw = (m[2] || '').trim();
|
|
143
|
-
let v = vRaw;
|
|
144
|
-
try {
|
|
145
|
-
v = JSON.parse(vRaw);
|
|
146
|
-
}
|
|
147
|
-
catch { /* keep as string */ }
|
|
148
|
-
if (nm === 'shell' && k === 'command') {
|
|
149
|
-
if (!Array.isArray(v))
|
|
150
|
-
v = splitCommand(String(vRaw));
|
|
151
|
-
}
|
|
152
|
-
argsObj[k] = v;
|
|
153
|
-
}
|
|
154
|
-
if (nm) {
|
|
155
|
-
const id = genId(ctx, 0);
|
|
156
|
-
events.push({ tool_calls: [{ index: 0, id, type: 'function', function: { name: nm } }] });
|
|
157
|
-
const argStr = toJsonString(argsObj);
|
|
158
|
-
const parts = chunkString(argStr, Math.max(32, Math.min(1024, ctx?.chunkSize || 256)));
|
|
159
|
-
for (const d of parts) {
|
|
160
|
-
events.push({ tool_calls: [{ index: 0, id, type: 'function', function: { arguments: d } }] });
|
|
161
|
-
}
|
|
162
|
-
matched = true;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
if (matched)
|
|
166
|
-
return events;
|
|
167
|
-
// Inline fallback: e.g., 'shell\n<arg_key>command</arg_key>...'
|
|
168
|
-
const inlineRe = /(shell|apply_patch|update_plan|view_image)[\s\S]*?<arg_key>([\s\S]*?)<\/arg_key>\s*<arg_value>([\s\S]*?)<\/arg_value>/i;
|
|
169
|
-
const mi = inlineRe.exec(content);
|
|
170
|
-
if (mi) {
|
|
171
|
-
const nm = String(mi[1]).trim();
|
|
172
|
-
const k = String(mi[2]).trim();
|
|
173
|
-
const vRaw = String(mi[3]).trim();
|
|
174
|
-
let v = vRaw;
|
|
175
|
-
try {
|
|
176
|
-
v = JSON.parse(vRaw);
|
|
177
|
-
}
|
|
178
|
-
catch { /* keep as string */ }
|
|
179
|
-
const args = {};
|
|
180
|
-
if (nm === 'shell' && k === 'command') {
|
|
181
|
-
if (!Array.isArray(v))
|
|
182
|
-
v = splitCommand(String(vRaw));
|
|
183
|
-
}
|
|
184
|
-
args[k] = v;
|
|
185
|
-
const id = genId(ctx, 0);
|
|
186
|
-
events.push({ tool_calls: [{ index: 0, id, type: 'function', function: { name: nm } }] });
|
|
187
|
-
const argStr = toJsonString(args);
|
|
188
|
-
for (const d of chunkString(argStr, Math.max(32, Math.min(1024, ctx?.chunkSize || 256)))) {
|
|
189
|
-
events.push({ tool_calls: [{ index: 0, id, type: 'function', function: { arguments: d } }] });
|
|
190
|
-
}
|
|
191
|
-
return events;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
catch { /* ignore textual tool_call parse errors */ }
|
|
195
|
-
return events;
|
|
196
|
-
}
|
|
197
|
-
function extractStructuredApplyPatchPayloads(text) {
|
|
198
|
-
const payloads = [];
|
|
199
|
-
try {
|
|
200
|
-
const fenceRe = /```(?:json|apply_patch|toon)?\s*([\s\S]*?)\s*```/gi;
|
|
201
|
-
let fm;
|
|
202
|
-
while ((fm = fenceRe.exec(text)) !== null) {
|
|
203
|
-
const body = fm[1] || '';
|
|
204
|
-
try {
|
|
205
|
-
const parsed = JSON.parse(body);
|
|
206
|
-
if (isStructuredApplyPatchPayload(parsed)) {
|
|
207
|
-
payloads.push(parsed);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
catch { /* ignore invalid JSON */ }
|
|
211
|
-
}
|
|
212
|
-
if (!payloads.length && typeof text === 'string' && text.includes('"changes"')) {
|
|
213
|
-
try {
|
|
214
|
-
const parsed = JSON.parse(text);
|
|
215
|
-
if (isStructuredApplyPatchPayload(parsed)) {
|
|
216
|
-
payloads.push(parsed);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
catch { /* ignore */ }
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
catch { /* ignore */ }
|
|
223
|
-
return payloads;
|
|
224
|
-
}
|
|
225
|
-
function splitCommand(s) {
|
|
226
|
-
try {
|
|
227
|
-
const out = [];
|
|
228
|
-
let cur = '';
|
|
229
|
-
let quote = null;
|
|
230
|
-
for (let i = 0; i < s.length; i++) {
|
|
231
|
-
const ch = s[i];
|
|
232
|
-
if (quote) {
|
|
233
|
-
if (ch === quote) {
|
|
234
|
-
quote = null;
|
|
235
|
-
}
|
|
236
|
-
else {
|
|
237
|
-
cur += ch;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
else {
|
|
241
|
-
if (ch === '"' || ch === '\'') {
|
|
242
|
-
quote = ch;
|
|
243
|
-
}
|
|
244
|
-
else if (/\s/.test(ch)) {
|
|
245
|
-
if (cur) {
|
|
246
|
-
out.push(cur);
|
|
247
|
-
cur = '';
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
else {
|
|
251
|
-
cur += ch;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
if (cur)
|
|
256
|
-
out.push(cur);
|
|
257
|
-
return out;
|
|
258
|
-
}
|
|
259
|
-
catch {
|
|
260
|
-
return [s];
|
|
1
|
+
import { harvestToolsWithNative } from '../../router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js';
|
|
2
|
+
function assertToolHarvesterNativeAvailable() {
|
|
3
|
+
if (typeof harvestToolsWithNative !== 'function') {
|
|
4
|
+
throw new Error('[tool-harvester] native bindings are required');
|
|
261
5
|
}
|
|
262
6
|
}
|
|
263
7
|
export function harvestTools(signal, ctx) {
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const argStr = toJsonString(fc.arguments);
|
|
280
|
-
const id = genId(ctx, 0);
|
|
281
|
-
if (name) {
|
|
282
|
-
events.push({ tool_calls: [{ index: 0, id, type: 'function', function: { name } }] });
|
|
283
|
-
for (const d of chunkString(argStr, chunkSize)) {
|
|
284
|
-
events.push({ tool_calls: [{ index: 0, id, type: 'function', function: { arguments: d } }] });
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
// direct delta.tool_calls
|
|
289
|
-
const tcd = delta?.choices?.[0]?.delta?.tool_calls;
|
|
290
|
-
if (Array.isArray(tcd)) {
|
|
291
|
-
let idx = 0;
|
|
292
|
-
for (const tc of tcd) {
|
|
293
|
-
const id = typeof tc?.id === 'string' ? tc.id : genId(ctx, idx);
|
|
294
|
-
const name = typeof tc?.function?.name === 'string' ? tc.function.name : undefined;
|
|
295
|
-
const args = (tc?.function?.arguments !== undefined) ? toJsonString(tc.function.arguments) : undefined;
|
|
296
|
-
// Adjacent dedupe per requestId
|
|
297
|
-
const key = ctx?.requestId || 'default';
|
|
298
|
-
const state = dedupeState.get(key) || {};
|
|
299
|
-
const argsHash = typeof args === 'string' ? hashString(args) : undefined;
|
|
300
|
-
const isDup = (state.name === name && state.argsHash && argsHash && state.argsHash === argsHash);
|
|
301
|
-
if (!isDup) {
|
|
302
|
-
if (name) {
|
|
303
|
-
events.push({ tool_calls: [{ index: idx, id, type: 'function', function: { name } }] });
|
|
304
|
-
}
|
|
305
|
-
if (typeof args === 'string') {
|
|
306
|
-
for (const d of chunkString(args, chunkSize)) {
|
|
307
|
-
events.push({ tool_calls: [{ index: idx, id, type: 'function', function: { arguments: d } }] });
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
dedupeState.set(key, { name, argsHash });
|
|
311
|
-
}
|
|
312
|
-
idx += 1;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
result.deltaEvents = events;
|
|
316
|
-
return result;
|
|
317
|
-
}
|
|
318
|
-
// final/compat: normalize choices[0].message.tool_calls (no synthesis)
|
|
319
|
-
const src = signal.payload;
|
|
320
|
-
const choice = Array.isArray(src?.choices) ? src.choices[0] : null;
|
|
321
|
-
const msg = choice?.message || {};
|
|
322
|
-
const toolCalls = Array.isArray(msg?.tool_calls) ? msg.tool_calls : [];
|
|
323
|
-
if (toolCalls.length > 0) {
|
|
324
|
-
// Ensure arguments are stringified
|
|
325
|
-
for (const tc of toolCalls) {
|
|
326
|
-
const fn = tc.function || {};
|
|
327
|
-
if (fn && fn.arguments && typeof fn.arguments !== 'string') {
|
|
328
|
-
fn.arguments = toJsonString(fn.arguments);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
// finish_reason
|
|
332
|
-
if (choice && choice.finish_reason !== 'tool_calls') {
|
|
333
|
-
choice.finish_reason = 'tool_calls';
|
|
334
|
-
}
|
|
335
|
-
// content policy: set to empty string or null unchanged
|
|
336
|
-
result.normalized = src;
|
|
337
|
-
return result;
|
|
338
|
-
}
|
|
339
|
-
// If no structured tool_calls, attempt textual harvesting from message.content
|
|
340
|
-
try {
|
|
341
|
-
const content = msg?.content;
|
|
342
|
-
if (typeof content === 'string' && content.trim()) {
|
|
343
|
-
const evs = extractFromTextual(content, ctx);
|
|
344
|
-
if (Array.isArray(evs) && evs.length > 0) {
|
|
345
|
-
// Aggregate deltaEvents into final tool_calls (group by id)
|
|
346
|
-
const map = {};
|
|
347
|
-
for (const e of evs) {
|
|
348
|
-
const tcs = Array.isArray(e.tool_calls) ? e.tool_calls : [];
|
|
349
|
-
for (const t of tcs) {
|
|
350
|
-
const id = String(t.id || genId(ctx, 0));
|
|
351
|
-
if (!map[id])
|
|
352
|
-
map[id] = { name: undefined, args: [], type: 'function' };
|
|
353
|
-
const nm = t.function?.name;
|
|
354
|
-
const arg = t.function?.arguments;
|
|
355
|
-
if (typeof nm === 'string' && !map[id].name)
|
|
356
|
-
map[id].name = nm;
|
|
357
|
-
if (typeof arg === 'string')
|
|
358
|
-
map[id].args.push(arg);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
const merged = [];
|
|
362
|
-
for (const [id, item] of Object.entries(map)) {
|
|
363
|
-
const argStr = item.args.join('');
|
|
364
|
-
merged.push({ id, type: 'function', function: { name: item.name, arguments: argStr } });
|
|
365
|
-
}
|
|
366
|
-
if (merged.length > 0) {
|
|
367
|
-
msg.tool_calls = merged;
|
|
368
|
-
// Clear textual content to avoid duplicate display
|
|
369
|
-
msg.content = '';
|
|
370
|
-
if (choice && choice.finish_reason !== 'tool_calls') {
|
|
371
|
-
choice.finish_reason = 'tool_calls';
|
|
372
|
-
}
|
|
373
|
-
result.normalized = src;
|
|
374
|
-
return result;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
catch { /* ignore textual harvest on final */ }
|
|
380
|
-
result.normalized = src;
|
|
381
|
-
return result;
|
|
382
|
-
}
|
|
383
|
-
catch {
|
|
384
|
-
return result;
|
|
385
|
-
}
|
|
8
|
+
assertToolHarvesterNativeAvailable();
|
|
9
|
+
const result = harvestToolsWithNative({
|
|
10
|
+
signal: signal,
|
|
11
|
+
context: ctx
|
|
12
|
+
});
|
|
13
|
+
const normalized = result?.normalized;
|
|
14
|
+
if (normalized && signal?.payload && typeof signal.payload === 'object') {
|
|
15
|
+
const target = signal.payload;
|
|
16
|
+
const next = normalized;
|
|
17
|
+
for (const key of Object.keys(target)) {
|
|
18
|
+
delete target[key];
|
|
19
|
+
}
|
|
20
|
+
Object.assign(target, next);
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
386
23
|
}
|
|
@@ -9,11 +9,10 @@ export interface ToolCallItem {
|
|
|
9
9
|
type: 'function';
|
|
10
10
|
function: ToolCallFunction;
|
|
11
11
|
}
|
|
12
|
-
export declare function stringifyArgs(args: unknown): string;
|
|
13
12
|
export interface BridgeToolMapOptions {
|
|
14
|
-
sanitizeName?: (
|
|
13
|
+
sanitizeName?: (name: string) => string | undefined;
|
|
15
14
|
}
|
|
16
|
-
export declare function
|
|
15
|
+
export declare function stringifyArgs(args: unknown): string;
|
|
17
16
|
export declare function bridgeToolToChatDefinition(rawTool: BridgeToolDefinition | Record<string, unknown> | null | undefined, options?: BridgeToolMapOptions): ChatToolDefinition | null;
|
|
18
17
|
export declare function mapBridgeToolsToChat(rawTools: unknown, options?: BridgeToolMapOptions): ChatToolDefinition[] | undefined;
|
|
19
18
|
export declare function chatToolToBridgeDefinition(rawTool: ChatToolDefinition | Record<string, unknown> | null | undefined, options?: BridgeToolMapOptions): BridgeToolDefinition | null;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { mapChatToolsToBridgeWithNative } from '../../router/virtual-router/engine-selection/native-shared-conversion-semantics.js';
|
|
1
|
+
import { bridgeToolToChatDefinitionWithNative, chatToolToBridgeDefinitionWithNative, mapBridgeToolsToChatWithNative, mapChatToolsToBridgeWithNative } from '../../router/virtual-router/engine-selection/native-shared-conversion-semantics.js';
|
|
3
2
|
export function stringifyArgs(args) {
|
|
4
3
|
if (typeof args === 'string')
|
|
5
4
|
return args;
|
|
@@ -10,212 +9,57 @@ export function stringifyArgs(args) {
|
|
|
10
9
|
return String(args);
|
|
11
10
|
}
|
|
12
11
|
}
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
function
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
return { ...value };
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
function asSchema(value) {
|
|
25
|
-
if (isPlainObject(value)) {
|
|
26
|
-
return clonePlainObject(value);
|
|
27
|
-
}
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
function isTruthyEnv(value) {
|
|
31
|
-
const v = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
32
|
-
return v === '1' || v === 'true' || v === 'yes' || v === 'on';
|
|
33
|
-
}
|
|
34
|
-
export function ensureApplyPatchSchema(seed) {
|
|
35
|
-
const schema = seed ? { ...seed } : {};
|
|
36
|
-
schema.type = typeof schema.type === 'string' ? schema.type : 'object';
|
|
37
|
-
const properties = isPlainObject(schema.properties) ? { ...schema.properties } : {};
|
|
38
|
-
properties.patch = {
|
|
39
|
-
type: 'string',
|
|
40
|
-
description: 'Patch text (*** Begin Patch / *** End Patch or GNU unified diff).'
|
|
41
|
-
};
|
|
42
|
-
properties.input = { type: 'string', description: 'Alias of patch (patch text). Prefer patch.' };
|
|
43
|
-
schema.properties = properties;
|
|
44
|
-
const requiredList = Array.isArray(schema.required)
|
|
45
|
-
? schema.required.filter((entry) => typeof entry === 'string')
|
|
46
|
-
: [];
|
|
47
|
-
if (!requiredList.includes('patch')) {
|
|
48
|
-
requiredList.push('patch');
|
|
49
|
-
}
|
|
50
|
-
schema.required = requiredList;
|
|
51
|
-
if (typeof schema.additionalProperties !== 'boolean') {
|
|
52
|
-
schema.additionalProperties = false;
|
|
53
|
-
}
|
|
54
|
-
return schema;
|
|
55
|
-
}
|
|
56
|
-
function enforceBuiltinToolSchema(name, candidate) {
|
|
57
|
-
const normalizedName = typeof name === 'string' ? name.trim().toLowerCase() : '';
|
|
58
|
-
if (normalizedName === 'apply_patch') {
|
|
59
|
-
const base = asSchema(candidate);
|
|
60
|
-
return ensureApplyPatchSchema(base);
|
|
61
|
-
}
|
|
62
|
-
if (normalizedName === 'web_search') {
|
|
63
|
-
// For web_search we currently accept any incoming schema and fall back to a
|
|
64
|
-
// minimal object definition. Server-side web_search execution only relies
|
|
65
|
-
// on the function name + JSON arguments, so tool schema is best-effort.
|
|
66
|
-
const base = asSchema(candidate) ?? {};
|
|
67
|
-
if (!base.type) {
|
|
68
|
-
base.type = 'object';
|
|
69
|
-
}
|
|
70
|
-
if (!Object.prototype.hasOwnProperty.call(base, 'properties')) {
|
|
71
|
-
base.properties = {};
|
|
72
|
-
}
|
|
73
|
-
return base;
|
|
74
|
-
}
|
|
75
|
-
return asSchema(candidate);
|
|
76
|
-
}
|
|
77
|
-
const DEFAULT_SANITIZER = (value) => {
|
|
78
|
-
return sanitizeResponsesFunctionNameWithNative(value);
|
|
79
|
-
};
|
|
80
|
-
function resolveToolName(candidate, options) {
|
|
81
|
-
const sanitized = options?.sanitizeName?.(candidate);
|
|
82
|
-
if (typeof sanitized === 'string' && sanitized.trim().length) {
|
|
83
|
-
return sanitized.trim();
|
|
84
|
-
}
|
|
85
|
-
return DEFAULT_SANITIZER(candidate);
|
|
86
|
-
}
|
|
87
|
-
function pickToolName(candidates, options) {
|
|
88
|
-
for (const candidate of candidates) {
|
|
89
|
-
const name = resolveToolName(candidate, options);
|
|
90
|
-
if (name) {
|
|
91
|
-
return name;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return undefined;
|
|
95
|
-
}
|
|
96
|
-
function resolveToolDescription(candidate) {
|
|
97
|
-
if (typeof candidate === 'string') {
|
|
98
|
-
return candidate;
|
|
12
|
+
function assertToolMappingNativeAvailable() {
|
|
13
|
+
if (typeof bridgeToolToChatDefinitionWithNative !== 'function' ||
|
|
14
|
+
typeof chatToolToBridgeDefinitionWithNative !== 'function' ||
|
|
15
|
+
typeof mapBridgeToolsToChatWithNative !== 'function' ||
|
|
16
|
+
typeof mapChatToolsToBridgeWithNative !== 'function') {
|
|
17
|
+
throw new Error('[tool-mapping] native bindings unavailable');
|
|
99
18
|
}
|
|
100
|
-
return undefined;
|
|
101
19
|
}
|
|
102
|
-
function
|
|
103
|
-
|
|
104
|
-
|
|
20
|
+
function resolveSanitizeMode(options, fallback = 'responses') {
|
|
21
|
+
const probe = options?.sanitizeName?.('shell_command') ?? options?.sanitizeName?.('Bash');
|
|
22
|
+
if (typeof probe === 'string' && probe === 'shell_command') {
|
|
23
|
+
return 'anthropic';
|
|
105
24
|
}
|
|
106
|
-
if (
|
|
107
|
-
return
|
|
25
|
+
if (typeof probe === 'string' && probe === 'Bash') {
|
|
26
|
+
return 'anthropic_denormalize';
|
|
108
27
|
}
|
|
109
|
-
return
|
|
110
|
-
}
|
|
111
|
-
function resolveToolStrict(container, fallback) {
|
|
112
|
-
if (container && typeof container.strict === 'boolean') {
|
|
113
|
-
return container.strict;
|
|
114
|
-
}
|
|
115
|
-
if (fallback && typeof fallback.strict === 'boolean') {
|
|
116
|
-
return fallback.strict;
|
|
117
|
-
}
|
|
118
|
-
return undefined;
|
|
28
|
+
return fallback;
|
|
119
29
|
}
|
|
120
30
|
export function bridgeToolToChatDefinition(rawTool, options) {
|
|
121
31
|
if (!rawTool || typeof rawTool !== 'object') {
|
|
122
32
|
return null;
|
|
123
33
|
}
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
// Codex / Claude‑code may send tools shaped as `{ type: "web_search", ... }`
|
|
129
|
-
// without a nested `function` node. Treat these as a canonical `web_search`
|
|
130
|
-
// function tool so downstream Chat / Standardized layers can reason over a
|
|
131
|
-
// single function-style web_search surface.
|
|
132
|
-
if (!name) {
|
|
133
|
-
const rawType = typeof tool.type === 'string' ? tool.type.trim().toLowerCase() : '';
|
|
134
|
-
if (rawType === 'web_search' || rawType.startsWith('web_search')) {
|
|
135
|
-
name = 'web_search';
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (!name)
|
|
139
|
-
return null;
|
|
140
|
-
const description = resolveToolDescription(fnNode?.description ?? tool.description);
|
|
141
|
-
const parameters = enforceBuiltinToolSchema(name, resolveToolParameters(fnNode, tool));
|
|
142
|
-
const strict = resolveToolStrict(fnNode, tool);
|
|
143
|
-
const rawType = typeof tool.type === 'string' && tool.type.trim().length ? tool.type.trim() : 'function';
|
|
144
|
-
const normalizedType = rawType.toLowerCase() === 'custom' ? 'function' : rawType;
|
|
145
|
-
const fnOut = { name };
|
|
146
|
-
if (description !== undefined) {
|
|
147
|
-
fnOut.description = description;
|
|
148
|
-
}
|
|
149
|
-
if (parameters !== undefined) {
|
|
150
|
-
fnOut.parameters = parameters;
|
|
151
|
-
}
|
|
152
|
-
if (strict !== undefined) {
|
|
153
|
-
fnOut.strict = strict;
|
|
154
|
-
}
|
|
155
|
-
return {
|
|
156
|
-
type: normalizedType,
|
|
157
|
-
function: fnOut
|
|
158
|
-
};
|
|
34
|
+
assertToolMappingNativeAvailable();
|
|
35
|
+
const sanitizeMode = resolveSanitizeMode(options, 'responses');
|
|
36
|
+
const mapped = bridgeToolToChatDefinitionWithNative(rawTool, { sanitizeMode });
|
|
37
|
+
return mapped ?? null;
|
|
159
38
|
}
|
|
160
39
|
export function mapBridgeToolsToChat(rawTools, options) {
|
|
161
40
|
if (!Array.isArray(rawTools) || rawTools.length === 0) {
|
|
162
41
|
return undefined;
|
|
163
42
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
43
|
+
assertToolMappingNativeAvailable();
|
|
44
|
+
const sanitizeMode = resolveSanitizeMode(options, 'responses');
|
|
45
|
+
const mapped = mapBridgeToolsToChatWithNative(rawTools, { sanitizeMode });
|
|
167
46
|
return mapped.length ? mapped : undefined;
|
|
168
47
|
}
|
|
169
48
|
export function chatToolToBridgeDefinition(rawTool, options) {
|
|
170
49
|
if (!rawTool || typeof rawTool !== 'object') {
|
|
171
50
|
return null;
|
|
172
51
|
}
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
return null;
|
|
178
|
-
}
|
|
179
|
-
const description = resolveToolDescription(fnNode?.description);
|
|
180
|
-
const parameters = enforceBuiltinToolSchema(name, resolveToolParameters(fnNode, undefined));
|
|
181
|
-
const strict = resolveToolStrict(fnNode, undefined);
|
|
182
|
-
const normalizedType = typeof tool.type === 'string' && tool.type.trim().length ? tool.type.trim() : 'function';
|
|
183
|
-
const responseShape = {
|
|
184
|
-
type: normalizedType,
|
|
185
|
-
name
|
|
186
|
-
};
|
|
187
|
-
if (description !== undefined) {
|
|
188
|
-
responseShape.description = description;
|
|
189
|
-
}
|
|
190
|
-
if (parameters !== undefined) {
|
|
191
|
-
responseShape.parameters = parameters;
|
|
192
|
-
}
|
|
193
|
-
if (strict !== undefined) {
|
|
194
|
-
responseShape.strict = strict;
|
|
195
|
-
}
|
|
196
|
-
const fnOut = { name };
|
|
197
|
-
if (description !== undefined) {
|
|
198
|
-
fnOut.description = description;
|
|
199
|
-
}
|
|
200
|
-
if (parameters !== undefined) {
|
|
201
|
-
fnOut.parameters = parameters;
|
|
202
|
-
}
|
|
203
|
-
if (strict !== undefined) {
|
|
204
|
-
fnOut.strict = strict;
|
|
205
|
-
}
|
|
206
|
-
responseShape.function = fnOut;
|
|
207
|
-
return responseShape;
|
|
52
|
+
assertToolMappingNativeAvailable();
|
|
53
|
+
const sanitizeMode = resolveSanitizeMode(options, 'responses');
|
|
54
|
+
const mapped = chatToolToBridgeDefinitionWithNative(rawTool, { sanitizeMode });
|
|
55
|
+
return mapped ?? null;
|
|
208
56
|
}
|
|
209
57
|
export function mapChatToolsToBridge(rawTools, options) {
|
|
210
58
|
if (!Array.isArray(rawTools) || rawTools.length === 0) {
|
|
211
59
|
return undefined;
|
|
212
60
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
const mapped = rawTools
|
|
218
|
-
.map((entry) => chatToolToBridgeDefinition(entry, options))
|
|
219
|
-
.filter((entry) => !!entry);
|
|
61
|
+
assertToolMappingNativeAvailable();
|
|
62
|
+
const sanitizeMode = resolveSanitizeMode(options, 'responses');
|
|
63
|
+
const mapped = mapChatToolsToBridgeWithNative(rawTools, { sanitizeMode });
|
|
220
64
|
return mapped.length ? mapped : undefined;
|
|
221
65
|
}
|