@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.
- 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/native/router_hotpath_napi.node +0 -0
- package/dist/router/virtual-router/bootstrap/routing-config.js +11 -3
- 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.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/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,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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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 (
|
|
34
|
-
return
|
|
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
|
|
16
|
+
return Object.values(value).every((entry) => isJsonValue(entry));
|
|
63
17
|
}
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
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
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
|
179
|
-
|
|
180
|
-
|
|
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
|
|
468
|
-
|
|
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
|
-
|
|
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
|
}
|