@jsonstudio/llms 0.6.3405 → 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.
- package/dist/conversion/codecs/anthropic-openai-codec.d.ts +12 -3
- package/dist/conversion/codecs/anthropic-openai-codec.js +32 -92
- package/dist/conversion/codecs/gemini-openai-codec.d.ts +6 -5
- package/dist/conversion/codecs/gemini-openai-codec.js +48 -685
- package/dist/conversion/codecs/openai-openai-codec.d.ts +1 -1
- package/dist/conversion/codecs/openai-openai-codec.js +34 -100
- package/dist/conversion/codecs/responses-openai-codec.d.ts +1 -1
- package/dist/conversion/codecs/responses-openai-codec.js +47 -159
- package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.d.ts +2 -6
- package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.js +29 -245
- package/dist/conversion/compat/actions/anthropic-claude-code-user-id.d.ts +3 -0
- package/dist/conversion/compat/actions/anthropic-claude-code-user-id.js +30 -0
- package/dist/conversion/compat/actions/antigravity-thought-signature-prepare.js +21 -232
- package/dist/conversion/compat/actions/deepseek-web-request.js +41 -276
- package/dist/conversion/compat/actions/deepseek-web-response.js +64 -859
- package/dist/conversion/compat/actions/gemini-cli-request.d.ts +1 -1
- package/dist/conversion/compat/actions/gemini-cli-request.js +20 -613
- package/dist/conversion/compat/actions/gemini-web-search.d.ts +1 -15
- package/dist/conversion/compat/actions/gemini-web-search.js +22 -69
- package/dist/conversion/compat/actions/glm-tool-extraction.d.ts +3 -2
- package/dist/conversion/compat/actions/glm-tool-extraction.js +28 -257
- package/dist/conversion/compat/actions/iflow-tool-text-fallback.d.ts +0 -8
- package/dist/conversion/compat/actions/iflow-tool-text-fallback.js +24 -206
- package/dist/conversion/compat/actions/qwen-transform.d.ts +3 -2
- package/dist/conversion/compat/actions/qwen-transform.js +30 -271
- package/dist/conversion/compat/actions/tool-text-request-guidance.js +3 -173
- package/dist/conversion/compat/actions/universal-shape-filter.d.ts +6 -23
- package/dist/conversion/compat/actions/universal-shape-filter.js +4 -383
- package/dist/conversion/hub/pipeline/compat/native-adapter-context.js +1 -0
- package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.d.ts +1 -2
- package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +50 -104
- package/dist/conversion/pipeline/codecs/v2/openai-openai-pipeline.js +12 -10
- package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.d.ts +0 -2
- package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.js +46 -67
- package/dist/conversion/pipeline/codecs/v2/shared/openai-chat-helpers.js +15 -40
- package/dist/conversion/responses/responses-openai-bridge/response-payload.js +47 -348
- package/dist/conversion/responses/responses-openai-bridge.js +129 -611
- package/dist/conversion/shared/chat-output-normalizer.js +6 -0
- package/dist/conversion/shared/chat-request-filters.js +1 -1
- package/dist/conversion/shared/output-content-normalizer.js +10 -0
- package/dist/conversion/shared/responses-conversation-store.js +22 -135
- package/dist/conversion/shared/responses-output-builder.d.ts +0 -2
- package/dist/conversion/shared/responses-output-builder.js +28 -318
- package/dist/conversion/shared/responses-response-utils.js +35 -86
- package/dist/conversion/shared/streaming-text-extractor.d.ts +1 -2
- package/dist/conversion/shared/streaming-text-extractor.js +13 -14
- package/dist/conversion/shared/tool-call-id-manager.js +18 -21
- package/dist/native/router_hotpath_napi.node +0 -0
- package/dist/router/virtual-router/bootstrap/routing-config.d.ts +2 -1
- package/dist/router/virtual-router/bootstrap/routing-config.js +57 -4
- package/dist/router/virtual-router/engine-legacy.d.ts +3 -3
- package/dist/router/virtual-router/engine-legacy.js +15 -7
- package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.d.ts +16 -0
- package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.js +434 -46
- package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.d.ts +83 -0
- package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js +295 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.d.ts +1 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.d.ts +7 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js +8 -1
- package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +383 -298
- package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.d.ts +20 -0
- package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +201 -0
- package/dist/router/virtual-router/engine-selection/native-virtual-router-routing-instructions-semantics.d.ts +1 -0
- package/dist/router/virtual-router/engine-selection/native-virtual-router-routing-instructions-semantics.js +37 -0
- package/dist/router/virtual-router/engine-selection/tier-load-balancing.d.ts +16 -0
- package/dist/router/virtual-router/engine-selection/tier-load-balancing.js +120 -0
- package/dist/router/virtual-router/engine-selection/tier-selection-quota-integration.d.ts +2 -0
- package/dist/router/virtual-router/engine-selection/tier-selection-quota-integration.js +44 -66
- package/dist/router/virtual-router/engine-selection/tier-selection-select.js +53 -84
- package/dist/router/virtual-router/engine.js +0 -38
- package/dist/router/virtual-router/features.js +44 -3
- package/dist/router/virtual-router/routing-instructions/parse.d.ts +0 -12
- package/dist/router/virtual-router/routing-instructions/parse.js +9 -389
- package/dist/router/virtual-router/stop-message-state-sync.d.ts +3 -6
- package/dist/router/virtual-router/stop-message-state-sync.js +50 -21
- package/dist/router/virtual-router/types.d.ts +16 -0
- package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +1 -0
- package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.js +26 -0
- package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +12 -2
- package/package.json +1 -1
- package/dist/router/virtual-router/engine-legacy/route-finalize.d.ts +0 -9
- package/dist/router/virtual-router/engine-legacy/route-finalize.js +0 -84
- package/dist/router/virtual-router/engine-legacy/route-selection.d.ts +0 -17
- package/dist/router/virtual-router/engine-legacy/route-selection.js +0 -205
- package/dist/router/virtual-router/engine-legacy/route-state-allowlist.d.ts +0 -3
- package/dist/router/virtual-router/engine-legacy/route-state-allowlist.js +0 -36
- package/dist/router/virtual-router/engine-legacy/route-state.d.ts +0 -12
- package/dist/router/virtual-router/engine-legacy/route-state.js +0 -386
- package/dist/router/virtual-router/engine-legacy/routing.d.ts +0 -8
- 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 '
|
|
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 {
|
|
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
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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:
|
|
108
|
-
tools:
|
|
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
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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 {
|
|
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
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
}
|