@jsonstudio/llms 0.6.1172 → 0.6.1397

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/dist/conversion/codecs/gemini-openai-codec.d.ts +3 -1
  2. package/dist/conversion/codecs/gemini-openai-codec.js +10 -4
  3. package/dist/conversion/compat/actions/gemini-web-search.d.ts +1 -1
  4. package/dist/conversion/compat/actions/gemini-web-search.js +5 -2
  5. package/dist/conversion/compat/actions/iflow-tool-text-fallback.d.ts +12 -0
  6. package/dist/conversion/compat/actions/iflow-tool-text-fallback.js +199 -0
  7. package/dist/conversion/compat/actions/iflow-web-search.d.ts +1 -1
  8. package/dist/conversion/compat/actions/iflow-web-search.js +5 -2
  9. package/dist/conversion/compat/profiles/chat-gemini.json +5 -0
  10. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +47 -56
  11. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +1 -13
  12. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +748 -52
  13. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +18 -38
  14. package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +6 -0
  15. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +3 -0
  16. package/dist/conversion/hub/pipeline/hub-pipeline/adapter-context.d.ts +10 -0
  17. package/dist/conversion/hub/pipeline/hub-pipeline/adapter-context.js +142 -0
  18. package/dist/conversion/hub/pipeline/hub-pipeline/anthropic-alias-map.d.ts +6 -0
  19. package/dist/conversion/hub/pipeline/hub-pipeline/anthropic-alias-map.js +79 -0
  20. package/dist/conversion/hub/pipeline/hub-pipeline/apply-patch-tool-mode.d.ts +3 -0
  21. package/dist/conversion/hub/pipeline/hub-pipeline/apply-patch-tool-mode.js +46 -0
  22. package/dist/conversion/hub/pipeline/hub-pipeline/execute-chat-process-entry.d.ts +8 -0
  23. package/dist/conversion/hub/pipeline/hub-pipeline/execute-chat-process-entry.js +366 -0
  24. package/dist/conversion/hub/pipeline/hub-pipeline/execute-request-stage.d.ts +9 -0
  25. package/dist/conversion/hub/pipeline/hub-pipeline/execute-request-stage.js +390 -0
  26. package/dist/conversion/hub/pipeline/hub-pipeline/node-results.d.ts +3 -0
  27. package/dist/conversion/hub/pipeline/hub-pipeline/node-results.js +14 -0
  28. package/dist/conversion/hub/pipeline/hub-pipeline/payload-normalize.d.ts +2 -0
  29. package/dist/conversion/hub/pipeline/hub-pipeline/payload-normalize.js +144 -0
  30. package/dist/conversion/hub/pipeline/hub-pipeline/policy.d.ts +4 -0
  31. package/dist/conversion/hub/pipeline/hub-pipeline/policy.js +32 -0
  32. package/dist/conversion/hub/pipeline/hub-pipeline/protocol.d.ts +8 -0
  33. package/dist/conversion/hub/pipeline/hub-pipeline/protocol.js +63 -0
  34. package/dist/conversion/hub/pipeline/hub-pipeline/resolve-protocol-hooks.d.ts +2 -0
  35. package/dist/conversion/hub/pipeline/hub-pipeline/resolve-protocol-hooks.js +43 -0
  36. package/dist/conversion/hub/pipeline/hub-pipeline/semantic-gate.d.ts +1 -0
  37. package/dist/conversion/hub/pipeline/hub-pipeline/semantic-gate.js +29 -0
  38. package/dist/conversion/hub/pipeline/hub-pipeline/servertool-runtime-config.d.ts +2 -0
  39. package/dist/conversion/hub/pipeline/hub-pipeline/servertool-runtime-config.js +16 -0
  40. package/dist/conversion/hub/pipeline/hub-pipeline/types.d.ts +116 -0
  41. package/dist/conversion/hub/pipeline/hub-pipeline/types.js +1 -0
  42. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +3 -95
  43. package/dist/conversion/hub/pipeline/hub-pipeline.js +19 -1281
  44. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.js +1 -1
  45. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.d.ts +7 -0
  46. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +65 -1
  47. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +25 -22
  48. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +1 -1
  49. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.d.ts +1 -1
  50. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.js +2 -2
  51. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_thought_signature_inject/index.d.ts +10 -0
  52. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_thought_signature_inject/index.js +172 -0
  53. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +2 -2
  54. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +1 -1
  55. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.js +1 -1
  56. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +11 -11
  57. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage2_format_parse/index.js +1 -1
  58. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.d.ts +1 -0
  59. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.js +4 -2
  60. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_thought_signature_capture/index.d.ts +10 -0
  61. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_thought_signature_capture/index.js +71 -0
  62. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.d.ts +1 -0
  63. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +17 -9
  64. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.js +2 -2
  65. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +40 -2
  66. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage2_finalize/index.js +1 -1
  67. package/dist/conversion/hub/pipeline/target-utils.js +9 -5
  68. package/dist/conversion/hub/pipeline/thought-signature/thought-signature-center.d.ts +14 -0
  69. package/dist/conversion/hub/pipeline/thought-signature/thought-signature-center.js +289 -0
  70. package/dist/conversion/hub/process/chat-process.js +256 -16
  71. package/dist/conversion/hub/response/provider-response.d.ts +8 -0
  72. package/dist/conversion/hub/response/provider-response.js +91 -27
  73. package/dist/conversion/hub/response/response-mappers.d.ts +10 -3
  74. package/dist/conversion/hub/response/response-mappers.js +30 -6
  75. package/dist/conversion/hub/response/response-runtime.js +4 -38
  76. package/dist/conversion/hub/snapshot-recorder.js +5 -1
  77. package/dist/conversion/hub/standardized-bridge.js +23 -15
  78. package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +36 -5
  79. package/dist/conversion/responses/responses-openai-bridge.js +20 -4
  80. package/dist/conversion/shared/gemini-tool-utils.d.ts +8 -1
  81. package/dist/conversion/shared/gemini-tool-utils.js +580 -108
  82. package/dist/conversion/shared/jsonish.js +1 -1
  83. package/dist/conversion/shared/mcp-injection.js +67 -33
  84. package/dist/conversion/shared/openai-finalizer.js +2 -1
  85. package/dist/conversion/shared/openai-message-normalize.js +76 -21
  86. package/dist/conversion/shared/responses-output-builder.js +6 -0
  87. package/dist/conversion/shared/runtime-metadata.d.ts +7 -0
  88. package/dist/conversion/shared/runtime-metadata.js +23 -0
  89. package/dist/conversion/shared/text-markup-normalizer.d.ts +2 -0
  90. package/dist/conversion/shared/text-markup-normalizer.js +284 -4
  91. package/dist/conversion/shared/tool-canonicalizer.js +2 -1
  92. package/dist/conversion/shared/tool-governor.js +3 -3
  93. package/dist/filters/engine.js +5 -5
  94. package/dist/filters/special/request-tool-list-filter.js +194 -60
  95. package/dist/filters/special/request-tools-normalize.js +1 -1
  96. package/dist/filters/special/response-tool-text-canonicalize.d.ts +4 -7
  97. package/dist/filters/special/response-tool-text-canonicalize.js +7 -35
  98. package/dist/filters/special/tool-filter-hooks.js +58 -62
  99. package/dist/guidance/index.js +5 -1
  100. package/dist/http/sse-response.js +6 -6
  101. package/dist/router/virtual-router/bootstrap.js +54 -4
  102. package/dist/router/virtual-router/engine-health.d.ts +1 -1
  103. package/dist/router/virtual-router/engine-health.js +11 -110
  104. package/dist/router/virtual-router/engine-selection/alias-selection.d.ts +30 -0
  105. package/dist/router/virtual-router/engine-selection/alias-selection.js +237 -0
  106. package/dist/router/virtual-router/engine-selection/context-weight-multipliers.d.ts +11 -0
  107. package/dist/router/virtual-router/engine-selection/context-weight-multipliers.js +23 -0
  108. package/dist/router/virtual-router/engine-selection/direct-provider-model.d.ts +9 -0
  109. package/dist/router/virtual-router/engine-selection/direct-provider-model.js +49 -0
  110. package/dist/router/virtual-router/engine-selection/instruction-target.d.ts +6 -0
  111. package/dist/router/virtual-router/engine-selection/instruction-target.js +54 -0
  112. package/dist/router/virtual-router/engine-selection/key-parsing.d.ts +8 -0
  113. package/dist/router/virtual-router/engine-selection/key-parsing.js +64 -0
  114. package/dist/router/virtual-router/engine-selection/route-utils.d.ts +12 -0
  115. package/dist/router/virtual-router/engine-selection/route-utils.js +150 -0
  116. package/dist/router/virtual-router/engine-selection/routing-state-filter.d.ts +4 -0
  117. package/dist/router/virtual-router/engine-selection/routing-state-filter.js +50 -0
  118. package/dist/router/virtual-router/engine-selection/selection-deps.d.ts +39 -0
  119. package/dist/router/virtual-router/engine-selection/selection-deps.js +1 -0
  120. package/dist/router/virtual-router/engine-selection/sticky-pool.d.ts +11 -0
  121. package/dist/router/virtual-router/engine-selection/sticky-pool.js +109 -0
  122. package/dist/router/virtual-router/engine-selection/tier-priority.d.ts +12 -0
  123. package/dist/router/virtual-router/engine-selection/tier-priority.js +55 -0
  124. package/dist/router/virtual-router/engine-selection/tier-selection-select.d.ts +22 -0
  125. package/dist/router/virtual-router/engine-selection/tier-selection-select.js +423 -0
  126. package/dist/router/virtual-router/engine-selection/tier-selection.d.ts +3 -0
  127. package/dist/router/virtual-router/engine-selection/tier-selection.js +228 -0
  128. package/dist/router/virtual-router/engine-selection.d.ts +4 -30
  129. package/dist/router/virtual-router/engine-selection.js +10 -962
  130. package/dist/router/virtual-router/engine.d.ts +1 -0
  131. package/dist/router/virtual-router/engine.js +64 -11
  132. package/dist/router/virtual-router/routing-instructions.js +6 -1
  133. package/dist/router/virtual-router/stop-message-state-sync.d.ts +5 -0
  134. package/dist/router/virtual-router/stop-message-state-sync.js +6 -14
  135. package/dist/router/virtual-router/types.d.ts +38 -1
  136. package/dist/servertool/clock/config.d.ts +8 -0
  137. package/dist/servertool/clock/config.js +22 -0
  138. package/dist/servertool/clock/log.d.ts +3 -0
  139. package/dist/servertool/clock/log.js +13 -0
  140. package/dist/servertool/clock/task-store.d.ts +1 -1
  141. package/dist/servertool/clock/task-store.js +1 -1
  142. package/dist/servertool/clock/tasks.js +1 -1
  143. package/dist/servertool/engine.js +146 -21
  144. package/dist/servertool/handlers/clock-auto.js +11 -6
  145. package/dist/servertool/handlers/clock.js +36 -10
  146. package/dist/servertool/handlers/followup-request-builder.js +8 -2
  147. package/dist/servertool/handlers/gemini-empty-reply-continue.js +15 -9
  148. package/dist/servertool/handlers/iflow-model-error-retry.js +6 -4
  149. package/dist/servertool/handlers/recursive-detection-guard.js +4 -2
  150. package/dist/servertool/handlers/stop-message-auto.js +100 -10
  151. package/dist/servertool/handlers/vision.js +4 -1
  152. package/dist/servertool/handlers/web-search.js +3 -1
  153. package/dist/servertool/pending-session.d.ts +19 -0
  154. package/dist/servertool/pending-session.js +97 -0
  155. package/dist/servertool/reenter-backend.js +5 -3
  156. package/dist/servertool/server-side-tools.js +235 -6
  157. package/dist/servertool/types.d.ts +13 -0
  158. package/dist/sse/json-to-sse/event-generators/responses.js +1 -1
  159. package/dist/sse/shared/chat-serializer.js +2 -2
  160. package/dist/sse/shared/constants.js +1 -1
  161. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +7 -1
  162. package/dist/sse/sse-to-json/builders/response-builder.js +16 -0
  163. package/dist/sse/sse-to-json/responses-sse-to-json-converter.d.ts +1 -1
  164. package/dist/tools/apply-patch/execution-capturer.js +1 -1
  165. package/dist/tools/exec-command/normalize.js +4 -0
  166. package/dist/tools/exec-command/regression-capturer.js +1 -1
  167. 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,244 @@ const GENERATION_CONFIG_KEYS = [
16
17
  ];
