@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,238 +1,43 @@
1
- import fs from 'node:fs';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
1
  import { ensureBridgeInstructions } from '../bridge-instructions.js';
5
2
  import { evaluateResponsesHostPolicy } from './responses-host-policy.js';
6
3
  import { convertBridgeInputToChatMessages } from '../bridge-message-utils.js';
7
- import { createToolCallIdTransformer, enforceToolCallIdStyle, resolveToolCallIdStyle, sanitizeResponsesFunctionName } from '../shared/responses-tool-utils.js';
4
+ import { createToolCallIdTransformer, enforceToolCallIdStyle, sanitizeResponsesFunctionName } from '../shared/responses-tool-utils.js';
8
5
  import { mapChatToolsToBridge } from '../shared/tool-mapping.js';
9
6
  import { ProviderProtocolError } from '../provider-protocol-error.js';
10
- import { readRuntimeMetadata } from '../runtime-metadata.js';
11
- import { clampResponsesInputItemId } from '../bridge-id-utils.js';
12
- import { isImagePath } from '../media.js';
13
7
  import { captureReqInboundResponsesContextSnapshotWithNative, mapReqInboundBridgeToolsToChatWithNative } from '../../router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.js';
14
- import { buildBridgeHistoryWithNative } from '../../router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js';
8
+ import { appendLocalImageBlockOnLatestUserInputWithNative, buildBridgeHistoryWithNative, filterBridgeInputForUpstreamWithNative, normalizeBridgeHistorySeedWithNative, prepareResponsesRequestEnvelopeWithNative, resolveResponsesRequestBridgeDecisionsWithNative, resolveResponsesBridgeToolsWithNative, runBridgeActionPipelineWithNative } from '../../router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js';
15
9
  // --- Utilities (ported strictly) ---
16
- import { createBridgeActionState, runBridgeActionPipeline } from '../bridge-actions.js';
17
10
  import { resolveBridgePolicy, resolvePolicyActions } from '../bridge-policies.js';
18
11
  function isObject(v) {
19
12
  return !!v && typeof v === 'object' && !Array.isArray(v);
20
13
  }
