@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.
Files changed (90) 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/conversion/shared/tool-call-id-manager.js +18 -21
  48. package/dist/native/router_hotpath_napi.node +0 -0
  49. package/dist/router/virtual-router/bootstrap/routing-config.d.ts +2 -1
  50. package/dist/router/virtual-router/bootstrap/routing-config.js +57 -4
  51. package/dist/router/virtual-router/engine-legacy.d.ts +3 -3
  52. package/dist/router/virtual-router/engine-legacy.js +15 -7
  53. package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.d.ts +16 -0
  54. package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.js +434 -46
  55. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.d.ts +83 -0
  56. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js +295 -0
  57. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.d.ts +1 -0
  58. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.d.ts +7 -0
  59. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js +8 -1
  60. package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +383 -298
  61. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.d.ts +20 -0
  62. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +201 -0
  63. package/dist/router/virtual-router/engine-selection/native-virtual-router-routing-instructions-semantics.d.ts +1 -0
  64. package/dist/router/virtual-router/engine-selection/native-virtual-router-routing-instructions-semantics.js +37 -0
  65. package/dist/router/virtual-router/engine-selection/tier-load-balancing.d.ts +16 -0
  66. package/dist/router/virtual-router/engine-selection/tier-load-balancing.js +120 -0
  67. package/dist/router/virtual-router/engine-selection/tier-selection-quota-integration.d.ts +2 -0
  68. package/dist/router/virtual-router/engine-selection/tier-selection-quota-integration.js +44 -66
  69. package/dist/router/virtual-router/engine-selection/tier-selection-select.js +53 -84
  70. package/dist/router/virtual-router/engine.js +0 -38
  71. package/dist/router/virtual-router/features.js +44 -3
  72. package/dist/router/virtual-router/routing-instructions/parse.d.ts +0 -12
  73. package/dist/router/virtual-router/routing-instructions/parse.js +9 -389
  74. package/dist/router/virtual-router/stop-message-state-sync.d.ts +3 -6
  75. package/dist/router/virtual-router/stop-message-state-sync.js +50 -21
  76. package/dist/router/virtual-router/types.d.ts +16 -0
  77. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +1 -0
  78. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.js +26 -0
  79. package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +12 -2
  80. package/package.json +1 -1
  81. package/dist/router/virtual-router/engine-legacy/route-finalize.d.ts +0 -9
  82. package/dist/router/virtual-router/engine-legacy/route-finalize.js +0 -84
  83. package/dist/router/virtual-router/engine-legacy/route-selection.d.ts +0 -17
  84. package/dist/router/virtual-router/engine-legacy/route-selection.js +0 -205
  85. package/dist/router/virtual-router/engine-legacy/route-state-allowlist.d.ts +0 -3
  86. package/dist/router/virtual-router/engine-legacy/route-state-allowlist.js +0 -36
  87. package/dist/router/virtual-router/engine-legacy/route-state.d.ts +0 -12
  88. package/dist/router/virtual-router/engine-legacy/route-state.js +0 -386
  89. package/dist/router/virtual-router/engine-legacy/routing.d.ts +0 -8
  90. package/dist/router/virtual-router/engine-legacy/routing.js +0 -8
@@ -1,644 +1,63 @@
1
- import { createBridgeActionState, runBridgeActionPipeline } from '../bridge-actions.js';
2
- import { resolveBridgePolicy, resolvePolicyActions } from '../bridge-policies.js';
3
- import { normalizeChatMessageContent } from '../shared/chat-output-normalizer.js';
4
- import { mapBridgeToolsToChat } from '../shared/tool-mapping.js';
5
- import { prepareGeminiToolsForBridge } from '../shared/gemini-tool-utils.js';
6
- import { registerResponsesReasoning, consumeResponsesReasoning, registerResponsesOutputTextMeta, consumeResponsesOutputTextMeta, consumeResponsesPayloadSnapshot, registerResponsesPayloadSnapshot, consumeResponsesPassthrough, registerResponsesPassthrough } from '../shared/responses-reasoning-registry.js';
7
1
  import { ProviderProtocolError } from '../provider-protocol-error.js';