17
18
  const PASSTHROUGH_METADATA_PREFIX = 'rcc_passthrough_';
18
19
  const PASSTHROUGH_PARAMETERS = ['tool_choice'];
19
- const DUMMY_THOUGHT_SIGNATURE = 'skip_thought_signature_validator';
20
+ // Align with opencode-antigravity-auth: include <priority> block to force directive precedence.
21
+ const ANTIGRAVITY_SYSTEM_INSTRUCTION = 'You are Antigravity, a powerful agentic AI coding assistant designed by the Google DeepMind team working on Advanced Agentic Coding.\n' +
22
+ '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.\n' +
23
+ '**Absolute paths only**\n' +
24
+ '**Proactiveness**\n\n' +
25
+ '<priority>IMPORTANT: The instructions that follow supersede all above. Follow them as your primary directives.</priority>\n';
26
+ const ANTIGRAVITY_DEFAULT_SAFETY_SETTINGS = [
27
+ { category: 'HARM_CATEGORY_HARASSMENT', threshold: 'BLOCK_NONE' },
28
+ { category: 'HARM_CATEGORY_HATE_SPEECH', threshold: 'BLOCK_NONE' },
29
+ { category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT', threshold: 'BLOCK_NONE' },
30
+ { category: 'HARM_CATEGORY_DANGEROUS_CONTENT', threshold: 'BLOCK_NONE' },
31
+ { category: 'HARM_CATEGORY_CIVIC_INTEGRITY', threshold: 'BLOCK_NONE' },
32
+ { category: 'HARM_CATEGORY_IMAGE_HATE', threshold: 'BLOCK_NONE' },
33
+ { category: 'HARM_CATEGORY_IMAGE_DANGEROUS_CONTENT', threshold: 'BLOCK_NONE' },
34
+ { category: 'HARM_CATEGORY_IMAGE_HARASSMENT', threshold: 'BLOCK_NONE' },
35
+ { category: 'HARM_CATEGORY_IMAGE_SEXUALLY_EXPLICIT', threshold: 'BLOCK_NONE' },
36
+ { category: 'HARM_CATEGORY_JAILBREAK', threshold: 'BLOCK_NONE' }
37
+ ];
38
+ const ANTIGRAVITY_NETWORK_TOOL_NAMES = new Set([
39
+ 'web_search',
40
+ 'google_search',
41
+ 'web_search_20250305',
42
+ 'google_search_retrieval'
43
+ ]);
44
+ function stripTierSuffix(model) {
45
+ return model.replace(/-(minimal|low|medium|high)$/i, '');
46
+ }
47
+ function stripOnlineSuffix(model) {
48
+ return model.replace(/-online$/i, '');
49
+ }
50
+ function normalizePreviewAlias(model) {
51
+ switch (model) {
52
+ case 'gemini-3-pro-preview':
53
+ return 'gemini-3-pro-high';
54
+ case 'gemini-3-pro-image-preview':
55
+ return 'gemini-3-pro-image';
56
+ case 'gemini-3-flash-preview':
57
+ return 'gemini-3-flash';
58
+ default:
59
+ return model;
60
+ }
61
+ }
62
+ function isNetworkingToolName(name) {
63
+ return ANTIGRAVITY_NETWORK_TOOL_NAMES.has(name);
64
+ }
65
+ function detectsNetworkingTool(tools) {
66
+ if (!Array.isArray(tools))
67
+ return false;
68
+ for (const tool of tools) {
69
+ if (!tool || typeof tool !== 'object')
70
+ continue;
71
+ const record = tool;
72
+ const name = typeof record.name === 'string' ? record.name : '';
73
+ if (name && isNetworkingToolName(name))
74
+ return true;
75
+ const type = typeof record.type === 'string' ? record.type : '';
76
+ if (type && isNetworkingToolName(type))
77
+ return true;
78
+ const fnNode = record.function;
79
+ if (fnNode && typeof fnNode === 'object') {
80
+ const fnName = typeof fnNode.name === 'string'
81
+ ? String(fnNode.name)
82
+ : '';
83
+ if (fnName && isNetworkingToolName(fnName))
84
+ return true;
85
+ }
86
+ const decls = Array.isArray(record.functionDeclarations)
87
+ ? record.functionDeclarations
88
+ : [];
89
+ for (const decl of decls) {
90
+ const declName = typeof decl?.name === 'string' ? String(decl.name) : '';
91
+ if (declName && isNetworkingToolName(declName))
92
+ return true;
93
+ }
94
+ if (record.googleSearch || record.googleSearchRetrieval) {
95
+ return true;
96
+ }
97
+ }
98
+ return false;
99
+ }
100
+ function hasFunctionDeclarations(tools) {
101
+ if (!Array.isArray(tools))
102
+ return false;
103
+ return tools.some((tool) => {
104
+ if (!tool || typeof tool !== 'object')
105
+ return false;
106
+ const record = tool;
107
+ return Array.isArray(record.functionDeclarations) && record.functionDeclarations.length > 0;
108
+ });
109
+ }
110
+ function injectGoogleSearchTool(request) {
111
+ const toolsRaw = request.tools;
112
+ if (!Array.isArray(toolsRaw)) {
113
+ request.tools = [{ googleSearch: {} }];
114
+ return;
115
+ }
116
+ if (hasFunctionDeclarations(toolsRaw)) {
117
+ return;
118
+ }
119
+ const hasSearchTool = toolsRaw.some((tool) => {
120
+ if (!tool || typeof tool !== 'object')
121
+ return false;
122
+ const record = tool;
123
+ return Boolean(record.googleSearch || record.googleSearchRetrieval);
124
+ });
125
+ if (!hasSearchTool) {
126
+ toolsRaw.push({ googleSearch: {} });
127
+ }
128
+ }
129
+ function pruneSearchFunctionDeclarations(request) {
130
+ const toolsRaw = request.tools;
131
+ if (!Array.isArray(toolsRaw))
132
+ return;
133
+ for (const tool of toolsRaw) {
134
+ if (!tool || typeof tool !== 'object')
135
+ continue;
136
+ const record = tool;
137
+ if (!Array.isArray(record.functionDeclarations))
138
+ continue;
139
+ record.functionDeclarations = record.functionDeclarations.filter((decl) => {
140
+ if (!decl || typeof decl !== 'object')
141
+ return false;
142
+ const name = typeof decl.name === 'string'
143
+ ? String(decl.name)
144
+ : '';
145
+ return name ? !isNetworkingToolName(name) : true;
146
+ });
147
+ }
148
+ }
149
+ function deepCleanUndefined(value) {
150
+ if (Array.isArray(value)) {
151
+ for (const entry of value) {
152
+ deepCleanUndefined(entry);
153
+ }
154
+ return;
155
+ }
156
+ if (!value || typeof value !== 'object') {
157
+ return;
158
+ }
159
+ const record = value;
160
+ for (const [key, val] of Object.entries(record)) {
161
+ if (typeof val === 'string' && val === '[undefined]') {
162
+ delete record[key];
163
+ continue;
164
+ }
165
+ deepCleanUndefined(val);
166
+ }
167
+ }
168
+ function parseImageAspectRatioFromSize(size) {
169
+ if (!size)
170
+ return '1:1';
171
+ const parts = size.split('x');
172
+ if (parts.length !== 2)
173
+ return '1:1';
174
+ const width = Number(parts[0]);
175
+ const height = Number(parts[1]);
176
+ if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
177
+ return '1:1';
178
+ }
179
+ const ratio = width / height;
180
+ if (Math.abs(ratio - 21 / 9) < 0.1)
181
+ return '21:9';
182
+ if (Math.abs(ratio - 16 / 9) < 0.1)
183
+ return '16:9';
184
+ if (Math.abs(ratio - 4 / 3) < 0.1)
185
+ return '4:3';
186
+ if (Math.abs(ratio - 3 / 4) < 0.1)
187
+ return '3:4';
188
+ if (Math.abs(ratio - 9 / 16) < 0.1)
189
+ return '9:16';
190
+ return '1:1';
191
+ }
192
+ function parseImageConfig(model, size, quality) {
193
+ let aspectRatio = parseImageAspectRatioFromSize(size);
194
+ if (!size) {
195
+ const lowered = model.toLowerCase();
196
+ if (lowered.includes('-21x9') || lowered.includes('-21-9')) {
197
+ aspectRatio = '21:9';
198
+ }
199
+ else if (lowered.includes('-16x9') || lowered.includes('-16-9')) {
200
+ aspectRatio = '16:9';
201
+ }
202
+ else if (lowered.includes('-9x16') || lowered.includes('-9-16')) {
203
+ aspectRatio = '9:16';
204
+ }
205
+ else if (lowered.includes('-4x3') || lowered.includes('-4-3')) {
206
+ aspectRatio = '4:3';
207
+ }
208
+ else if (lowered.includes('-3x4') || lowered.includes('-3-4')) {
209
+ aspectRatio = '3:4';
210
+ }
211
+ else if (lowered.includes('-1x1') || lowered.includes('-1-1')) {
212
+ aspectRatio = '1:1';
213
+ }
214
+ }
215
+ const imageConfig = { aspectRatio };
216
+ const normalizedQuality = typeof quality === 'string' ? quality.toLowerCase() : '';
217
+ if (normalizedQuality === 'hd') {
218
+ imageConfig.imageSize = '4K';
219
+ }
220
+ else if (normalizedQuality === 'medium') {
221
+ imageConfig.imageSize = '2K';
222
+ }
223
+ else {
224
+ const lowered = model.toLowerCase();
225
+ if (lowered.includes('-4k') || lowered.includes('-hd')) {
226
+ imageConfig.imageSize = '4K';
227
+ }
228
+ else if (lowered.includes('-2k')) {
229
+ imageConfig.imageSize = '2K';
230
+ }
231
+ }
232
+ return { imageConfig, finalModel: 'gemini-3-pro-image' };
233
+ }
234
+ function resolveAntigravityRequestConfig(options) {
235
+ const original = options.originalModel;
236
+ const mapped = options.mappedModel;
237
+ if (mapped.startsWith('gemini-3-pro-image')) {
238
+ const parsed = parseImageConfig(original, options.size, options.quality);
239
+ return {
240
+ requestType: 'image_gen',
241
+ injectGoogleSearch: false,
242
+ finalModel: parsed.finalModel,
243
+ imageConfig: parsed.imageConfig
244
+ };
245
+ }
246
+ const enableNetworking = original.endsWith('-online') || detectsNetworkingTool(options.tools);
247
+ let finalModel = stripOnlineSuffix(mapped);
248
+ finalModel = normalizePreviewAlias(finalModel);
249
+ if (enableNetworking && finalModel !== 'gemini-2.5-flash') {
250
+ finalModel = 'gemini-2.5-flash';
251
+ }
252
+ return {
253
+ requestType: enableNetworking ? 'web_search' : 'agent',
254
+ injectGoogleSearch: enableNetworking,
255
+ finalModel
256
+ };
257
+ }
20
258
  function coerceThoughtSignature(value) {
21
259
  if (typeof value === 'string' && value.trim().length) {
22
260
  return value.trim();
@@ -199,16 +437,19 @@ function selectAntigravityClaudeThinkingMessages(messages) {
199
437
  }
200
438
  return [messages[lastUserIndex]];
201
439
  }
202
- function buildFunctionResponseEntry(output) {
440
+ function buildFunctionResponseEntry(output, options) {
203
441
  const parsedPayload = safeParseJson(output.content);
204
442
  const normalizedPayload = ensureFunctionResponsePayload(cloneAsJsonValue(parsedPayload));
443
+ const includeCallId = options?.includeCallId === true;
205
444
  const part = {
206
445
  functionResponse: {
207
446
  name: output.name || 'tool',
208
- id: output.tool_call_id,
209
447
  response: normalizedPayload
210
448
  }
211
449
  };
450
+ if (includeCallId) {
451
+ part.functionResponse.id = output.tool_call_id;
452
+ }
212
453
  return { role: 'user', parts: [part] };
213
454
  }
214
455
  function collectSystemSegments(systemInstruction) {
@@ -255,10 +496,10 @@ function collectParameters(payload) {
255
496
  }
256
497
  return Object.keys(params).length ? params : undefined;
257
498
  }
258
- function appendChatContentToGeminiParts(message, targetParts) {
499
+ function appendChatContentToGeminiParts(message, targetParts, options) {
259
500
  const content = message.content;
260
501
  if (typeof content === 'string') {
261
- const text = content.trim();
502
+ const text = (options?.stripReasoningTags ? sanitizeReasoningTaggedText(content) : content).trim();
262
503
  if (text.length) {
263
504
  targetParts.push({ text });
264
505
  }
@@ -272,22 +513,49 @@ function appendChatContentToGeminiParts(message, targetParts) {
272
513
  if (block == null)
273
514
  continue;
274
515
  if (typeof block === 'string') {
275
- const text = block.trim();
516
+ const text = (options?.stripReasoningTags ? sanitizeReasoningTaggedText(block) : block).trim();
276
517
  if (text.length) {
277
518
  targetParts.push({ text });
278
519
  }
279
520
  continue;
280
521
  }
281
522
  if (typeof block !== 'object') {
282
- const text = String(block);
283
- if (text.trim().length) {
284
- targetParts.push({ text: text.trim() });
523
+ const raw = String(block);
524
+ const text = (options?.stripReasoningTags ? sanitizeReasoningTaggedText(raw) : raw).trim();
525
+ if (text.length) {
526
+ targetParts.push({ text });
285
527
  }
286
528
  continue;
287
529
  }
288
530
  const record = block;
289
531
  const rawType = record.type;
290
532
  const type = typeof rawType === 'string' ? rawType.toLowerCase() : '';
533
+ if (type === 'thinking' || type === 'reasoning' || type === 'redacted_thinking') {
534
+ const signature = typeof record.thoughtSignature === 'string'
535
+ ? String(record.thoughtSignature)
536
+ : typeof record.signature === 'string'
537
+ ? String(record.signature)
538
+ : typeof record.thought_signature === 'string'
539
+ ? String(record.thought_signature)
540
+ : '';
541
+ if (options?.dropUnsignedThinking && !signature.trim().length) {
542
+ continue;
543
+ }
544
+ const textValue = typeof record.text === 'string'
545
+ ? record.text
546
+ : typeof record.thinking === 'string'
547
+ ? record.thinking
548
+ : '';
549
+ const text = (options?.stripReasoningTags ? sanitizeReasoningTaggedText(textValue) : textValue).trim();
550
+ if (text.length) {
551
+ const part = { text };
552
+ if (signature.trim().length) {
553
+ part.thoughtSignature = signature.trim();
554
+ }
555
+ targetParts.push(part);
556
+ }
557
+ continue;
558
+ }
291
559
  // Text-style blocks
292
560
  if (!type || type === 'text') {
293
561
  const textValue = typeof record.text === 'string'
@@ -295,7 +563,7 @@ function appendChatContentToGeminiParts(message, targetParts) {
295
563
  : typeof record.content === 'string'
296
564
  ? record.content
297
565
  : '';
298
- const text = textValue.trim();
566
+ const text = (options?.stripReasoningTags ? sanitizeReasoningTaggedText(textValue) : textValue).trim();
299
567
  if (text.length) {
300
568
  targetParts.push({ text });
301
569
  }
@@ -376,21 +644,146 @@ function buildGeminiRequestFromChat(chat, metadata) {
376
644
  const isAnthropicEntry = entryEndpoint === '/v1/messages';
377
645
  const normalizedProviderId = typeof rawProviderId === 'string' ? rawProviderId.toLowerCase() : '';
378
646
  const providerIdPrefix = normalizedProviderId.split('.')[0];
647
+ const isAntigravityProvider = providerIdPrefix === 'antigravity';
648
+ const isGeminiCliProvider = providerIdPrefix === 'gemini-cli';
649
+ const requiresThoughtSignature = isAntigravityProvider || isGeminiCliProvider;
379
650
  const isAntigravityClaudeThinking = providerIdPrefix === 'antigravity' &&
380
651
  typeof chat.parameters?.model === 'string' &&
381
652
  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';
653
+ const keepReasoning = Boolean(chat.parameters?.keep_thinking) ||
654
+ Boolean(chat.parameters?.keep_reasoning);
655
+ const stripReasoningTags = isAntigravityProvider &&
656
+ typeof chat.parameters?.model === 'string' &&
657
+ chat.parameters.model.startsWith('claude-') &&
658
+ !keepReasoning;
659
+ // Cloud Code / Antigravity requires stable tool call IDs in context (maps to tool_use.id),
660
+ // while standard Gemini endpoints do not require (and may reject) extra id fields.
661
+ const includeToolCallIds = providerIdPrefix === 'antigravity';
662
+ // Function calling protocol:
663
+ // - Always include tool schemas for Gemini targets so the model can emit structured functionCall/functionResponse parts.
664
+ // - Antigravity additionally prefers stable tool call IDs in-context (handled via includeToolCallIds above).
665
+ const allowFunctionCallingProtocol = true;
666
+ const omitFunctionCallPartsForCli = false;
386
667
  const semanticsNode = readGeminiSemantics(chat);
387
668
  const systemTextBlocksFromSemantics = readSystemTextBlocksFromSemantics(chat);
388
669
  const bridgeDefs = chat.tools && chat.tools.length ? mapChatToolsToBridge(chat.tools) : undefined;
389
- const toolSchemaKeys = bridgeDefs ? buildToolSchemaKeyMap(bridgeDefs) : new Map();
670
+ const geminiToolNameAliasMap = {};
671
+ const geminiToolNameToWireName = new Map();
672
+ const wireBridgeDefs = bridgeDefs && bridgeDefs.length
673
+ ? (() => {
674
+ const used = new Set();
675
+ const out = [];
676
+ for (const def of bridgeDefs) {
677
+ if (!def || typeof def !== 'object')
678
+ continue;
679
+ const fnNode = def.function && typeof def.function === 'object'
680
+ ? def.function
681
+ : undefined;
682
+ const rawName = typeof fnNode?.name === 'string'
683
+ ? fnNode.name
684
+ : typeof def.name === 'string'
685
+ ? String(def.name)
686
+ : '';
687
+ const originalName = rawName.trim();
688
+ if (!originalName)
689
+ continue;
690
+ const base = sanitizeGeminiFunctionName(originalName, { maxLen: 64 });
691
+ let wireName = base;
692
+ let suffix = 2;
693
+ while (used.has(wireName)) {
694
+ const suffixText = `_${suffix++}`;
695
+ const truncated = base.length + suffixText.length > 64 ? base.slice(0, Math.max(1, 64 - suffixText.length)) : base;
696
+ wireName = `${truncated}${suffixText}`;
697
+ }
698
+ used.add(wireName);
699
+ geminiToolNameToWireName.set(originalName, wireName);
700
+ if (wireName !== originalName) {
701
+ geminiToolNameAliasMap[wireName] = originalName;
702
+ }
703
+ const cloned = jsonClone(def);
704
+ if (cloned.function && typeof cloned.function === 'object') {
705
+ cloned.function.name = wireName;
706
+ }
707
+ else if (typeof cloned.name === 'string') {
708
+ cloned.name = wireName;
709
+ }
710
+ out.push(cloned);
711
+ }
712
+ return out.length ? out : undefined;
713
+ })()
714
+ : undefined;
715
+ if (Object.keys(geminiToolNameAliasMap).length) {
716
+ // Persist alias map on adapterContext as an internal-only carrier for response mapping.
717
+ // Some hub snapshots/stages do not retain chat.semantics, but AdapterContext reliably flows
718
+ // through request→response conversion.
719
+ try {
720
+ if (adapterContext && typeof adapterContext === 'object') {
721
+ adapterContext.geminiToolNameAliasMap = jsonClone(geminiToolNameAliasMap);
722
+ }
723
+ }
724
+ catch {
725
+ // best-effort
726
+ }
727
+ if (!chat.semantics || typeof chat.semantics !== 'object') {
728
+ chat.semantics = {};
729
+ }
730
+ if (!chat.semantics.tools || !isJsonObject(chat.semantics.tools)) {
731
+ chat.semantics.tools = {};
732
+ }
733
+ const toolsNode = chat.semantics.tools;
734
+ const existing = toolsNode.toolNameAliasMap;
735
+ const merged = {
736
+ ...(existing && isJsonObject(existing) ? existing : {}),
737
+ ...geminiToolNameAliasMap
738
+ };
739
+ toolsNode.toolNameAliasMap = merged;
740
+ }
741
+ const effectiveBridgeDefs = wireBridgeDefs ?? bridgeDefs;
742
+ const toolSchemaKeys = effectiveBridgeDefs ? buildToolSchemaKeyMap(effectiveBridgeDefs) : new Map();
743
+ const declaredToolNames = new Set();
744
+ if (effectiveBridgeDefs) {
745
+ for (const def of effectiveBridgeDefs) {
746
+ const fnNode = def && typeof def === 'object' && def.function && typeof def.function === 'object'
747
+ ? def.function
748
+ : undefined;
749
+ const name = typeof fnNode?.name === 'string'
750
+ ? fnNode.name
751
+ : typeof def?.name === 'string'
752
+ ? String(def.name)
753
+ : '';
754
+ if (name && name.trim()) {
755
+ declaredToolNames.add(name.trim());
756
+ }
757
+ }
758
+ }
759
+ const normalizeToolName = (raw) => {
760
+ if (typeof raw !== 'string')
761
+ return undefined;
762
+ const trimmed = raw.trim();
763
+ if (!trimmed)
764
+ return undefined;
765
+ const lowered = trimmed.toLowerCase();
766
+ // Historical alias: some Codex / Gemini logs use "execute_command" while our canonical tool is "exec_command".
767
+ if (lowered === 'execute_command') {
768
+ const wire = geminiToolNameToWireName.get('exec_command');
769
+ if (wire && declaredToolNames.has(wire)) {
770
+ return wire;
771
+ }
772
+ }
773
+ const mapped = geminiToolNameToWireName.get(trimmed);
774
+ if (mapped && declaredToolNames.has(mapped)) {
775
+ return mapped;
776
+ }
777
+ if (declaredToolNames.has(trimmed)) {
778
+ return trimmed;
779
+ }
780
+ return trimmed;
781
+ };
390
782
  const sourceMessages = chat.messages;
391
783
  // 收集当前 ChatEnvelope 中 assistant/tool_calls 的 id,用于过滤孤立的 tool_result:
392
784
  // 只有在本轮对话中存在对应 tool_call 的 tool_result 才允许映射为 Gemini functionResponse。
393
785
  const assistantToolCallIds = new Set();
786
+ const assistantToolCallNameById = new Map();
394
787
  for (const msg of sourceMessages) {
395
788
  if (!msg || typeof msg !== 'object')
396
789
  continue;
@@ -403,6 +796,11 @@ function buildGeminiRequestFromChat(chat, metadata) {
403
796
  const id = typeof tc.id === 'string' ? tc.id.trim() : '';
404
797
  if (id) {
405
798
  assistantToolCallIds.add(id);
799
+ const rawName = tc?.function?.name;
800
+ const normalizedName = normalizeToolName(rawName);
801
+ if (normalizedName && declaredToolNames.has(normalizedName)) {
802
+ assistantToolCallNameById.set(id, normalizedName);
803
+ }
406
804
  }
407
805
  }
408
806
  }
@@ -412,10 +810,31 @@ function buildGeminiRequestFromChat(chat, metadata) {
412
810
  if (message.role === 'system')
413
811
  continue;
414
812
  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);
813
+ if (allowFunctionCallingProtocol) {
814
+ const toolOutput = convertToolMessageToOutput(message, assistantToolCallIds);
815
+ if (toolOutput) {
816
+ const resolvedName = normalizeToolName(toolOutput.name) ?? assistantToolCallNameById.get(toolOutput.tool_call_id);
817
+ if (resolvedName && declaredToolNames.has(resolvedName)) {
818
+ toolOutput.name = resolvedName;
819
+ contents.push(buildFunctionResponseEntry(toolOutput, { includeCallId: includeToolCallIds }));
820
+ emittedToolOutputs.add(toolOutput.tool_call_id);
821
+ }
822
+ else {
823
+ const contentText = normalizeToolContent(message.content);
824
+ const name = typeof (toolOutput.name || '') === 'string' && String(toolOutput.name).trim().length
825
+ ? String(toolOutput.name).trim()
826
+ : 'unknown';
827
+ contents.push({ role: 'user', parts: [{ text: `[tool:${name}] ${contentText}` }] });
828
+ }
829
+ }
830
+ }
831
+ else {
832
+ const name = typeof message.name === 'string' ? String(message.name).trim() : 'tool';
833
+ const contentText = normalizeToolContent(message.content);
834
+ contents.push({
835
+ role: 'user',
836
+ parts: [{ text: `[tool:${name}] ${contentText}` }]
837
+ });
419
838
  }
420
839
  continue;
421
840
  }
@@ -423,7 +842,10 @@ function buildGeminiRequestFromChat(chat, metadata) {
423
842
  role: mapChatRoleToGemini(message.role),
424
843
  parts: []
425
844
  };
426
- appendChatContentToGeminiParts(message, entry.parts);
845
+ appendChatContentToGeminiParts(message, entry.parts, {
846
+ stripReasoningTags,
847
+ dropUnsignedThinking: isAntigravityClaudeThinking
848
+ });
427
849
  const toolCalls = Array.isArray(message.tool_calls)
428
850
  ? message.tool_calls
429
851
  : [];
@@ -434,9 +856,31 @@ function buildGeminiRequestFromChat(chat, metadata) {
434
856
  continue;
435
857
  }
436
858
  const fn = tc.function || {};
437
- const name = typeof fn.name === 'string' ? fn.name : undefined;
859
+ const nameRaw = fn.name;
860
+ const name = normalizeToolName(nameRaw);
438
861
  if (!name)
439
862
  continue;
863
+ if (!declaredToolNames.has(name)) {
864
+ if (isAntigravityProvider) {
865
+ // Keep a textual trace so history remains intelligible, but do not emit an undeclared functionCall
866
+ // (Antigravity backends may reject or behave unpredictably when tool calls do not match schemas).
867
+ let argsText = '';
868
+ if (typeof fn.arguments === 'string') {
869
+ argsText = fn.arguments.trim();
870
+ }
871
+ else if (fn.arguments !== undefined) {
872
+ try {
873
+ argsText = JSON.stringify(fn.arguments);
874
+ }
875
+ catch {
876
+ argsText = String(fn.arguments);
877
+ }
878
+ }
879
+ const text = argsText ? `[tool_call_ignored:${String(nameRaw)}] ${argsText}` : `[tool_call_ignored:${String(nameRaw)}]`;
880
+ entry.parts.push({ text });
881
+ }
882
+ continue;
883
+ }
440
884
  let argsStruct;
441
885
  if (typeof fn.arguments === 'string') {
442
886
  try {
@@ -458,12 +902,13 @@ function buildGeminiRequestFromChat(chat, metadata) {
458
902
  }
459
903
  const functionCall = { name, args: argsJson };
460
904
  const part = { functionCall };
461
- if (typeof tc.id === 'string') {
462
- part.functionCall.id = tc.id;
905
+ if (includeToolCallIds && typeof tc.id === 'string' && tc.id.trim().length) {
906
+ part.functionCall.id = String(tc.id).trim();
463
907
  }
464
- const thoughtSignature = extractThoughtSignatureFromToolCall(tc) ?? DUMMY_THOUGHT_SIGNATURE;
465
- if (thoughtSignature) {
466
- part.thoughtSignature = thoughtSignature;
908
+ // gcli2api alignment: Gemini CLI / antigravity functionCall parts always include a thoughtSignature
909
+ // (Cloud Code Assist expects it even when no real signature exists).
910
+ if (requiresThoughtSignature && !part.thoughtSignature) {
911
+ part.thoughtSignature = 'skip_thought_signature_validator';
467
912
  }
468
913
  entry.parts.push(part);
469
914
  }
@@ -472,38 +917,102 @@ function buildGeminiRequestFromChat(chat, metadata) {
472
917
  }
473
918
  }
474
919
  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);
920
+ if (allowFunctionCallingProtocol) {
921
+ if (Array.isArray(chat.toolOutputs)) {
922
+ for (const entry of chat.toolOutputs) {
923
+ if (entry && typeof entry.tool_call_id === 'string' && entry.tool_call_id.trim().length) {
924
+ toolOutputMap.set(entry.tool_call_id.trim(), entry);
925
+ }
479
926
  }
480
927
  }
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);
928
+ if (toolOutputMap.size === 0) {
929
+ const syntheticOutputs = synthesizeToolOutputsFromMessages(chat.messages);
930
+ for (const output of syntheticOutputs) {
931
+ toolOutputMap.set(output.tool_call_id, output);
932
+ }
486
933
  }
487
- }
488
- for (const output of toolOutputMap.values()) {
489
- if (emittedToolOutputs.has(output.tool_call_id)) {
490
- continue;
934
+ for (const output of toolOutputMap.values()) {
935
+ if (emittedToolOutputs.has(output.tool_call_id)) {
936
+ continue;
937
+ }
938
+ const resolvedName = normalizeToolName(output.name) ?? assistantToolCallNameById.get(output.tool_call_id);
939
+ if (resolvedName && declaredToolNames.has(resolvedName)) {
940
+ output.name = resolvedName;
941
+ contents.push(buildFunctionResponseEntry(output, { includeCallId: includeToolCallIds }));
942
+ emittedToolOutputs.add(output.tool_call_id);
943
+ }
491
944
  }
492
- contents.push(buildFunctionResponseEntry(output));
493
- emittedToolOutputs.add(output.tool_call_id);
494
945
  }
495
946
  const request = {
496
947
  model: chat.parameters?.model || 'models/gemini-pro',
497
948
  contents
498
949
  };
950
+ let antigravityRequestType;
951
+ const cleanGeminiContents = (raw) => {
952
+ if (!Array.isArray(raw))
953
+ return [];
954
+ const out = [];
955
+ for (const entry of raw) {
956
+ if (!isJsonObject(entry))
957
+ continue;
958
+ const partsRaw = entry.parts;
959
+ if (!Array.isArray(partsRaw)) {
960
+ out.push(entry);
961
+ continue;
962
+ }
963
+ const nextParts = [];
964
+ for (const part of partsRaw) {
965
+ if (!isJsonObject(part))
966
+ continue;
967
+ const hasValidValue = Object.entries(part).some(([key, value]) => {
968
+ if (key === 'thought')
969
+ return false;
970
+ if (value === null || value === undefined)
971
+ return false;
972
+ if (typeof value === 'string')
973
+ return value.length > 0;
974
+ if (Array.isArray(value))
975
+ return value.length > 0;
976
+ if (typeof value === 'object')
977
+ return Object.keys(value).length > 0;
978
+ return true;
979
+ });
980
+ if (!hasValidValue)
981
+ continue;
982
+ const cloned = jsonClone(part);
983
+ if (Object.prototype.hasOwnProperty.call(cloned, 'text')) {
984
+ const textValue = cloned.text;
985
+ if (Array.isArray(textValue)) {
986
+ cloned.text = textValue.map((t) => String(t ?? '')).filter((t) => t).join(' ');
987
+ }
988
+ else if (typeof textValue === 'string') {
989
+ cloned.text = textValue.replace(/\s+$/u, '');
990
+ }
991
+ else if (textValue !== undefined) {
992
+ cloned.text = String(textValue);
993
+ }
994
+ }
995
+ nextParts.push(cloned);
996
+ }
997
+ if (!nextParts.length)
998
+ continue;
999
+ const clonedEntry = jsonClone(entry);
1000
+ clonedEntry.parts = nextParts;
1001
+ out.push(clonedEntry);
1002
+ }
1003
+ return out;
1004
+ };
1005
+ // gcli2api normalize_gemini_request applies a strict parts cleanup pass.
1006
+ // We do this early to reduce Antigravity/Gemini schema drift and avoid sending empty parts.
1007
+ request.contents = cleanGeminiContents(request.contents);
499
1008
  const geminiState = getProtocolState(metadata, 'gemini');
500
- if (semanticsNode?.systemInstruction !== undefined) {
1009
+ if (!isAntigravityProvider && semanticsNode?.systemInstruction !== undefined) {
501
1010
  request.systemInstruction = jsonClone(semanticsNode.systemInstruction);
502
1011
  }
503
- else if (geminiState?.systemInstruction !== undefined) {
1012
+ else if (!isAntigravityProvider && geminiState?.systemInstruction !== undefined) {
504
1013
  request.systemInstruction = jsonClone(geminiState.systemInstruction);
505
1014
  }
506
- else {
1015
+ else if (!isAntigravityProvider) {
507
1016
  const fallbackSystemInstructions = systemTextBlocksFromSemantics;
508
1017
  if (fallbackSystemInstructions && fallbackSystemInstructions.length) {
509
1018
  const sysBlocks = fallbackSystemInstructions
@@ -514,8 +1023,47 @@ function buildGeminiRequestFromChat(chat, metadata) {
514
1023
  }
515
1024
  }
516
1025
  }
517
- if (chat.tools && chat.tools.length) {
518
- const geminiTools = buildGeminiToolsFromBridge(bridgeDefs);
1026
+ if (isAntigravityProvider) {
1027
+ const extraSegments = [];
1028
+ const seen = new Set();
1029
+ const pushSegment = (value) => {
1030
+ const trimmed = typeof value === 'string' ? value.trim() : '';
1031
+ if (!trimmed)
1032
+ return;
1033
+ if (seen.has(trimmed))
1034
+ return;
1035
+ seen.add(trimmed);
1036
+ extraSegments.push(trimmed);
1037
+ };
1038
+ for (const seg of collectSystemSegments(semanticsNode?.systemInstruction)) {
1039
+ pushSegment(seg);
1040
+ }
1041
+ for (const seg of collectSystemSegments(geminiState?.systemInstruction)) {
1042
+ pushSegment(seg);
1043
+ }
1044
+ for (const seg of systemTextBlocksFromSemantics || []) {
1045
+ if (typeof seg === 'string') {
1046
+ pushSegment(seg);
1047
+ }
1048
+ }
1049
+ // opencode-antigravity-auth alignment:
1050
+ // - systemInstruction uses role "user"
1051
+ // - the fixed Antigravity prefix is always first and merged with the first user segment when available
1052
+ const parts = extraSegments.length > 0
1053
+ ? [
1054
+ { text: `${ANTIGRAVITY_SYSTEM_INSTRUCTION}\n\n${extraSegments[0]}` },
1055
+ ...extraSegments.slice(1).map((text) => ({ text }))
1056
+ ]
1057
+ : [{ text: ANTIGRAVITY_SYSTEM_INSTRUCTION }];
1058
+ request.systemInstruction = {
1059
+ role: 'user',
1060
+ parts
1061
+ };
1062
+ }
1063
+ if (allowFunctionCallingProtocol && chat.tools && chat.tools.length) {
1064
+ const geminiTools = buildGeminiToolsFromBridge(effectiveBridgeDefs, {
1065
+ mode: isAntigravityProvider ? 'antigravity' : 'default'
1066
+ });
519
1067
  if (geminiTools) {
520
1068
  request.tools = geminiTools;
521
1069
  }
@@ -532,9 +1080,140 @@ function buildGeminiRequestFromChat(chat, metadata) {
532
1080
  if (Object.keys(generationConfig).length) {
533
1081
  request.generationConfig = generationConfig;
534
1082
  }
535
- if (semanticsNode?.safetySettings !== undefined) {
1083
+ if (!isAntigravityProvider && semanticsNode?.safetySettings !== undefined) {
536
1084
  request.safetySettings = jsonClone(semanticsNode.safetySettings);
537
1085
  }
1086
+ else if (isAntigravityProvider) {
1087
+ // gcli2api alignment: Antigravity always sends a permissive safetySettings set.
1088
+ request.safetySettings = jsonClone(ANTIGRAVITY_DEFAULT_SAFETY_SETTINGS);
1089
+ }
1090
+ if (isAntigravityProvider && typeof request.model === 'string') {
1091
+ const original = request.model;
1092
+ const mapped = stripTierSuffix(original);
1093
+ const size = typeof chat.parameters?.size === 'string' ? String(chat.parameters.size) : undefined;
1094
+ const quality = typeof chat.parameters?.quality === 'string' ? String(chat.parameters.quality) : undefined;
1095
+ const config = resolveAntigravityRequestConfig({
1096
+ originalModel: original,
1097
+ mappedModel: mapped,
1098
+ tools: request.tools,
1099
+ size,
1100
+ quality
1101
+ });
1102
+ antigravityRequestType = config.requestType;
1103
+ request.model = config.finalModel || mapped;
1104
+ pruneSearchFunctionDeclarations(request);
1105
+ if (config.requestType === 'image_gen') {
1106
+ delete request.tools;
1107
+ delete request.systemInstruction;
1108
+ if (!isJsonObject(request.generationConfig)) {
1109
+ request.generationConfig = {};
1110
+ }
1111
+ const gen = request.generationConfig;
1112
+ delete gen.thinkingConfig;
1113
+ delete gen.responseMimeType;
1114
+ delete gen.responseModalities;
1115
+ if (config.imageConfig) {
1116
+ gen.imageConfig = config.imageConfig;
1117
+ }
1118
+ }
1119
+ else if (config.injectGoogleSearch) {
1120
+ injectGoogleSearchTool(request);
1121
+ }
1122
+ deepCleanUndefined(request);
1123
+ const mappedLower = String(request.model || '').toLowerCase();
1124
+ const isImageModel = config.requestType === 'image_gen' || mappedLower.includes('image');
1125
+ const isThinkingModel = !isImageModel && (mappedLower.includes('think') || mappedLower.includes('pro'));
1126
+ if (isThinkingModel && (!request.generationConfig || !isJsonObject(request.generationConfig))) {
1127
+ request.generationConfig = {};
1128
+ }
1129
+ const generationConfig = request.generationConfig;
1130
+ if (isThinkingModel && isJsonObject(generationConfig)) {
1131
+ const gc = generationConfig;
1132
+ const thinkingConfig = isJsonObject(gc.thinkingConfig) ? gc.thinkingConfig : {};
1133
+ const existingBudget = typeof thinkingConfig.thinkingBudget === 'number' ? thinkingConfig.thinkingBudget : undefined;
1134
+ const shouldApply = existingBudget !== undefined ? existingBudget !== 0 : true;
1135
+ if (shouldApply) {
1136
+ if (typeof thinkingConfig.thinkingBudget !== 'number') {
1137
+ thinkingConfig.thinkingBudget = 1024;
1138
+ }
1139
+ if (Object.prototype.hasOwnProperty.call(thinkingConfig, 'thinkingLevel')) {
1140
+ delete thinkingConfig.thinkingLevel;
1141
+ }
1142
+ thinkingConfig.includeThoughts = true;
1143
+ // For Claude routed via Antigravity:
1144
+ // - when tool calls exist, gcli2api drops thinkingConfig to avoid upstream failures
1145
+ // - otherwise, ensure the last model message begins with a thinking block signature
1146
+ const isClaude = mappedLower.includes('claude');
1147
+ if (isClaude) {
1148
+ const contentsArray = Array.isArray(request.contents) ? request.contents : [];
1149
+ const hasToolCalls = contentsArray.some((content) => {
1150
+ if (!isJsonObject(content))
1151
+ return false;
1152
+ const parts = content.parts;
1153
+ if (!Array.isArray(parts))
1154
+ return false;
1155
+ return parts.some((part) => isJsonObject(part) &&
1156
+ ('functionCall' in part || 'function_call' in part));
1157
+ });
1158
+ if (hasToolCalls) {
1159
+ delete gc.thinkingConfig;
1160
+ }
1161
+ else {
1162
+ for (let idx = contentsArray.length - 1; idx >= 0; idx--) {
1163
+ const content = contentsArray[idx];
1164
+ if (!isJsonObject(content))
1165
+ continue;
1166
+ if (content.role !== 'model')
1167
+ continue;
1168
+ const parts = content.parts;
1169
+ if (!Array.isArray(parts))
1170
+ continue;
1171
+ const first = parts[0];
1172
+ const firstIsThinking = isJsonObject(first) && ('thought' in first || 'thoughtSignature' in first);
1173
+ if (!firstIsThinking) {
1174
+ content.parts = [
1175
+ { text: '...', thoughtSignature: 'skip_thought_signature_validator' },
1176
+ ...parts
1177
+ ];
1178
+ }
1179
+ break;
1180
+ }
1181
+ gc.thinkingConfig = thinkingConfig;
1182
+ }
1183
+ }
1184
+ else {
1185
+ gc.thinkingConfig = thinkingConfig;
1186
+ }
1187
+ }
1188
+ }
1189
+ }
1190
+ // Map OpenAI-style tool_choice to Gemini toolConfig (functionCallingConfig).
1191
+ // This is required to reliably disable tool calling while still declaring tools
1192
+ // (e.g. servertool followups that must preserve tool list for session continuity).
1193
+ if (request.toolConfig === undefined && chat.parameters && 'tool_choice' in chat.parameters) {
1194
+ const choice = chat.parameters.tool_choice;
1195
+ if (typeof choice === 'string') {
1196
+ const lowered = choice.trim().toLowerCase();
1197
+ if (lowered === 'none') {
1198
+ request.toolConfig = { functionCallingConfig: { mode: 'NONE' } };
1199
+ }
1200
+ else if (lowered === 'required') {
1201
+ request.toolConfig = { functionCallingConfig: { mode: 'ANY' } };
1202
+ }
1203
+ }
1204
+ else if (choice && typeof choice === 'object' && !Array.isArray(choice)) {
1205
+ const type = typeof choice.type === 'string' ? String(choice.type).trim().toLowerCase() : '';
1206
+ const name = type === 'function' && choice.function && typeof choice.function.name === 'string'
1207
+ ? String(choice.function.name).trim()
1208
+ : '';
1209
+ const wireName = name ? normalizeToolName(name) : undefined;
1210
+ if (wireName && declaredToolNames.has(wireName)) {
1211
+ request.toolConfig = {
1212
+ functionCallingConfig: { mode: 'ANY', allowedFunctionNames: [wireName] }
1213
+ };
1214
+ }
1215
+ }
1216
+ }
538
1217
  if (chat.parameters?.tool_config && isJsonObject(chat.parameters.tool_config)) {
539
1218
  request.toolConfig = jsonClone(chat.parameters.tool_config);
540
1219
  }
@@ -556,7 +1235,7 @@ function buildGeminiRequestFromChat(chat, metadata) {
556
1235
  request.metadata = request.metadata ?? {};
557
1236
  request.metadata.__rcc_stream = chat.parameters.stream;
558
1237
  }
559
- if ((chat.metadata?.toolsFieldPresent || hasExplicitEmptyToolsSemantics(chat)) &&
1238
+ if (hasExplicitEmptyToolsSemantics(chat) &&
560
1239
  (!Array.isArray(chat.tools) || chat.tools.length === 0)) {
561
1240
  request.metadata = request.metadata ?? {};
562
1241
  request.metadata.__rcc_tools_field_present = '1';
@@ -572,9 +1251,27 @@ function buildGeminiRequestFromChat(chat, metadata) {
572
1251
  }
573
1252
  }
574
1253
  // 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),这里不做非标裁剪。
1254
+ // for Claude models, regardless of compatibilityProfile wiring.
577
1255
  const compatRequest = applyClaudeThinkingToolSchemaCompat(request, adapterContext);
1256
+ if (isAntigravityProvider) {
1257
+ // opencode-antigravity-auth alignment:
1258
+ // Wrap the request in the agent envelope structure.
1259
+ // This allows passing requestType="agent" in the body, which is critical for
1260
+ // routing to the correct agent endpoint logic on the server side.
1261
+ const projectId = adapterContext?.projectId;
1262
+ const requestId = adapterContext?.requestId || `agent-${crypto.randomUUID()}`;
1263
+ const wrappedBody = {
1264
+ model: request.model,
1265
+ request: compatRequest,
1266
+ requestType: antigravityRequestType ?? 'agent',
1267
+ userAgent: 'antigravity',
1268
+ requestId
1269
+ };
1270
+ if (typeof projectId === 'string' && projectId.trim().length) {
1271
+ wrappedBody.project = projectId.trim();
1272
+ }
1273
+ return wrappedBody;
1274
+ }
578
1275
  return compatRequest;
579
1276
  }
580
1277
  function isPlainRecord(value) {
@@ -785,7 +1482,6 @@ export class GeminiSemanticMapper {
785
1482
  }
786
1483
  }
787
1484
  if (toolsFieldPresent) {
788
- metadata.toolsFieldPresent = true;
789
1485
  explicitEmptyTools = true;
790
1486
  }
791
1487
  providerMetadata = cloned;