@jsonstudio/llms 0.6.567 → 0.6.586

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 (62) hide show
  1. package/dist/conversion/codecs/gemini-openai-codec.js +33 -4
  2. package/dist/conversion/codecs/openai-openai-codec.js +2 -1
  3. package/dist/conversion/codecs/responses-openai-codec.js +3 -2
  4. package/dist/conversion/compat/actions/glm-history-image-trim.d.ts +2 -0
  5. package/dist/conversion/compat/actions/glm-history-image-trim.js +88 -0
  6. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +6 -2
  7. package/dist/conversion/hub/pipeline/hub-pipeline.js +72 -81
  8. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +0 -34
  9. package/dist/conversion/hub/process/chat-process.js +68 -24
  10. package/dist/conversion/hub/response/provider-response.js +0 -8
  11. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +22 -3
  12. package/dist/conversion/hub/semantic-mappers/responses-mapper.js +267 -14
  13. package/dist/conversion/hub/types/chat-envelope.d.ts +1 -0
  14. package/dist/conversion/responses/responses-openai-bridge.d.ts +3 -2
  15. package/dist/conversion/responses/responses-openai-bridge.js +1 -13
  16. package/dist/conversion/shared/anthropic-message-utils.js +54 -0
  17. package/dist/conversion/shared/args-mapping.js +11 -3
  18. package/dist/conversion/shared/responses-output-builder.js +42 -21
  19. package/dist/conversion/shared/streaming-text-extractor.d.ts +25 -0
  20. package/dist/conversion/shared/streaming-text-extractor.js +31 -38
  21. package/dist/conversion/shared/text-markup-normalizer.d.ts +20 -0
  22. package/dist/conversion/shared/text-markup-normalizer.js +118 -31
  23. package/dist/conversion/shared/tool-filter-pipeline.js +56 -30
  24. package/dist/conversion/shared/tool-harvester.js +43 -12
  25. package/dist/conversion/shared/tool-mapping.d.ts +1 -0
  26. package/dist/conversion/shared/tool-mapping.js +33 -19
  27. package/dist/filters/index.d.ts +1 -0
  28. package/dist/filters/index.js +1 -0
  29. package/dist/filters/special/request-tools-normalize.js +14 -4
  30. package/dist/filters/special/response-apply-patch-toon-decode.d.ts +23 -0
  31. package/dist/filters/special/response-apply-patch-toon-decode.js +117 -0
  32. package/dist/filters/special/response-tool-arguments-toon-decode.d.ts +10 -0
  33. package/dist/filters/special/response-tool-arguments-toon-decode.js +154 -26
  34. package/dist/guidance/index.js +71 -42
  35. package/dist/router/virtual-router/bootstrap.js +10 -5
  36. package/dist/router/virtual-router/classifier.js +16 -7
  37. package/dist/router/virtual-router/engine-health.d.ts +11 -0
  38. package/dist/router/virtual-router/engine-health.js +217 -4
  39. package/dist/router/virtual-router/engine-logging.d.ts +2 -1
  40. package/dist/router/virtual-router/engine-logging.js +35 -3
  41. package/dist/router/virtual-router/engine.d.ts +17 -1
  42. package/dist/router/virtual-router/engine.js +184 -6
  43. package/dist/router/virtual-router/routing-instructions.d.ts +2 -0
  44. package/dist/router/virtual-router/routing-instructions.js +19 -1
  45. package/dist/router/virtual-router/tool-signals.d.ts +2 -1
  46. package/dist/router/virtual-router/tool-signals.js +324 -119
  47. package/dist/router/virtual-router/types.d.ts +31 -1
  48. package/dist/router/virtual-router/types.js +2 -2
  49. package/dist/servertool/engine.js +3 -0
  50. package/dist/servertool/handlers/iflow-model-error-retry.d.ts +1 -0
  51. package/dist/servertool/handlers/iflow-model-error-retry.js +93 -0
  52. package/dist/servertool/handlers/stop-message-auto.js +61 -4
  53. package/dist/servertool/server-side-tools.d.ts +1 -0
  54. package/dist/servertool/server-side-tools.js +27 -0
  55. package/dist/sse/json-to-sse/event-generators/responses.js +9 -2
  56. package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +23 -3
  57. package/dist/tools/apply-patch-structured.d.ts +20 -0
  58. package/dist/tools/apply-patch-structured.js +240 -0
  59. package/dist/tools/tool-description-utils.d.ts +5 -0
  60. package/dist/tools/tool-description-utils.js +50 -0
  61. package/dist/tools/tool-registry.js +11 -193
  62. package/package.json +1 -1
