@jsonstudio/llms 0.6.938 → 0.6.1164
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/hub/operation-table/operation-table-runner.d.ts +18 -0
- package/dist/conversion/hub/operation-table/operation-table-runner.js +158 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.d.ts +8 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +303 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.d.ts +8 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +413 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.d.ts +7 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +841 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.d.ts +21 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +535 -0
- package/dist/conversion/hub/ops/operations.d.ts +19 -0
- package/dist/conversion/hub/ops/operations.js +126 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +9 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +533 -24
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +6 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +6 -3
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +11 -0
- package/dist/conversion/hub/policy/policy-engine.js +41 -9
- package/dist/conversion/hub/policy/protocol-spec.d.ts +25 -0
- package/dist/conversion/hub/policy/protocol-spec.js +73 -23
- package/dist/conversion/hub/process/chat-process.js +252 -41
- package/dist/conversion/hub/response/provider-response.js +175 -2
- package/dist/conversion/hub/response/response-runtime.js +1 -1
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.d.ts +1 -8
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +1 -365
- package/dist/conversion/hub/semantic-mappers/chat-mapper.d.ts +1 -8
- package/dist/conversion/hub/semantic-mappers/chat-mapper.js +1 -436
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.d.ts +1 -7
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +1 -894
- package/dist/conversion/hub/semantic-mappers/responses-mapper.d.ts +1 -21
- package/dist/conversion/hub/semantic-mappers/responses-mapper.js +1 -593
- package/dist/conversion/hub/tool-surface/tool-surface-engine.d.ts +18 -0
- package/dist/conversion/hub/tool-surface/tool-surface-engine.js +571 -0
- package/dist/conversion/responses/responses-openai-bridge.js +14 -2
- package/dist/conversion/shared/bridge-message-utils.js +2 -8
- package/dist/conversion/shared/bridge-policies.js +5 -105
- package/dist/conversion/shared/gemini-tool-utils.js +121 -4
- package/dist/conversion/shared/protocol-field-allowlists.d.ts +7 -0
- package/dist/conversion/shared/protocol-field-allowlists.js +145 -0
- package/dist/conversion/shared/reasoning-tool-normalizer.js +4 -2
- package/dist/conversion/shared/snapshot-hooks.js +166 -3
- package/dist/conversion/shared/text-markup-normalizer.d.ts +2 -0
- package/dist/conversion/shared/text-markup-normalizer.js +345 -9
- package/dist/conversion/shared/thought-signature-validator.d.ts +52 -0
- package/dist/conversion/shared/thought-signature-validator.js +170 -0
- package/dist/conversion/shared/tool-argument-repairer.d.ts +39 -0
- package/dist/conversion/shared/tool-argument-repairer.js +56 -0
- package/dist/conversion/shared/tool-call-id-manager.d.ts +113 -0
- package/dist/conversion/shared/tool-call-id-manager.js +231 -0
- package/dist/conversion/shared/tool-canonicalizer.js +2 -11
- package/dist/router/virtual-router/bootstrap.js +54 -5
- package/dist/router/virtual-router/engine-selection.js +132 -42
- package/dist/router/virtual-router/engine.d.ts +3 -0
- package/dist/router/virtual-router/engine.js +142 -33
- package/dist/router/virtual-router/health-weighted.d.ts +25 -0
- package/dist/router/virtual-router/health-weighted.js +63 -0
- package/dist/router/virtual-router/load-balancer.d.ts +2 -0
- package/dist/router/virtual-router/load-balancer.js +45 -16
- package/dist/router/virtual-router/routing-instructions.js +17 -1
- package/dist/router/virtual-router/sticky-session-store.js +136 -24
- package/dist/router/virtual-router/stop-message-file-resolver.d.ts +1 -0
- package/dist/router/virtual-router/stop-message-file-resolver.js +74 -0
- package/dist/router/virtual-router/stop-message-state-sync.d.ts +15 -0
- package/dist/router/virtual-router/stop-message-state-sync.js +57 -0
- package/dist/router/virtual-router/types.d.ts +70 -0
- package/dist/servertool/clock/config.d.ts +7 -0
- package/dist/servertool/clock/config.js +27 -0
- package/dist/servertool/clock/daemon.d.ts +3 -0
- package/dist/servertool/clock/daemon.js +79 -0
- package/dist/servertool/clock/io.d.ts +2 -0
- package/dist/servertool/clock/io.js +13 -0
- package/dist/servertool/clock/paths.d.ts +4 -0
- package/dist/servertool/clock/paths.js +25 -0
- package/dist/servertool/clock/session-store.d.ts +3 -0
- package/dist/servertool/clock/session-store.js +56 -0
- package/dist/servertool/clock/state.d.ts +5 -0
- package/dist/servertool/clock/state.js +62 -0
- package/dist/servertool/clock/task-store.d.ts +5 -0
- package/dist/servertool/clock/task-store.js +4 -0
- package/dist/servertool/clock/tasks.d.ts +17 -0
- package/dist/servertool/clock/tasks.js +221 -0
- package/dist/servertool/clock/types.d.ts +36 -0
- package/dist/servertool/clock/types.js +1 -0
- package/dist/servertool/engine.d.ts +2 -0
- package/dist/servertool/engine.js +164 -8
- package/dist/servertool/followup-shadow.d.ts +16 -0
- package/dist/servertool/followup-shadow.js +145 -0
- package/dist/servertool/handlers/apply-patch-guard.js +1 -265
- package/dist/servertool/handlers/clock-auto.d.ts +1 -0
- package/dist/servertool/handlers/clock-auto.js +160 -0
- package/dist/servertool/handlers/clock.d.ts +1 -0
- package/dist/servertool/handlers/clock.js +197 -0
- package/dist/servertool/handlers/exec-command-guard.js +7 -555
- package/dist/servertool/handlers/followup-request-builder.d.ts +15 -7
- package/dist/servertool/handlers/followup-request-builder.js +248 -28
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +62 -169
- package/dist/servertool/handlers/iflow-model-error-retry.js +18 -28
- package/dist/servertool/handlers/recursive-detection-guard.d.ts +1 -0
- package/dist/servertool/handlers/recursive-detection-guard.js +333 -0
- package/dist/servertool/handlers/stop-message-auto.js +47 -175
- package/dist/servertool/handlers/vision.d.ts +7 -1
- package/dist/servertool/handlers/vision.js +61 -117
- package/dist/servertool/handlers/web-search.d.ts +7 -1
- package/dist/servertool/handlers/web-search.js +122 -105
- package/dist/servertool/reenter-backend.d.ts +23 -0
- package/dist/servertool/reenter-backend.js +18 -0
- package/dist/servertool/server-side-tools.d.ts +3 -2
- package/dist/servertool/server-side-tools.js +64 -10
- package/dist/servertool/types.d.ts +92 -3
- package/dist/sse/json-to-sse/event-generators/responses.js +3 -21
- package/dist/sse/shared/serializers/responses-event-serializer.d.ts +8 -0
- package/dist/sse/shared/serializers/responses-event-serializer.js +19 -0
- package/dist/sse/shared/writer.js +24 -7
- package/dist/tools/apply-patch/execution-capturer.js +3 -1
- package/dist/tools/apply-patch/json/parse-loose.d.ts +3 -0
- package/dist/tools/apply-patch/json/parse-loose.js +139 -0
- package/dist/tools/apply-patch/patch-text/context-diff.d.ts +1 -0
- package/dist/tools/apply-patch/patch-text/context-diff.js +173 -0
- package/dist/tools/apply-patch/patch-text/git-diff.d.ts +1 -0
- package/dist/tools/apply-patch/patch-text/git-diff.js +138 -0
- package/dist/tools/apply-patch/patch-text/looks-like-patch.d.ts +1 -0
- package/dist/tools/apply-patch/patch-text/looks-like-patch.js +13 -0
- package/dist/tools/apply-patch/patch-text/normalize.d.ts +3 -0
- package/dist/tools/apply-patch/patch-text/normalize.js +262 -0
- package/dist/tools/apply-patch/structured/coercion.d.ts +3 -0
- package/dist/tools/apply-patch/structured/coercion.js +82 -0
- package/dist/tools/apply-patch/validation/shared.d.ts +3 -0
- package/dist/tools/apply-patch/validation/shared.js +6 -0
- package/dist/tools/apply-patch/validator.d.ts +2 -2
- package/dist/tools/apply-patch/validator.js +6 -556
- package/package.json +1 -1
|
@@ -0,0 +1,841 @@
|
|
|
1
|
+
import { isJsonObject, jsonClone } from '../../types/json.js';
|
|
2
|
+
import { buildOpenAIChatFromGeminiRequest } from '../../../codecs/gemini-openai-codec.js';
|
|
3
|
+
import { encodeMetadataPassthrough, extractMetadataPassthrough } from '../../../shared/metadata-passthrough.js';
|
|
4
|
+
import { mapBridgeToolsToChat, mapChatToolsToBridge } from '../../../shared/tool-mapping.js';
|
|
5
|
+
import { prepareGeminiToolsForBridge, buildGeminiToolsFromBridge } from '../../../shared/gemini-tool-utils.js';
|
|
6
|
+
import { ensureProtocolState, getProtocolState } from '../../../shared/protocol-state.js';
|
|
7
|
+
import { applyClaudeThinkingToolSchemaCompat } from '../../../compat/actions/claude-thinking-tools.js';
|
|
8
|
+
const GENERATION_CONFIG_KEYS = [
|
|
9
|
+
{ source: 'temperature', target: 'temperature' },
|
|
10
|
+
{ source: 'topP', target: 'top_p' },
|
|
11
|
+
{ source: 'topK', target: 'top_k' },
|
|
12
|
+
{ source: 'maxOutputTokens', target: 'max_output_tokens' },
|
|
13
|
+
{ source: 'candidateCount', target: 'candidate_count' },
|
|
14
|
+
{ source: 'responseMimeType', target: 'response_mime_type' },
|
|
15
|
+
{ source: 'stopSequences', target: 'stop_sequences' }
|
|
16
|
+
];
|
|
17
|
+
const PASSTHROUGH_METADATA_PREFIX = 'rcc_passthrough_';
|
|
18
|
+
const PASSTHROUGH_PARAMETERS = ['tool_choice'];
|
|
19
|
+
const DUMMY_THOUGHT_SIGNATURE = 'skip_thought_signature_validator';
|
|
20
|
+
function coerceThoughtSignature(value) {
|
|
21
|
+
if (typeof value === 'string' && value.trim().length) {
|
|
22
|
+
return value.trim();
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
function ensureGeminiSemanticsNode(chat) {
|
|
27
|
+
if (!chat.semantics || typeof chat.semantics !== 'object') {
|
|
28
|
+
chat.semantics = {};
|
|
29
|
+
}
|
|
30
|
+
if (!chat.semantics.gemini || !isJsonObject(chat.semantics.gemini)) {
|
|
31
|
+
chat.semantics.gemini = {};
|
|
32
|
+
}
|
|
33
|
+
return chat.semantics.gemini;
|
|
34
|
+
}
|
|
35
|
+
function ensureSystemSemantics(chat) {
|
|
36
|
+
if (!chat.semantics || typeof chat.semantics !== 'object') {
|
|
37
|
+
chat.semantics = {};
|
|
38
|
+
}
|
|
39
|
+
if (!chat.semantics.system || !isJsonObject(chat.semantics.system)) {
|
|
40
|
+
chat.semantics.system = {};
|
|
41
|
+
}
|
|
42
|
+
return chat.semantics.system;
|
|
43
|
+
}
|
|
44
|
+
function markGeminiExplicitEmptyTools(chat) {
|
|
45
|
+
if (!chat.semantics || typeof chat.semantics !== 'object') {
|
|
46
|
+
chat.semantics = {};
|
|
47
|
+
}
|
|
48
|
+
if (!chat.semantics.tools || !isJsonObject(chat.semantics.tools)) {
|
|
49
|
+
chat.semantics.tools = {};
|
|
50
|
+
}
|
|
51
|
+
chat.semantics.tools.explicitEmpty = true;
|
|
52
|
+
}
|
|
53
|
+
function readGeminiSemantics(chat) {
|
|
54
|
+
if (!chat.semantics || typeof chat.semantics !== 'object') {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
const node = chat.semantics.gemini;
|
|
58
|
+
return node && isJsonObject(node) ? node : undefined;
|
|
59
|
+
}
|
|
60
|
+
function hasExplicitEmptyToolsSemantics(chat) {
|
|
61
|
+
if (!chat.semantics || typeof chat.semantics !== 'object') {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
const toolsNode = chat.semantics.tools;
|
|
65
|
+
if (!toolsNode || !isJsonObject(toolsNode)) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return Boolean(toolsNode.explicitEmpty);
|
|
69
|
+
}
|
|
70
|
+
function readSystemTextBlocksFromSemantics(chat) {
|
|
71
|
+
if (!chat.semantics || typeof chat.semantics !== 'object') {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
const systemNode = chat.semantics.system;
|
|
75
|
+
if (!systemNode || !isJsonObject(systemNode)) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
const rawBlocks = systemNode.textBlocks;
|
|
79
|
+
if (!Array.isArray(rawBlocks)) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
const normalized = rawBlocks
|
|
83
|
+
.map((entry) => (typeof entry === 'string' ? entry : undefined))
|
|
84
|
+
.filter((value) => typeof value === 'string' && value.trim().length > 0);
|
|
85
|
+
return normalized.length ? normalized : undefined;
|
|
86
|
+
}
|
|
87
|
+
function extractThoughtSignatureFromToolCall(tc) {
|
|
88
|
+
if (!tc || typeof tc !== 'object') {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
const node = tc;
|
|
92
|
+
const direct = coerceThoughtSignature(node.thought_signature ?? node.thoughtSignature);
|
|
93
|
+
if (direct) {
|
|
94
|
+
return direct;
|
|
95
|
+
}
|
|
96
|
+
const extra = node.extra_content ?? node.extraContent;
|
|
97
|
+
if (extra && typeof extra === 'object') {
|
|
98
|
+
const googleNode = extra.google ?? extra.Google;
|
|
99
|
+
if (googleNode && typeof googleNode === 'object') {
|
|
100
|
+
return coerceThoughtSignature(googleNode.thought_signature ?? googleNode.thoughtSignature);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
function normalizeToolOutputs(messages, missing) {
|
|
106
|
+
const outputs = [];
|
|
107
|
+
messages.forEach((msg, index) => {
|
|
108
|
+
if (msg.role !== 'tool')
|
|
109
|
+
return;
|
|
110
|
+
const callId = msg.tool_call_id || msg.id;
|
|
111
|
+
if (typeof callId !== 'string' || !callId.trim()) {
|
|
112
|
+
missing.push({ path: `messages[${index}].tool_call_id`, reason: 'missing_tool_call_id' });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
outputs.push({
|
|
116
|
+
tool_call_id: callId.trim(),
|
|
117
|
+
content: normalizeToolContent(msg.content),
|
|
118
|
+
name: typeof msg.name === 'string' ? msg.name : undefined
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
return outputs.length ? outputs : undefined;
|
|
122
|
+
}
|
|
123
|
+
function synthesizeToolOutputsFromMessages(messages) {
|
|
124
|
+
if (!Array.isArray(messages)) {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
const outputs = [];
|
|
128
|
+
for (const message of messages) {
|
|
129
|
+
if (!message || typeof message !== 'object')
|
|
130
|
+
continue;
|
|
131
|
+
if (message.role !== 'assistant')
|
|
132
|
+
continue;
|
|
133
|
+
const toolCalls = Array.isArray(message.tool_calls)
|
|
134
|
+
? message.tool_calls
|
|
135
|
+
: [];
|
|
136
|
+
for (const call of toolCalls) {
|
|
137
|
+
const callId = typeof call.id === 'string' ? call.id : undefined;
|
|
138
|
+
if (!callId) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const existing = outputs.find((entry) => entry.tool_call_id === callId);
|
|
142
|
+
if (existing) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
outputs.push({
|
|
146
|
+
tool_call_id: callId,
|
|
147
|
+
content: '',
|
|
148
|
+
name: (call.function && call.function.name) || undefined
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return outputs;
|
|
153
|
+
}
|
|
154
|
+
function normalizeToolContent(value) {
|
|
155
|
+
if (typeof value === 'string')
|
|
156
|
+
return value;
|
|
157
|
+
if (value == null)
|
|
158
|
+
return '';
|
|
159
|
+
try {
|
|
160
|
+
return JSON.stringify(value);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return String(value ?? '');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function convertToolMessageToOutput(message, allowedIds) {
|
|
167
|
+
const rawId = (message.tool_call_id ?? message.id);
|
|
168
|
+
const callId = typeof rawId === 'string' && rawId.trim().length ? rawId.trim() : undefined;
|
|
169
|
+
if (!callId) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
if (allowedIds && !allowedIds.has(callId)) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
tool_call_id: callId,
|
|
177
|
+
content: normalizeToolContent(message.content),
|
|
178
|
+
name: typeof message.name === 'string' ? message.name : undefined
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function selectAntigravityClaudeThinkingMessages(messages) {
|
|
182
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
183
|
+
return messages ?? [];
|
|
184
|
+
}
|
|
185
|
+
// 为了与 Responses 入口对齐,Claude-thinking 在发往 Antigravity 时仅保留
|
|
186
|
+
// 当前这一轮的 user 消息,丢弃历史 model/assistant 片段(例如错误日志中的「{」)。
|
|
187
|
+
let lastUserIndex = -1;
|
|
188
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
189
|
+
const msg = messages[i];
|
|
190
|
+
if (!msg || typeof msg !== 'object')
|
|
191
|
+
continue;
|
|
192
|
+
if (msg.role === 'user') {
|
|
193
|
+
lastUserIndex = i;
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (lastUserIndex === -1) {
|
|
198
|
+
return messages;
|
|
199
|
+
}
|
|
200
|
+
return [messages[lastUserIndex]];
|
|
201
|
+
}
|
|
202
|
+
function buildFunctionResponseEntry(output) {
|
|
203
|
+
const parsedPayload = safeParseJson(output.content);
|
|
204
|
+
const normalizedPayload = ensureFunctionResponsePayload(cloneAsJsonValue(parsedPayload));
|
|
205
|
+
const part = {
|
|
206
|
+
functionResponse: {
|
|
207
|
+
name: output.name || 'tool',
|
|
208
|
+
id: output.tool_call_id,
|
|
209
|
+
response: normalizedPayload
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
return { role: 'user', parts: [part] };
|
|
213
|
+
}
|
|
214
|
+
function collectSystemSegments(systemInstruction) {
|
|
215
|
+
if (!systemInstruction)
|
|
216
|
+
return [];
|
|
217
|
+
const flatten = (val) => {
|
|
218
|
+
if (typeof val === 'string')
|
|
219
|
+
return val;
|
|
220
|
+
if (Array.isArray(val))
|
|
221
|
+
return val.map((entry) => flatten(entry)).filter(Boolean).join('\n');
|
|
222
|
+
if (val && typeof val === 'object') {
|
|
223
|
+
const text = val.text;
|
|
224
|
+
if (typeof text === 'string')
|
|
225
|
+
return text;
|
|
226
|
+
const parts = val.parts;
|
|
227
|
+
if (Array.isArray(parts))
|
|
228
|
+
return parts.map((entry) => flatten(entry)).filter(Boolean).join('\n');
|
|
229
|
+
}
|
|
230
|
+
return '';
|
|
231
|
+
};
|
|
232
|
+
const text = flatten(systemInstruction).trim();
|
|
233
|
+
return text ? [text] : [];
|
|
234
|
+
}
|
|
235
|
+
function collectParameters(payload) {
|
|
236
|
+
const params = {};
|
|
237
|
+
if (typeof payload.model === 'string') {
|
|
238
|
+
params.model = payload.model;
|
|
239
|
+
}
|
|
240
|
+
const gen = payload.generationConfig;
|
|
241
|
+
if (gen && typeof gen === 'object') {
|
|
242
|
+
for (const { source, target } of GENERATION_CONFIG_KEYS) {
|
|
243
|
+
const value = gen[source];
|
|
244
|
+
if (value !== undefined) {
|
|
245
|
+
params[target] = value;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (payload.toolConfig !== undefined) {
|
|
250
|
+
params.tool_config = jsonClone(payload.toolConfig);
|
|
251
|
+
}
|
|
252
|
+
const meta = payload.metadata;
|
|
253
|
+
if (meta && typeof meta === 'object' && Object.prototype.hasOwnProperty.call(meta, '__rcc_stream')) {
|
|
254
|
+
params.stream = Boolean(meta.__rcc_stream);
|
|
255
|
+
}
|
|
256
|
+
return Object.keys(params).length ? params : undefined;
|
|
257
|
+
}
|
|
258
|
+
function appendChatContentToGeminiParts(message, targetParts) {
|
|
259
|
+
const content = message.content;
|
|
260
|
+
if (typeof content === 'string') {
|
|
261
|
+
const text = content.trim();
|
|
262
|
+
if (text.length) {
|
|
263
|
+
targetParts.push({ text });
|
|
264
|
+
}
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (!Array.isArray(content)) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const items = content;
|
|
271
|
+
for (const block of items) {
|
|
272
|
+
if (block == null)
|
|
273
|
+
continue;
|
|
274
|
+
if (typeof block === 'string') {
|
|
275
|
+
const text = block.trim();
|
|
276
|
+
if (text.length) {
|
|
277
|
+
targetParts.push({ text });
|
|
278
|
+
}
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (typeof block !== 'object') {
|
|
282
|
+
const text = String(block);
|
|
283
|
+
if (text.trim().length) {
|
|
284
|
+
targetParts.push({ text: text.trim() });
|
|
285
|
+
}
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const record = block;
|
|
289
|
+
const rawType = record.type;
|
|
290
|
+
const type = typeof rawType === 'string' ? rawType.toLowerCase() : '';
|
|
291
|
+
// Text-style blocks
|
|
292
|
+
if (!type || type === 'text') {
|
|
293
|
+
const textValue = typeof record.text === 'string'
|
|
294
|
+
? record.text
|
|
295
|
+
: typeof record.content === 'string'
|
|
296
|
+
? record.content
|
|
297
|
+
: '';
|
|
298
|
+
const text = textValue.trim();
|
|
299
|
+
if (text.length) {
|
|
300
|
+
targetParts.push({ text });
|
|
301
|
+
}
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
// Image-style blocks -> Gemini inlineData
|
|
305
|
+
if (type === 'image' || type === 'image_url') {
|
|
306
|
+
// Prefer OpenAI-style image_url.url, but also accept uri/url/data.
|
|
307
|
+
let url;
|
|
308
|
+
const imageUrlRaw = record.image_url;
|
|
309
|
+
if (typeof imageUrlRaw === 'string') {
|
|
310
|
+
url = imageUrlRaw;
|
|
311
|
+
}
|
|
312
|
+
else if (imageUrlRaw && typeof imageUrlRaw === 'object' && typeof imageUrlRaw.url === 'string') {
|
|
313
|
+
url = imageUrlRaw.url;
|
|
314
|
+
}
|
|
315
|
+
else if (typeof record.uri === 'string') {
|
|
316
|
+
url = record.uri;
|
|
317
|
+
}
|
|
318
|
+
else if (typeof record.url === 'string') {
|
|
319
|
+
url = record.url;
|
|
320
|
+
}
|
|
321
|
+
else if (typeof record.data === 'string') {
|
|
322
|
+
url = record.data;
|
|
323
|
+
}
|
|
324
|
+
const trimmed = (url ?? '').trim();
|
|
325
|
+
if (!trimmed.length) {
|
|
326
|
+
// Fallback: at least emit a textual marker so内容不会完全丢失
|
|
327
|
+
targetParts.push({ text: '[image]' });
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
let mimeType;
|
|
331
|
+
let data;
|
|
332
|
+
// data:URL → inlineData { mimeType, data }
|
|
333
|
+
if (trimmed.startsWith('data:')) {
|
|
334
|
+
const match = /^data:([^;,]+)?(?:;base64)?,(.*)$/s.exec(trimmed);
|
|
335
|
+
if (match) {
|
|
336
|
+
mimeType = (match[1] || '').trim() || undefined;
|
|
337
|
+
data = match[2] || '';
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (data && data.trim().length) {
|
|
341
|
+
const inline = {
|
|
342
|
+
inlineData: {
|
|
343
|
+
data: data.trim()
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
if (mimeType && mimeType.length) {
|
|
347
|
+
inline.inlineData.mimeType = mimeType;
|
|
348
|
+
}
|
|
349
|
+
targetParts.push(inline);
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
// 非 data: URL 暂时作为文本 URL 传递,保持语义可见
|
|
353
|
+
targetParts.push({ text: trimmed });
|
|
354
|
+
}
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
// 默认:回退为文本 JSON 表示,避免静默丢失内容
|
|
358
|
+
try {
|
|
359
|
+
const jsonText = JSON.stringify(record);
|
|
360
|
+
if (jsonText.trim().length) {
|
|
361
|
+
targetParts.push({ text: jsonText });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
// ignore malformed block
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
function buildGeminiRequestFromChat(chat, metadata) {
|
|
370
|
+
const contents = [];
|
|
371
|
+
const emittedToolOutputs = new Set();
|
|
372
|
+
const adapterContext = metadata?.context;
|
|
373
|
+
const rawProviderId = adapterContext?.providerId;
|
|
374
|
+
const entryEndpointRaw = adapterContext?.entryEndpoint;
|
|
375
|
+
const entryEndpoint = typeof entryEndpointRaw === 'string' ? entryEndpointRaw.trim().toLowerCase() : '';
|
|
376
|
+
const isAnthropicEntry = entryEndpoint === '/v1/messages';
|
|
377
|
+
const normalizedProviderId = typeof rawProviderId === 'string' ? rawProviderId.toLowerCase() : '';
|
|
378
|
+
const providerIdPrefix = normalizedProviderId.split('.')[0];
|
|
379
|
+
const isAntigravityClaudeThinking = providerIdPrefix === 'antigravity' &&
|
|
380
|
+
typeof chat.parameters?.model === 'string' &&
|
|
381
|
+
chat.parameters.model.includes('claude-sonnet-4-5-thinking');
|
|
382
|
+
// 保持对通用 gemini-cli 的保护(避免上游直接执行 functionCall),
|
|
383
|
+
// 但对于 antigravity.* 明确允许通过 Gemini functionCall 协议执行工具,
|
|
384
|
+
// 以便完整打通 tools → functionCall → functionResponse 链路。
|
|
385
|
+
const omitFunctionCallPartsForCli = providerIdPrefix === 'gemini-cli';
|
|
386
|
+
const semanticsNode = readGeminiSemantics(chat);
|
|
387
|
+
const systemTextBlocksFromSemantics = readSystemTextBlocksFromSemantics(chat);
|
|
388
|
+
const bridgeDefs = chat.tools && chat.tools.length ? mapChatToolsToBridge(chat.tools) : undefined;
|
|
389
|
+
const toolSchemaKeys = bridgeDefs ? buildToolSchemaKeyMap(bridgeDefs) : new Map();
|
|
390
|
+
const sourceMessages = chat.messages;
|
|
391
|
+
// 收集当前 ChatEnvelope 中 assistant/tool_calls 的 id,用于过滤孤立的 tool_result:
|
|
392
|
+
// 只有在本轮对话中存在对应 tool_call 的 tool_result 才允许映射为 Gemini functionResponse。
|
|
393
|
+
const assistantToolCallIds = new Set();
|
|
394
|
+
for (const msg of sourceMessages) {
|
|
395
|
+
if (!msg || typeof msg !== 'object')
|
|
396
|
+
continue;
|
|
397
|
+
if (msg.role !== 'assistant')
|
|
398
|
+
continue;
|
|
399
|
+
const tcs = Array.isArray(msg.tool_calls)
|
|
400
|
+
? msg.tool_calls
|
|
401
|
+
: [];
|
|
402
|
+
for (const tc of tcs) {
|
|
403
|
+
const id = typeof tc.id === 'string' ? tc.id.trim() : '';
|
|
404
|
+
if (id) {
|
|
405
|
+
assistantToolCallIds.add(id);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
for (const message of sourceMessages) {
|
|
410
|
+
if (!message || typeof message !== 'object')
|
|
411
|
+
continue;
|
|
412
|
+
if (message.role === 'system')
|
|
413
|
+
continue;
|
|
414
|
+
if (message.role === 'tool') {
|
|
415
|
+
const toolOutput = convertToolMessageToOutput(message, assistantToolCallIds);
|
|
416
|
+
if (toolOutput) {
|
|
417
|
+
contents.push(buildFunctionResponseEntry(toolOutput));
|
|
418
|
+
emittedToolOutputs.add(toolOutput.tool_call_id);
|
|
419
|
+
}
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
const entry = {
|
|
423
|
+
role: mapChatRoleToGemini(message.role),
|
|
424
|
+
parts: []
|
|
425
|
+
};
|
|
426
|
+
appendChatContentToGeminiParts(message, entry.parts);
|
|
427
|
+
const toolCalls = Array.isArray(message.tool_calls)
|
|
428
|
+
? message.tool_calls
|
|
429
|
+
: [];
|
|
430
|
+
for (const tc of toolCalls) {
|
|
431
|
+
if (!tc || typeof tc !== 'object')
|
|
432
|
+
continue;
|
|
433
|
+
if (omitFunctionCallPartsForCli) {
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
const fn = tc.function || {};
|
|
437
|
+
const name = typeof fn.name === 'string' ? fn.name : undefined;
|
|
438
|
+
if (!name)
|
|
439
|
+
continue;
|
|
440
|
+
let argsStruct;
|
|
441
|
+
if (typeof fn.arguments === 'string') {
|
|
442
|
+
try {
|
|
443
|
+
argsStruct = JSON.parse(fn.arguments);
|
|
444
|
+
}
|
|
445
|
+
catch {
|
|
446
|
+
argsStruct = { _raw: fn.arguments };
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
argsStruct = fn.arguments ?? {};
|
|
451
|
+
}
|
|
452
|
+
argsStruct = alignToolCallArgsToSchema({ toolName: name, args: argsStruct, schemaKeys: toolSchemaKeys });
|
|
453
|
+
let argsJson = cloneAsJsonValue(argsStruct);
|
|
454
|
+
// Gemini / Antigravity 期望 functionCall.args 为对象(Struct),
|
|
455
|
+
// 若顶层为数组或原始类型,则包装到 value 字段下,避免产生非法的 list 形状。
|
|
456
|
+
if (!argsJson || typeof argsJson !== 'object' || Array.isArray(argsJson)) {
|
|
457
|
+
argsJson = { value: argsJson };
|
|
458
|
+
}
|
|
459
|
+
const functionCall = { name, args: argsJson };
|
|
460
|
+
const part = { functionCall };
|
|
461
|
+
if (typeof tc.id === 'string') {
|
|
462
|
+
part.functionCall.id = tc.id;
|
|
463
|
+
}
|
|
464
|
+
const thoughtSignature = extractThoughtSignatureFromToolCall(tc) ?? DUMMY_THOUGHT_SIGNATURE;
|
|
465
|
+
if (thoughtSignature) {
|
|
466
|
+
part.thoughtSignature = thoughtSignature;
|
|
467
|
+
}
|
|
468
|
+
entry.parts.push(part);
|
|
469
|
+
}
|
|
470
|
+
if (entry.parts.length) {
|
|
471
|
+
contents.push(entry);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
const toolOutputMap = new Map();
|
|
475
|
+
if (Array.isArray(chat.toolOutputs)) {
|
|
476
|
+
for (const entry of chat.toolOutputs) {
|
|
477
|
+
if (entry && typeof entry.tool_call_id === 'string' && entry.tool_call_id.trim().length) {
|
|
478
|
+
toolOutputMap.set(entry.tool_call_id.trim(), entry);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
if (toolOutputMap.size === 0) {
|
|
483
|
+
const syntheticOutputs = synthesizeToolOutputsFromMessages(chat.messages);
|
|
484
|
+
for (const output of syntheticOutputs) {
|
|
485
|
+
toolOutputMap.set(output.tool_call_id, output);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
for (const output of toolOutputMap.values()) {
|
|
489
|
+
if (emittedToolOutputs.has(output.tool_call_id)) {
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
contents.push(buildFunctionResponseEntry(output));
|
|
493
|
+
emittedToolOutputs.add(output.tool_call_id);
|
|
494
|
+
}
|
|
495
|
+
const request = {
|
|
496
|
+
model: chat.parameters?.model || 'models/gemini-pro',
|
|
497
|
+
contents
|
|
498
|
+
};
|
|
499
|
+
const geminiState = getProtocolState(metadata, 'gemini');
|
|
500
|
+
if (semanticsNode?.systemInstruction !== undefined) {
|
|
501
|
+
request.systemInstruction = jsonClone(semanticsNode.systemInstruction);
|
|
502
|
+
}
|
|
503
|
+
else if (geminiState?.systemInstruction !== undefined) {
|
|
504
|
+
request.systemInstruction = jsonClone(geminiState.systemInstruction);
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
const fallbackSystemInstructions = systemTextBlocksFromSemantics;
|
|
508
|
+
if (fallbackSystemInstructions && fallbackSystemInstructions.length) {
|
|
509
|
+
const sysBlocks = fallbackSystemInstructions
|
|
510
|
+
.filter((value) => typeof value === 'string' && value.trim().length > 0)
|
|
511
|
+
.map((value) => ({ text: value }));
|
|
512
|
+
if (sysBlocks.length) {
|
|
513
|
+
request.systemInstruction = { role: 'system', parts: sysBlocks };
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
if (chat.tools && chat.tools.length) {
|
|
518
|
+
const geminiTools = buildGeminiToolsFromBridge(bridgeDefs);
|
|
519
|
+
if (geminiTools) {
|
|
520
|
+
request.tools = geminiTools;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
const generationConfig = buildGenerationConfigFromParameters(chat.parameters || {});
|
|
524
|
+
if (semanticsNode?.generationConfig && isJsonObject(semanticsNode.generationConfig)) {
|
|
525
|
+
for (const [key, value] of Object.entries(semanticsNode.generationConfig)) {
|
|
526
|
+
if (generationConfig[key] !== undefined) {
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
generationConfig[key] = jsonClone(value);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (Object.keys(generationConfig).length) {
|
|
533
|
+
request.generationConfig = generationConfig;
|
|
534
|
+
}
|
|
535
|
+
if (semanticsNode?.safetySettings !== undefined) {
|
|
536
|
+
request.safetySettings = jsonClone(semanticsNode.safetySettings);
|
|
537
|
+
}
|
|
538
|
+
if (chat.parameters?.tool_config && isJsonObject(chat.parameters.tool_config)) {
|
|
539
|
+
request.toolConfig = jsonClone(chat.parameters.tool_config);
|
|
540
|
+
}
|
|
541
|
+
else if (semanticsNode?.toolConfig && isJsonObject(semanticsNode.toolConfig)) {
|
|
542
|
+
request.toolConfig = jsonClone(semanticsNode.toolConfig);
|
|
543
|
+
}
|
|
544
|
+
// 为了保持协议解耦,只在 Gemini 自身或开放式 Chat 入口下透传 providerMetadata;
|
|
545
|
+
// 对于 Anthropic (/v1/messages) 等其它协议的入口,不再将其 metadata 整块转发给 Gemini,
|
|
546
|
+
// 避免跨协议泄漏上游专有字段。
|
|
547
|
+
if (!isAnthropicEntry) {
|
|
548
|
+
if (semanticsNode?.providerMetadata && isJsonObject(semanticsNode.providerMetadata)) {
|
|
549
|
+
request.metadata = jsonClone(semanticsNode.providerMetadata);
|
|
550
|
+
}
|
|
551
|
+
else if (metadata?.providerMetadata && isJsonObject(metadata.providerMetadata)) {
|
|
552
|
+
request.metadata = jsonClone(metadata.providerMetadata);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
if (chat.parameters && chat.parameters.stream !== undefined) {
|
|
556
|
+
request.metadata = request.metadata ?? {};
|
|
557
|
+
request.metadata.__rcc_stream = chat.parameters.stream;
|
|
558
|
+
}
|
|
559
|
+
if ((chat.metadata?.toolsFieldPresent || hasExplicitEmptyToolsSemantics(chat)) &&
|
|
560
|
+
(!Array.isArray(chat.tools) || chat.tools.length === 0)) {
|
|
561
|
+
request.metadata = request.metadata ?? {};
|
|
562
|
+
request.metadata.__rcc_tools_field_present = '1';
|
|
563
|
+
}
|
|
564
|
+
const passthrough = encodeMetadataPassthrough(chat.parameters, {
|
|
565
|
+
prefix: PASSTHROUGH_METADATA_PREFIX,
|
|
566
|
+
keys: PASSTHROUGH_PARAMETERS
|
|
567
|
+
});
|
|
568
|
+
if (passthrough) {
|
|
569
|
+
request.metadata = request.metadata ?? {};
|
|
570
|
+
for (const [key, value] of Object.entries(passthrough)) {
|
|
571
|
+
request.metadata[key] = value;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
// Apply claude-thinking compat at Gemini mapping time to ensure it is always active
|
|
575
|
+
// for Claude models, regardless of compatibilityProfile wiring. Provider层负责进一步的
|
|
576
|
+
// 传输层收紧(如 session_id / generationConfig),这里不做非标裁剪。
|
|
577
|
+
const compatRequest = applyClaudeThinkingToolSchemaCompat(request, adapterContext);
|
|
578
|
+
return compatRequest;
|
|
579
|
+
}
|
|
580
|
+
function isPlainRecord(value) {
|
|
581
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
582
|
+
}
|
|
583
|
+
function buildToolSchemaKeyMap(defs) {
|
|
584
|
+
const map = new Map();
|
|
585
|
+
for (const def of defs) {
|
|
586
|
+
const fnNode = def && typeof def === 'object' && def.function && typeof def.function === 'object'
|
|
587
|
+
? def.function
|
|
588
|
+
: undefined;
|
|
589
|
+
const name = typeof fnNode?.name === 'string'
|
|
590
|
+
? fnNode.name
|
|
591
|
+
: typeof def?.name === 'string'
|
|
592
|
+
? String(def.name)
|
|
593
|
+
: '';
|
|
594
|
+
if (!name || !name.trim())
|
|
595
|
+
continue;
|
|
596
|
+
const parameters = (fnNode && fnNode.parameters) ??
|
|
597
|
+
(def.parameters);
|
|
598
|
+
if (!isPlainRecord(parameters))
|
|
599
|
+
continue;
|
|
600
|
+
const props = parameters.properties;
|
|
601
|
+
if (!isPlainRecord(props))
|
|
602
|
+
continue;
|
|
603
|
+
const keys = Object.keys(props).filter((k) => typeof k === 'string' && k.trim().length > 0);
|
|
604
|
+
if (!keys.length)
|
|
605
|
+
continue;
|
|
606
|
+
map.set(name, new Set(keys));
|
|
607
|
+
}
|
|
608
|
+
return map;
|
|
609
|
+
}
|
|
610
|
+
function alignToolCallArgsToSchema(options) {
|
|
611
|
+
const name = typeof options.toolName === 'string' ? options.toolName.trim() : '';
|
|
612
|
+
if (!name)
|
|
613
|
+
return options.args;
|
|
614
|
+
const schema = options.schemaKeys.get(name);
|
|
615
|
+
if (!schema || schema.size === 0) {
|
|
616
|
+
return options.args;
|
|
617
|
+
}
|
|
618
|
+
if (!isPlainRecord(options.args)) {
|
|
619
|
+
return options.args;
|
|
620
|
+
}
|
|
621
|
+
const lowered = name.toLowerCase();
|
|
622
|
+
const next = { ...options.args };
|
|
623
|
+
// Align historical Codex tool args to the *declared schema* for Gemini.
|
|
624
|
+
// Gemini validates historical functionCall.args against tool declarations, so mismatches like:
|
|
625
|
+
// - exec_command: { cmd } vs schema { command } (or vice-versa)
|
|
626
|
+
// - apply_patch: { patch/input } vs schema { instructions } (or vice-versa)
|
|
627
|
+
// can cause MALFORMED_FUNCTION_CALL and empty responses.
|
|
628
|
+
if (lowered === 'exec_command') {
|
|
629
|
+
// Prefer the declared schema key; do not delete keys blindly.
|
|
630
|
+
if (schema.has('cmd') && !Object.prototype.hasOwnProperty.call(next, 'cmd') && Object.prototype.hasOwnProperty.call(next, 'command')) {
|
|
631
|
+
next.cmd = next.command;
|
|
632
|
+
}
|
|
633
|
+
if (schema.has('command') && !Object.prototype.hasOwnProperty.call(next, 'command') && Object.prototype.hasOwnProperty.call(next, 'cmd')) {
|
|
634
|
+
next.command = next.cmd;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
else if (lowered === 'write_stdin') {
|
|
638
|
+
if (schema.has('chars') && !Object.prototype.hasOwnProperty.call(next, 'chars') && Object.prototype.hasOwnProperty.call(next, 'text')) {
|
|
639
|
+
next.chars = next.text;
|
|
640
|
+
}
|
|
641
|
+
if (schema.has('text') && !Object.prototype.hasOwnProperty.call(next, 'text') && Object.prototype.hasOwnProperty.call(next, 'chars')) {
|
|
642
|
+
next.text = next.chars;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
else if (lowered === 'apply_patch') {
|
|
646
|
+
if (schema.has('instructions') && !Object.prototype.hasOwnProperty.call(next, 'instructions')) {
|
|
647
|
+
const patch = typeof next.patch === 'string' ? next.patch : undefined;
|
|
648
|
+
const input = typeof next.input === 'string' ? next.input : undefined;
|
|
649
|
+
const candidate = patch && patch.trim().length ? patch : input && input.trim().length ? input : undefined;
|
|
650
|
+
if (candidate) {
|
|
651
|
+
next.instructions = candidate;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if (schema.has('patch') && !Object.prototype.hasOwnProperty.call(next, 'patch')) {
|
|
655
|
+
const input = typeof next.input === 'string' ? next.input : undefined;
|
|
656
|
+
if (input && input.trim().length) {
|
|
657
|
+
next.patch = input;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
// Prune to schema keys for known Codex tools to reduce strict upstream validation failures.
|
|
662
|
+
if (lowered === 'exec_command' || lowered === 'write_stdin' || lowered === 'apply_patch') {
|
|
663
|
+
const pruned = {};
|
|
664
|
+
for (const key of schema) {
|
|
665
|
+
if (Object.prototype.hasOwnProperty.call(next, key)) {
|
|
666
|
+
pruned[key] = next[key];
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return pruned;
|
|
670
|
+
}
|
|
671
|
+
return next;
|
|
672
|
+
}
|
|
673
|
+
function buildGenerationConfigFromParameters(parameters) {
|
|
674
|
+
const config = {};
|
|
675
|
+
for (const { source, target } of GENERATION_CONFIG_KEYS) {
|
|
676
|
+
const value = parameters[target] ?? (target === 'max_output_tokens' ? parameters.max_tokens : undefined);
|
|
677
|
+
if (value !== undefined) {
|
|
678
|
+
config[source] = value;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return config;
|
|
682
|
+
}
|
|
683
|
+
function mapChatRoleToGemini(role) {
|
|
684
|
+
const r = role.toLowerCase();
|
|
685
|
+
if (r === 'assistant')
|
|
686
|
+
return 'model';
|
|
687
|
+
if (r === 'system')
|
|
688
|
+
return 'system';
|
|
689
|
+
if (r === 'tool')
|
|
690
|
+
return 'tool';
|
|
691
|
+
return 'user';
|
|
692
|
+
}
|
|
693
|
+
function safeParseJson(value) {
|
|
694
|
+
try {
|
|
695
|
+
return JSON.parse(value);
|
|
696
|
+
}
|
|
697
|
+
catch {
|
|
698
|
+
return value;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
function ensureFunctionResponsePayload(value) {
|
|
702
|
+
// Gemini function_response.response 字段在 CloudCode/Gemini CLI 协议里对应的是
|
|
703
|
+
// protobuf Struct(JSON object),而不是顶层数组。
|
|
704
|
+
// 这里做一层规范化:
|
|
705
|
+
// - 对象:直接透传;
|
|
706
|
+
// - 数组:包一层 { result: [...] } 避免把数组作为 Struct 根节点;
|
|
707
|
+
// - 原始值:包一层 { result: value },并把 undefined 映射为 null。
|
|
708
|
+
if (value && typeof value === 'object') {
|
|
709
|
+
if (Array.isArray(value)) {
|
|
710
|
+
return {
|
|
711
|
+
result: value
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
return value;
|
|
715
|
+
}
|
|
716
|
+
return {
|
|
717
|
+
result: value === undefined ? null : value
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
function cloneAsJsonValue(value) {
|
|
721
|
+
try {
|
|
722
|
+
return JSON.parse(JSON.stringify(value ?? null));
|
|
723
|
+
}
|
|
724
|
+
catch {
|
|
725
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || value === null) {
|
|
726
|
+
return value;
|
|
727
|
+
}
|
|
728
|
+
if (Array.isArray(value)) {
|
|
729
|
+
return value.map((entry) => cloneAsJsonValue(entry));
|
|
730
|
+
}
|
|
731
|
+
if (value && typeof value === 'object') {
|
|
732
|
+
const out = {};
|
|
733
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
734
|
+
out[key] = cloneAsJsonValue(entry);
|
|
735
|
+
}
|
|
736
|
+
return out;
|
|
737
|
+
}
|
|
738
|
+
return String(value ?? '');
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
export class GeminiSemanticMapper {
|
|
742
|
+
async toChat(format, ctx) {
|
|
743
|
+
const payload = (format.payload ?? {});
|
|
744
|
+
const missing = [];
|
|
745
|
+
const { messages: builtMessages } = buildOpenAIChatFromGeminiRequest(payload);
|
|
746
|
+
let messages = Array.isArray(builtMessages) ? builtMessages : [];
|
|
747
|
+
if (!Array.isArray(payload.contents)) {
|
|
748
|
+
missing.push({ path: 'contents', reason: 'absent' });
|
|
749
|
+
}
|
|
750
|
+
const bridgeTools = prepareGeminiToolsForBridge(payload.tools, missing);
|
|
751
|
+
const tools = bridgeTools ? mapBridgeToolsToChat(bridgeTools) : undefined;
|
|
752
|
+
let parameters = collectParameters(payload);
|
|
753
|
+
const metadata = { context: ctx };
|
|
754
|
+
const systemSegments = collectSystemSegments(payload.systemInstruction);
|
|
755
|
+
if (payload.systemInstruction !== undefined) {
|
|
756
|
+
const rawSystem = jsonClone(payload.systemInstruction);
|
|
757
|
+
ensureProtocolState(metadata, 'gemini').systemInstruction = rawSystem;
|
|
758
|
+
}
|
|
759
|
+
if (missing.length) {
|
|
760
|
+
metadata.missingFields = missing;
|
|
761
|
+
}
|
|
762
|
+
const toolOutputs = normalizeToolOutputs(messages, missing);
|
|
763
|
+
const passthrough = extractMetadataPassthrough(payload.metadata, {
|
|
764
|
+
prefix: PASSTHROUGH_METADATA_PREFIX,
|
|
765
|
+
keys: PASSTHROUGH_PARAMETERS
|
|
766
|
+
});
|
|
767
|
+
if (passthrough.passthrough) {
|
|
768
|
+
parameters = { ...(parameters || {}), ...passthrough.passthrough };
|
|
769
|
+
}
|
|
770
|
+
const providerMetadataSource = passthrough.metadata ?? payload.metadata;
|
|
771
|
+
let providerMetadata;
|
|
772
|
+
let explicitEmptyTools = Array.isArray(payload.tools) && payload.tools.length === 0;
|
|
773
|
+
if (providerMetadataSource) {
|
|
774
|
+
const cloned = jsonClone(providerMetadataSource);
|
|
775
|
+
let toolsFieldPresent = false;
|
|
776
|
+
if (isJsonObject(cloned)) {
|
|
777
|
+
delete cloned.__rcc_stream;
|
|
778
|
+
if (Object.prototype.hasOwnProperty.call(cloned, '__rcc_tools_field_present')) {
|
|
779
|
+
const sentinel = cloned.__rcc_tools_field_present;
|
|
780
|
+
toolsFieldPresent = sentinel === '1' || sentinel === true;
|
|
781
|
+
delete cloned.__rcc_tools_field_present;
|
|
782
|
+
}
|
|
783
|
+
if (Object.prototype.hasOwnProperty.call(cloned, '__rcc_raw_system')) {
|
|
784
|
+
delete cloned.__rcc_raw_system;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
if (toolsFieldPresent) {
|
|
788
|
+
metadata.toolsFieldPresent = true;
|
|
789
|
+
explicitEmptyTools = true;
|
|
790
|
+
}
|
|
791
|
+
providerMetadata = cloned;
|
|
792
|
+
metadata.providerMetadata = providerMetadata;
|
|
793
|
+
}
|
|
794
|
+
const chatEnvelope = {
|
|
795
|
+
messages,
|
|
796
|
+
tools,
|
|
797
|
+
toolOutputs,
|
|
798
|
+
parameters,
|
|
799
|
+
metadata
|
|
800
|
+
};
|
|
801
|
+
if (systemSegments.length) {
|
|
802
|
+
const systemNode = ensureSystemSemantics(chatEnvelope);
|
|
803
|
+
systemNode.textBlocks = systemSegments.map((segment) => segment);
|
|
804
|
+
}
|
|
805
|
+
let semanticsNode;
|
|
806
|
+
const ensureSemanticsNode = () => {
|
|
807
|
+
semanticsNode = semanticsNode ?? ensureGeminiSemanticsNode(chatEnvelope);
|
|
808
|
+
return semanticsNode;
|
|
809
|
+
};
|
|
810
|
+
if (payload.systemInstruction !== undefined) {
|
|
811
|
+
ensureSemanticsNode().systemInstruction = jsonClone(payload.systemInstruction);
|
|
812
|
+
}
|
|
813
|
+
if (payload.safetySettings) {
|
|
814
|
+
ensureSemanticsNode().safetySettings = jsonClone(payload.safetySettings);
|
|
815
|
+
}
|
|
816
|
+
if (payload.generationConfig && isJsonObject(payload.generationConfig)) {
|
|
817
|
+
ensureSemanticsNode().generationConfig = jsonClone(payload.generationConfig);
|
|
818
|
+
}
|
|
819
|
+
if (payload.toolConfig && isJsonObject(payload.toolConfig)) {
|
|
820
|
+
ensureSemanticsNode().toolConfig = jsonClone(payload.toolConfig);
|
|
821
|
+
}
|
|
822
|
+
if (providerMetadata) {
|
|
823
|
+
ensureSemanticsNode().providerMetadata = jsonClone(providerMetadata);
|
|
824
|
+
}
|
|
825
|
+
if (explicitEmptyTools) {
|
|
826
|
+
markGeminiExplicitEmptyTools(chatEnvelope);
|
|
827
|
+
}
|
|
828
|
+
return chatEnvelope;
|
|
829
|
+
}
|
|
830
|
+
async fromChat(chat, ctx) {
|
|
831
|
+
const envelopePayload = buildGeminiRequestFromChat(chat, chat.metadata);
|
|
832
|
+
return {
|
|
833
|
+
protocol: 'gemini-chat',
|
|
834
|
+
direction: 'response',
|
|
835
|
+
payload: envelopePayload,
|
|
836
|
+
meta: {
|
|
837
|
+
context: ctx
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
}
|