@jsonstudio/llms 0.6.3409 → 0.6.3539

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 (82) hide show
  1. package/dist/conversion/codecs/anthropic-openai-codec.d.ts +12 -3
  2. package/dist/conversion/codecs/anthropic-openai-codec.js +32 -92
  3. package/dist/conversion/codecs/gemini-openai-codec.d.ts +6 -5
  4. package/dist/conversion/codecs/gemini-openai-codec.js +48 -685
  5. package/dist/conversion/codecs/openai-openai-codec.d.ts +1 -1
  6. package/dist/conversion/codecs/openai-openai-codec.js +34 -100
  7. package/dist/conversion/codecs/responses-openai-codec.d.ts +1 -1
  8. package/dist/conversion/codecs/responses-openai-codec.js +47 -159
  9. package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.d.ts +2 -6
  10. package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.js +29 -245
  11. package/dist/conversion/compat/actions/anthropic-claude-code-user-id.d.ts +3 -0
  12. package/dist/conversion/compat/actions/anthropic-claude-code-user-id.js +30 -0
  13. package/dist/conversion/compat/actions/antigravity-thought-signature-prepare.js +21 -232
  14. package/dist/conversion/compat/actions/deepseek-web-request.js +41 -276
  15. package/dist/conversion/compat/actions/deepseek-web-response.js +64 -859
  16. package/dist/conversion/compat/actions/gemini-cli-request.d.ts +1 -1
  17. package/dist/conversion/compat/actions/gemini-cli-request.js +20 -613
  18. package/dist/conversion/compat/actions/gemini-web-search.d.ts +1 -15
  19. package/dist/conversion/compat/actions/gemini-web-search.js +22 -69
  20. package/dist/conversion/compat/actions/glm-tool-extraction.d.ts +3 -2
  21. package/dist/conversion/compat/actions/glm-tool-extraction.js +28 -257
  22. package/dist/conversion/compat/actions/iflow-tool-text-fallback.d.ts +0 -8
  23. package/dist/conversion/compat/actions/iflow-tool-text-fallback.js +24 -206
  24. package/dist/conversion/compat/actions/qwen-transform.d.ts +3 -2
  25. package/dist/conversion/compat/actions/qwen-transform.js +30 -271
  26. package/dist/conversion/compat/actions/tool-text-request-guidance.js +3 -173
  27. package/dist/conversion/compat/actions/universal-shape-filter.d.ts +6 -23
  28. package/dist/conversion/compat/actions/universal-shape-filter.js +4 -383
  29. package/dist/conversion/hub/pipeline/compat/native-adapter-context.js +1 -0
  30. package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.d.ts +1 -2
  31. package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +50 -104
  32. package/dist/conversion/pipeline/codecs/v2/openai-openai-pipeline.js +12 -10
  33. package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.d.ts +0 -2
  34. package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.js +46 -67
  35. package/dist/conversion/pipeline/codecs/v2/shared/openai-chat-helpers.js +15 -40
  36. package/dist/conversion/responses/responses-openai-bridge/response-payload.js +47 -348
  37. package/dist/conversion/responses/responses-openai-bridge.js +129 -611
  38. package/dist/conversion/shared/chat-output-normalizer.js +6 -0
  39. package/dist/conversion/shared/chat-request-filters.js +1 -1
  40. package/dist/conversion/shared/output-content-normalizer.js +10 -0
  41. package/dist/conversion/shared/responses-conversation-store.js +22 -135
  42. package/dist/conversion/shared/responses-output-builder.d.ts +0 -2
  43. package/dist/conversion/shared/responses-output-builder.js +28 -318
  44. package/dist/conversion/shared/responses-response-utils.js +35 -86
  45. package/dist/conversion/shared/streaming-text-extractor.d.ts +1 -2
  46. package/dist/conversion/shared/streaming-text-extractor.js +13 -14
  47. package/dist/native/router_hotpath_napi.node +0 -0
  48. package/dist/router/virtual-router/bootstrap/routing-config.js +11 -3
  49. package/dist/router/virtual-router/engine-legacy.d.ts +3 -3
  50. package/dist/router/virtual-router/engine-legacy.js +15 -7
  51. package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.d.ts +16 -0
  52. package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.js +434 -46
  53. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.d.ts +83 -0
  54. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js +295 -0
  55. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.d.ts +1 -0
  56. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.d.ts +7 -0
  57. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js +8 -1
  58. package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +383 -298
  59. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.d.ts +20 -0
  60. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +201 -0
  61. package/dist/router/virtual-router/engine-selection/native-virtual-router-routing-instructions-semantics.d.ts +1 -0
  62. package/dist/router/virtual-router/engine-selection/native-virtual-router-routing-instructions-semantics.js +37 -0
  63. package/dist/router/virtual-router/engine.js +0 -38
  64. package/dist/router/virtual-router/features.js +44 -3
  65. package/dist/router/virtual-router/routing-instructions/parse.d.ts +0 -12
  66. package/dist/router/virtual-router/routing-instructions/parse.js +9 -389
  67. package/dist/router/virtual-router/stop-message-state-sync.d.ts +3 -6
  68. package/dist/router/virtual-router/stop-message-state-sync.js +50 -21
  69. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +1 -0
  70. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.js +26 -0
  71. package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +12 -2
  72. package/package.json +1 -1
  73. package/dist/router/virtual-router/engine-legacy/route-finalize.d.ts +0 -9
  74. package/dist/router/virtual-router/engine-legacy/route-finalize.js +0 -84
  75. package/dist/router/virtual-router/engine-legacy/route-selection.d.ts +0 -17
  76. package/dist/router/virtual-router/engine-legacy/route-selection.js +0 -205
  77. package/dist/router/virtual-router/engine-legacy/route-state-allowlist.d.ts +0 -3
  78. package/dist/router/virtual-router/engine-legacy/route-state-allowlist.js +0 -36
  79. package/dist/router/virtual-router/engine-legacy/route-state.d.ts +0 -12
  80. package/dist/router/virtual-router/engine-legacy/route-state.js +0 -386
  81. package/dist/router/virtual-router/engine-legacy/routing.d.ts +0 -8
  82. package/dist/router/virtual-router/engine-legacy/routing.js +0 -8