@@ -19,6 +19,7 @@ const RESPONSES_PARAMETER_KEYS = [
19
19
  'stop_sequences',
20
20
  'modalities'
21
21
  ];
22
+ const RESPONSES_SUBMIT_ENDPOINT = '/v1/responses.submit_tool_outputs';
22
23
  function mapToolOutputs(entries, missing) {
23
24
  if (!entries || !entries.length)
24
25
  return undefined;
@@ -53,6 +54,41 @@ function mapToolOutputs(entries, missing) {
53
54
  });
54
55
  return outputs.length ? outputs : undefined;
55
56
  }
57
+ function deriveResumeToolOutputs(ctx) {
58
+ if (!ctx || typeof ctx !== 'object') {
59
+ return undefined;
60
+ }
61
+ const resume = ctx.responsesResume;
62
+ if (!resume || typeof resume !== 'object') {
63
+ return undefined;
64
+ }
65
+ const detailed = Array.isArray(resume.toolOutputsDetailed)
66
+ ? resume.toolOutputsDetailed
67
+ : undefined;
68
+ if (!detailed || detailed.length === 0) {
69
+ return undefined;
70
+ }
71
+ const outputs = [];
72
+ detailed.forEach((entry, index) => {
73
+ if (!entry || typeof entry !== 'object') {
74
+ return;
75
+ }
76
+ const callIdRaw = typeof entry.callId === 'string' && entry.callId.trim().length
77
+ ? entry.callId.trim()
78
+ : typeof entry.originalId === 'string' && entry.originalId.trim().length
79
+ ? entry.originalId.trim()
80
+ : `resume_tool_${index}`;
81
+ if (!callIdRaw) {
82
+ return;
83
+ }
84
+ const outputText = typeof entry.outputText === 'string' ? entry.outputText : '';
85
+ outputs.push({
86
+ tool_call_id: callIdRaw,
87
+ content: outputText
88
+ });
89
+ });
90
+ return outputs.length ? outputs : undefined;
91
+ }
56
92
  function collectParameters(payload, streamHint) {
57
93
  const params = {};
58
94
  for (const key of RESPONSES_PARAMETER_KEYS) {
@@ -162,6 +198,198 @@ function mergeMetadata(a, b) {
162
198
  const right = jsonClone(b);
163
199
  return { ...left, ...right };
164
200
  }
201
+ function isSubmitToolOutputsEndpoint(ctx) {
202
+ if (!ctx || typeof ctx !== 'object') {
203
+ return false;
204
+ }
205
+ const entry = typeof ctx.entryEndpoint === 'string' ? ctx.entryEndpoint.trim().toLowerCase() : '';
206
+ return entry === RESPONSES_SUBMIT_ENDPOINT;
207
+ }
208
+ function resolveSubmitResponseId(ctx, responsesContext) {
209
+ const resumeMeta = ctx.responsesResume && typeof ctx.responsesResume === 'object'
210
+ ? ctx.responsesResume
211
+ : undefined;
212
+ const resumeId = typeof resumeMeta?.restoredFromResponseId === 'string'
213
+ ? resumeMeta.restoredFromResponseId.trim()
214
+ : undefined;
215
+ if (resumeId) {
216
+ return resumeId;
217
+ }
218
+ const contextRecord = responsesContext && typeof responsesContext === 'object'
219
+ ? responsesContext
220
+ : undefined;
221
+ const previousId = typeof contextRecord?.previous_response_id === 'string'
222
+ ? contextRecord.previous_response_id.trim()
223
+ : undefined;
224
+ if (previousId) {
225
+ return previousId;
226
+ }
227
+ return undefined;
228
+ }
229
+ function extractSubmitMetadata(responsesContext, resumeMeta) {
230
+ if (responsesContext && responsesContext.metadata && isJsonObject(responsesContext.metadata)) {
231
+ return jsonClone(responsesContext.metadata);
232
+ }
233
+ if (resumeMeta && resumeMeta.metadata && isJsonObject(resumeMeta.metadata)) {
234
+ return jsonClone(resumeMeta.metadata);
235
+ }
236
+ return undefined;
237
+ }
238
+ function coerceOutputText(value) {
239
+ if (typeof value === 'string') {
240
+ return value;
241
+ }
242
+ if (value === undefined || value === null) {
243
+ return '';
244
+ }
245
+ try {
246
+ return JSON.stringify(value);
247
+ }
248
+ catch {
249
+ return String(value);
250
+ }
251
+ }
252
+ function extractCapturedToolOutputs(responsesContext) {
253
+ if (!responsesContext || typeof responsesContext !== 'object') {
254
+ return [];
255
+ }
256
+ const snapshot = responsesContext.__captured_tool_results;
257
+ if (!Array.isArray(snapshot) || !snapshot.length) {
258
+ return [];
259
+ }
260
+ const entries = [];
261
+ snapshot.forEach((entry) => {
262
+ if (!entry || typeof entry !== 'object') {
263
+ return;
264
+ }
265
+ const record = entry;
266
+ const toolId = typeof record.tool_call_id === 'string' && record.tool_call_id.trim().length
267
+ ? record.tool_call_id.trim()
268
+ : typeof record.call_id === 'string' && record.call_id.trim().length
269
+ ? record.call_id.trim()
270
+ : undefined;
271
+ if (!toolId) {
272
+ return;
273
+ }
274
+ entries.push({
275
+ tool_call_id: toolId,
276
+ id: toolId,
277
+ output: typeof record.output === 'string' ? record.output : coerceOutputText(record.output),
278
+ name: typeof record.name === 'string' ? record.name : undefined
279
+ });
280
+ });
281
+ return entries;
282
+ }
283
+ function collectSubmitToolOutputs(chat, ctx, responsesContext) {
284
+ const outputs = [];
285
+ const seen = new Set();
286
+ const append = (idSeed, outputSeed, name) => {
287
+ const trimmed = typeof idSeed === 'string' && idSeed.trim().length ? idSeed.trim() : '';
288
+ const fallbackId = trimmed || `submit_tool_${outputs.length + 1}`;
289
+ if (seen.has(fallbackId)) {
290
+ return;
291
+ }
292
+ seen.add(fallbackId);
293
+ outputs.push({
294
+ tool_call_id: fallbackId,
295
+ id: fallbackId,
296
+ output: coerceOutputText(outputSeed),
297
+ name
298
+ });
299
+ };
300
+ if (Array.isArray(chat.toolOutputs) && chat.toolOutputs.length) {
301
+ chat.toolOutputs.forEach((entry) => {
302
+ append(entry.tool_call_id ?? entry.call_id, entry.content, entry.name);
303
+ });
304
+ }
305
+ if (!outputs.length) {
306
+ const captured = extractCapturedToolOutputs(responsesContext);
307
+ captured.forEach((entry) => append(entry.tool_call_id ?? entry.id, entry.output, entry.name));
308
+ }
309
+ if (!outputs.length) {
310
+ const resume = ctx.responsesResume && typeof ctx.responsesResume === 'object'
311
+ ? ctx.responsesResume
312
+ : undefined;
313
+ const detailed = Array.isArray(resume?.toolOutputsDetailed)
314
+ ? resume?.toolOutputsDetailed
315
+ : undefined;
316
+ if (detailed) {
317
+ detailed.forEach((entry, index) => {
318
+ if (!entry || typeof entry !== 'object') {
319
+ return;
320
+ }
321
+ const callId = typeof entry.callId === 'string' && entry.callId.trim().length
322
+ ? entry.callId.trim()
323
+ : typeof entry.originalId === 'string' && entry.originalId.trim().length
324
+ ? entry.originalId.trim()
325
+ : `resume_tool_${index + 1}`;
326
+ append(callId, typeof entry.outputText === 'string' ? entry.outputText : entry.outputText ?? '');
327
+ });
328
+ }
329
+ }
330
+ return outputs;
331
+ }
332
+ function resolveSubmitStreamFlag(chat, ctx, responsesContext) {
333
+ if (chat.parameters && typeof chat.parameters.stream === 'boolean') {
334
+ return chat.parameters.stream;
335
+ }
336
+ if (responsesContext && typeof responsesContext.stream === 'boolean') {
337
+ return responsesContext.stream;
338
+ }
339
+ if (ctx.streamingHint === 'force') {
340
+ return true;
341
+ }
342
+ if (ctx.streamingHint === 'disable') {
343
+ return false;
344
+ }
345
+ return undefined;
346
+ }
347
+ function resolveSubmitModel(chat, responsesContext) {
348
+ const direct = chat.parameters && typeof chat.parameters.model === 'string'
349
+ ? chat.parameters.model.trim()
350
+ : undefined;
351
+ if (direct) {
352
+ return direct;
353
+ }
354
+ if (responsesContext && typeof responsesContext.parameters === 'object') {
355
+ const params = responsesContext.parameters;
356
+ const model = typeof params.model === 'string' ? params.model.trim() : undefined;
357
+ if (model) {
358
+ return model;
359
+ }
360
+ }
361
+ return undefined;
362
+ }
363
+ function buildSubmitToolOutputsPayload(chat, ctx, responsesContext) {
364
+ const responseId = resolveSubmitResponseId(ctx, responsesContext);
365
+ if (!responseId) {
366
+ throw new Error('Submit tool outputs requires response_id from Responses resume context');
367
+ }
368
+ const toolOutputs = collectSubmitToolOutputs(chat, ctx, responsesContext);
369
+ if (!toolOutputs.length) {
370
+ throw new Error('Submit tool outputs requires at least one tool output entry');
371
+ }
372
+ const resumeMeta = ctx.responsesResume && typeof ctx.responsesResume === 'object'
373
+ ? ctx.responsesResume
374
+ : undefined;
375
+ const payload = {
376
+ response_id: responseId,
377
+ tool_outputs: toolOutputs
378
+ };
379
+ const modelValue = resolveSubmitModel(chat, responsesContext);
380
+ if (modelValue) {
381
+ payload.model = modelValue;
382
+ }
383
+ const streamValue = resolveSubmitStreamFlag(chat, ctx, responsesContext);
384
+ if (typeof streamValue === 'boolean') {
385
+ payload.stream = streamValue;
386
+ }
387
+ const metadata = extractSubmitMetadata(responsesContext, resumeMeta);
388
+ if (metadata) {
389
+ payload.metadata = metadata;
390
+ }
391
+ return payload;
392
+ }
165
393
  export class ResponsesSemanticMapper {
166
394
  async toChat(format, ctx) {
167
395
  const payload = format.payload || {};
@@ -169,16 +397,30 @@ export class ResponsesSemanticMapper {
169
397
  const { request, toolsNormalized } = buildChatRequestFromResponses(payload, responsesContext);
170
398
  const missingFields = [];
171
399
  const messages = normalizeMessages(request.messages, missingFields);
172
- const toolOutputs = mapToolOutputs(payload.tool_outputs, missingFields);
400
+ let toolOutputs = mapToolOutputs(payload.tool_outputs, missingFields);
401
+ if (!toolOutputs || toolOutputs.length === 0) {
402
+ const resumeToolOutputs = deriveResumeToolOutputs(ctx);
403
+ if (resumeToolOutputs && resumeToolOutputs.length) {
404
+ toolOutputs = resumeToolOutputs;
405
+ }
406
+ }
173
407
  const parameters = collectParameters(payload, responsesContext.stream);
174
408
  const metadata = { context: ctx };
175
409
  try {
176
410
  const bridgePolicy = resolveBridgePolicy({ protocol: 'openai-responses', moduleType: 'openai-responses' });
177
411
  const actions = resolvePolicyActions(bridgePolicy, 'request_inbound');
178
412
  if (actions?.length) {
413
+ const capturedToolResults = Array.isArray(toolOutputs)
414
+ ? toolOutputs.map((entry) => ({
415
+ tool_call_id: entry.tool_call_id,
416
+ output: entry.content,
417
+ name: entry.name
418
+ }))
419
+ : undefined;
179
420
  const actionState = createBridgeActionState({
180
421
  rawRequest: payload,
181
- metadata: metadata
422
+ metadata: metadata,
423
+ capturedToolResults
182
424
  });
183
425
  runBridgeActionPipeline({
184
426
  stage: 'request_inbound',
@@ -208,6 +450,28 @@ export class ResponsesSemanticMapper {
208
450
  };
209
451
  }
210
452
  async fromChat(chat, ctx) {
453
+ const capturedContext = chat.metadata?.responsesContext;
454
+ const envelopeMetadata = chat.metadata && isJsonObject(chat.metadata) ? chat.metadata : undefined;
455
+ const responsesContext = isJsonObject(capturedContext)
456
+ ? {
457
+ ...capturedContext,
458
+ metadata: mergeMetadata(capturedContext.metadata, envelopeMetadata)
459
+ }
460
+ : {
461
+ metadata: envelopeMetadata
462
+ };
463
+ if (isSubmitToolOutputsEndpoint(ctx)) {
464
+ const submitPayload = buildSubmitToolOutputsPayload(chat, ctx, responsesContext);
465
+ return {
466
+ protocol: 'openai-responses',
467
+ direction: 'response',
468
+ payload: submitPayload,
469
+ meta: {
470
+ context: ctx,
471
+ submitToolOutputs: true
472
+ }
473
+ };
474
+ }
211
475
  const modelValue = chat.parameters?.model;
212
476
  if (typeof modelValue !== 'string' || !modelValue.trim()) {
213
477
  throw new Error('ChatEnvelope.parameters.model is required for openai-responses outbound conversion');
@@ -222,18 +486,7 @@ export class ResponsesSemanticMapper {
222
486
  .filter((message) => Boolean(message && typeof message === 'object' && message.role === 'system'))
223
487
  .map(message => serializeSystemContent(message))
224
488
  .filter((content) => typeof content === 'string' && content.length > 0);
225
- const capturedContext = chat.metadata?.responsesContext;
226
- const envelopeMetadata = chat.metadata && isJsonObject(chat.metadata) ? chat.metadata : undefined;
227
- const responsesContext = isJsonObject(capturedContext)
228
- ? {
229
- ...capturedContext,
230
- metadata: mergeMetadata(capturedContext.metadata, envelopeMetadata),
231
- originalSystemMessages
232
- }
233
- : {
234
- metadata: envelopeMetadata,
235
- originalSystemMessages
236
- };
489
+ responsesContext.originalSystemMessages = originalSystemMessages;
237
490
  const responsesResult = buildResponsesRequestFromChat(requestShape, responsesContext);
238
491
  const responses = responsesResult.request;
239
492
  if (chat.parameters && chat.parameters.stream !== undefined) {
@@ -53,6 +53,7 @@ export interface AdapterContext {
53
53
  originalModelId?: string;
54
54
  clientModelId?: string;
55
55
  toolCallIdStyle?: 'fc' | 'preserve';
56
+ responsesResume?: JsonObject;
56
57
  [key: string]: JsonValue;
57
58
  }
58
59
  export interface ChatEnvelope {
@@ -1,5 +1,6 @@
1
1
  import type { BridgeInputItem, BridgeToolDefinition } from '../shared/bridge-message-types.js';
2
2
  import type { ChatToolDefinition } from '../hub/types/chat-envelope.js';
3
+ import type { JsonObject, JsonValue } from '../hub/types/json.js';
3
4
  import type { BridgeInputBuildResult } from '../shared/bridge-message-utils.js';
4
5
  import type { ToolCallIdStyle } from '../shared/responses-tool-utils.js';
5
6
  type Unknown = Record<string, unknown>;
@@ -11,8 +12,8 @@ export interface ResponsesRequestContext extends Unknown {
11
12
  store?: unknown;
12
13
  toolChoice?: unknown;
13
14
  parallelToolCalls?: boolean;
14
- metadata?: Record<string, unknown> | undefined;
15
- responseFormat?: unknown;
15
+ metadata?: JsonObject;
16
+ responseFormat?: JsonValue;
16
17
  stream?: boolean;
17
18
  isChatPayload?: boolean;
18
19
  isResponsesPayload?: boolean;
@@ -10,7 +10,6 @@ import { normalizeMessageReasoningTools } from '../shared/reasoning-tool-normali
10
10
  import { createBridgeActionState, runBridgeActionPipeline } from '../shared/bridge-actions.js';
11
11
  import { resolveBridgePolicy, resolvePolicyActions } from '../shared/bridge-policies.js';
12
12
  import { buildResponsesOutputFromChat } from '../shared/responses-output-builder.js';
13
- import { consumeResponsesPayloadSnapshot, consumeResponsesPassthrough } from '../shared/responses-reasoning-registry.js';
14
13
  function isObject(v) {
15
14
  return !!v && typeof v === 'object' && !Array.isArray(v);
16
15
  }
@@ -29,7 +28,7 @@ export function captureResponsesContext(payload, dto) {
29
28
  store: payload.store,
30
29
  toolChoice: payload.tool_choice,
31
30
  parallelToolCalls: typeof payload.parallel_tool_calls === 'boolean' ? payload.parallel_tool_calls : undefined,
32
- metadata: (payload.metadata && typeof payload.metadata === 'object') ? { ...payload.metadata } : undefined,
31
+ metadata: (payload.metadata && typeof payload.metadata === 'object') ? payload.metadata : undefined,
33
32
  responseFormat: payload.response_format,
34
33
  stream: typeof payload.stream === 'boolean' ? payload.stream : undefined,
35
34
  isChatPayload: Array.isArray(payload.messages)
@@ -461,17 +460,6 @@ export function buildResponsesPayloadFromChat(payload, context) {
461
460
  const response = unwrapData(payload);
462
461
  if (!response || typeof response !== 'object')
463
462
  return payload;
464
- const snapshotKey = resolveSnapshotLookupKey(response, context);
465
- if (snapshotKey) {
466
- const passthrough = consumeResponsesPassthrough(snapshotKey);
467
- if (passthrough) {
468
- return passthrough;
469
- }
470
- const snapshot = consumeResponsesPayloadSnapshot(snapshotKey);
471
- if (snapshot) {
472
- return snapshot;
473
- }
474
- }
475
463
  if (response.object === 'response' && Array.isArray(response.output)) {
476
464
  return response;
477
465
  }
@@ -647,6 +647,56 @@ function createAnthropicToolNameResolver(source) {
647
647
  return lookup.get(trimmed) ?? lookup.get(trimmed.toLowerCase()) ?? trimmed;
648
648
  };
649
649
  }
650
+ function normalizeAnthropicToolChoice(value) {
651
+ if (value === undefined || value === null) {
652
+ return undefined;
653
+ }
654
+ if (isPlainRecord(value)) {
655
+ // Already an object – best-effort clone while trimming type, and also support
656
+ // Chat-style { type: 'function', function: { name } } by mapping to Anthropic's
657
+ // { type: 'tool', name } shape.
658
+ const cloned = cloneAnthropicSchema(value);
659
+ const rawType = typeof cloned.type === 'string' ? String(cloned.type).trim() : '';
660
+ if (rawType) {
661
+ cloned.type = rawType;
662
+ return cloned;
663
+ }
664
+ const selectorType = typeof cloned.type === 'string' ? String(cloned.type).trim() : '';
665
+ const fn = cloned.function;
666
+ if (selectorType === 'function' &&
667
+ fn &&
668
+ typeof fn === 'object' &&
669
+ typeof fn.name === 'string' &&
670
+ String(fn.name).trim().length) {
671
+ return { type: 'tool', name: String(fn.name).trim() };
672
+ }
673
+ return cloned;
674
+ }
675
+ if (typeof value === 'string') {
676
+ const trimmed = value.trim();
677
+ if (!trimmed.length) {
678
+ return undefined;
679
+ }
680
+ const lower = trimmed.toLowerCase();
681
+ if (lower === 'auto') {
682
+ return { type: 'auto' };
683
+ }
684
+ if (lower === 'none') {
685
+ return { type: 'none' };
686
+ }
687
+ if (lower === 'any') {
688
+ return { type: 'any' };
689
+ }
690
+ if (lower === 'required') {
691
+ // "required" in canonical Chat roughly maps to Anthropic's "any" semantics:
692
+ // the model must choose some tool if available.
693
+ return { type: 'any' };
694
+ }
695
+ // Fallback: preserve custom mode as-is in type field.
696
+ return { type: trimmed };
697
+ }
698
+ return undefined;
699
+ }
650
700
  export function buildAnthropicRequestFromOpenAIChat(chatReq) {
651
701
  const requestBody = isObject(chatReq) ? chatReq : {};
652
702
  const model = String(requestBody?.model || 'unknown');
@@ -882,6 +932,10 @@ export function buildAnthropicRequestFromOpenAIChat(chatReq) {
882
932
  if (anthropicTools !== undefined) {
883
933
  out.tools = anthropicTools;
884
934
  }
935
+ const normalizedToolChoice = normalizeAnthropicToolChoice(requestBody.tool_choice);
936
+ if (normalizedToolChoice !== undefined) {
937
+ out.tool_choice = normalizedToolChoice;
938
+ }
885
939
  try {
886
940
  if (requestBody.metadata && typeof requestBody.metadata === 'object') {
887
941
  out.metadata = JSON.parse(JSON.stringify(requestBody.metadata));
@@ -1,4 +1,4 @@
1
- // Shared tool + argument mapping helpers (schema-driven)
1
+ import { buildShellDescription, hasApplyPatchToolDeclared, isShellToolName } from '../../tools/tool-description-utils.js';
2
2
  function isObject(v) {
3
3
  return !!v && typeof v === 'object' && !Array.isArray(v);
4
4
  }
@@ -155,6 +155,7 @@ export function normalizeTools(tools) {
155
155
  if (!Array.isArray(tools))
156
156
  return [];
157
157
  const out = [];
158
+ const applyPatchAvailable = hasApplyPatchToolDeclared(tools);
158
159
  for (const t of tools) {
159
160
  if (!t || typeof t !== 'object')
160
161
  continue;
@@ -175,7 +176,7 @@ export function normalizeTools(tools) {
175
176
  }
176
177
  // Enforce schema for known tools with minimal, compatible constraints
177
178
  let finalParams;
178
- if (typeof name === 'string' && name.trim().toLowerCase() === 'shell') {
179
+ if (isShellToolName(name)) {
179
180
  // Do NOT downgrade an existing schema; prefer string command, allow argv array as fallback
180
181
  const base = isObject(params) ? params : {};
181
182
  const props = isObject(base.properties) ? base.properties : {};
@@ -205,7 +206,14 @@ export function normalizeTools(tools) {
205
206
  else {
206
207
  finalParams = { type: 'object', properties: {}, additionalProperties: true };
207
208
  }
208
- const norm = { type: 'function', function: { name, ...(desc ? { description: desc } : {}), parameters: finalParams } };
209
+ const functionNode = { name, ...(desc ? { description: desc } : {}), parameters: finalParams };
210
+ if (isShellToolName(name)) {
211
+ const display = (typeof name === 'string' && name.trim().length > 0)
212
+ ? name.trim()
213
+ : ((typeof topName === 'string' && topName.trim().length > 0) ? topName.trim() : 'shell');
214
+ functionNode.description = buildShellDescription(display, applyPatchAvailable);
215
+ }
216
+ const norm = { type: 'function', function: functionNode };
209
217
  if (norm.function?.name)
210
218
  out.push(norm);
211
219
  }
@@ -153,29 +153,50 @@ export function buildResponsesOutputFromChat(options) {
153
153
  };
154
154
  }
155
155
  function normalizeUsage(usageRaw) {
156
- if (usageRaw && typeof usageRaw === 'object') {
157
- const usage = { ...usageRaw };
158
- if (usage.input_tokens != null && usage.prompt_tokens == null) {
159
- usage.prompt_tokens = usage.input_tokens;
160
- }
161
- if (usage.output_tokens != null && usage.completion_tokens == null) {
162
- usage.completion_tokens = usage.output_tokens;
163
- }
164
- if (usage.prompt_tokens != null && usage.completion_tokens != null && usage.total_tokens == null) {
165
- const total = Number(usage.prompt_tokens) + Number(usage.completion_tokens);
166
- if (!Number.isNaN(total))
167
- usage.total_tokens = total;
168
- }
169
- try {
170
- delete usage.input_tokens;
171
- delete usage.output_tokens;
172
- }
173
- catch {
174
- /* ignore */
156
+ if (!usageRaw || typeof usageRaw !== 'object') {
157
+ return usageRaw;
158
+ }
159
+ const usage = { ...usageRaw };
160
+ // 统一 Responses 与 Chat 两种 usage 形态:
161
+ // - Responses: input_tokens / output_tokens / total_tokens
162
+ // - Chat: prompt_tokens / completion_tokens / total_tokens
163
+ const inputTokens = typeof usage.input_tokens === 'number'
164
+ ? usage.input_tokens
165
+ : typeof usage.prompt_tokens === 'number'
166
+ ? usage.prompt_tokens
167
+ : undefined;
168
+ const outputTokens = typeof usage.output_tokens === 'number'
169
+ ? usage.output_tokens
170
+ : typeof usage.completion_tokens === 'number'
171
+ ? usage.completion_tokens
172
+ : undefined;
173
+ let totalTokens = typeof usage.total_tokens === 'number'
174
+ ? usage.total_tokens
175
+ : undefined;
176
+ if (totalTokens === undefined && inputTokens !== undefined && outputTokens !== undefined) {
177
+ const total = Number(inputTokens) + Number(outputTokens);
178
+ if (!Number.isNaN(total)) {
179
+ totalTokens = total;
175
180
  }
176
- return usage;
177
181
  }
178
- return usageRaw;
182
+ // Responses 规范字段:input_tokens / output_tokens / total_tokens
183
+ if (inputTokens !== undefined) {
184
+ usage.input_tokens = inputTokens;
185
+ }
186
+ if (outputTokens !== undefined) {
187
+ usage.output_tokens = outputTokens;
188
+ }
189
+ if (totalTokens !== undefined) {
190
+ usage.total_tokens = totalTokens;
191
+ }
192
+ // 为了兼容内部统计逻辑,保留 prompt_tokens / completion_tokens 映射(如果原本没有)
193
+ if (usage.prompt_tokens == null && inputTokens !== undefined) {
194
+ usage.prompt_tokens = inputTokens;
195
+ }
196
+ if (usage.completion_tokens == null && outputTokens !== undefined) {
197
+ usage.completion_tokens = outputTokens;
198
+ }
199
+ return usage;
179
200
  }
180
201
  function buildFunctionCallOutput(call, allocateOutputId, sanitizeFunctionName, baseCount, offset) {
181
202
  try {
@@ -0,0 +1,25 @@
1
+ export interface StreamingToolCall {
2
+ id?: string;
3
+ type: 'function';
4
+ function: {
5
+ name?: string;
6
+ arguments?: string;
7
+ };
8
+ }
9
+ export interface StreamingToolExtractorOptions {
10
+ idPrefix?: string;
11
+ }
12
+ export declare class StreamingTextToolExtractor {
13
+ private opts;
14
+ private buffer;
15
+ private idCounter;
16
+ constructor(opts?: StreamingToolExtractorOptions);
17
+ reset(): void;
18
+ feedText(text: string): StreamingToolCall[];
19
+ private genId;
20
+ private toToolCall;
21
+ private tryExtractStructuredBlocks;
22
+ private tryExtractFunctionExecuteBlocks;
23
+ private splitCommand;
24
+ }
25
+ export declare function createStreamingToolExtractor(opts?: StreamingToolExtractorOptions): StreamingTextToolExtractor;