21
- function filterBridgeInputForUpstream(input, options) {
22
- // Upstream `/v1/responses` create only accepts a subset of input item types.
23
- // In particular, `type:"reasoning"` entries are output-only artifacts (often
24
- // captured from previous responses) and OpenAI rejects them with schema errors
25
- // like `input[N].content: array too long (max 0)`.
26
- return (Array.isArray(input) ? input : []).flatMap((item) => {
27
- if (!item || typeof item !== 'object')
28
- return [];
29
- if (item.type === 'reasoning') {
30
- return [];
31
- }
32
- // OpenAI `/v1/responses` request schema uses `call_id` for tool outputs.
33
- // Some internal carriers may set `tool_call_id`; strip it before sending upstream
34
- // to avoid strict schema errors (e.g. "Unknown parameter: input[N].tool_call_id").
35
- const clone = { ...item };
36
- if (options?.allowToolCallId !== true && clone.tool_call_id !== undefined) {
37
- delete clone.tool_call_id;
38
- }
39
- // OpenAI /v1/responses enforces max length (64) on input item id fields.
40
- // Keep this as the single outbound guardrail for all bridged input item types.
41
- const normalizedId = clampResponsesInputItemId(clone.id);
42
- if (normalizedId) {
43
- clone.id = normalizedId;
44
- }
45
- return [clone];
46
- });
47
- }
48
- // normalizeTools unified in ../args-mapping.ts
49
- // --- Structured self-repair helpers for tool failures (Responses path) ---
50
- const IMAGE_MIME_BY_EXT = {
51
- '.png': 'image/png',
52
- '.jpg': 'image/jpeg',
53
- '.jpeg': 'image/jpeg',
54
- '.gif': 'image/gif',
55
- '.webp': 'image/webp',
56
- '.bmp': 'image/bmp',
57
- '.svg': 'image/svg+xml',
58
- '.tif': 'image/tiff',
59
- '.tiff': 'image/tiff',
60
- '.ico': 'image/x-icon',
61
- '.heic': 'image/heic',
62
- '.jxl': 'image/jxl'
63
- };
64
- function decodeEscapedPathLikeText(input) {
65
- if (!input || input.indexOf('\\') < 0) {
66
- return input;
67
- }
68
- return input
69
- .replace(/\\\//g, '/')
70
- .replace(/\\u([0-9a-fA-F]{4})/g, (_m, hex) => {
71
- const codepoint = Number.parseInt(hex, 16);
72
- return Number.isFinite(codepoint) ? String.fromCharCode(codepoint) : '';
73
- })
74
- .replace(/\\x([0-9a-fA-F]{2})/g, (_m, hex) => {
75
- const codepoint = Number.parseInt(hex, 16);
76
- return Number.isFinite(codepoint) ? String.fromCharCode(codepoint) : '';
77
- })
78
- .replace(/\\\\/g, '\\');
79
- }
80
- function normalizeLocalImagePath(candidate) {
81
- if (!candidate || typeof candidate !== 'string')
82
- return null;
83
- let value = decodeEscapedPathLikeText(candidate.trim());
84
- if (!value)
85
- return null;
86
- if (/^https?:\/\//i.test(value))
87
- return null;
88
- if (value.toLowerCase().startsWith('file://')) {
89
- try {
90
- const parsed = new URL(value);
91
- value = decodeURIComponent(parsed.pathname || '');
92
- }
93
- catch {
94
- return null;
95
- }
96
- }
97
- if (!value)
98
- return null;
99
- if (value.startsWith('~')) {
100
- value = path.join(os.homedir(), value.slice(1));
101
- }
102
- const isAbsolutePath = path.isAbsolute(value);
103
- const normalized = isAbsolutePath ? path.normalize(value) : path.resolve(process.cwd(), value);
104
- return isImagePath(normalized) ? normalized : null;
14
+ function readCapturedToolResults(context) {
15
+ const raw = context.__captured_tool_results;
16
+ return Array.isArray(raw)
17
+ ? raw.filter((entry) => Boolean(entry && typeof entry === 'object' && !Array.isArray(entry)))
18
+ : undefined;
105
19
  }
106
- function collectLocalImagePathCandidates(text) {
107
- if (typeof text !== 'string' || !text.trim())
108
- return [];
109
- const candidates = new Set();
110
- const variants = [text, decodeEscapedPathLikeText(text)].filter(Boolean);
111
- const quotedPathRegex = /(["'`])((?:\\.|(?!\1).)+)\1/g;
112
- const barePathRegex = /(?:^|[\s(])((?:~|\/|\.\.?\/)[^\s"'`<>]+?\.(?:png|jpg|jpeg|gif|webp|bmp|svg|tiff?|ico|heic|jxl))(?=$|[\s),.;!?])/gi;
113
- for (const variant of variants) {
114
- let quotedMatch;
115
- quotedPathRegex.lastIndex = 0;
116
- while ((quotedMatch = quotedPathRegex.exec(variant)) !== null) {
117
- const normalized = normalizeLocalImagePath(quotedMatch[2] || '');
118
- if (normalized)
119
- candidates.add(normalized);
20
+ function runNativeResponsesBridgePipeline(input) {
21
+ const output = runBridgeActionPipelineWithNative({
22
+ stage: input.stage,
23
+ actions: input.actions,
24
+ protocol: input.protocol,
25
+ moduleType: input.moduleType,
26
+ requestId: input.requestId,
27
+ state: {
28
+ messages: input.messages,
29
+ ...(Array.isArray(input.capturedToolResults) ? { capturedToolResults: input.capturedToolResults } : {}),
30
+ ...(input.rawRequest ? { rawRequest: input.rawRequest } : {})
120
31
  }
121
- let bareMatch;
122
- barePathRegex.lastIndex = 0;
123
- while ((bareMatch = barePathRegex.exec(variant)) !== null) {
124
- const normalized = normalizeLocalImagePath(bareMatch[1] || '');
125
- if (normalized)
126
- candidates.add(normalized);
127
- }
128
- }
129
- return Array.from(candidates);
130
- }
131
- function detectImageMime(filePath) {
132
- const ext = path.extname(filePath).toLowerCase();
133
- return IMAGE_MIME_BY_EXT[ext] || 'application/octet-stream';
134
- }
135
- function encodeLocalImageAsDataUrl(filePath) {
136
- const imageBuffer = fs.readFileSync(filePath);
137
- const mimeType = detectImageMime(filePath);
138
- return `data:${mimeType};base64,${imageBuffer.toString('base64')}`;
139
- }
140
- function messageHasImageContent(content) {
141
- if (!Array.isArray(content))
142
- return false;
143
- return content.some((part) => {
144
- if (!part || typeof part !== 'object')
145
- return false;
146
- const type = String(part.type || '').toLowerCase();
147
- if (type !== 'image_url' && type !== 'input_image' && type !== 'image')
148
- return false;
149
- const imageUrl = part.image_url;
150
- return typeof imageUrl === 'string' || Boolean(imageUrl && typeof imageUrl === 'object' && typeof imageUrl.url === 'string');
151
32
  });
33
+ return {
34
+ messages: Array.isArray(output?.messages) ? output.messages : input.messages,
35
+ metadata: output?.metadata && typeof output.metadata === 'object' && !Array.isArray(output.metadata)
36
+ ? output.metadata
37
+ : undefined
38
+ };
152
39
  }
153
- function appendLocalImageBlockOnLatestUserInput(messages, context) {
154
- if (!Array.isArray(messages) || !messages.length)
155
- return;
156
- let latestUserIndex = -1;
157
- for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
158
- const role = String(messages[idx]?.role || '').toLowerCase();
159
- if (role === 'user') {
160
- latestUserIndex = idx;
161
- break;
162
- }
163
- }
164
- if (latestUserIndex < 0)
165
- return;
166
- const latestUserMessage = messages[latestUserIndex];
167
- const originalContent = latestUserMessage.content;
168
- if (messageHasImageContent(originalContent))
169
- return;
170
- const textCandidates = [];
171
- if (typeof originalContent === 'string' && originalContent.trim().length) {
172
- textCandidates.push(originalContent);
173
- }
174
- else if (Array.isArray(originalContent)) {
175
- for (const part of originalContent) {
176
- if (!part || typeof part !== 'object')
177
- continue;
178
- const type = String(part.type || '').toLowerCase();
179
- if ((type === 'text' || type === 'input_text' || type === 'output_text' || type === 'commentary') &&
180
- typeof part.text === 'string' &&
181
- part.text.trim().length) {
182
- textCandidates.push(part.text);
183
- }
184
- }
185
- }
186
- if (!textCandidates.length)
187
- return;
188
- const imagePaths = new Set();
189
- for (const text of textCandidates) {
190
- for (const candidate of collectLocalImagePathCandidates(text)) {
191
- imagePaths.add(candidate);
192
- }
193
- }
194
- if (!imagePaths.size)
195
- return;
196
- const normalizedContent = [];
197
- if (typeof originalContent === 'string') {
198
- normalizedContent.push({ type: 'text', text: originalContent });
199
- }
200
- else if (Array.isArray(originalContent)) {
201
- for (const part of originalContent) {
202
- if (part && typeof part === 'object') {
203
- normalizedContent.push({ ...part });
204
- }
205
- else if (typeof part === 'string') {
206
- normalizedContent.push({ type: 'text', text: part });
207
- }
208
- }
209
- }
210
- const unreadableImageNotices = [];
211
- for (const imagePath of imagePaths) {
212
- let dataUrl = '';
213
- try {
214
- dataUrl = encodeLocalImageAsDataUrl(imagePath);
215
- }
216
- catch (error) {
217
- const reason = error instanceof Error
218
- ? `${error.code ?? 'READ_FAILED'}: ${error.message}`
219
- : String(error ?? 'READ_FAILED');
220
- unreadableImageNotices.push(`[local_image_unreadable] 文件不可读,已跳过该图片路径: ${imagePath} (${reason})`);
221
- continue;
222
- }
223
- normalizedContent.push({ type: 'image_url', image_url: { url: dataUrl } });
224
- }
225
- if (unreadableImageNotices.length) {
226
- normalizedContent.push({
227
- type: 'text',
228
- text: unreadableImageNotices.join('\n')
229
- });
230
- }
231
- if (!messageHasImageContent(normalizedContent) && !unreadableImageNotices.length) {
232
- return;
233
- }
234
- latestUserMessage.content = normalizedContent;
235
- }
40
+ // normalizeTools unified in ../args-mapping.ts
236
41
  // NOTE: 自修复提示已移除(统一标准:不做模糊兜底)。
237
42
  // --- Public bridge functions ---
238
43
  export function captureResponsesContext(payload, dto) {
@@ -257,7 +62,7 @@ export function captureResponsesContext(payload, dto) {
257
62
  captured.__rcc_reasoning_instructions_segments = normalized;
258
63
  }
259
64
  }
260
- if (preservedInput) {
65
+ if (preservedInput && (!Array.isArray(captured.input) || captured.input.length === 0)) {
261
66
  captured.input = preservedInput;
262
67
  }
263
68
  if (!captured.systemInstruction && typeof payload.instructions === 'string' && payload.instructions.trim().length) {
@@ -276,25 +81,22 @@ export function buildChatRequestFromResponses(payload, context) {
276
81
  let messages = convertBridgeInputToChatMessages({
277
82
  input: context.input,
278
83
  tools: toolsNormalized,
279
- normalizeFunctionName: sanitizeResponsesFunctionName,
84
+ normalizeFunctionName: 'responses',
280
85
  toolResultFallbackText: 'Command succeeded (no output).'
281
86
  });
282
87
  try {
283
88
  const bridgePolicy = resolveBridgePolicy({ protocol: 'openai-responses', moduleType: 'openai-responses' });
284
89
  const policyActions = resolvePolicyActions(bridgePolicy, 'request_inbound');
285
90
  if (policyActions?.length) {
286
- const actionState = createBridgeActionState({
287
- messages,
288
- requiredAction: typeof payload?.required_action === 'object' ? payload.required_action : undefined,
289
- rawRequest: payload
290
- });
291
- runBridgeActionPipeline({
91
+ const actionState = runNativeResponsesBridgePipeline({
292
92
  stage: 'request_inbound',
293
93
  actions: policyActions,
294
- protocol: bridgePolicy?.protocol ?? 'openai-responses',
295
- moduleType: bridgePolicy?.moduleType ?? 'openai-responses',
94
+ protocol: (bridgePolicy?.protocol ?? 'openai-responses'),
95
+ moduleType: (bridgePolicy?.moduleType ?? 'openai-responses'),
296
96
  requestId: context.requestId,
297
- state: actionState
97
+ messages,
98
+ capturedToolResults: readCapturedToolResults(context),
99
+ rawRequest: payload
298
100
  });
299
101
  messages = actionState.messages;
300
102
  }
@@ -311,7 +113,7 @@ export function buildChatRequestFromResponses(payload, context) {
311
113
  messages = [...preservedSystems, ...nonSystemMessages];
312
114
  }
313
115
  }
314
- appendLocalImageBlockOnLatestUserInput(messages, context);
116
+ messages = appendLocalImageBlockOnLatestUserInputWithNative({ messages }).messages;
315
117
  // 不在 Responses 路径做工具治理;统一在 Chat 后半段处理
316
118
  // No system tips for MCP on OpenAI Responses path (avoid leaking tool names)
317
119
  if (!messages.length) {
@@ -354,207 +156,34 @@ export function buildChatRequestFromResponses(payload, context) {
354
156
  * - 将 user/assistant/tool 消息编码为 input[] 中的 message 块,使得 mapResponsesInputToChat 能够还原为等价 Chat 请求。
355
157
  */
356
158
  function normalizeBridgeHistory(seed) {
357
- if (!seed || typeof seed !== 'object') {
358
- return undefined;
359
- }
360
- const record = seed;
361
- if (!Array.isArray(record.input)) {
159
+ if (!seed || typeof seed !== 'object' || Array.isArray(seed)) {
362
160
  return undefined;
363
161
  }
364
- const systemMessages = Array.isArray(record.originalSystemMessages)
365
- ? record.originalSystemMessages.filter((entry) => typeof entry === 'string' && entry.trim().length > 0)
366
- : [];
367
- return {
368
- input: record.input,
369
- combinedSystemInstruction: typeof record.combinedSystemInstruction === 'string' && record.combinedSystemInstruction.trim().length
370
- ? record.combinedSystemInstruction
371
- : undefined,
372
- latestUserInstruction: typeof record.latestUserInstruction === 'string' && record.latestUserInstruction.trim().length
373
- ? record.latestUserInstruction
374
- : undefined,
375
- originalSystemMessages: systemMessages
376
- };
377
- }
378
- function stripRoutingTagsFromText(text) {
379
- if (typeof text !== 'string') {
380
- return typeof text === 'string' ? text : String(text ?? '');
381
- }
382
- // NOTE: Do not strip <**...**> here. Routing instructions must reach the
383
- // virtual router so allowlist/sticky directives can be applied. The router
384
- // will clean them from messages after parsing.
385
- return text;
386
- }
387
- function sanitizeBridgeHistory(history) {
388
- if (!history || typeof history !== 'object') {
389
- return history;
390
- }
391
- if (Array.isArray(history.input)) {
392
- for (const entry of history.input) {
393
- if (!entry || typeof entry !== 'object')
394
- continue;
395
- const blocks = entry.content;
396
- if (!Array.isArray(blocks))
397
- continue;
398
- for (const block of blocks) {
399
- if (!block || typeof block !== 'object')
400
- continue;
401
- const record = block;
402
- if (typeof record.text === 'string') {
403
- record.text = stripRoutingTagsFromText(record.text);
404
- }
405
- }
406
- }
407
- }
408
- if (typeof history.combinedSystemInstruction === 'string') {
409
- history.combinedSystemInstruction = stripRoutingTagsFromText(history.combinedSystemInstruction);
410
- }
411
- if (typeof history.latestUserInstruction === 'string') {
412
- history.latestUserInstruction = stripRoutingTagsFromText(history.latestUserInstruction);
413
- }
414
- if (Array.isArray(history.originalSystemMessages)) {
415
- history.originalSystemMessages = history.originalSystemMessages.map((msg) => stripRoutingTagsFromText(msg));
416
- }
417
- return history;
418
- }
419
- function mergeResponsesTools(originalTools, fromChat) {
420
- const result = [];
421
- const byKey = new Map();
422
- const norm = (value) => {
423
- if (typeof value !== 'string')
424
- return undefined;
425
- const trimmed = value.trim();
426
- return trimmed.length ? trimmed.toLowerCase() : undefined;
427
- };
428
- const register = (tool) => {
429
- const fn = tool.function ?? tool.function;
430
- const baseName = norm(fn?.name ?? tool.name);
431
- const typeName = norm(tool.type);
432
- const key = baseName || typeName;
433
- if (!key || byKey.has(key))
434
- return;
435
- byKey.set(key, tool);
436
- result.push(tool);
437
- };
438
- if (Array.isArray(fromChat)) {
439
- for (const t of fromChat) {
440
- if (t && typeof t === 'object') {
441
- register(t);
442
- }
443
- }
444
- }
445
- if (Array.isArray(originalTools)) {
446
- for (const t of originalTools) {
447
- if (!t || typeof t !== 'object')
448
- continue;
449
- const typeName = norm(t.type);
450
- const isWebSearch = typeName === 'web_search' || (typeof typeName === 'string' && typeName.startsWith('web_search'));
451
- if (!isWebSearch) {
452
- // 目前仅恢复原始载荷中的 builtin web_search 工具,其它非函数工具保持忽略,避免意外改变行为。
453
- continue;
454
- }
455
- register(t);
456
- }
457
- }
458
- return result.length ? result : undefined;
162
+ return normalizeBridgeHistorySeedWithNative(seed);
459
163
  }
460
164
  export function buildResponsesRequestFromChat(payload, ctx, extras) {
461
165
  const chat = unwrapData(payload);
462
166
  const out = {};
463
- const forceWebSearch = (() => {
464
- if (!ctx || typeof ctx !== 'object') {
465
- return false;
466
- }
467
- const metaCarrier = ctx.metadata && typeof ctx.metadata === 'object'
468
- ? ctx.metadata
469
- : undefined;
470
- const rt = readRuntimeMetadata(metaCarrier ?? null);
471
- if (rt && typeof rt === 'object') {
472
- if (rt.forceWebSearch === true) {
473
- return true;
474
- }
475
- const webSearch = rt.webSearch;
476
- if (webSearch && typeof webSearch === 'object' && webSearch.force === true) {
477
- return true;
478
- }
479
- }
480
- return false;
481
- })();
167
+ const envelopeMetadata = ctx?.metadata && typeof ctx.metadata === 'object' ? ctx.metadata : undefined;
168
+ const requestMetadata = chat && typeof chat === 'object' && chat.metadata && typeof chat.metadata === 'object'
169
+ ? chat.metadata
170
+ : undefined;
482
171
  // 基本字段
483
172
  out.model = chat.model;
484
- // tools: 反向映射为 ResponsesToolDefinition 形状
485
- const chatTools = Array.isArray(chat.tools) ? chat.tools : [];
486
- // 对于 openai-responses upstream,内建 web_search 由官方服务器处理。
487
- // Chat 侧注入的 server-side web_search 函数(带 engine/query/recency/count)
488
- // 仅用于非 Responses provider 的 server-tool 回环;在这里构造真正的
489
- // `/v1/responses` 请求时,需要:
490
- // 1) 不再把函数版 web_search 透传上游;
491
- // 2) 若检测到 Chat 侧启用了 web_search 且原始请求中没有 builtin web_search,
492
- // 则补一个 `{ type: "web_search" }` 内建工具给 OpenAI Responses。
493
- const hasServerSideWebSearch = !forceWebSearch && chatTools.some((tool) => {
494
- const fn = tool && typeof tool === 'object' ? tool.function : undefined;
495
- const name = typeof fn?.name === 'string' ? fn.name.trim().toLowerCase() : '';
496
- return name === 'web_search';
497
- });
498
- const toolsForBridge = hasServerSideWebSearch
499
- ? chatTools.filter((tool) => {
500
- const fn = tool && typeof tool === 'object' ? tool.function : undefined;
501
- const name = typeof fn?.name === 'string' ? fn.name.trim().toLowerCase() : '';
502
- return name !== 'web_search';
503
- })
504
- : chatTools;
505
- const responsesToolsFromChat = mapChatToolsToBridge(toolsForBridge, {
506
- sanitizeName: sanitizeResponsesFunctionName
507
- });
508
- // Prefer Chat‑normalized tools, but if the original Responses payload carried
509
- // non‑function tools (such as builtin `web_search`), merge them back so that
510
- // upstream `/v1/responses` providers see their original tool definitions.
511
- const originalTools = Array.isArray(ctx?.toolsRaw) ? ctx.toolsRaw : undefined;
512
- let mergedTools = mergeResponsesTools(originalTools, responsesToolsFromChat);
513
- if (hasServerSideWebSearch) {
514
- const normalizeType = (value) => typeof value === 'string' ? value.trim().toLowerCase() : '';
515
- const hasBuiltinWebSearch = (mergedTools && mergedTools.some((tool) => normalizeType(tool.type) === 'web_search')) ||
516
- (originalTools && originalTools.some((tool) => normalizeType(tool.type) === 'web_search'));
517
- if (!hasBuiltinWebSearch) {
518
- const injected = { type: 'web_search' };
519
- mergedTools = mergedTools ? [...mergedTools, injected] : [injected];
520
- }
521
- }
522
- if (mergedTools?.length) {
523
- out.tools = mergedTools;
524
- }
525
- const passthroughKeys = [
526
- 'temperature',
527
- 'tool_choice',
528
- 'parallel_tool_calls',
529
- 'response_format',
530
- 'user',
531
- 'top_p',
532
- 'prompt_cache_key',
533
- 'reasoning',
534
- 'logit_bias',
535
- 'seed'
536
- ];
537
- for (const key of passthroughKeys) {
538
- if (chat[key] !== undefined)
539
- out[key] = chat[key];
540
- }
541
173
  let messages = Array.isArray(chat.messages) ? chat.messages : [];
542
174
  let bridgeMetadata;
543
175
  try {
544
176
  const bridgePolicy = resolveBridgePolicy({ protocol: 'openai-responses', moduleType: 'openai-responses' });
545
177
  const policyActions = resolvePolicyActions(bridgePolicy, 'request_outbound');
546
178
  if (policyActions?.length) {
547
- const actionState = createBridgeActionState({
548
- messages,
549
- rawRequest: chat
550
- });
551
- runBridgeActionPipeline({
179
+ const actionState = runNativeResponsesBridgePipeline({
552
180
  stage: 'request_outbound',
553
181
  actions: policyActions,
554
- protocol: bridgePolicy?.protocol ?? 'openai-responses',
555
- moduleType: bridgePolicy?.moduleType ?? 'openai-responses',
182
+ protocol: (bridgePolicy?.protocol ?? 'openai-responses'),
183
+ moduleType: (bridgePolicy?.moduleType ?? 'openai-responses'),
556
184
  requestId: ctx?.requestId,
557
- state: actionState
185
+ messages,
186
+ rawRequest: chat
558
187
  });
559
188
  messages = actionState.messages;
560
189
  if (actionState.metadata && Object.keys(actionState.metadata).length) {
@@ -565,78 +194,67 @@ export function buildResponsesRequestFromChat(payload, ctx, extras) {
565
194
  catch {
566
195
  // ignore policy errors
567
196
  }
568
- const envelopeMetadata = ctx?.metadata && typeof ctx.metadata === 'object' ? ctx.metadata : undefined;
569
197
  const metadataExtraFields = extractMetadataExtraFields(envelopeMetadata);
570
- const contextToolCallIdStyle = readToolCallIdStyleFromContext(ctx);
571
- const envelopeToolCallIdStyle = resolveToolCallIdStyle(envelopeMetadata);
572
- const requestMetadata = chat && typeof chat === 'object' && chat.metadata && typeof chat.metadata === 'object'
573
- ? chat.metadata
574
- : undefined;
575
- const routeToolCallIdStyle = requestMetadata
576
- ? normalizeToolCallIdStyleCandidate(requestMetadata.toolCallIdStyle)
577
- : undefined;
578
- // Route-selected toolCallIdStyle must win over captured context to prevent cross-provider leakage
579
- // (e.g. LM Studio "preserve" contaminating OpenAI "fc").
580
- const toolCallIdStyle = routeToolCallIdStyle ?? envelopeToolCallIdStyle ?? contextToolCallIdStyle;
581
- const fallbackHistory = ctx?.input && Array.isArray(ctx.input)
582
- ? {
583
- input: ctx.input,
584
- originalSystemMessages: ctx.originalSystemMessages,
585
- combinedSystemInstruction: ctx.systemInstruction
198
+ const bridgeDecisions = resolveResponsesRequestBridgeDecisionsWithNative({
199
+ context: ctx && typeof ctx === 'object' ? ctx : undefined,
200
+ requestMetadata,
201
+ envelopeMetadata,
202
+ bridgeMetadata,
203
+ extraBridgeHistory: extras?.bridgeHistory
204
+ });
205
+ const forceWebSearch = bridgeDecisions.forceWebSearch === true;
206
+ const toolCallIdStyle = bridgeDecisions.toolCallIdStyle;
207
+ const historySeed = bridgeDecisions.historySeed;
208
+ // tools: 反向映射为 ResponsesToolDefinition 形状
209
+ const chatTools = Array.isArray(chat.tools) ? chat.tools : [];
210
+ const responsesToolsFromChat = mapChatToolsToBridge(chatTools, {
211
+ sanitizeName: sanitizeResponsesFunctionName
212
+ });
213
+ const originalTools = Array.isArray(ctx?.toolsRaw) ? ctx.toolsRaw : undefined;
214
+ const resolvedBridgeTools = resolveResponsesBridgeToolsWithNative({
215
+ originalTools: Array.isArray(originalTools) ? originalTools : undefined,
216
+ chatTools: Array.isArray(responsesToolsFromChat) ? responsesToolsFromChat : undefined,
217
+ hasServerSideWebSearch: !forceWebSearch,
218
+ passthroughKeys: [
219
+ 'temperature',
220
+ 'tool_choice',
221
+ 'parallel_tool_calls',
222
+ 'response_format',
223
+ 'user',
224
+ 'top_p',
225
+ 'prompt_cache_key',
226
+ 'reasoning',
227
+ 'logit_bias',
228
+ 'seed'
229
+ ],
230
+ request: chat
231
+ });
232
+ const mergedTools = resolvedBridgeTools.mergedTools;
233
+ if (mergedTools?.length) {
234
+ out.tools = mergedTools;
235
+ }
236
+ if (resolvedBridgeTools.request && typeof resolvedBridgeTools.request === 'object') {
237
+ for (const [key, value] of Object.entries(resolvedBridgeTools.request)) {
238
+ if (out[key] === undefined) {
239
+ out[key] = value;
240
+ }
586
241
  }
587
- : undefined;
588
- const historySeed = sanitizeBridgeHistory(normalizeBridgeHistory(extras?.bridgeHistory)) ??
589
- sanitizeBridgeHistory(normalizeBridgeHistory(fallbackHistory)) ??
590
- sanitizeBridgeHistory(normalizeBridgeHistory(bridgeMetadata?.bridgeHistory)) ??
591
- sanitizeBridgeHistory(normalizeBridgeHistory(envelopeMetadata?.bridgeHistory)) ??
592
- sanitizeBridgeHistory(fallbackHistory);
242
+ }
593
243
  const history = historySeed ??
594
- sanitizeBridgeHistory(buildBridgeHistoryWithNative({
244
+ buildBridgeHistoryWithNative({
595
245
  messages,
596
246
  tools: Array.isArray(out.tools) ? out.tools : undefined
597
- }));
247
+ });
598
248
  const callIdTransformer = createToolCallIdTransformer(toolCallIdStyle);
599
249
  if (callIdTransformer) {
600
250
  enforceToolCallIdStyle(history.input, callIdTransformer);
601
251
  }
602
252
  const { input, combinedSystemInstruction, originalSystemMessages } = history;
603
- const instructionCandidates = [
604
- typeof ctx?.systemInstruction === 'string' && ctx.systemInstruction.trim().length ? ctx.systemInstruction.trim() : undefined,
605
- typeof extras?.systemInstruction === 'string' && extras.systemInstruction.trim().length ? extras.systemInstruction.trim() : undefined,
606
- typeof envelopeMetadata?.systemInstruction === 'string' && envelopeMetadata.systemInstruction.trim().length ? envelopeMetadata.systemInstruction.trim() : undefined,
607
- combinedSystemInstruction && combinedSystemInstruction.length > 0 ? combinedSystemInstruction : undefined
608
- ];
609
- let resolvedInstruction;
610
- for (const candidate of instructionCandidates) {
611
- if (candidate && candidate.length) {
612
- resolvedInstruction = candidate;
613
- break;
614
- }
615
- }
616
- if (resolvedInstruction && resolvedInstruction.length) {
617
- const reasoningSegments = (() => {
618
- if (!ctx || typeof ctx !== 'object')
619
- return [];
620
- const ctxAny = ctx;
621
- const raw = ctxAny.__rcc_reasoning_instructions_segments;
622
- if (!raw)
623
- return [];
624
- const segments = Array.isArray(raw) ? raw : [raw];
625
- return segments
626
- .map((entry) => (typeof entry === 'string' ? entry.trim() : String(entry ?? '').trim()))
627
- .filter((entry) => entry.length > 0);
628
- })();
629
- if (reasoningSegments.length) {
630
- const combined = `${reasoningSegments.join('\n')}\n${resolvedInstruction}`.trim();
631
- out.instructions = combined;
632
- out.instructions_is_raw = true;
633
- }
634
- else {
635
- out.instructions = resolvedInstruction;
636
- }
637
- }
638
253
  // 不追加 metadata,以便 roundtrip 与原始 payload 对齐;系统提示直接写入 instructions。
639
- const upstreamInput = filterBridgeInputForUpstream(input, { allowToolCallId: toolCallIdStyle === 'preserve' });
254
+ const upstreamInput = filterBridgeInputForUpstreamWithNative({
255
+ input,
256
+ allowToolCallId: toolCallIdStyle === 'preserve'
257
+ }).input;
640
258
  if (upstreamInput.length) {
641
259
  out.input = upstreamInput;
642
260
  }
@@ -644,144 +262,44 @@ export function buildResponsesRequestFromChat(payload, ctx, extras) {
644
262
  const streamFromParameters = chat?.parameters && typeof chat.parameters?.stream === 'boolean'
645
263
  ? chat.parameters.stream
646
264
  : undefined;
647
- const resolvedStream = typeof ctx?.stream === 'boolean'
648
- ? ctx.stream
649
- : (streamFromChat !== undefined ? streamFromChat : streamFromParameters);
650
- if (resolvedStream === true) {
651
- out.stream = true;
652
- }
653
- else if (resolvedStream === false) {
654
- out.stream = false;
655
- }
656
- if (ctx?.include !== undefined && out.include === undefined) {
657
- out.include = ctx.include;
658
- }
659
- else if (metadataExtraFields?.include !== undefined && out.include === undefined) {
660
- out.include = metadataExtraFields.include;
661
- }
662
265
  const stripHostFields = shouldStripHostManagedFields(ctx);
663
- if (stripHostFields) {
664
- delete out.store;
665
- }
666
- else if (ctx?.store !== undefined && out.store === undefined) {
667
- out.store = ctx.store;
668
- }
669
- else if (metadataExtraFields?.store !== undefined && out.store === undefined) {
670
- out.store = metadataExtraFields.store;
671
- }
672
- else if (out.store === undefined) {
673
- // Chat 入口无 store 概念,但 Responses provider 仍要求显式声明。
674
- out.store = false;
675
- }
676
- if (ctx?.toolChoice !== undefined && out.tool_choice === undefined) {
677
- out.tool_choice = ctx.toolChoice;
678
- }
679
- else if (metadataExtraFields?.tool_choice !== undefined && out.tool_choice === undefined) {
680
- out.tool_choice = metadataExtraFields.tool_choice;
681
- }
682
- if (typeof ctx?.parallelToolCalls === 'boolean' && out.parallel_tool_calls === undefined) {
683
- out.parallel_tool_calls = ctx.parallelToolCalls;
684
- }
685
- else if (typeof metadataExtraFields?.parallel_tool_calls === 'boolean' && out.parallel_tool_calls === undefined) {
686
- out.parallel_tool_calls = metadataExtraFields.parallel_tool_calls;
687
- }
688
- if (ctx?.responseFormat !== undefined && out.response_format === undefined) {
689
- out.response_format = ctx.responseFormat;
690
- }
691
- else if (metadataExtraFields?.response_format !== undefined && out.response_format === undefined) {
692
- out.response_format = metadataExtraFields.response_format;
693
- }
694
- if (ctx?.serviceTier !== undefined && out.service_tier === undefined) {
695
- out.service_tier = ctx.serviceTier;
696
- }
697
- else if (metadataExtraFields?.service_tier !== undefined && out.service_tier === undefined) {
698
- out.service_tier = metadataExtraFields.service_tier;
699
- }
700
- if (ctx?.truncation !== undefined && out.truncation === undefined) {
701
- out.truncation = ctx.truncation;
702
- }
703
- else if (metadataExtraFields?.truncation !== undefined && out.truncation === undefined) {
704
- out.truncation = metadataExtraFields.truncation;
705
- }
706
- if (ctx?.metadata && Object.keys(ctx.metadata).length) {
707
- out.metadata = { ...ctx.metadata };
708
- }
709
- else if (isPlainObject(metadataExtraFields?.metadata)) {
710
- out.metadata = { ...metadataExtraFields.metadata };
711
- }
712
- // Some upstream `/v1/responses` providers reject an unknown top-level `parameters` object
713
- // (e.g. HTTP 400: Unsupported parameter: parameters).
714
- // We accept `parameters` as an internal carrier (capturedChatRequest.parameters, followups),
715
- // but must flatten supported fields into the top-level request shape before sending upstream.
716
- const parametersCandidate = (ctx?.parameters && Object.keys(ctx.parameters).length ? { ...ctx.parameters } : undefined) ??
717
- (chat.parameters && typeof chat.parameters === 'object' && !Array.isArray(chat.parameters) ? { ...chat.parameters } : undefined) ??
718
- (isPlainObject(metadataExtraFields?.parameters) ? { ...metadataExtraFields.parameters } : undefined);
719
- if (parametersCandidate) {
720
- const allowed = new Set([
721
- // Common OpenAI Responses parameters
722
- 'temperature',
723
- 'top_p',
724
- 'max_output_tokens',
725
- 'seed',
726
- 'logit_bias',
727
- 'user',
728
- 'parallel_tool_calls',
729
- 'tool_choice',
730
- 'response_format',
731
- 'service_tier',
732
- 'truncation',
733
- 'include',
734
- 'store',
735
- 'prompt_cache_key',
736
- 'reasoning',
737
- 'stream'
738
- ]);
739
- // Back-compat: StandardizedRequest uses max_tokens; map it to Responses max_output_tokens.
740
- if (parametersCandidate.max_output_tokens === undefined && parametersCandidate.max_tokens !== undefined) {
741
- parametersCandidate.max_output_tokens = parametersCandidate.max_tokens;
742
- }
743
- for (const [key, value] of Object.entries(parametersCandidate)) {
744
- if (!allowed.has(key))
745
- continue;
746
- if (key === 'stream')
747
- continue; // handled by resolvedStream above
748
- if (out[key] === undefined) {
749
- out[key] = value;
750
- }
751
- }
752
- }
266
+ const preparedEnvelope = prepareResponsesRequestEnvelopeWithNative({
267
+ request: out,
268
+ contextSystemInstruction: ctx?.systemInstruction,
269
+ extraSystemInstruction: extras?.systemInstruction,
270
+ metadataSystemInstruction: envelopeMetadata?.systemInstruction,
271
+ combinedSystemInstruction,
272
+ reasoningInstructionSegments: ctx?.__rcc_reasoning_instructions_segments,
273
+ contextParameters: ctx?.parameters,
274
+ chatParameters: chat.parameters,
275
+ metadataParameters: metadataExtraFields?.parameters,
276
+ contextStream: ctx?.stream,
277
+ metadataStream: metadataExtraFields?.stream,
278
+ chatStream: streamFromChat,
279
+ chatParametersStream: streamFromParameters,
280
+ contextInclude: ctx?.include,
281
+ metadataInclude: metadataExtraFields?.include,
282
+ contextStore: ctx?.store,
283
+ metadataStore: metadataExtraFields?.store,
284
+ stripHostFields,
285
+ contextToolChoice: ctx?.toolChoice,
286
+ metadataToolChoice: metadataExtraFields?.tool_choice,
287
+ contextParallelToolCalls: ctx?.parallelToolCalls,
288
+ metadataParallelToolCalls: metadataExtraFields?.parallel_tool_calls,
289
+ contextResponseFormat: ctx?.responseFormat,
290
+ metadataResponseFormat: metadataExtraFields?.response_format,
291
+ contextServiceTier: ctx?.serviceTier,
292
+ metadataServiceTier: metadataExtraFields?.service_tier,
293
+ contextTruncation: ctx?.truncation,
294
+ metadataTruncation: metadataExtraFields?.truncation,
295
+ contextMetadata: ctx?.metadata,
296
+ metadataMetadata: metadataExtraFields?.metadata
297
+ });
298
+ Object.assign(out, preparedEnvelope.request);
299
+ delete out.parameters;
753
300
  ensureBridgeInstructions(out);
754
301
  return { request: out, originalSystemMessages };
755
302
  }
756
- function readToolCallIdStyleFromContext(ctx) {
757
- if (!ctx) {
758
- return undefined;
759
- }
760
- const direct = normalizeToolCallIdStyleCandidate(ctx.toolCallIdStyle);
761
- if (direct) {
762
- return direct;
763
- }
764
- if (ctx.metadata && typeof ctx.metadata === 'object') {
765
- const fromMetadata = normalizeToolCallIdStyleCandidate(ctx.metadata.toolCallIdStyle);
766
- if (fromMetadata) {
767
- return fromMetadata;
768
- }
769
- }
770
- return undefined;
771
- }
772
- function normalizeToolCallIdStyleCandidate(candidate) {
773
- if (typeof candidate !== 'string') {
774
- return undefined;
775
- }
776
- const normalized = candidate.trim().toLowerCase();
777
- if (normalized === 'fc') {
778
- return 'fc';
779
- }
780
- if (normalized === 'preserve') {
781
- return 'preserve';
782
- }
783
- return undefined;
784
- }
785
303
  function cloneBridgeEntries(entries) {
786
304
  if (!Array.isArray(entries)) {
787
305
  return undefined;