@@ -1,4 +1,10 @@
1
1
  import { normalizeChatMessageContentWithNative } from '../../router/virtual-router/engine-selection/native-shared-conversion-semantics.js';
2
+ function assertChatOutputNormalizerNativeAvailable() {
3
+ if (typeof normalizeChatMessageContentWithNative !== 'function') {
4
+ throw new Error('[chat-output-normalizer] native bindings unavailable');
5
+ }
6
+ }
2
7
  export function normalizeChatMessageContent(content) {
8
+ assertChatOutputNormalizerNativeAvailable();
3
9
  return normalizeChatMessageContentWithNative(content);
4
10
  }
@@ -1,4 +1,4 @@
1
- import { normalizeChatRequest } from '../index.js';
1
+ import { normalizeChatRequest } from './openai-message-normalize.js';
2
2
  import { createSnapshotWriter } from '../snapshot-utils.js';
3
3
  import { buildGovernedFilterPayloadWithNativeFallback } from '../../router/virtual-router/engine-selection/native-chat-request-filter-semantics.js';
4
4
  import { pruneChatRequestPayloadWithNative } from '../../router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.js';
@@ -1,13 +1,23 @@
1
1
  import { extractOutputSegmentsWithNative, normalizeContentPartWithNative, normalizeMessageContentPartsWithNative } from '../../router/virtual-router/engine-selection/native-shared-conversion-semantics.js';
2
+ function assertOutputContentNormalizerNativeAvailable() {
3
+ if (typeof extractOutputSegmentsWithNative !== 'function' ||
4
+ typeof normalizeContentPartWithNative !== 'function' ||
5
+ typeof normalizeMessageContentPartsWithNative !== 'function') {
6
+ throw new Error('[output-content-normalizer] native bindings unavailable');
7
+ }
8
+ }
2
9
  export function extractOutputSegments(source, itemsKey = 'output') {
10
+ assertOutputContentNormalizerNativeAvailable();
3
11
  return extractOutputSegmentsWithNative(source, itemsKey);
4
12
  }