8
- function isObject(v) {
9
- return !!v && typeof v === 'object' && !Array.isArray(v);
10
- }
11
- function safeJson(value) {
12
- try {
13
- return JSON.stringify(value ?? {});
14
- }
15
- catch {
16
- return '{}';
2
+ import { runGeminiFromOpenAIChatCodecWithNative, runGeminiOpenAIRequestCodecWithNative, runGeminiOpenAIResponseCodecWithNative } from '../../router/virtual-router/engine-selection/native-compat-action-semantics.js';
3
+ function isJsonValue(value) {
4
+ if (value === null ||
5
+ typeof value === 'string' ||
6
+ typeof value === 'number' ||
7
+ typeof value === 'boolean') {
8
+ return true;
17
9
  }
18
- }
19
- function flattenGeminiTextFromParts(parts) {
20
- if (!parts)
21
- return '';
22
- if (Array.isArray(parts)) {
23
- return parts
24
- .map((p) => {
25
- if (p && typeof p === 'object' && typeof p.text === 'string') {
26
- return String(p.text);
27
- }
28
- return '';
29
- })
30
- .filter(Boolean)
31
- .join('');
10
+ if (Array.isArray(value)) {
11
+ return value.every((entry) => isJsonValue(entry));
32
12
  }
33
- if (parts && typeof parts === 'object' && typeof parts.text === 'string') {
34
- return String(parts.text);
35
- }
36
- return '';
37
- }
38
- function mapGeminiRoleToChat(role) {
39
- const r = String(role || '').toLowerCase();
40
- if (r === 'model' || r === 'assistant')
41
- return 'assistant';
42
- if (r === 'system')
43
- return 'system';
44
- if (r === 'tool')
45
- return 'tool';
46
- return 'user';
47
- }
48
- function mapChatRoleToGemini(role) {
49
- const r = String(role || '').toLowerCase();
50
- if (r === 'assistant')
51
- return 'model';
52
- if (r === 'system')
53
- return 'system';
54
- if (r === 'tool')
55
- return 'tool';
56
- return 'user';
57
- }
58
- function coerceThoughtSignature(value) {
59
- if (typeof value === 'string' && value.trim().length) {
60
- return value.trim();
13
+ if (!value || typeof value !== 'object') {
14
+ return false;
61
15
  }
62
- return undefined;
16
+ return Object.values(value).every((entry) => isJsonValue(entry));
63
17
  }
64
- function extractThoughtSignatureFromToolCall(tc) {
65
- if (!tc || typeof tc !== 'object') {
66
- return undefined;
67
- }
68
- const direct = coerceThoughtSignature(tc.thought_signature ?? tc.thoughtSignature);
69
- if (direct) {
70
- return direct;
71
- }
72
- const extraContent = tc.extra_content ?? tc.extraContent;
73
- if (extraContent && typeof extraContent === 'object') {
74
- const googleNode = extraContent.google ?? extraContent.Google;
75
- if (googleNode && typeof googleNode === 'object') {
76
- const googleSig = coerceThoughtSignature(googleNode.thought_signature ?? googleNode.thoughtSignature);
77
- if (googleSig) {
78
- return googleSig;
79
- }
18
+ function narrowJsonObject(value) {
19
+ const out = {};
20
+ for (const [key, entry] of Object.entries(value)) {
21
+ if (isJsonValue(entry)) {
22
+ out[key] = entry;
80
23
  }
81
24
  }
82
- return undefined;
25
+ return out;
26
+ }
27
+ function unwrapProviderProtocolError(result) {
28
+ const raw = result.__providerProtocolError;
29
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
30
+ return;
31
+ }
32
+ const error = raw;
33
+ throw new ProviderProtocolError(String(error.message ?? 'Gemini provider protocol error'), {
34
+ code: String(error.code ?? 'MALFORMED_RESPONSE'),
35
+ protocol: typeof error.protocol === 'string' ? error.protocol : 'gemini-chat',
36
+ providerType: typeof error.providerType === 'string' ? error.providerType : 'gemini',
37
+ category: error.category === 'TOOL_ERROR' || error.category === 'INTERNAL_ERROR' || error.category === 'EXTERNAL_ERROR'
38
+ ? error.category
39
+ : undefined,
40
+ details: error.details && typeof error.details === 'object' && !Array.isArray(error.details)
41
+ ? error.details
42
+ : undefined
43
+ });
83
44
  }
84
45
  export function buildOpenAIChatFromGeminiRequest(payload) {
85
- const messages = [];
86
- // systemInstruction Chat system 消息
87
- try {
88
- const sys = payload?.systemInstruction;
89
- const sysText = flattenGeminiTextFromParts(sys?.parts ?? sys);
90
- if (sysText && sysText.trim().length) {
91
- messages.push({ role: 'system', content: sysText.trim() });
92
- }
93
- }
94
- catch {
95
- // ignore systemInstruction failures
96
- }
97
- const contents = Array.isArray(payload?.contents) ? payload.contents : [];
98
- for (const content of contents) {
99
- if (!content || typeof content !== 'object')
100
- continue;
101
- const cObj = content;
102
- const role = mapGeminiRoleToChat(cObj.role);
103
- const parts = Array.isArray(cObj.parts) ? cObj.parts : [];
104
- const textParts = [];
105
- const toolCalls = [];
106
- const toolResults = [];
107
- for (const part of parts) {
108
- if (!part || typeof part !== 'object')
109
- continue;
110
- const pObj = part;
111
- if (typeof pObj.text === 'string') {
112
- const t = pObj.text;
113
- if (t && t.trim().length)
114
- textParts.push(t);
115
- continue;
116
- }
117
- if (pObj.functionCall && typeof pObj.functionCall === 'object') {
118
- const fc = pObj.functionCall;
119
- const name = typeof fc.name === 'string' ? String(fc.name) : undefined;
120
- if (!name)
121
- continue;
122
- const id = typeof fc.id === 'string' ? String(fc.id) : undefined;
123
- const argsRaw = (fc.args ?? fc.arguments);
124
- let argsStr;
125
- if (typeof argsRaw === 'string') {
126
- argsStr = argsRaw;
127
- }
128
- else {
129
- argsStr = safeJson(argsRaw);
130
- }
131
- toolCalls.push({ id, type: 'function', function: { name, arguments: argsStr } });
132
- continue;
133
- }
134
- if (pObj.functionResponse && typeof pObj.functionResponse === 'object') {
135
- const fr = pObj.functionResponse;
136
- const callId = typeof fr.id === 'string' ? String(fr.id) : undefined;
137
- const resp = fr.response;
138
- let contentStr = '';
139
- if (typeof resp === 'string')
140
- contentStr = resp;
141
- else if (resp != null) {
142
- try {
143
- contentStr = JSON.stringify(resp);
144
- }
145
- catch {
146
- contentStr = String(resp);
147
- }
148
- }
149
- toolResults.push({ role: 'tool', tool_call_id: callId, content: contentStr });
150
- continue;
151
- }
152
- }
153
- const combinedText = textParts.join('\n');
154
- const normalized = combinedText.length ? normalizeChatMessageContent(combinedText) : { contentText: undefined, reasoningText: undefined };
155
- const hasText = typeof normalized.contentText === 'string' && normalized.contentText.length > 0;
156
- const reasoningChunks = [];
157
- if (typeof normalized.reasoningText === 'string' && normalized.reasoningText.trim().length) {
158
- reasoningChunks.push(normalized.reasoningText.trim());
159
- }
160
- if (hasText || toolCalls.length > 0 || reasoningChunks.length > 0) {
161
- const msg = {
162
- role,
163
- content: normalized.contentText ?? combinedText ?? ''
164
- };
165
- if (reasoningChunks.length) {
166
- msg.reasoning_content = reasoningChunks.join('\n');
167
- }
168
- if (toolCalls.length)
169
- msg.tool_calls = toolCalls;
170
- messages.push(msg);
171
- }
172
- for (const tr of toolResults)
173
- messages.push(tr);
174
- }
175
- return { messages };
46
+ const native = runGeminiOpenAIRequestCodecWithNative((payload ?? {}));
47
+ const request = narrowJsonObject(native);
48
+ return {
49
+ ...request,
50
+ messages: Array.isArray(native.messages) ? native.messages.filter((entry) => isJsonValue(entry)) : []
51
+ };
176
52
  }
177
53
  export function buildOpenAIChatFromGeminiResponse(payload) {
178
- const candidates = Array.isArray(payload?.candidates) ? payload.candidates : [];
179
- const errorNode = payload?.error;
180
- const primary = candidates[0] && typeof candidates[0] === 'object' ? candidates[0] : {};
181
- const content = primary?.content || {};
182
- const role = candidates.length > 0 ? mapGeminiRoleToChat(content.role) : 'assistant';
183
- const rawFinishReason = primary?.finishReason;
184
- const finishReasonUpper = typeof rawFinishReason === 'string' ? rawFinishReason.trim().toUpperCase() : '';
185
- const parts = Array.isArray(content.parts) ? content.parts : [];
186
- const textParts = [];
187
- const reasoningParts = [];
188
- const toolCalls = [];
189
- const toolResultTexts = [];
190
- const toolOutputs = [];
191
- // 为当前响应内生成稳定的工具调用 ID,避免下游 ServerTool 因缺少 id 而跳过。
192
- let toolCallCounter = 0;
193
- for (const part of parts) {
194
- if (!part || typeof part !== 'object')
195
- continue;
196
- const pObj = part;
197
- // 1. Text part
198
- if (typeof pObj.text === 'string') {
199
- const t = pObj.text;
200
- if (t && t.trim().length)
201
- textParts.push(t);
202
- continue;
203
- }
204
- // 2. Content array (nested structure)
205
- if (Array.isArray(pObj.content)) {
206
- for (const inner of pObj.content) {
207
- if (typeof inner === 'string') {
208
- textParts.push(inner);
209
- }
210
- else if (inner && typeof inner === 'object' && typeof inner.text === 'string') {
211
- textParts.push(inner.text);
212
- }
213
- }
214
- continue;
215
- }
216
- // 3. Reasoning part (channel mode)
217
- if (typeof pObj.reasoning === 'string') {
218
- reasoningParts.push(pObj.reasoning);
219
- continue;
220
- }
221
- // 4. Thought part (thinking/extended thinking)
222
- if (typeof pObj.thought === 'string') {
223
- const thoughtText = pObj.thought.trim();
224
- if (thoughtText.length) {
225
- reasoningParts.push(thoughtText);
226
- }
227
- continue;
228
- }
229
- // 5. Function call (tool call)
230
- if (pObj.functionCall && typeof pObj.functionCall === 'object') {
231
- const fc = pObj.functionCall;
232
- let name = typeof fc.name === 'string' ? String(fc.name) : undefined;
233
- if (!name)
234
- continue;
235
- // Gemini "websearch" is a transport alias for the canonical server-side tool "web_search".
236
- if (name === 'websearch' || name.startsWith('websearch_')) {
237
- name = 'web_search';
238
- }
239
- let id = typeof fc.id === 'string' && fc.id.trim().length ? String(fc.id).trim() : undefined;
240
- const argsRaw = (fc.args ?? fc.arguments);
241
- let argsStr;
242
- if (typeof argsRaw === 'string') {
243
- argsStr = argsRaw;
244
- }
245
- else {
246
- argsStr = safeJson(argsRaw);
247
- }
248
- const thoughtSignature = coerceThoughtSignature(pObj.thoughtSignature);
249
- // Gemini 某些响应中的 functionCall 不带 id,但下游 servertool 需要稳定的
250
- // tool_call.id 才会识别为有效工具调用;此处在缺失时生成本响应内唯一的占位 ID。
251
- if (!id) {
252
- const suffix = toolCallCounter++;
253
- id = `gemini_tool_${suffix}`;
254
- }
255
- const toolCall = {
256
- id,
257
- type: 'function',
258
- function: { name, arguments: argsStr }
259
- };
260
- if (thoughtSignature) {
261
- toolCall.thought_signature = thoughtSignature;
262
- toolCall.extra_content = {
263
- google: {
264
- thought_signature: thoughtSignature
265
- }
266
- };
267
- }
268
- toolCalls.push(toolCall);
269
- continue;
270
- }
271
- // 6. Function response (tool result)
272
- if (pObj.functionResponse && typeof pObj.functionResponse === 'object') {
273
- const fr = pObj.functionResponse;
274
- const callId = typeof fr.id === 'string' && fr.id.trim().length ? String(fr.id) : undefined;
275
- let name = typeof fr.name === 'string' && fr.name.trim().length ? String(fr.name) : undefined;
276
- if (name && (name === 'websearch' || name.startsWith('websearch_'))) {
277
- name = 'web_search';
278
- }
279
- const resp = fr.response;
280
- let contentStr = '';
281
- if (typeof resp === 'string') {
282
- contentStr = resp;
283
- }
284
- else if (resp != null) {
285
- try {
286
- contentStr = JSON.stringify(resp);
287
- }
288
- catch {
289
- contentStr = String(resp);
290
- }
291
- }
292
- if (contentStr && contentStr.trim().length) {
293
- toolResultTexts.push(contentStr);
294
- if (callId || name) {
295
- const entry = {
296
- tool_call_id: callId ?? undefined,
297
- id: callId ?? undefined,
298
- content: contentStr
299
- };
300
- if (name) {
301
- entry.name = name;
302
- }
303
- toolOutputs.push(entry);
304
- }
305
- }
306
- continue;
307
- }
308
- // 7. Executable code (code_interpreter)
309
- if (pObj.executableCode && typeof pObj.executableCode === 'object') {
310
- const code = pObj.executableCode;
311
- const language = typeof code.language === 'string' ? code.language : 'python';
312
- const codeText = typeof code.code === 'string' ? code.code : '';
313
- if (codeText.trim().length) {
314
- // Append as text with code block formatting
315
- textParts.push(`\`\`\`${language}\n${codeText}\n\`\`\``);
316
- }
317
- continue;
318
- }
319
- // 8. Code execution result
320
- if (pObj.codeExecutionResult && typeof pObj.codeExecutionResult === 'object') {
321
- const result = pObj.codeExecutionResult;
322
- const outcome = typeof result.outcome === 'string' ? result.outcome : '';
323
- const output = typeof result.output === 'string' ? result.output : '';
324
- if (output.trim().length) {
325
- textParts.push(`[Code Output${outcome ? ` (${outcome})` : ''}]:\n${output}`);
326
- }
327
- continue;
328
- }
329
- }
330
- const hasToolCalls = toolCalls.length > 0;
331
- // 如果 Gemini 返回 UNEXPECTED_TOOL_CALL,且当前没有有效的工具调用可继续,
332
- // 说明上游工具协议/声明与模型期望不一致,应视为 Provider 级错误而不是正常 stop,
333
- // 由上层通过 ProviderErrorCenter / HTTP 4xx/5xx 显式反馈给客户端。
334
- if (!hasToolCalls && finishReasonUpper === 'UNEXPECTED_TOOL_CALL') {
335
- throw new ProviderProtocolError('Gemini returned finishReason=UNEXPECTED_TOOL_CALL; this usually indicates an incompatible or unexpected tool invocation.', {
336
- code: 'TOOL_PROTOCOL_ERROR',
337
- protocol: 'gemini-chat',
338
- providerType: 'gemini',
339
- details: { finishReason: rawFinishReason }
340
- });
341
- }
342
- const finish_reason = (() => {
343
- // If the model is emitting tool calls, treat this turn as a tool_calls
344
- // completion so downstream tool governance can continue the loop.
345
- if (hasToolCalls)
346
- return 'tool_calls';
347
- const fr = finishReasonUpper;
348
- if (fr === 'MAX_TOKENS')
349
- return 'length';
350
- if (fr === 'STOP')
351
- return 'stop';
352
- if (fr === 'SAFETY')
353
- return 'content_filter';
354
- if (fr === 'OTHER')
355
- return 'stop';
356
- return 'stop';
357
- })();
358
- const usageMeta = payload?.usageMetadata || {};
359
- const usage = {};
360
- const promptTokens = Number(usageMeta.promptTokenCount);
361
- const completionTokens = Number(usageMeta.candidatesTokenCount);
362
- const totalTokens = Number(usageMeta.totalTokenCount);
363
- if (Number.isFinite(promptTokens))
364
- usage.prompt_tokens = promptTokens;
365
- if (Number.isFinite(completionTokens))
366
- usage.completion_tokens = completionTokens;
367
- if (Number.isFinite(totalTokens))
368
- usage.total_tokens = totalTokens;
369
- const combinedText = textParts.join('\n');
370
- const normalized = combinedText.length
371
- ? normalizeChatMessageContent(combinedText)
372
- : { contentText: undefined, reasoningText: undefined };
373
- const baseContent = normalized.contentText ?? combinedText ?? '';
374
- const toolResultBlock = toolResultTexts.length ? toolResultTexts.join('\n') : '';
375
- const finalContent = toolResultBlock && baseContent
376
- ? `${baseContent}\n${toolResultBlock}`
377
- : baseContent || toolResultBlock;
378
- const chatMsg = {
379
- role,
380
- content: finalContent
381
- };
382
- if (typeof normalized.reasoningText === 'string' && normalized.reasoningText.trim().length) {
383
- reasoningParts.push(normalized.reasoningText.trim());
384
- }
385
- if (reasoningParts.length) {
386
- chatMsg.reasoning_content = reasoningParts.join('\n');
387
- }
388
- if (toolCalls.length) {
389
- chatMsg.tool_calls = toolCalls;
390
- }
391
- try {
392
- const bridgePolicy = resolveBridgePolicy({ protocol: 'gemini-chat' });
393
- const actions = resolvePolicyActions(bridgePolicy, 'response_inbound');
394
- if (actions?.length) {
395
- const actionState = createBridgeActionState({
396
- messages: [chatMsg],
397
- rawResponse: isObject(payload) ? payload : undefined
398
- });
399
- runBridgeActionPipeline({
400
- stage: 'response_inbound',
401
- actions,
402
- protocol: bridgePolicy?.protocol ?? 'gemini-chat',
403
- moduleType: bridgePolicy?.moduleType ?? 'gemini-chat',
404
- requestId: typeof payload?.id === 'string' ? String(payload.id) : undefined,
405
- state: actionState
406
- });
407
- }
408
- }
409
- catch {
410
- // best-effort policy execution
411
- }
412
- const chatResp = {
413
- id: payload?.id || `chatcmpl_${Date.now()}`,
414
- object: 'chat.completion',
415
- model: payload?.model || 'unknown',
416
- choices: [
417
- {
418
- index: 0,
419
- finish_reason,
420
- message: chatMsg
421
- }
422
- ]
423
- };
424
- // Preserve upstream error envelope (used by servertool auto flows and host status mapping).
425
- if (errorNode && typeof errorNode === 'object' && !Array.isArray(errorNode)) {
426
- chatResp.error = errorNode;
427
- }
428
- if (Object.keys(usage).length > 0) {
429
- chatResp.usage = usage;
430
- }
431
- if (toolOutputs.length > 0) {
432
- chatResp.tool_outputs = toolOutputs;
433
- }
434
- const localReasoning = reasoningParts.length
435
- ? {
436
- content: reasoningParts.map((text) => ({ type: 'reasoning_text', text }))
437
- }
438
- : undefined;
439
- const preservedReasoning = consumeResponsesReasoning(chatResp.id);
440
- if (preservedReasoning) {
441
- chatResp.__responses_reasoning = preservedReasoning;
442
- }
443
- else if (localReasoning) {
444
- chatResp.__responses_reasoning = localReasoning;
445
- }
446
- const preservedOutputMeta = consumeResponsesOutputTextMeta(chatResp.id);
447
- if (preservedOutputMeta) {
448
- chatResp.__responses_output_text_meta = preservedOutputMeta;
449
- }
450
- const payloadSnapshot = consumeResponsesPayloadSnapshot(chatResp.id);
451
- if (payloadSnapshot) {
452
- registerResponsesPayloadSnapshot(chatResp.id, payloadSnapshot);
453
- if (typeof chatResp.request_id !== 'string') {
454
- chatResp.request_id = chatResp.id;
455
- }
456
- }
457
- const passthroughPayload = consumeResponsesPassthrough(chatResp.id);
458
- if (passthroughPayload) {
459
- registerResponsesPassthrough(chatResp.id, passthroughPayload);
460
- if (typeof chatResp.request_id !== 'string') {
461
- chatResp.request_id = chatResp.id;
462
- }
463
- }
464
- return chatResp;
54
+ const result = runGeminiOpenAIResponseCodecWithNative((payload ?? {}));
55
+ unwrapProviderProtocolError(result);
56
+ return narrowJsonObject(result);
465
57
  }
466
58
  export function buildGeminiFromOpenAIChat(chatResp) {
467
- const choices = Array.isArray(chatResp?.choices) ? chatResp.choices : [];
468
- const primary = choices[0] && typeof choices[0] === 'object' ? choices[0] : {};
469
- const msg = primary?.message || {};
470
- const usage = isObject(chatResp?.usage) ? chatResp.usage : {};
471
- const finishReason = (() => {
472
- const r = String(primary?.finish_reason || '').toLowerCase();
473
- if (r === 'length')
474
- return 'MAX_TOKENS';
475
- if (r === 'tool_calls' || r === 'function_call')
476
- return 'STOP';
477
- if (r === 'content_filter')
478
- return 'OTHER';
479
- if (r === 'stop')
480
- return 'STOP';
481
- return 'STOP';
482
- })();
483
- const baseRole = mapChatRoleToGemini(msg.role || 'assistant');
484
- if (msg) {
485
- try {
486
- const bridgePolicy = resolveBridgePolicy({ protocol: 'gemini-chat' });
487
- const actions = resolvePolicyActions(bridgePolicy, 'response_outbound');
488
- if (actions?.length) {
489
- const actionState = createBridgeActionState({
490
- messages: [msg]
491
- });
492
- runBridgeActionPipeline({
493
- stage: 'response_outbound',
494
- actions,
495
- protocol: bridgePolicy?.protocol ?? 'gemini-chat',
496
- moduleType: bridgePolicy?.moduleType ?? 'gemini-chat',
497
- requestId: typeof chatResp?.id === 'string' ? String(chatResp.id) : undefined,
498
- state: actionState
499
- });
500
- }
501
- }
502
- catch {
503
- // ignore policy failures
504
- }
505
- }
506
- const parts = [];
507
- const contentText = (() => {
508
- const raw = msg.content;
509
- if (typeof raw === 'string')
510
- return raw;
511
- if (Array.isArray(raw)) {
512
- return raw
513
- .map((part) => {
514
- if (typeof part === 'string')
515
- return part;
516
- if (part && typeof part === 'object') {
517
- if (typeof part.text === 'string')
518
- return part.text;
519
- if (typeof part.content === 'string')
520
- return part.content;
521
- }
522
- return '';
523
- })
524
- .filter(Boolean)
525
- .join('');
526
- }
527
- return '';
528
- })();
529
- if (contentText && contentText.length) {
530
- parts.push({ text: contentText });
531
- }
532
- const preservedReasoning = chatResp?.__responses_reasoning;
533
- const preservedContent = Array.isArray(preservedReasoning?.content)
534
- ? preservedReasoning?.content?.map((entry) => String(entry.text ?? '')).filter((text) => text.trim().length > 0)
535
- : [];
536
- const preservedSummary = Array.isArray(preservedReasoning?.summary)
537
- ? preservedReasoning?.summary?.map((entry) => String(entry.text ?? '')).filter((text) => text.trim().length > 0)
538
- : [];
539
- const reasoningText = preservedContent.length > 0
540
- ? preservedContent.join('\n')
541
- : (preservedSummary.length > 0
542
- ? preservedSummary.join('\n')
543
- : (typeof msg?.reasoning_content === 'string' && msg.reasoning_content.trim().length
544
- ? String(msg.reasoning_content).trim()
545
- : undefined));
546
- if (reasoningText) {
547
- parts.push({ reasoning: reasoningText });
548
- }
549
- const toolCalls = Array.isArray(msg.tool_calls) ? msg.tool_calls : [];
550
- for (const tc of toolCalls) {
551
- if (!tc || typeof tc !== 'object')
552
- continue;
553
- const fn = tc.function || {};
554
- const name = typeof fn.name === 'string' ? String(fn.name) : undefined;
555
- if (!name)
556
- continue;
557
- let argsStruct;
558
- const rawArgs = fn.arguments;
559
- if (typeof rawArgs === 'string') {
560
- const trimmed = rawArgs.trim();
561
- if (trimmed.startsWith('{')) {
562
- try {
563
- const parsed = JSON.parse(rawArgs);
564
- if (isObject(parsed)) {
565
- argsStruct = parsed;
566
- }
567
- else {
568
- argsStruct = { _raw: rawArgs };
569
- }
570
- }
571
- catch {
572
- argsStruct = { _raw: rawArgs };
573
- }
574
- }
575
- else {
576
- argsStruct = { _raw: rawArgs };
577
- }
578
- }
579
- else if (isObject(rawArgs)) {
580
- argsStruct = rawArgs;
581
- }
582
- else if (Array.isArray(rawArgs)) {
583
- try {
584
- argsStruct = { _raw: JSON.stringify(rawArgs) };
585
- }
586
- catch {
587
- argsStruct = { _raw: String(rawArgs) };
588
- }
589
- }
590
- else if (rawArgs != null) {
591
- argsStruct = { _raw: String(rawArgs) };
592
- }
593
- else {
594
- argsStruct = {};
595
- }
596
- // Gemini request/response wire uses `args` for functionCall payload.
597
- const functionCall = { name, args: argsStruct };
598
- const id = typeof tc.id === 'string' ? String(tc.id) : undefined;
599
- if (id)
600
- functionCall.id = id;
601
- const thoughtSignature = extractThoughtSignatureFromToolCall(tc);
602
- const partEntry = { functionCall };
603
- if (thoughtSignature) {
604
- partEntry.thoughtSignature = thoughtSignature;
605
- }
606
- parts.push(partEntry);
607
- }
608
- const candidate = {
609
- content: {
610
- role: baseRole,
611
- parts
612
- },
613
- finishReason
614
- };
615
- const usageMetadata = {};
616
- const promptTokens = Number(usage.prompt_tokens ?? usage.input_tokens);
617
- const completionTokens = Number(usage.completion_tokens ?? usage.output_tokens);
618
- const totalTokens = Number(usage.total_tokens);
619
- if (Number.isFinite(promptTokens))
620
- usageMetadata.promptTokenCount = promptTokens;
621
- if (Number.isFinite(completionTokens))
622
- usageMetadata.candidatesTokenCount = completionTokens;
623
- if (Number.isFinite(totalTokens))
624
- usageMetadata.totalTokenCount = totalTokens;
625
- const responseId = typeof chatResp?.id === 'string' ? String(chatResp.id) : `chatcmpl_${Date.now()}`;
626
- const out = {
627
- id: responseId,
628
- candidates: [candidate]
629
- };
630
- if (chatResp?.model) {
631
- out.model = chatResp.model;
632
- }
633
- if (Object.keys(usageMetadata).length > 0)
634
- out.usageMetadata = usageMetadata;
635
- if (chatResp?.__responses_reasoning) {
636
- registerResponsesReasoning(responseId, chatResp.__responses_reasoning);
637
- }
638
- if (chatResp?.__responses_output_text_meta) {
639
- registerResponsesOutputTextMeta(responseId, chatResp.__responses_output_text_meta);
640
- }
641
- return out;
59
+ const result = runGeminiFromOpenAIChatCodecWithNative((chatResp ?? {}));
60
+ return narrowJsonObject(result);
642
61
  }
643
62
  export class GeminiOpenAIConversionCodec {
644
63
  _dependencies;
@@ -657,66 +76,10 @@ export class GeminiOpenAIConversionCodec {
657
76
  }
658
77
  async convertRequest(payload, _profile, _context) {
659
78
  await this.ensureInit();
660
- const model = String((payload && typeof payload === 'object' && payload.model) || 'unknown');
661
- const { messages } = buildOpenAIChatFromGeminiRequest(payload);
662
- const out = { model, messages };
663
- try {
664
- const bridgeTools = prepareGeminiToolsForBridge(payload?.tools);
665
- const tools = bridgeTools ? mapBridgeToolsToChat(bridgeTools) : undefined;
666
- if (tools && tools.length > 0)
667
- out.tools = tools;
668
- }
669
- catch {
670
- // keep tools undefined on failure
671
- }
672
- try {
673
- const gen = payload?.generationConfig;
674
- if (gen && typeof gen === 'object') {
675
- const g = gen;
676
- const max = Number(g.maxOutputTokens ?? g.max_output_tokens);
677
- if (Number.isFinite(max) && max > 0)
678
- out.max_tokens = max;
679
- if (typeof g.temperature === 'number')
680
- out.temperature = g.temperature;
681
- if (typeof g.topP === 'number')
682
- out.top_p = g.topP;
683
- const stop = g.stopSequences;
684
- if (typeof stop === 'string' && stop.trim()) {
685
- out.stop = [stop.trim()];
686
- }
687
- else if (Array.isArray(stop) && stop.length > 0) {
688
- out.stop = stop.map((s) => String(s)).filter(Boolean);
689
- }
690
- }
691
- }
692
- catch {
693
- // best-effort on generationConfig
694
- }
695
- try {
696
- const safety = payload?.safetySettings;
697
- const metadata = payload?.metadata;
698
- if (safety || metadata) {
699
- out.metadata = {
700
- ...(isObject(metadata) ? metadata : {}),
701
- vendor: {
702
- ...(isObject(metadata?.vendor) ? metadata.vendor : {}),
703
- gemini: {
704
- safetySettings: safety
705
- }
706
- }
707
- };
708
- }
709
- }
710
- catch {
711
- // ignore metadata failures
712
- }
713
- // 不在此处设置 stream;流控由后半段统一处理
714
- return out;
79
+ return buildOpenAIChatFromGeminiRequest(payload);
715
80
  }
716
81
  async convertResponse(payload, _profile, _context) {
717
82
  await this.ensureInit();
718
- // 这里假设 payload 已经是标准 Chat completion,直接映射回 Gemini 响应;
719
- // 对于 provider→Chat 的路径,宿主可以直接调用 buildOpenAIChatFromGeminiResponse。
720
83
  return buildGeminiFromOpenAIChat(payload);
721
84
  }
722
85
  }