@jsonstudio/llms 0.6.1164 → 0.6.1354
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/gemini-openai-codec.d.ts +3 -1
- package/dist/conversion/codecs/gemini-openai-codec.js +10 -4
- package/dist/conversion/compat/actions/gemini-web-search.d.ts +1 -1
- package/dist/conversion/compat/actions/gemini-web-search.js +5 -2
- package/dist/conversion/compat/actions/iflow-tool-text-fallback.d.ts +12 -0
- package/dist/conversion/compat/actions/iflow-tool-text-fallback.js +199 -0
- package/dist/conversion/compat/actions/iflow-web-search.d.ts +1 -1
- package/dist/conversion/compat/actions/iflow-web-search.js +5 -2
- package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +47 -56
- package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +1 -13
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +523 -50
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +18 -38
- package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +6 -0
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +3 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/adapter-context.d.ts +10 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/adapter-context.js +134 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/anthropic-alias-map.d.ts +6 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/anthropic-alias-map.js +79 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/apply-patch-tool-mode.d.ts +3 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/apply-patch-tool-mode.js +46 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/execute-chat-process-entry.d.ts +8 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/execute-chat-process-entry.js +366 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/execute-request-stage.d.ts +9 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/execute-request-stage.js +384 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/node-results.d.ts +3 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/node-results.js +14 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/payload-normalize.d.ts +2 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/payload-normalize.js +144 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/policy.d.ts +4 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/policy.js +32 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/protocol.d.ts +8 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/protocol.js +63 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/resolve-protocol-hooks.d.ts +2 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/resolve-protocol-hooks.js +43 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/semantic-gate.d.ts +1 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/semantic-gate.js +29 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/servertool-runtime-config.d.ts +2 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/servertool-runtime-config.js +16 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/types.d.ts +116 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/types.js +1 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +3 -95
- package/dist/conversion/hub/pipeline/hub-pipeline.js +19 -1281
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.js +1 -1
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.d.ts +7 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +65 -1
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +25 -22
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +1 -1
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.d.ts +1 -1
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.js +2 -2
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +2 -2
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +1 -1
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.js +1 -1
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +11 -11
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage2_format_parse/index.js +1 -1
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.d.ts +1 -0
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.js +4 -2
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.d.ts +1 -0
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +17 -9
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.js +2 -2
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +40 -2
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage2_finalize/index.js +1 -1
- package/dist/conversion/hub/pipeline/target-utils.js +9 -5
- package/dist/conversion/hub/process/chat-process.js +256 -16
- package/dist/conversion/hub/response/provider-response.d.ts +8 -0
- package/dist/conversion/hub/response/provider-response.js +85 -27
- package/dist/conversion/hub/response/response-mappers.d.ts +10 -3
- package/dist/conversion/hub/response/response-mappers.js +30 -6
- package/dist/conversion/hub/response/response-runtime.js +4 -38
- package/dist/conversion/hub/snapshot-recorder.js +5 -1
- package/dist/conversion/hub/standardized-bridge.js +23 -15
- package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +36 -5
- package/dist/conversion/responses/responses-openai-bridge.js +20 -4
- package/dist/conversion/shared/gemini-tool-utils.d.ts +8 -1
- package/dist/conversion/shared/gemini-tool-utils.js +580 -108
- package/dist/conversion/shared/jsonish.js +1 -1
- package/dist/conversion/shared/mcp-injection.js +67 -33
- package/dist/conversion/shared/openai-finalizer.js +2 -1
- package/dist/conversion/shared/openai-message-normalize.js +76 -21
- package/dist/conversion/shared/responses-output-builder.js +6 -0
- package/dist/conversion/shared/runtime-metadata.d.ts +7 -0
- package/dist/conversion/shared/runtime-metadata.js +23 -0
- package/dist/conversion/shared/text-markup-normalizer.d.ts +2 -0
- package/dist/conversion/shared/text-markup-normalizer.js +284 -4
- package/dist/conversion/shared/tool-canonicalizer.js +2 -1
- package/dist/conversion/shared/tool-governor.js +3 -3
- package/dist/filters/engine.js +5 -5
- package/dist/filters/special/request-tool-list-filter.js +194 -60
- package/dist/filters/special/request-tools-normalize.js +1 -1
- package/dist/filters/special/response-tool-text-canonicalize.d.ts +4 -7
- package/dist/filters/special/response-tool-text-canonicalize.js +7 -35
- package/dist/filters/special/tool-filter-hooks.js +58 -62
- package/dist/guidance/index.js +5 -1
- package/dist/http/sse-response.js +6 -6
- package/dist/router/virtual-router/bootstrap.js +65 -5
- package/dist/router/virtual-router/context-advisor.d.ts +4 -0
- package/dist/router/virtual-router/context-advisor.js +3 -0
- package/dist/router/virtual-router/context-weighted.d.ts +31 -0
- package/dist/router/virtual-router/context-weighted.js +54 -0
- package/dist/router/virtual-router/engine-health.d.ts +1 -1
- package/dist/router/virtual-router/engine-health.js +11 -110
- package/dist/router/virtual-router/engine-selection/alias-selection.d.ts +15 -0
- package/dist/router/virtual-router/engine-selection/alias-selection.js +156 -0
- package/dist/router/virtual-router/engine-selection/context-weight-multipliers.d.ts +11 -0
- package/dist/router/virtual-router/engine-selection/context-weight-multipliers.js +23 -0
- package/dist/router/virtual-router/engine-selection/direct-provider-model.d.ts +9 -0
- package/dist/router/virtual-router/engine-selection/direct-provider-model.js +49 -0
- package/dist/router/virtual-router/engine-selection/instruction-target.d.ts +6 -0
- package/dist/router/virtual-router/engine-selection/instruction-target.js +54 -0
- package/dist/router/virtual-router/engine-selection/key-parsing.d.ts +8 -0
- package/dist/router/virtual-router/engine-selection/key-parsing.js +64 -0
- package/dist/router/virtual-router/engine-selection/route-utils.d.ts +12 -0
- package/dist/router/virtual-router/engine-selection/route-utils.js +150 -0
- package/dist/router/virtual-router/engine-selection/routing-state-filter.d.ts +4 -0
- package/dist/router/virtual-router/engine-selection/routing-state-filter.js +50 -0
- package/dist/router/virtual-router/engine-selection/selection-deps.d.ts +39 -0
- package/dist/router/virtual-router/engine-selection/selection-deps.js +1 -0
- package/dist/router/virtual-router/engine-selection/sticky-pool.d.ts +11 -0
- package/dist/router/virtual-router/engine-selection/sticky-pool.js +109 -0
- package/dist/router/virtual-router/engine-selection/tier-priority.d.ts +12 -0
- package/dist/router/virtual-router/engine-selection/tier-priority.js +55 -0
- package/dist/router/virtual-router/engine-selection/tier-selection-select.d.ts +22 -0
- package/dist/router/virtual-router/engine-selection/tier-selection-select.js +400 -0
- package/dist/router/virtual-router/engine-selection/tier-selection.d.ts +3 -0
- package/dist/router/virtual-router/engine-selection/tier-selection.js +225 -0
- package/dist/router/virtual-router/engine-selection.d.ts +4 -30
- package/dist/router/virtual-router/engine-selection.js +10 -815
- package/dist/router/virtual-router/engine.d.ts +1 -0
- package/dist/router/virtual-router/engine.js +55 -10
- package/dist/router/virtual-router/routing-instructions.js +6 -1
- package/dist/router/virtual-router/stop-message-state-sync.d.ts +5 -0
- package/dist/router/virtual-router/stop-message-state-sync.js +6 -14
- package/dist/router/virtual-router/types.d.ts +53 -1
- package/dist/servertool/clock/config.d.ts +8 -0
- package/dist/servertool/clock/config.js +22 -0
- package/dist/servertool/clock/log.d.ts +3 -0
- package/dist/servertool/clock/log.js +13 -0
- package/dist/servertool/clock/task-store.d.ts +1 -1
- package/dist/servertool/clock/task-store.js +1 -1
- package/dist/servertool/clock/tasks.js +1 -1
- package/dist/servertool/engine.js +146 -21
- package/dist/servertool/handlers/clock-auto.js +11 -6
- package/dist/servertool/handlers/clock.js +36 -10
- package/dist/servertool/handlers/followup-request-builder.js +8 -2
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +15 -9
- package/dist/servertool/handlers/iflow-model-error-retry.js +6 -4
- package/dist/servertool/handlers/recursive-detection-guard.js +4 -2
- package/dist/servertool/handlers/stop-message-auto.js +100 -10
- package/dist/servertool/handlers/vision.js +4 -1
- package/dist/servertool/handlers/web-search.js +3 -1
- package/dist/servertool/pending-session.d.ts +19 -0
- package/dist/servertool/pending-session.js +97 -0
- package/dist/servertool/reenter-backend.js +5 -3
- package/dist/servertool/server-side-tools.js +235 -6
- package/dist/servertool/types.d.ts +13 -0
- package/dist/sse/json-to-sse/event-generators/responses.js +1 -1
- package/dist/sse/shared/chat-serializer.js +2 -2
- package/dist/sse/shared/constants.js +1 -1
- package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +7 -1
- package/dist/sse/sse-to-json/builders/response-builder.js +16 -0
- package/dist/sse/sse-to-json/responses-sse-to-json-converter.d.ts +1 -1
- package/dist/tools/apply-patch/execution-capturer.js +1 -1
- package/dist/tools/exec-command/normalize.js +4 -0
- package/dist/tools/exec-command/regression-capturer.js +1 -1
- package/package.json +10 -5
|
@@ -2,8 +2,9 @@ import { isJsonObject, jsonClone } from '../../types/json.js';
|
|
|
2
2
|
import { buildOpenAIChatFromGeminiRequest } from '../../../codecs/gemini-openai-codec.js';
|
|
3
3
|
import { encodeMetadataPassthrough, extractMetadataPassthrough } from '../../../shared/metadata-passthrough.js';
|
|
4
4
|
import { mapBridgeToolsToChat, mapChatToolsToBridge } from '../../../shared/tool-mapping.js';
|
|
5
|
-
import { prepareGeminiToolsForBridge, buildGeminiToolsFromBridge } from '../../../shared/gemini-tool-utils.js';
|
|
5
|
+
import { prepareGeminiToolsForBridge, buildGeminiToolsFromBridge, sanitizeGeminiFunctionName } from '../../../shared/gemini-tool-utils.js';
|
|
6
6
|
import { ensureProtocolState, getProtocolState } from '../../../shared/protocol-state.js';
|
|
7
|
+
import { sanitizeReasoningTaggedText } from '../../../shared/reasoning-utils.js';
|
|
7
8
|
import { applyClaudeThinkingToolSchemaCompat } from '../../../compat/actions/claude-thinking-tools.js';
|
|
8
9
|
const GENERATION_CONFIG_KEYS = [
|
|
9
10
|
{ source: 'temperature', target: 'temperature' },
|
|
@@ -16,7 +17,21 @@ const GENERATION_CONFIG_KEYS = [
|
|
|
16
17
|
];
|
|
17
18
|
const PASSTHROUGH_METADATA_PREFIX = 'rcc_passthrough_';
|
|
18
19
|
const PASSTHROUGH_PARAMETERS = ['tool_choice'];
|
|
19
|
-
|
|
20
|
+
// Align with ../gcli2api/src/converter/gemini_fix.py (mode="antigravity"): the systemInstruction prefix is a single
|
|
21
|
+
// text segment wrapped in an [ignore] directive, then any user-provided system parts follow as-is.
|
|
22
|
+
const ANTIGRAVITY_SYSTEM_INSTRUCTION = 'Please ignore the following [ignore]You are Antigravity, a powerful agentic AI coding assistant designed by the Google Deepmind team working on Advanced Agentic Coding.You are pair programming with a USER to solve their coding task. The task may require creating a new codebase, modifying or debugging an existing codebase, or simply answering a question.**Absolute paths only****Proactiveness**[/ignore]';
|
|
23
|
+
const ANTIGRAVITY_DEFAULT_SAFETY_SETTINGS = [
|
|
24
|
+
{ category: 'HARM_CATEGORY_HARASSMENT', threshold: 'BLOCK_NONE' },
|
|
25
|
+
{ category: 'HARM_CATEGORY_HATE_SPEECH', threshold: 'BLOCK_NONE' },
|
|
26
|
+
{ category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT', threshold: 'BLOCK_NONE' },
|
|
27
|
+
{ category: 'HARM_CATEGORY_DANGEROUS_CONTENT', threshold: 'BLOCK_NONE' },
|
|
28
|
+
{ category: 'HARM_CATEGORY_CIVIC_INTEGRITY', threshold: 'BLOCK_NONE' },
|
|
29
|
+
{ category: 'HARM_CATEGORY_IMAGE_HATE', threshold: 'BLOCK_NONE' },
|
|
30
|
+
{ category: 'HARM_CATEGORY_IMAGE_DANGEROUS_CONTENT', threshold: 'BLOCK_NONE' },
|
|
31
|
+
{ category: 'HARM_CATEGORY_IMAGE_HARASSMENT', threshold: 'BLOCK_NONE' },
|
|
32
|
+
{ category: 'HARM_CATEGORY_IMAGE_SEXUALLY_EXPLICIT', threshold: 'BLOCK_NONE' },
|
|
33
|
+
{ category: 'HARM_CATEGORY_JAILBREAK', threshold: 'BLOCK_NONE' }
|
|
34
|
+
];
|
|
20
35
|
function coerceThoughtSignature(value) {
|
|
21
36
|
if (typeof value === 'string' && value.trim().length) {
|
|
22
37
|
return value.trim();
|
|
@@ -199,16 +214,19 @@ function selectAntigravityClaudeThinkingMessages(messages) {
|
|
|
199
214
|
}
|
|
200
215
|
return [messages[lastUserIndex]];
|
|
201
216
|
}
|
|
202
|
-
function buildFunctionResponseEntry(output) {
|
|
217
|
+
function buildFunctionResponseEntry(output, options) {
|
|
203
218
|
const parsedPayload = safeParseJson(output.content);
|
|
204
219
|
const normalizedPayload = ensureFunctionResponsePayload(cloneAsJsonValue(parsedPayload));
|
|
220
|
+
const includeCallId = options?.includeCallId === true;
|
|
205
221
|
const part = {
|
|
206
222
|
functionResponse: {
|
|
207
223
|
name: output.name || 'tool',
|
|
208
|
-
id: output.tool_call_id,
|
|
209
224
|
response: normalizedPayload
|
|
210
225
|
}
|
|
211
226
|
};
|
|
227
|
+
if (includeCallId) {
|
|
228
|
+
part.functionResponse.id = output.tool_call_id;
|
|
229
|
+
}
|
|
212
230
|
return { role: 'user', parts: [part] };
|
|
213
231
|
}
|
|
214
232
|
function collectSystemSegments(systemInstruction) {
|
|
@@ -255,10 +273,10 @@ function collectParameters(payload) {
|
|
|
255
273
|
}
|
|
256
274
|
return Object.keys(params).length ? params : undefined;
|
|
257
275
|
}
|
|
258
|
-
function appendChatContentToGeminiParts(message, targetParts) {
|
|
276
|
+
function appendChatContentToGeminiParts(message, targetParts, options) {
|
|
259
277
|
const content = message.content;
|
|
260
278
|
if (typeof content === 'string') {
|
|
261
|
-
const text = content.trim();
|
|
279
|
+
const text = (options?.stripReasoningTags ? sanitizeReasoningTaggedText(content) : content).trim();
|
|
262
280
|
if (text.length) {
|
|
263
281
|
targetParts.push({ text });
|
|
264
282
|
}
|
|
@@ -272,22 +290,49 @@ function appendChatContentToGeminiParts(message, targetParts) {
|
|
|
272
290
|
if (block == null)
|
|
273
291
|
continue;
|
|
274
292
|
if (typeof block === 'string') {
|
|
275
|
-
const text = block.trim();
|
|
293
|
+
const text = (options?.stripReasoningTags ? sanitizeReasoningTaggedText(block) : block).trim();
|
|
276
294
|
if (text.length) {
|
|
277
295
|
targetParts.push({ text });
|
|
278
296
|
}
|
|
279
297
|
continue;
|
|
280
298
|
}
|
|
281
299
|
if (typeof block !== 'object') {
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
300
|
+
const raw = String(block);
|
|
301
|
+
const text = (options?.stripReasoningTags ? sanitizeReasoningTaggedText(raw) : raw).trim();
|
|
302
|
+
if (text.length) {
|
|
303
|
+
targetParts.push({ text });
|
|
285
304
|
}
|
|
286
305
|
continue;
|
|
287
306
|
}
|
|
288
307
|
const record = block;
|
|
289
308
|
const rawType = record.type;
|
|
290
309
|
const type = typeof rawType === 'string' ? rawType.toLowerCase() : '';
|
|
310
|
+
if (type === 'thinking' || type === 'reasoning' || type === 'redacted_thinking') {
|
|
311
|
+
const signature = typeof record.thoughtSignature === 'string'
|
|
312
|
+
? String(record.thoughtSignature)
|
|
313
|
+
: typeof record.signature === 'string'
|
|
314
|
+
? String(record.signature)
|
|
315
|
+
: typeof record.thought_signature === 'string'
|
|
316
|
+
? String(record.thought_signature)
|
|
317
|
+
: '';
|
|
318
|
+
if (options?.dropUnsignedThinking && !signature.trim().length) {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const textValue = typeof record.text === 'string'
|
|
322
|
+
? record.text
|
|
323
|
+
: typeof record.thinking === 'string'
|
|
324
|
+
? record.thinking
|
|
325
|
+
: '';
|
|
326
|
+
const text = (options?.stripReasoningTags ? sanitizeReasoningTaggedText(textValue) : textValue).trim();
|
|
327
|
+
if (text.length) {
|
|
328
|
+
const part = { text };
|
|
329
|
+
if (signature.trim().length) {
|
|
330
|
+
part.thoughtSignature = signature.trim();
|
|
331
|
+
}
|
|
332
|
+
targetParts.push(part);
|
|
333
|
+
}
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
291
336
|
// Text-style blocks
|
|
292
337
|
if (!type || type === 'text') {
|
|
293
338
|
const textValue = typeof record.text === 'string'
|
|
@@ -295,7 +340,7 @@ function appendChatContentToGeminiParts(message, targetParts) {
|
|
|
295
340
|
: typeof record.content === 'string'
|
|
296
341
|
? record.content
|
|
297
342
|
: '';
|
|
298
|
-
const text = textValue.trim();
|
|
343
|
+
const text = (options?.stripReasoningTags ? sanitizeReasoningTaggedText(textValue) : textValue).trim();
|
|
299
344
|
if (text.length) {
|
|
300
345
|
targetParts.push({ text });
|
|
301
346
|
}
|
|
@@ -376,21 +421,144 @@ function buildGeminiRequestFromChat(chat, metadata) {
|
|
|
376
421
|
const isAnthropicEntry = entryEndpoint === '/v1/messages';
|
|
377
422
|
const normalizedProviderId = typeof rawProviderId === 'string' ? rawProviderId.toLowerCase() : '';
|
|
378
423
|
const providerIdPrefix = normalizedProviderId.split('.')[0];
|
|
424
|
+
const isAntigravityProvider = providerIdPrefix === 'antigravity';
|
|
379
425
|
const isAntigravityClaudeThinking = providerIdPrefix === 'antigravity' &&
|
|
380
426
|
typeof chat.parameters?.model === 'string' &&
|
|
381
427
|
chat.parameters.model.includes('claude-sonnet-4-5-thinking');
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
428
|
+
const keepReasoning = Boolean(chat.parameters?.keep_thinking) ||
|
|
429
|
+
Boolean(chat.parameters?.keep_reasoning);
|
|
430
|
+
const stripReasoningTags = isAntigravityProvider &&
|
|
431
|
+
typeof chat.parameters?.model === 'string' &&
|
|
432
|
+
chat.parameters.model.startsWith('claude-') &&
|
|
433
|
+
!keepReasoning;
|
|
434
|
+
// Cloud Code / Antigravity requires stable tool call IDs in context (maps to tool_use.id),
|
|
435
|
+
// while standard Gemini endpoints do not require (and may reject) extra id fields.
|
|
436
|
+
const includeToolCallIds = providerIdPrefix === 'antigravity';
|
|
437
|
+
// Function calling protocol:
|
|
438
|
+
// - Always include tool schemas for Gemini targets so the model can emit structured functionCall/functionResponse parts.
|
|
439
|
+
// - Antigravity additionally prefers stable tool call IDs in-context (handled via includeToolCallIds above).
|
|
440
|
+
const allowFunctionCallingProtocol = true;
|
|
441
|
+
const omitFunctionCallPartsForCli = false;
|
|
386
442
|
const semanticsNode = readGeminiSemantics(chat);
|
|
387
443
|
const systemTextBlocksFromSemantics = readSystemTextBlocksFromSemantics(chat);
|
|
388
444
|
const bridgeDefs = chat.tools && chat.tools.length ? mapChatToolsToBridge(chat.tools) : undefined;
|
|
389
|
-
const
|
|
445
|
+
const geminiToolNameAliasMap = {};
|
|
446
|
+
const geminiToolNameToWireName = new Map();
|
|
447
|
+
const wireBridgeDefs = bridgeDefs && bridgeDefs.length
|
|
448
|
+
? (() => {
|
|
449
|
+
const used = new Set();
|
|
450
|
+
const out = [];
|
|
451
|
+
for (const def of bridgeDefs) {
|
|
452
|
+
if (!def || typeof def !== 'object')
|
|
453
|
+
continue;
|
|
454
|
+
const fnNode = def.function && typeof def.function === 'object'
|
|
455
|
+
? def.function
|
|
456
|
+
: undefined;
|
|
457
|
+
const rawName = typeof fnNode?.name === 'string'
|
|
458
|
+
? fnNode.name
|
|
459
|
+
: typeof def.name === 'string'
|
|
460
|
+
? String(def.name)
|
|
461
|
+
: '';
|
|
462
|
+
const originalName = rawName.trim();
|
|
463
|
+
if (!originalName)
|
|
464
|
+
continue;
|
|
465
|
+
const base = sanitizeGeminiFunctionName(originalName, { maxLen: 64 });
|
|
466
|
+
let wireName = base;
|
|
467
|
+
let suffix = 2;
|
|
468
|
+
while (used.has(wireName)) {
|
|
469
|
+
const suffixText = `_${suffix++}`;
|
|
470
|
+
const truncated = base.length + suffixText.length > 64 ? base.slice(0, Math.max(1, 64 - suffixText.length)) : base;
|
|
471
|
+
wireName = `${truncated}${suffixText}`;
|
|
472
|
+
}
|
|
473
|
+
used.add(wireName);
|
|
474
|
+
geminiToolNameToWireName.set(originalName, wireName);
|
|
475
|
+
if (wireName !== originalName) {
|
|
476
|
+
geminiToolNameAliasMap[wireName] = originalName;
|
|
477
|
+
}
|
|
478
|
+
const cloned = jsonClone(def);
|
|
479
|
+
if (cloned.function && typeof cloned.function === 'object') {
|
|
480
|
+
cloned.function.name = wireName;
|
|
481
|
+
}
|
|
482
|
+
else if (typeof cloned.name === 'string') {
|
|
483
|
+
cloned.name = wireName;
|
|
484
|
+
}
|
|
485
|
+
out.push(cloned);
|
|
486
|
+
}
|
|
487
|
+
return out.length ? out : undefined;
|
|
488
|
+
})()
|
|
489
|
+
: undefined;
|
|
490
|
+
if (Object.keys(geminiToolNameAliasMap).length) {
|
|
491
|
+
// Persist alias map on adapterContext as an internal-only carrier for response mapping.
|
|
492
|
+
// Some hub snapshots/stages do not retain chat.semantics, but AdapterContext reliably flows
|
|
493
|
+
// through request→response conversion.
|
|
494
|
+
try {
|
|
495
|
+
if (adapterContext && typeof adapterContext === 'object') {
|
|
496
|
+
adapterContext.geminiToolNameAliasMap = jsonClone(geminiToolNameAliasMap);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
catch {
|
|
500
|
+
// best-effort
|
|
501
|
+
}
|
|
502
|
+
if (!chat.semantics || typeof chat.semantics !== 'object') {
|
|
503
|
+
chat.semantics = {};
|
|
504
|
+
}
|
|
505
|
+
if (!chat.semantics.tools || !isJsonObject(chat.semantics.tools)) {
|
|
506
|
+
chat.semantics.tools = {};
|
|
507
|
+
}
|
|
508
|
+
const toolsNode = chat.semantics.tools;
|
|
509
|
+
const existing = toolsNode.toolNameAliasMap;
|
|
510
|
+
const merged = {
|
|
511
|
+
...(existing && isJsonObject(existing) ? existing : {}),
|
|
512
|
+
...geminiToolNameAliasMap
|
|
513
|
+
};
|
|
514
|
+
toolsNode.toolNameAliasMap = merged;
|
|
515
|
+
}
|
|
516
|
+
const effectiveBridgeDefs = wireBridgeDefs ?? bridgeDefs;
|
|
517
|
+
const toolSchemaKeys = effectiveBridgeDefs ? buildToolSchemaKeyMap(effectiveBridgeDefs) : new Map();
|
|
518
|
+
const declaredToolNames = new Set();
|
|
519
|
+
if (effectiveBridgeDefs) {
|
|
520
|
+
for (const def of effectiveBridgeDefs) {
|
|
521
|
+
const fnNode = def && typeof def === 'object' && def.function && typeof def.function === 'object'
|
|
522
|
+
? def.function
|
|
523
|
+
: undefined;
|
|
524
|
+
const name = typeof fnNode?.name === 'string'
|
|
525
|
+
? fnNode.name
|
|
526
|
+
: typeof def?.name === 'string'
|
|
527
|
+
? String(def.name)
|
|
528
|
+
: '';
|
|
529
|
+
if (name && name.trim()) {
|
|
530
|
+
declaredToolNames.add(name.trim());
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
const normalizeToolName = (raw) => {
|
|
535
|
+
if (typeof raw !== 'string')
|
|
536
|
+
return undefined;
|
|
537
|
+
const trimmed = raw.trim();
|
|
538
|
+
if (!trimmed)
|
|
539
|
+
return undefined;
|
|
540
|
+
const lowered = trimmed.toLowerCase();
|
|
541
|
+
// Historical alias: some Codex / Gemini logs use "execute_command" while our canonical tool is "exec_command".
|
|
542
|
+
if (lowered === 'execute_command') {
|
|
543
|
+
const wire = geminiToolNameToWireName.get('exec_command');
|
|
544
|
+
if (wire && declaredToolNames.has(wire)) {
|
|
545
|
+
return wire;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
const mapped = geminiToolNameToWireName.get(trimmed);
|
|
549
|
+
if (mapped && declaredToolNames.has(mapped)) {
|
|
550
|
+
return mapped;
|
|
551
|
+
}
|
|
552
|
+
if (declaredToolNames.has(trimmed)) {
|
|
553
|
+
return trimmed;
|
|
554
|
+
}
|
|
555
|
+
return trimmed;
|
|
556
|
+
};
|
|
390
557
|
const sourceMessages = chat.messages;
|
|
391
558
|
// 收集当前 ChatEnvelope 中 assistant/tool_calls 的 id,用于过滤孤立的 tool_result:
|
|
392
559
|
// 只有在本轮对话中存在对应 tool_call 的 tool_result 才允许映射为 Gemini functionResponse。
|
|
393
560
|
const assistantToolCallIds = new Set();
|
|
561
|
+
const assistantToolCallNameById = new Map();
|
|
394
562
|
for (const msg of sourceMessages) {
|
|
395
563
|
if (!msg || typeof msg !== 'object')
|
|
396
564
|
continue;
|
|
@@ -403,6 +571,11 @@ function buildGeminiRequestFromChat(chat, metadata) {
|
|
|
403
571
|
const id = typeof tc.id === 'string' ? tc.id.trim() : '';
|
|
404
572
|
if (id) {
|
|
405
573
|
assistantToolCallIds.add(id);
|
|
574
|
+
const rawName = tc?.function?.name;
|
|
575
|
+
const normalizedName = normalizeToolName(rawName);
|
|
576
|
+
if (normalizedName && declaredToolNames.has(normalizedName)) {
|
|
577
|
+
assistantToolCallNameById.set(id, normalizedName);
|
|
578
|
+
}
|
|
406
579
|
}
|
|
407
580
|
}
|
|
408
581
|
}
|
|
@@ -412,10 +585,31 @@ function buildGeminiRequestFromChat(chat, metadata) {
|
|
|
412
585
|
if (message.role === 'system')
|
|
413
586
|
continue;
|
|
414
587
|
if (message.role === 'tool') {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
588
|
+
if (allowFunctionCallingProtocol) {
|
|
589
|
+
const toolOutput = convertToolMessageToOutput(message, assistantToolCallIds);
|
|
590
|
+
if (toolOutput) {
|
|
591
|
+
const resolvedName = normalizeToolName(toolOutput.name) ?? assistantToolCallNameById.get(toolOutput.tool_call_id);
|
|
592
|
+
if (resolvedName && declaredToolNames.has(resolvedName)) {
|
|
593
|
+
toolOutput.name = resolvedName;
|
|
594
|
+
contents.push(buildFunctionResponseEntry(toolOutput, { includeCallId: includeToolCallIds }));
|
|
595
|
+
emittedToolOutputs.add(toolOutput.tool_call_id);
|
|
596
|
+
}
|
|
597
|
+
else {
|
|
598
|
+
const contentText = normalizeToolContent(message.content);
|
|
599
|
+
const name = typeof (toolOutput.name || '') === 'string' && String(toolOutput.name).trim().length
|
|
600
|
+
? String(toolOutput.name).trim()
|
|
601
|
+
: 'unknown';
|
|
602
|
+
contents.push({ role: 'user', parts: [{ text: `[tool:${name}] ${contentText}` }] });
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
const name = typeof message.name === 'string' ? String(message.name).trim() : 'tool';
|
|
608
|
+
const contentText = normalizeToolContent(message.content);
|
|
609
|
+
contents.push({
|
|
610
|
+
role: 'user',
|
|
611
|
+
parts: [{ text: `[tool:${name}] ${contentText}` }]
|
|
612
|
+
});
|
|
419
613
|
}
|
|
420
614
|
continue;
|
|
421
615
|
}
|
|
@@ -423,7 +617,10 @@ function buildGeminiRequestFromChat(chat, metadata) {
|
|
|
423
617
|
role: mapChatRoleToGemini(message.role),
|
|
424
618
|
parts: []
|
|
425
619
|
};
|
|
426
|
-
appendChatContentToGeminiParts(message, entry.parts
|
|
620
|
+
appendChatContentToGeminiParts(message, entry.parts, {
|
|
621
|
+
stripReasoningTags,
|
|
622
|
+
dropUnsignedThinking: isAntigravityClaudeThinking
|
|
623
|
+
});
|
|
427
624
|
const toolCalls = Array.isArray(message.tool_calls)
|
|
428
625
|
? message.tool_calls
|
|
429
626
|
: [];
|
|
@@ -434,9 +631,29 @@ function buildGeminiRequestFromChat(chat, metadata) {
|
|
|
434
631
|
continue;
|
|
435
632
|
}
|
|
436
633
|
const fn = tc.function || {};
|
|
437
|
-
const
|
|
634
|
+
const nameRaw = fn.name;
|
|
635
|
+
const name = normalizeToolName(nameRaw);
|
|
438
636
|
if (!name)
|
|
439
637
|
continue;
|
|
638
|
+
if (!declaredToolNames.has(name)) {
|
|
639
|
+
// Keep a textual trace so history remains intelligible, but do not emit an undeclared functionCall
|
|
640
|
+
// (Gemini / Antigravity backends may reject or behave unpredictably when tool calls do not match schemas).
|
|
641
|
+
let argsText = '';
|
|
642
|
+
if (typeof fn.arguments === 'string') {
|
|
643
|
+
argsText = fn.arguments.trim();
|
|
644
|
+
}
|
|
645
|
+
else if (fn.arguments !== undefined) {
|
|
646
|
+
try {
|
|
647
|
+
argsText = JSON.stringify(fn.arguments);
|
|
648
|
+
}
|
|
649
|
+
catch {
|
|
650
|
+
argsText = String(fn.arguments);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
const text = argsText ? `[tool_call_ignored:${String(nameRaw)}] ${argsText}` : `[tool_call_ignored:${String(nameRaw)}]`;
|
|
654
|
+
entry.parts.push({ text });
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
440
657
|
let argsStruct;
|
|
441
658
|
if (typeof fn.arguments === 'string') {
|
|
442
659
|
try {
|
|
@@ -458,12 +675,13 @@ function buildGeminiRequestFromChat(chat, metadata) {
|
|
|
458
675
|
}
|
|
459
676
|
const functionCall = { name, args: argsJson };
|
|
460
677
|
const part = { functionCall };
|
|
461
|
-
if (typeof tc.id === 'string') {
|
|
462
|
-
part.functionCall.id = tc.id;
|
|
678
|
+
if (includeToolCallIds && typeof tc.id === 'string' && tc.id.trim().length) {
|
|
679
|
+
part.functionCall.id = String(tc.id).trim();
|
|
463
680
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
681
|
+
// gcli2api alignment: antigravity functionCall parts always include a thoughtSignature
|
|
682
|
+
// (Cloud Code Assist expects it even when no real signature exists).
|
|
683
|
+
if (isAntigravityProvider && !part.thoughtSignature) {
|
|
684
|
+
part.thoughtSignature = 'skip_thought_signature_validator';
|
|
467
685
|
}
|
|
468
686
|
entry.parts.push(part);
|
|
469
687
|
}
|
|
@@ -472,38 +690,101 @@ function buildGeminiRequestFromChat(chat, metadata) {
|
|
|
472
690
|
}
|
|
473
691
|
}
|
|
474
692
|
const toolOutputMap = new Map();
|
|
475
|
-
if (
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
693
|
+
if (allowFunctionCallingProtocol) {
|
|
694
|
+
if (Array.isArray(chat.toolOutputs)) {
|
|
695
|
+
for (const entry of chat.toolOutputs) {
|
|
696
|
+
if (entry && typeof entry.tool_call_id === 'string' && entry.tool_call_id.trim().length) {
|
|
697
|
+
toolOutputMap.set(entry.tool_call_id.trim(), entry);
|
|
698
|
+
}
|
|
479
699
|
}
|
|
480
700
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
701
|
+
if (toolOutputMap.size === 0) {
|
|
702
|
+
const syntheticOutputs = synthesizeToolOutputsFromMessages(chat.messages);
|
|
703
|
+
for (const output of syntheticOutputs) {
|
|
704
|
+
toolOutputMap.set(output.tool_call_id, output);
|
|
705
|
+
}
|
|
486
706
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
707
|
+
for (const output of toolOutputMap.values()) {
|
|
708
|
+
if (emittedToolOutputs.has(output.tool_call_id)) {
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
const resolvedName = normalizeToolName(output.name) ?? assistantToolCallNameById.get(output.tool_call_id);
|
|
712
|
+
if (resolvedName && declaredToolNames.has(resolvedName)) {
|
|
713
|
+
output.name = resolvedName;
|
|
714
|
+
contents.push(buildFunctionResponseEntry(output, { includeCallId: includeToolCallIds }));
|
|
715
|
+
emittedToolOutputs.add(output.tool_call_id);
|
|
716
|
+
}
|
|
491
717
|
}
|
|
492
|
-
contents.push(buildFunctionResponseEntry(output));
|
|
493
|
-
emittedToolOutputs.add(output.tool_call_id);
|
|
494
718
|
}
|
|
495
719
|
const request = {
|
|
496
720
|
model: chat.parameters?.model || 'models/gemini-pro',
|
|
497
721
|
contents
|
|
498
722
|
};
|
|
723
|
+
const cleanGeminiContents = (raw) => {
|
|
724
|
+
if (!Array.isArray(raw))
|
|
725
|
+
return [];
|
|
726
|
+
const out = [];
|
|
727
|
+
for (const entry of raw) {
|
|
728
|
+
if (!isJsonObject(entry))
|
|
729
|
+
continue;
|
|
730
|
+
const partsRaw = entry.parts;
|
|
731
|
+
if (!Array.isArray(partsRaw)) {
|
|
732
|
+
out.push(entry);
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
const nextParts = [];
|
|
736
|
+
for (const part of partsRaw) {
|
|
737
|
+
if (!isJsonObject(part))
|
|
738
|
+
continue;
|
|
739
|
+
const hasValidValue = Object.entries(part).some(([key, value]) => {
|
|
740
|
+
if (key === 'thought')
|
|
741
|
+
return false;
|
|
742
|
+
if (value === null || value === undefined)
|
|
743
|
+
return false;
|
|
744
|
+
if (typeof value === 'string')
|
|
745
|
+
return value.length > 0;
|
|
746
|
+
if (Array.isArray(value))
|
|
747
|
+
return value.length > 0;
|
|
748
|
+
if (typeof value === 'object')
|
|
749
|
+
return Object.keys(value).length > 0;
|
|
750
|
+
return true;
|
|
751
|
+
});
|
|
752
|
+
if (!hasValidValue)
|
|
753
|
+
continue;
|
|
754
|
+
const cloned = jsonClone(part);
|
|
755
|
+
if (Object.prototype.hasOwnProperty.call(cloned, 'text')) {
|
|
756
|
+
const textValue = cloned.text;
|
|
757
|
+
if (Array.isArray(textValue)) {
|
|
758
|
+
cloned.text = textValue.map((t) => String(t ?? '')).filter((t) => t).join(' ');
|
|
759
|
+
}
|
|
760
|
+
else if (typeof textValue === 'string') {
|
|
761
|
+
cloned.text = textValue.replace(/\s+$/u, '');
|
|
762
|
+
}
|
|
763
|
+
else if (textValue !== undefined) {
|
|
764
|
+
cloned.text = String(textValue);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
nextParts.push(cloned);
|
|
768
|
+
}
|
|
769
|
+
if (!nextParts.length)
|
|
770
|
+
continue;
|
|
771
|
+
const clonedEntry = jsonClone(entry);
|
|
772
|
+
clonedEntry.parts = nextParts;
|
|
773
|
+
out.push(clonedEntry);
|
|
774
|
+
}
|
|
775
|
+
return out;
|
|
776
|
+
};
|
|
777
|
+
// gcli2api normalize_gemini_request applies a strict parts cleanup pass.
|
|
778
|
+
// We do this early to reduce Antigravity/Gemini schema drift and avoid sending empty parts.
|
|
779
|
+
request.contents = cleanGeminiContents(request.contents);
|
|
499
780
|
const geminiState = getProtocolState(metadata, 'gemini');
|
|
500
|
-
if (semanticsNode?.systemInstruction !== undefined) {
|
|
781
|
+
if (!isAntigravityProvider && semanticsNode?.systemInstruction !== undefined) {
|
|
501
782
|
request.systemInstruction = jsonClone(semanticsNode.systemInstruction);
|
|
502
783
|
}
|
|
503
|
-
else if (geminiState?.systemInstruction !== undefined) {
|
|
784
|
+
else if (!isAntigravityProvider && geminiState?.systemInstruction !== undefined) {
|
|
504
785
|
request.systemInstruction = jsonClone(geminiState.systemInstruction);
|
|
505
786
|
}
|
|
506
|
-
else {
|
|
787
|
+
else if (!isAntigravityProvider) {
|
|
507
788
|
const fallbackSystemInstructions = systemTextBlocksFromSemantics;
|
|
508
789
|
if (fallbackSystemInstructions && fallbackSystemInstructions.length) {
|
|
509
790
|
const sysBlocks = fallbackSystemInstructions
|
|
@@ -514,8 +795,40 @@ function buildGeminiRequestFromChat(chat, metadata) {
|
|
|
514
795
|
}
|
|
515
796
|
}
|
|
516
797
|
}
|
|
517
|
-
if (
|
|
518
|
-
const
|
|
798
|
+
if (isAntigravityProvider) {
|
|
799
|
+
const extraSegments = [];
|
|
800
|
+
const seen = new Set();
|
|
801
|
+
const pushSegment = (value) => {
|
|
802
|
+
const trimmed = typeof value === 'string' ? value.trim() : '';
|
|
803
|
+
if (!trimmed)
|
|
804
|
+
return;
|
|
805
|
+
if (seen.has(trimmed))
|
|
806
|
+
return;
|
|
807
|
+
seen.add(trimmed);
|
|
808
|
+
extraSegments.push(trimmed);
|
|
809
|
+
};
|
|
810
|
+
for (const seg of collectSystemSegments(semanticsNode?.systemInstruction)) {
|
|
811
|
+
pushSegment(seg);
|
|
812
|
+
}
|
|
813
|
+
for (const seg of collectSystemSegments(geminiState?.systemInstruction)) {
|
|
814
|
+
pushSegment(seg);
|
|
815
|
+
}
|
|
816
|
+
for (const seg of systemTextBlocksFromSemantics || []) {
|
|
817
|
+
if (typeof seg === 'string') {
|
|
818
|
+
pushSegment(seg);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
// gcli2api antigravity normalization:
|
|
822
|
+
// - systemInstruction is a plain { parts: [...] } object (no role field)
|
|
823
|
+
// - the fixed Antigravity prefix is always the first part
|
|
824
|
+
request.systemInstruction = {
|
|
825
|
+
parts: [{ text: ANTIGRAVITY_SYSTEM_INSTRUCTION }, ...extraSegments.map((text) => ({ text }))]
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
if (allowFunctionCallingProtocol && chat.tools && chat.tools.length) {
|
|
829
|
+
const geminiTools = buildGeminiToolsFromBridge(effectiveBridgeDefs, {
|
|
830
|
+
mode: isAntigravityProvider ? 'antigravity' : 'default'
|
|
831
|
+
});
|
|
519
832
|
if (geminiTools) {
|
|
520
833
|
request.tools = geminiTools;
|
|
521
834
|
}
|
|
@@ -532,9 +845,170 @@ function buildGeminiRequestFromChat(chat, metadata) {
|
|
|
532
845
|
if (Object.keys(generationConfig).length) {
|
|
533
846
|
request.generationConfig = generationConfig;
|
|
534
847
|
}
|
|
535
|
-
if (semanticsNode?.safetySettings !== undefined) {
|
|
848
|
+
if (!isAntigravityProvider && semanticsNode?.safetySettings !== undefined) {
|
|
536
849
|
request.safetySettings = jsonClone(semanticsNode.safetySettings);
|
|
537
850
|
}
|
|
851
|
+
else if (isAntigravityProvider) {
|
|
852
|
+
// gcli2api alignment: Antigravity always sends a permissive safetySettings set.
|
|
853
|
+
request.safetySettings = jsonClone(ANTIGRAVITY_DEFAULT_SAFETY_SETTINGS);
|
|
854
|
+
}
|
|
855
|
+
if (isAntigravityProvider && isJsonObject(request.generationConfig)) {
|
|
856
|
+
// gcli2api alignment: when generationConfig is present, clamp the key parameters.
|
|
857
|
+
request.generationConfig.maxOutputTokens = 64000;
|
|
858
|
+
request.generationConfig.topK = 64;
|
|
859
|
+
// gcli2api alignment: Antigravity strips unsupported penalty fields (no-op for OpenAI-derived params,
|
|
860
|
+
// but it matters when users send direct Gemini generationConfig).
|
|
861
|
+
delete request.generationConfig.presencePenalty;
|
|
862
|
+
delete request.generationConfig.frequencyPenalty;
|
|
863
|
+
}
|
|
864
|
+
if (isAntigravityProvider && typeof request.model === 'string') {
|
|
865
|
+
// gcli2api antigravity: normalize model name variants for Claude routed via Antigravity.
|
|
866
|
+
const original = request.model;
|
|
867
|
+
const lowered = original.toLowerCase();
|
|
868
|
+
const withoutThinking = original.replace(/-thinking/giu, '');
|
|
869
|
+
let mapped = withoutThinking;
|
|
870
|
+
if (lowered.includes('opus')) {
|
|
871
|
+
mapped = 'claude-opus-4-5-thinking';
|
|
872
|
+
}
|
|
873
|
+
else if (lowered.includes('sonnet') || lowered.includes('haiku')) {
|
|
874
|
+
mapped = 'claude-sonnet-4-5-thinking';
|
|
875
|
+
}
|
|
876
|
+
else if (lowered.includes('claude')) {
|
|
877
|
+
mapped = 'claude-sonnet-4-5-thinking';
|
|
878
|
+
}
|
|
879
|
+
request.model = mapped;
|
|
880
|
+
// gcli2api antigravity: image generation request normalization.
|
|
881
|
+
// When model name indicates image generation, use the fixed base model and a minimal generationConfig,
|
|
882
|
+
// and drop systemInstruction/tools/toolConfig (Antigravity image endpoint is strict about schema).
|
|
883
|
+
const mappedLower = String(request.model || '').toLowerCase();
|
|
884
|
+
const isImageModel = mappedLower.includes('image');
|
|
885
|
+
if (isImageModel) {
|
|
886
|
+
const aspectSuffixes = [
|
|
887
|
+
['-21x9', '21:9'],
|
|
888
|
+
['-16x9', '16:9'],
|
|
889
|
+
['-9x16', '9:16'],
|
|
890
|
+
['-4x3', '4:3'],
|
|
891
|
+
['-3x4', '3:4'],
|
|
892
|
+
['-1x1', '1:1']
|
|
893
|
+
];
|
|
894
|
+
const imageSize = mappedLower.includes('-4k') ? '4K' : mappedLower.includes('-2k') ? '2K' : undefined;
|
|
895
|
+
const aspectRatio = aspectSuffixes.find(([suffix]) => mappedLower.includes(suffix))?.[1];
|
|
896
|
+
const imageConfig = {};
|
|
897
|
+
if (aspectRatio)
|
|
898
|
+
imageConfig.aspectRatio = aspectRatio;
|
|
899
|
+
if (imageSize)
|
|
900
|
+
imageConfig.imageSize = imageSize;
|
|
901
|
+
request.model = 'gemini-3-pro-image';
|
|
902
|
+
request.generationConfig = {
|
|
903
|
+
candidateCount: 1,
|
|
904
|
+
imageConfig
|
|
905
|
+
};
|
|
906
|
+
delete request.systemInstruction;
|
|
907
|
+
delete request.tools;
|
|
908
|
+
delete request.toolConfig;
|
|
909
|
+
}
|
|
910
|
+
// gcli2api antigravity: default thinkingConfig for "thinking models" (pro/think).
|
|
911
|
+
// This matches normalize_gemini_request(mode="antigravity") where is_thinking_model(model) => true.
|
|
912
|
+
const isThinkingModel = !isImageModel && (lowered.includes('think') || lowered.includes('pro'));
|
|
913
|
+
if (isThinkingModel && (!request.generationConfig || !isJsonObject(request.generationConfig))) {
|
|
914
|
+
request.generationConfig = {};
|
|
915
|
+
}
|
|
916
|
+
const generationConfig = request.generationConfig;
|
|
917
|
+
if (isThinkingModel && isJsonObject(generationConfig)) {
|
|
918
|
+
const gc = generationConfig;
|
|
919
|
+
const thinkingConfig = isJsonObject(gc.thinkingConfig) ? gc.thinkingConfig : {};
|
|
920
|
+
const existingBudget = typeof thinkingConfig.thinkingBudget === 'number' ? thinkingConfig.thinkingBudget : undefined;
|
|
921
|
+
const shouldApply = existingBudget !== undefined ? existingBudget !== 0 : true;
|
|
922
|
+
if (shouldApply) {
|
|
923
|
+
if (typeof thinkingConfig.thinkingBudget !== 'number') {
|
|
924
|
+
thinkingConfig.thinkingBudget = 1024;
|
|
925
|
+
}
|
|
926
|
+
if (Object.prototype.hasOwnProperty.call(thinkingConfig, 'thinkingLevel')) {
|
|
927
|
+
delete thinkingConfig.thinkingLevel;
|
|
928
|
+
}
|
|
929
|
+
thinkingConfig.includeThoughts = true;
|
|
930
|
+
// For Claude routed via Antigravity:
|
|
931
|
+
// - when tool calls exist, gcli2api drops thinkingConfig to avoid upstream failures
|
|
932
|
+
// - otherwise, ensure the last model message begins with a thinking block signature
|
|
933
|
+
const isClaude = lowered.includes('claude');
|
|
934
|
+
if (isClaude) {
|
|
935
|
+
const contentsArray = Array.isArray(request.contents) ? request.contents : [];
|
|
936
|
+
const hasToolCalls = contentsArray.some((content) => {
|
|
937
|
+
if (!isJsonObject(content))
|
|
938
|
+
return false;
|
|
939
|
+
const parts = content.parts;
|
|
940
|
+
if (!Array.isArray(parts))
|
|
941
|
+
return false;
|
|
942
|
+
return parts.some((part) => isJsonObject(part) &&
|
|
943
|
+
('functionCall' in part || 'function_call' in part));
|
|
944
|
+
});
|
|
945
|
+
if (hasToolCalls) {
|
|
946
|
+
delete gc.thinkingConfig;
|
|
947
|
+
}
|
|
948
|
+
else {
|
|
949
|
+
for (let idx = contentsArray.length - 1; idx >= 0; idx--) {
|
|
950
|
+
const content = contentsArray[idx];
|
|
951
|
+
if (!isJsonObject(content))
|
|
952
|
+
continue;
|
|
953
|
+
if (content.role !== 'model')
|
|
954
|
+
continue;
|
|
955
|
+
const parts = content.parts;
|
|
956
|
+
if (!Array.isArray(parts))
|
|
957
|
+
continue;
|
|
958
|
+
const first = parts[0];
|
|
959
|
+
const firstIsThinking = isJsonObject(first) && ('thought' in first || 'thoughtSignature' in first);
|
|
960
|
+
if (!firstIsThinking) {
|
|
961
|
+
content.parts = [
|
|
962
|
+
{ text: '...', thoughtSignature: 'skip_thought_signature_validator' },
|
|
963
|
+
...parts
|
|
964
|
+
];
|
|
965
|
+
}
|
|
966
|
+
break;
|
|
967
|
+
}
|
|
968
|
+
gc.thinkingConfig = thinkingConfig;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
else {
|
|
972
|
+
gc.thinkingConfig = thinkingConfig;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
// gcli2api alignment: when generationConfig exists (and Antigravity sets thinkingConfig),
|
|
977
|
+
// clamp top-level parameters.
|
|
978
|
+
if (!isImageModel && isJsonObject(request.generationConfig)) {
|
|
979
|
+
request.generationConfig.maxOutputTokens = 64000;
|
|
980
|
+
request.generationConfig.topK = 64;
|
|
981
|
+
delete request.generationConfig.presencePenalty;
|
|
982
|
+
delete request.generationConfig.frequencyPenalty;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
// Map OpenAI-style tool_choice to Gemini toolConfig (functionCallingConfig).
|
|
986
|
+
// This is required to reliably disable tool calling while still declaring tools
|
|
987
|
+
// (e.g. servertool followups that must preserve tool list for session continuity).
|
|
988
|
+
if (request.toolConfig === undefined && chat.parameters && 'tool_choice' in chat.parameters) {
|
|
989
|
+
const choice = chat.parameters.tool_choice;
|
|
990
|
+
if (typeof choice === 'string') {
|
|
991
|
+
const lowered = choice.trim().toLowerCase();
|
|
992
|
+
if (lowered === 'none') {
|
|
993
|
+
request.toolConfig = { functionCallingConfig: { mode: 'NONE' } };
|
|
994
|
+
}
|
|
995
|
+
else if (lowered === 'required') {
|
|
996
|
+
request.toolConfig = { functionCallingConfig: { mode: 'ANY' } };
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
else if (choice && typeof choice === 'object' && !Array.isArray(choice)) {
|
|
1000
|
+
const type = typeof choice.type === 'string' ? String(choice.type).trim().toLowerCase() : '';
|
|
1001
|
+
const name = type === 'function' && choice.function && typeof choice.function.name === 'string'
|
|
1002
|
+
? String(choice.function.name).trim()
|
|
1003
|
+
: '';
|
|
1004
|
+
const wireName = name ? normalizeToolName(name) : undefined;
|
|
1005
|
+
if (wireName && declaredToolNames.has(wireName)) {
|
|
1006
|
+
request.toolConfig = {
|
|
1007
|
+
functionCallingConfig: { mode: 'ANY', allowedFunctionNames: [wireName] }
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
538
1012
|
if (chat.parameters?.tool_config && isJsonObject(chat.parameters.tool_config)) {
|
|
539
1013
|
request.toolConfig = jsonClone(chat.parameters.tool_config);
|
|
540
1014
|
}
|
|
@@ -556,7 +1030,7 @@ function buildGeminiRequestFromChat(chat, metadata) {
|
|
|
556
1030
|
request.metadata = request.metadata ?? {};
|
|
557
1031
|
request.metadata.__rcc_stream = chat.parameters.stream;
|
|
558
1032
|
}
|
|
559
|
-
if (
|
|
1033
|
+
if (hasExplicitEmptyToolsSemantics(chat) &&
|
|
560
1034
|
(!Array.isArray(chat.tools) || chat.tools.length === 0)) {
|
|
561
1035
|
request.metadata = request.metadata ?? {};
|
|
562
1036
|
request.metadata.__rcc_tools_field_present = '1';
|
|
@@ -785,7 +1259,6 @@ export class GeminiSemanticMapper {
|
|
|
785
1259
|
}
|
|
786
1260
|
}
|
|
787
1261
|
if (toolsFieldPresent) {
|
|
788
|
-
metadata.toolsFieldPresent = true;
|
|
789
1262
|
explicitEmptyTools = true;
|
|
790
1263
|
}
|
|
791
1264
|
providerMetadata = cloned;
|