5
13
  export function normalizeContentPart(part, reasoningCollector) {
14
+ assertOutputContentNormalizerNativeAvailable();
6
15
  const normalized = normalizeContentPartWithNative(part, reasoningCollector);
7
16
  reasoningCollector.splice(0, reasoningCollector.length, ...normalized.reasoningCollector);
8
17
  return normalized.normalized;
9
18
  }
10
19
  export function normalizeMessageContentParts(parts, reasoningCollector) {
20
+ assertOutputContentNormalizerNativeAvailable();
11
21
  const normalized = normalizeMessageContentPartsWithNative(parts, reasoningCollector ?? []);
12
22
  if (reasoningCollector) {
13
23
  reasoningCollector.splice(0, reasoningCollector.length, ...normalized.reasoningChunks);
@@ -1,85 +1,22 @@
1
1
  import { ProviderProtocolError } from '../provider-protocol-error.js';
2
- import { normalizeFunctionCallOutputIdWithNative as normalizeFunctionCallOutputId } from '../../router/virtual-router/engine-selection/native-shared-conversion-semantics.js';
3
- import { convertResponsesOutputToInputItemsWithNative, pickResponsesPersistedFieldsWithNative } from '../../router/virtual-router/engine-selection/native-shared-conversion-semantics.js';
2
+ import { convertResponsesOutputToInputItemsWithNative, pickResponsesPersistedFieldsWithNative, prepareResponsesConversationEntryWithNative, resumeResponsesConversationPayloadWithNative } from '../../router/virtual-router/engine-selection/native-shared-conversion-semantics.js';
4
3
  const TTL_MS = 1000 * 60 * 30; // 30min
5
- function cloneDeep(value) {
6
- try {
7
- if (typeof globalThis.structuredClone === 'function') {
8
- return globalThis.structuredClone(value);
9
- }
10
- }
11
- catch {
12
- /* ignore */
13
- }
14
- return JSON.parse(JSON.stringify(value ?? null));
15
- }
16
4
  function isRecord(value) {
17
5
  return Boolean(value && typeof value === 'object' && !Array.isArray(value));
18
6
  }
19
- function coerceInputArray(input) {
20
- if (!Array.isArray(input)) {
21
- return [];
22
- }
23
- return cloneDeep(input);
24
- }
25
- function coerceTools(tools) {
26
- if (!Array.isArray(tools)) {
27
- return undefined;
28
- }
29
- return cloneDeep(tools);
30
- }
31
7
  function pickPersistedFields(payload) {
32
8
  return pickResponsesPersistedFieldsWithNative(payload);
33
9
  }
34
10
  function convertOutputToInputItems(response) {
35
11
  return convertResponsesOutputToInputItemsWithNative(response);
36
12
  }
37
- function normalizeSubmittedToolOutputs(toolOutputs, callIdToFunctionItemId) {
38
- const items = [];
39
- const submitted = [];
40
- toolOutputs.forEach((entry, index) => {
41
- if (!entry || typeof entry !== 'object')
42
- return;
43
- const rec = entry;
44
- const rawId = typeof rec.tool_call_id === 'string'
45
- ? rec.tool_call_id
46
- : typeof rec.call_id === 'string'
47
- ? rec.call_id
48
- : typeof rec.id === 'string'
49
- ? rec.id
50
- : undefined;
51
- const trimmed = typeof rawId === 'string' ? rawId.trim() : '';
52
- const callId = trimmed.length ? trimmed : `call_resume_${index}`;
53
- const mappedItemId = callIdToFunctionItemId && typeof callId === 'string' && callIdToFunctionItemId.has(callId)
54
- ? callIdToFunctionItemId.get(callId)
55
- : undefined;
56
- const mappedIdTrimmed = typeof mappedItemId === 'string' ? mappedItemId.trim() : '';
57
- const outputId = mappedIdTrimmed.length
58
- ? normalizeFunctionCallOutputId({ callId: mappedIdTrimmed, fallback: mappedIdTrimmed })
59
- : normalizeFunctionCallOutputId({
60
- callId,
61
- fallback: trimmed.length ? trimmed : `fc_resume_${index}`
62
- });
63
- const outputValue = rec.output ?? null;
64
- const normalizedOutput = typeof outputValue === 'string'
65
- ? outputValue
66
- : (() => {
67
- try {
68
- return JSON.stringify(outputValue ?? null);
69
- }
70
- catch {
71
- return String(outputValue ?? '');
72
- }
73
- })();
74
- items.push({
75
- type: 'function_call_output',
76
- id: outputId,
77
- call_id: callId,
78
- output: normalizedOutput
79
- });
80
- submitted.push({ callId, originalId: rawId ?? callId, outputText: normalizedOutput });
81
- });
82
- return { items, submitted };
13
+ function assertResponsesConversationStoreNativeAvailable() {
14
+ if (typeof pickResponsesPersistedFieldsWithNative !== 'function' ||
15
+ typeof convertResponsesOutputToInputItemsWithNative !== 'function' ||
16
+ typeof prepareResponsesConversationEntryWithNative !== 'function' ||
17
+ typeof resumeResponsesConversationPayloadWithNative !== 'function') {
18
+ throw new Error('[responses-conversation-store] native bindings unavailable');
19
+ }
83
20
  }
84
21
  class ResponsesConversationStore {
85
22
  requestMap = new Map();
@@ -101,24 +38,16 @@ class ResponsesConversationStore {
101
38
  if (!requestId || !payload)
102
39
  return;
103
40
  this.prune();
41
+ assertResponsesConversationStoreNativeAvailable();
42
+ const prepared = prepareResponsesConversationEntryWithNative(payload, context);
104
43
  const entry = {
105
44
  requestId,
106
- basePayload: pickPersistedFields(payload),
107
- input: coerceInputArray(context.input),
108
- tools: coerceTools(context.toolsRaw) ||
109
- coerceTools(Array.isArray(payload.tools) ? payload.tools : undefined),
45
+ basePayload: isRecord(prepared.basePayload) ? prepared.basePayload : pickPersistedFields(payload),
46
+ input: Array.isArray(prepared.input) ? prepared.input : [],
47
+ tools: Array.isArray(prepared.tools) ? prepared.tools : undefined,
110
48
  createdAt: Date.now(),
111
49
  updatedAt: Date.now()
112
50
  };
113
- if (typeof payload.model === 'string' && payload.model.trim()) {
114
- entry.basePayload.model = payload.model;
115
- }
116
- if (typeof payload.stream === 'boolean') {
117
- entry.basePayload.stream = payload.stream;
118
- }
119
- if (entry.tools) {
120
- entry.basePayload.tools = entry.tools;
121
- }
122
51
  this.requestMap.set(requestId, entry);
123
52
  }
124
53
  recordResponse(args) {
@@ -176,59 +105,17 @@ class ResponsesConversationStore {
176
105
  }
177
106
  });
178
107
  }
179
- const mergedInput = coerceInputArray(entry.input);
180
- const callIdToFunctionItemId = new Map();
181
- for (const item of mergedInput) {
182
- if (!item || typeof item !== 'object')
183
- continue;
184
- const type = typeof item.type === 'string' ? String(item.type) : '';
185
- if (type !== 'function_call')
186
- continue;
187
- const id = typeof item.id === 'string' ? String(item.id).trim() : '';
188
- const callId = typeof item.call_id === 'string' ? String(item.call_id).trim() : '';
189
- if (id) {
190
- callIdToFunctionItemId.set(id, id);
191
- }
192
- if (callId) {
193
- callIdToFunctionItemId.set(callId, id || callId);
194
- }
195
- }
196
- const normalizedOutputs = normalizeSubmittedToolOutputs(toolOutputs, callIdToFunctionItemId);
197
- mergedInput.push(...normalizedOutputs.items);
198
- const payload = cloneDeep(entry.basePayload);
199
- payload.input = mergedInput;
200
- // Preserve the caller's streaming intent for the resume request.
201
- // Do not force-enable streaming here: some upstreams return JSON even when we send `Accept: text/event-stream`,
202
- // and forcing `stream=true` can trip SSE decoders on non-SSE bodies.
203
- payload.stream =
204
- typeof submitPayload.stream === 'boolean'
205
- ? Boolean(submitPayload.stream)
206
- : typeof entry.basePayload.stream === 'boolean'
207
- ? Boolean(entry.basePayload.stream)
208
- : false;
209
- payload.previous_response_id = responseId;
210
- if (Array.isArray(entry.tools) && entry.tools.length) {
211
- payload.tools = cloneDeep(entry.tools);
212
- }
213
- if (typeof submitPayload.model === 'string' && submitPayload.model.trim()) {
214
- payload.model = submitPayload.model.trim();
215
- }
216
- if (submitPayload.metadata && isRecord(submitPayload.metadata)) {
217
- const baseMeta = isRecord(payload.metadata) ? payload.metadata : {};
218
- payload.metadata = { ...baseMeta, ...cloneDeep(submitPayload.metadata) };
219
- }
220
- delete payload.tool_outputs;
221
- delete payload.response_id;
108
+ assertResponsesConversationStoreNativeAvailable();
109
+ const resumed = resumeResponsesConversationPayloadWithNative({
110
+ requestId: entry.requestId,
111
+ basePayload: entry.basePayload,
112
+ input: entry.input,
113
+ tools: entry.tools
114
+ }, responseId, submitPayload, options?.requestId);
222
115
  this.cleanupEntry(entry, responseId);
223
116
  return {
224
- payload,
225
- meta: {
226
- restoredFromResponseId: responseId,
227
- previousRequestId: entry.requestId,
228
- toolOutputs: toolOutputs.length,
229
- toolOutputsDetailed: normalizedOutputs.submitted,
230
- requestId: options?.requestId
231
- }
117
+ payload: resumed.payload,
118
+ meta: resumed.meta
232
119
  };
233
120
  }
234
121
  clearRequest(requestId) {
@@ -2,8 +2,6 @@ import type { ResponsesOutputItem } from '../../sse/types/index.js';
2
2
  export interface BuildResponsesOutputOptions {
3
3
  response: Record<string, unknown>;
4
4
  message?: Record<string, unknown>;
5
- requestId?: string;
6
- sanitizeFunctionName: (raw: unknown) => string | undefined;
7
5
  }
8
6
  export interface BuildResponsesOutputResult {
9
7
  outputItems: ResponsesOutputItem[];
@@ -1,322 +1,32 @@
1
- import { normalizeFunctionCallId, normalizeFunctionCallOutputId } from '../bridge-id-utils.js';
2
- import { normalizeContentPart } from './output-content-normalizer.js';
3
- import { expandResponsesMessageItem } from '../../sse/shared/responses-output-normalizer.js';
4
- import { normalizeResponsesUsageWithNative } from '../../router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js';
5
- function isStableToolCallId(raw) {
6
- return /^((fc|call)_[A-Za-z0-9_-]+)$/i.test(raw);
7
- }
8
- function buildToolOutputIndex(response) {
9
- const ids = new Set();
10
- try {
11
- const primary = Array.isArray(response.tool_outputs)
12
- ? response.tool_outputs
13
- : [];
14
- for (const entry of primary) {
15
- if (!entry || typeof entry !== 'object')
16
- continue;
17
- const raw = entry.tool_call_id ||
18
- entry.call_id ||
19
- entry.id;
20
- if (typeof raw === 'string' && raw.trim().length) {
21
- const trimmed = raw.trim();
22
- // 记录原始 ID(例如 OpenAI 的 toolu_ 前缀),以兼容直接使用
23
- // tool_call_id 的客户端;同时记录归一化后的 fc_ 形式,保证与
24
- // buildFunctionCallOutput 中 normalizeFunctionCallId 的结果对齐。
25
- ids.add(trimmed);
26
- try {
27
- const normalized = normalizeFunctionCallId({ callId: trimmed, fallback: trimmed });
28
- if (normalized && normalized !== trimmed) {
29
- ids.add(normalized);
30
- }
31
- }
32
- catch {
33
- // 归一化失败不应影响主流程
34
- }
35
- }
36
- }
37
- }
38
- catch {
39
- // best-effort: 不因索引构建失败影响主流程
40
- }
41
- return ids;
42
- }
43
- function appendReasoningSegments(target, raw) {
44
- if (typeof raw !== 'string' || !raw.length) {
45
- return;
46
- }
47
- const segments = raw.split('\n');
48
- let previousValue;
49
- for (const segment of segments) {
50
- if (!segment.length)
51
- continue;
52
- let value = segment;
53
- if (previousValue) {
54
- const prevLast = previousValue.charAt(previousValue.length - 1) || '';
55
- const currFirst = segment.charAt(0) || '';
56
- const prevWord = /[A-Za-z0-9_]$/.test(prevLast);
57
- const currWord = /^[A-Za-z0-9]/.test(currFirst);
58
- const currQuote = currFirst === '"' || currFirst === "'";
59
- const prevPunct = /[:;]/.test(prevLast);
60
- const prevEndsWhitespace = /\s$/.test(previousValue);
61
- const prevSentenceEnd = /[.?!"]$/.test(prevLast);
62
- if (((prevWord || prevSentenceEnd) && currWord && !prevEndsWhitespace) ||
63
- (prevPunct && currQuote)) {
64
- value = ` ${segment}`;
65
- }
66
- }
67
- target.push(value);
68
- previousValue = value;
69
- }
70
- }
1
+ import { buildResponsesPayloadFromChatWithNative } from '../../router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js';
71
2
  export function buildResponsesOutputFromChat(options) {
72
- const { response, message, requestId, sanitizeFunctionName } = options;
73
- const outputItems = [];
74
- const allocateOutputId = (prefix) => `${prefix}_${requestId ?? Date.now()}_${outputItems.length + 1}`;
75
- const role = message?.role || 'assistant';
76
- const content = message?.content;
77
- let toolCalls = Array.isArray(message?.tool_calls) ? message.tool_calls : [];
78
- try {
79
- toolCalls = toolCalls.filter((it) => {
80
- const nm = (it && typeof it === 'object') ? (it?.function?.name || it.name) : undefined;
81
- return typeof nm === 'string' && nm.trim().length > 0 && nm.toLowerCase() !== 'tool';
82
- });
83
- }
84
- catch {
85
- /* ignore */
86
- }
87
- const hasToolCalls = toolCalls.length > 0;
88
- const reasoningChunks = [];
89
- const preservedReasoning = response?.__responses_reasoning;
90
- const preservedSummary = preservedReasoning?.summary ?? [];
91
- const preservedContent = preservedReasoning?.content ?? [];
92
- const preservedEncrypted = preservedReasoning?.encrypted_content;
93
- if (preservedContent.length) {
94
- for (const entry of preservedContent) {
95
- if (!entry || typeof entry !== 'object')
96
- continue;
97
- const text = String(entry.text ?? '').trim();
98
- if (text.length) {
99
- reasoningChunks.push(text);
100
- }
101
- }
102
- }
103
- else {
104
- appendReasoningSegments(reasoningChunks, message?.reasoning_content);
105
- }
106
- const convertedContent = convertChatContentToResponses(content);
107
- const shouldEmitMessage = Boolean(message) && (convertedContent.length > 0 ||
108
- reasoningChunks.length > 0 ||
109
- preservedSummary.length > 0 ||
110
- !hasToolCalls);
111
- if (shouldEmitMessage) {
112
- const responsesMessage = {
113
- id: allocateOutputId('message'),
114
- type: 'message',
115
- status: 'completed',
116
- role,
117
- content: convertedContent.length > 0 ? convertedContent : []
118
- };
119
- const expandedItems = expandResponsesMessageItem(responsesMessage, {
120
- requestId: requestId ?? 'responses_outbound',
121
- outputIndex: outputItems.length,
122
- extraReasoning: reasoningChunks
123
- });
124
- const summaryItems = preservedSummary
125
- .map((entry) => ({ type: 'summary_text', text: String(entry.text ?? '') }))
126
- .filter((entry) => entry.text.trim().length > 0);
127
- let reasoningItem = expandedItems.find((item) => item.type === 'reasoning');
128
- if (!reasoningItem && summaryItems.length > 0) {
129
- reasoningItem = {
130
- id: `${responsesMessage.id}_reasoning`,
131
- type: 'reasoning',
132
- summary: summaryItems,
133
- content: undefined,
134
- ...(typeof preservedEncrypted === 'string' ? { encrypted_content: preservedEncrypted } : {})
135
- };
136
- expandedItems.unshift(reasoningItem);
137
- }
138
- else if (reasoningItem) {
139
- if (summaryItems.length > 0) {
140
- reasoningItem.summary = summaryItems;
141
- }
142
- if (typeof preservedEncrypted === 'string') {
143
- reasoningItem.encrypted_content = preservedEncrypted;
144
- }
145
- }
146
- for (const expanded of expandedItems) {
147
- outputItems.push(expanded);
148
- }
149
- }
150
- const normalizedToolCalls = [];
151
- let toolFallbackCounter = 0;
152
- for (const call of toolCalls) {
153
- const outputEntry = buildFunctionCallOutput(call, allocateOutputId, sanitizeFunctionName, outputItems.length, ++toolFallbackCounter);
154
- if (outputEntry) {
155
- outputItems.push(outputEntry.output);
156
- normalizedToolCalls.push({
157
- id: outputEntry.call_id,
158
- name: outputEntry.name,
159
- args: outputEntry.arguments
160
- });
161
- }
162
- }
163
- const usage = normalizeUsage(response.usage);
164
- const outputTextMeta = response?.__responses_output_text_meta;
165
- const outputText = resolveOutputText(convertedContent, outputTextMeta);
166
- // 如果顶层 tool_outputs 已经为所有 tool_calls 提供了结果,说明这些函数调用
167
- // 已在服务端(例如 server-side web_search)完成,不应再对客户端暴露
168
- // required_action/submit_tool_outputs。此时只需返回 completed 状态即可,避免
169
- // 再触发一轮工具回合。
170
- const executedIds = buildToolOutputIndex(response);
171
- const pendingToolCalls = normalizedToolCalls.filter((entry) => !executedIds.has(entry.id));
172
- const hasNormalizedToolCalls = pendingToolCalls.length > 0;
173
- if (hasNormalizedToolCalls) {
174
- const pendingIds = new Set(pendingToolCalls.map((entry) => entry.id));
175
- for (const item of outputItems) {
176
- const type = item.type;
177
- if (type === 'message') {
178
- item.status = 'in_progress';
179
- }
180
- if (type === 'function_call') {
181
- const callId = item.call_id;
182
- if (typeof callId === 'string' && pendingIds.has(callId)) {
183
- item.status = 'in_progress';
184
- }
185
- }
186
- }
187
- }
188
- const requiredAction = hasNormalizedToolCalls
189
- ? buildRequiredActionFromNormalized(pendingToolCalls)
190
- : undefined;
191
- const status = hasNormalizedToolCalls ? 'requires_action' : 'completed';
3
+ const built = buildResponsesPayloadFromChatWithNative({
4
+ id: options.response.id,
5
+ created_at: options.response.created_at,
6
+ created: options.response.created,
7
+ model: options.response.model,
8
+ usage: options.response.usage,
9
+ request_id: options.response.request_id,
10
+ tool_outputs: options.response.tool_outputs,
11
+ __responses_reasoning: options.response.__responses_reasoning,
12
+ __responses_output_text_meta: options.response.__responses_output_text_meta,
13
+ choices: [
14
+ {
15
+ message: options.message ?? null
16
+ }
17
+ ]
18
+ }, {
19
+ requestId: typeof options.response.request_id === 'string'
20
+ ? options.response.request_id
21
+ : (typeof options.response.id === 'string' ? options.response.id : undefined)
22
+ });
192
23
  return {
193
- outputItems,
194
- outputText,
195
- status,
196
- requiredAction,
197
- usage
24
+ outputItems: Array.isArray(built.output) ? built.output : [],
25
+ outputText: typeof built.output_text === 'string' ? built.output_text : undefined,
26
+ status: typeof built.status === 'string' ? built.status : 'completed',
27
+ requiredAction: built.required_action && typeof built.required_action === 'object' && !Array.isArray(built.required_action)
28
+ ? built.required_action
29
+ : undefined,
30
+ usage: built.usage
198
31
  };
199
32
  }
200
- function normalizeUsage(usageRaw) {
201
- if (usageRaw === null || usageRaw === undefined) {
202
- return undefined;
203
- }
204
- const normalized = normalizeResponsesUsageWithNative(usageRaw);
205
- if (normalized === null) {
206
- return undefined;
207
- }
208
- return normalized;
209
- }
210
- function buildFunctionCallOutput(call, allocateOutputId, sanitizeFunctionName, baseCount, offset) {
211
- try {
212
- const fn = call?.function || {};
213
- const callName = typeof fn?.name === 'string' && fn.name.trim().length
214
- ? fn.name
215
- : (typeof call.name === 'string' ? call.name : 'tool');
216
- const sanitized = sanitizeFunctionName(callName);
217
- if (!sanitized || sanitized.toLowerCase() === 'tool')
218
- return null;
219
- const rawArgs = fn?.arguments ?? call.arguments ?? {};
220
- const argsStr = typeof rawArgs === 'string'
221
- ? rawArgs
222
- : (() => {
223
- try {
224
- return JSON.stringify(rawArgs ?? {});
225
- }
226
- catch {
227
- return '{}';
228
- }
229
- })();
230
- const originalCallId = typeof call.id === 'string' && call.id.trim().length
231
- ? String(call.id)
232
- : (typeof call.call_id === 'string' && call.call_id.trim().length ? String(call.call_id) : undefined);
233
- const stableOriginalCallId = typeof originalCallId === 'string' && originalCallId.trim().length && isStableToolCallId(originalCallId.trim())
234
- ? originalCallId.trim()
235
- : undefined;
236
- // Preserve original tool call IDs when they already follow supported call-id styles.
237
- // Otherwise normalize to fc_* to keep tool_call_id invariants stable across outputs.
238
- const callId = typeof stableOriginalCallId === 'string' && stableOriginalCallId.length
239
- ? stableOriginalCallId
240
- : normalizeFunctionCallId({
241
- callId: originalCallId,
242
- fallback: `fc_call_${baseCount + offset}`
243
- });
244
- const outputId = normalizeFunctionCallOutputId({
245
- callId,
246
- fallback: allocateOutputId('fc')
247
- });
248
- const output = {
249
- id: outputId,
250
- type: 'function_call',
251
- status: 'completed',
252
- name: sanitized,
253
- call_id: callId,
254
- arguments: argsStr
255
- };
256
- return { output, call_id: callId, name: sanitized, arguments: argsStr };
257
- }
258
- catch {
259
- return null;
260
- }
261
- }
262
- function buildRequiredActionFromNormalized(calls) {
263
- if (!calls.length)
264
- return undefined;
265
- const submitCalls = calls.map((entry) => ({
266
- // Internal + client compat:
267
- // - keep OpenAI-style `id` + `function`
268
- // - also expose `tool_call_id` + top-level `name` for legacy/bridge clients
269
- id: entry.id,
270
- tool_call_id: entry.id,
271
- type: 'function',
272
- name: entry.name,
273
- arguments: entry.args,
274
- function: {
275
- name: entry.name,
276
- arguments: entry.args
277
- }
278
- }));
279
- return { type: 'submit_tool_outputs', submit_tool_outputs: { tool_calls: submitCalls } };
280
- }
281
- function convertChatContentToResponses(content) {
282
- if (content == null)
283
- return [];
284
- if (Array.isArray(content)) {
285
- return content
286
- .map((part) => normalizeContentPart(part, []))
287
- .filter((entry) => !!entry);
288
- }
289
- const normalized = normalizeContentPart(content, []);
290
- return normalized ? [normalized] : [];
291
- }
292
- function extractOutputText(parts) {
293
- if (!parts.length)
294
- return '';
295
- const reasoningCollector = [];
296
- const normalizedTexts = parts
297
- .map((part) => normalizeContentPart(part, reasoningCollector))
298
- .filter((entry) => !!entry)
299
- .map((entry) => (typeof entry.text === 'string' ? entry.text : ''))
300
- .filter(Boolean);
301
- const text = normalizedTexts.join('\n').trim();
302
- return text.length ? text : '';
303
- }
304
- function resolveOutputText(parts, meta) {
305
- if (meta && typeof meta === 'object') {
306
- const hasField = Boolean(meta.hasField);
307
- if (hasField) {
308
- const rawValue = meta.raw;
309
- if (typeof rawValue === 'string') {
310
- return rawValue;
311
- }
312
- const fallbackValue = meta.value;
313
- if (typeof fallbackValue === 'string') {
314
- return fallbackValue;
315
- }
316
- return '';
317
- }
318
- return undefined;
319
- }
320
- const text = extractOutputText(parts);
321
- return text.length ? text : undefined;
322
- }