@jsonstudio/llms 0.6.3271 → 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.
Files changed (49) hide show
  1. package/dist/conversion/bridge-message-utils.d.ts +4 -4
  2. package/dist/conversion/bridge-message-utils.js +28 -538
  3. package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.js +38 -0
  4. package/dist/conversion/compat/profiles/responses-crs.json +15 -0
  5. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +16 -5
  6. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +1 -6
  7. package/dist/conversion/hub/response/response-runtime.js +14 -6
  8. package/dist/conversion/responses/responses-openai-bridge/response-payload.js +11 -11
  9. package/dist/conversion/shared/anthropic-message-utils.js +2 -12
  10. package/dist/conversion/shared/chat-request-filters.js +2 -61
  11. package/dist/conversion/shared/reasoning-mapping.js +3 -0
  12. package/dist/conversion/shared/reasoning-normalizer.d.ts +1 -0
  13. package/dist/conversion/shared/reasoning-normalizer.js +35 -388
  14. package/dist/conversion/shared/reasoning-tool-normalizer.js +8 -15
  15. package/dist/conversion/shared/reasoning-utils.js +13 -35
  16. package/dist/conversion/shared/responses-tool-utils.d.ts +1 -1
  17. package/dist/conversion/shared/responses-tool-utils.js +63 -65
  18. package/dist/conversion/shared/streaming-text-extractor.d.ts +0 -5
  19. package/dist/conversion/shared/streaming-text-extractor.js +18 -111
  20. package/dist/conversion/shared/text-markup-normalizer/normalize.d.ts +1 -1
  21. package/dist/conversion/shared/text-markup-normalizer/normalize.js +3 -91
  22. package/dist/conversion/shared/thought-signature-validator.js +19 -133
  23. package/dist/conversion/shared/tool-argument-repairer.js +16 -19
  24. package/dist/conversion/shared/tool-call-id-manager.d.ts +1 -5
  25. package/dist/conversion/shared/tool-call-id-manager.js +74 -98
  26. package/dist/conversion/shared/tool-harvester.js +19 -382
  27. package/dist/conversion/shared/tool-mapping.d.ts +2 -3
  28. package/dist/conversion/shared/tool-mapping.js +28 -184
  29. package/dist/conversion/shared/tooling.js +20 -151
  30. package/dist/filters/special/response-tool-arguments-stringify.js +9 -1
  31. package/dist/guidance/index.js +2 -2
  32. package/dist/native/router_hotpath_napi.node +0 -0
  33. package/dist/router/virtual-router/engine-legacy/helpers.js +1 -1
  34. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.d.ts +39 -0
  35. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js +196 -0
  36. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.d.ts +1 -0
  37. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.js +27 -0
  38. package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +34 -0
  39. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.d.ts +65 -1
  40. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +836 -35
  41. package/dist/router/virtual-router/engine.js +3 -2
  42. package/dist/router/virtual-router/routing-instructions/parse.js +30 -3
  43. package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +28 -3
  44. package/dist/sse/types/anthropic-types.d.ts +3 -1
  45. package/dist/tools/apply-patch/args-normalizer/extract-patch.js +2 -2
  46. package/dist/tools/apply-patch/patch-text/looks-like-patch.js +3 -6
  47. package/dist/tools/apply-patch/patch-text/normalize.js +14 -3
  48. package/dist/tools/tool-registry.js +12 -0
  49. package/package.json +6 -1
@@ -1,386 +1,23 @@
1
- // Unified tool harvesting (对齐, single entry)
2
- // - First-time harvesting only (no late-stage repair)
3
- // - Handles textual markers (<function=execute>, structured apply_patch payloads)
4
- // - Handles structural shapes (function_call legacy, tool_calls)
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
- const result = { deltaEvents: [] };
265
- const chunkSize = Math.max(32, Math.min(1024, ctx?.chunkSize || 256));
266
- try {
267
- if (signal.type === 'delta') {
268
- // OpenAI streaming chunk delta
269
- const delta = signal.payload;
270
- const events = [];
271
- const content = delta?.choices?.[0]?.delta?.content;
272
- if (typeof content === 'string' && content.trim()) {
273
- events.push(...extractFromTextual(content, ctx));
274
- }
275
- // legacy function_call → tool_calls
276
- const fc = delta?.choices?.[0]?.delta?.function_call;
277
- if (isObject(fc)) {
278
- const name = typeof fc.name === 'string' ? fc.name : undefined;
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?: (raw: unknown) => string | undefined;
13
+ sanitizeName?: (name: string) => string | undefined;
15
14
  }
16
- export declare function ensureApplyPatchSchema(seed?: Record<string, unknown>): Record<string, unknown>;
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 { sanitizeResponsesFunctionNameWithNative } from '../../router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js';
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 isPlainObject(value) {
14
- return !!value && typeof value === 'object' && !Array.isArray(value);
15
- }
16
- function clonePlainObject(value) {
17
- try {
18
- return JSON.parse(JSON.stringify(value));
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 resolveToolParameters(container, fallback) {
103
- if (container && Object.prototype.hasOwnProperty.call(container, 'parameters')) {
104
- return container.parameters;
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 (fallback && Object.prototype.hasOwnProperty.call(fallback, 'parameters')) {
107
- return fallback.parameters;
25
+ if (typeof probe === 'string' && probe === 'Bash') {
26
+ return 'anthropic_denormalize';
108
27
  }
109
- return undefined;
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
- const tool = rawTool;
125
- const fnNode = tool.function && typeof tool.function === 'object' ? tool.function : undefined;
126
- let name = pickToolName([fnNode?.name, tool.name], options);
127
- // Special case for Responses builtin web_search tools:
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
- const mapped = rawTools
165
- .map((entry) => bridgeToolToChatDefinition(entry, options))
166
- .filter((entry) => !!entry);
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
- const tool = rawTool;
174
- const fnNode = tool.function && typeof tool.function === 'object' ? tool.function : undefined;
175
- const name = pickToolName([fnNode?.name, tool.name], options);
176
- if (!name) {
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
- if (!options?.sanitizeName) {
214
- const mapped = mapChatToolsToBridgeWithNative(rawTools);
215
- return mapped && mapped.length ? mapped : undefined;
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
  }