@jsonstudio/llms 0.6.1354 → 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 (19) hide show
  1. package/dist/conversion/compat/profiles/chat-gemini.json +5 -0
  2. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +310 -87
  3. package/dist/conversion/hub/pipeline/hub-pipeline/adapter-context.js +8 -0
  4. package/dist/conversion/hub/pipeline/hub-pipeline/execute-request-stage.js +6 -0
  5. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_thought_signature_inject/index.d.ts +10 -0
  6. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_thought_signature_inject/index.js +172 -0
  7. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_thought_signature_capture/index.d.ts +10 -0
  8. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_thought_signature_capture/index.js +71 -0
  9. package/dist/conversion/hub/pipeline/thought-signature/thought-signature-center.d.ts +14 -0
  10. package/dist/conversion/hub/pipeline/thought-signature/thought-signature-center.js +289 -0
  11. package/dist/conversion/hub/response/provider-response.js +6 -0
  12. package/dist/router/virtual-router/bootstrap.js +6 -0
  13. package/dist/router/virtual-router/engine-selection/alias-selection.d.ts +15 -0
  14. package/dist/router/virtual-router/engine-selection/alias-selection.js +85 -4
  15. package/dist/router/virtual-router/engine-selection/tier-selection-select.js +40 -17
  16. package/dist/router/virtual-router/engine-selection/tier-selection.js +5 -2
  17. package/dist/router/virtual-router/engine.js +9 -1
  18. package/dist/router/virtual-router/types.d.ts +14 -1
  19. package/package.json +1 -1
@@ -12,6 +12,11 @@
12
12
  "action": "shallow_pick",
13
13
  "allowTopLevel": [
14
14
  "model",
15
+ "project",
16
+ "request",
17
+ "requestId",
18
+ "requestType",
19
+ "userAgent",
15
20
  "contents",
16
21
  "systemInstruction",
17
22
  "tools",
@@ -17,9 +17,12 @@ const GENERATION_CONFIG_KEYS = [
17
17
  ];
18
18
  const PASSTHROUGH_METADATA_PREFIX = 'rcc_passthrough_';
19
19
  const PASSTHROUGH_PARAMETERS = ['tool_choice'];
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]';
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';
23
26
  const ANTIGRAVITY_DEFAULT_SAFETY_SETTINGS = [
24
27
  { category: 'HARM_CATEGORY_HARASSMENT', threshold: 'BLOCK_NONE' },
25
28
  { category: 'HARM_CATEGORY_HATE_SPEECH', threshold: 'BLOCK_NONE' },
@@ -32,6 +35,226 @@ const ANTIGRAVITY_DEFAULT_SAFETY_SETTINGS = [
32
35
  { category: 'HARM_CATEGORY_IMAGE_SEXUALLY_EXPLICIT', threshold: 'BLOCK_NONE' },
33
36
  { category: 'HARM_CATEGORY_JAILBREAK', threshold: 'BLOCK_NONE' }
34
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
+ }
35
258
  function coerceThoughtSignature(value) {
36
259
  if (typeof value === 'string' && value.trim().length) {
37
260
  return value.trim();
@@ -422,6 +645,8 @@ function buildGeminiRequestFromChat(chat, metadata) {
422
645
  const normalizedProviderId = typeof rawProviderId === 'string' ? rawProviderId.toLowerCase() : '';
423
646
  const providerIdPrefix = normalizedProviderId.split('.')[0];
424
647
  const isAntigravityProvider = providerIdPrefix === 'antigravity';
648
+ const isGeminiCliProvider = providerIdPrefix === 'gemini-cli';
649
+ const requiresThoughtSignature = isAntigravityProvider || isGeminiCliProvider;
425
650
  const isAntigravityClaudeThinking = providerIdPrefix === 'antigravity' &&
426
651
  typeof chat.parameters?.model === 'string' &&
427
652
  chat.parameters.model.includes('claude-sonnet-4-5-thinking');
@@ -636,22 +861,24 @@ function buildGeminiRequestFromChat(chat, metadata) {
636
861
  if (!name)
637
862
  continue;
638
863
  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);
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();
648
870
  }
649
- catch {
650
- argsText = String(fn.arguments);
871
+ else if (fn.arguments !== undefined) {
872
+ try {
873
+ argsText = JSON.stringify(fn.arguments);
874
+ }
875
+ catch {
876
+ argsText = String(fn.arguments);
877
+ }
651
878
  }
879
+ const text = argsText ? `[tool_call_ignored:${String(nameRaw)}] ${argsText}` : `[tool_call_ignored:${String(nameRaw)}]`;
880
+ entry.parts.push({ text });
652
881
  }
653
- const text = argsText ? `[tool_call_ignored:${String(nameRaw)}] ${argsText}` : `[tool_call_ignored:${String(nameRaw)}]`;
654
- entry.parts.push({ text });
655
882
  continue;
656
883
  }
657
884
  let argsStruct;
@@ -678,9 +905,9 @@ function buildGeminiRequestFromChat(chat, metadata) {
678
905
  if (includeToolCallIds && typeof tc.id === 'string' && tc.id.trim().length) {
679
906
  part.functionCall.id = String(tc.id).trim();
680
907
  }
681
- // gcli2api alignment: antigravity functionCall parts always include a thoughtSignature
908
+ // gcli2api alignment: Gemini CLI / antigravity functionCall parts always include a thoughtSignature
682
909
  // (Cloud Code Assist expects it even when no real signature exists).
683
- if (isAntigravityProvider && !part.thoughtSignature) {
910
+ if (requiresThoughtSignature && !part.thoughtSignature) {
684
911
  part.thoughtSignature = 'skip_thought_signature_validator';
685
912
  }
686
913
  entry.parts.push(part);
@@ -720,6 +947,7 @@ function buildGeminiRequestFromChat(chat, metadata) {
720
947
  model: chat.parameters?.model || 'models/gemini-pro',
721
948
  contents
722
949
  };
950
+ let antigravityRequestType;
723
951
  const cleanGeminiContents = (raw) => {
724
952
  if (!Array.isArray(raw))
725
953
  return [];
@@ -818,11 +1046,18 @@ function buildGeminiRequestFromChat(chat, metadata) {
818
1046
  pushSegment(seg);
819
1047
  }
820
1048
  }
821
- // gcli2api antigravity normalization:
822
- // - systemInstruction is a plain { parts: [...] } object (no role field)
823
- // - the fixed Antigravity prefix is always the first part
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 }];
824
1058
  request.systemInstruction = {
825
- parts: [{ text: ANTIGRAVITY_SYSTEM_INSTRUCTION }, ...extraSegments.map((text) => ({ text }))]
1059
+ role: 'user',
1060
+ parts
826
1061
  };
827
1062
  }
828
1063
  if (allowFunctionCallingProtocol && chat.tools && chat.tools.length) {
@@ -852,64 +1087,42 @@ function buildGeminiRequestFromChat(chat, metadata) {
852
1087
  // gcli2api alignment: Antigravity always sends a permissive safetySettings set.
853
1088
  request.safetySettings = jsonClone(ANTIGRAVITY_DEFAULT_SAFETY_SETTINGS);
854
1089
  }
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
1090
  if (isAntigravityProvider && typeof request.model === 'string') {
865
- // gcli2api antigravity: normalize model name variants for Claude routed via Antigravity.
866
1091
  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;
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') {
907
1106
  delete request.tools;
908
- delete request.toolConfig;
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);
909
1121
  }
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'));
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'));
913
1126
  if (isThinkingModel && (!request.generationConfig || !isJsonObject(request.generationConfig))) {
914
1127
  request.generationConfig = {};
915
1128
  }
@@ -930,7 +1143,7 @@ function buildGeminiRequestFromChat(chat, metadata) {
930
1143
  // For Claude routed via Antigravity:
931
1144
  // - when tool calls exist, gcli2api drops thinkingConfig to avoid upstream failures
932
1145
  // - otherwise, ensure the last model message begins with a thinking block signature
933
- const isClaude = lowered.includes('claude');
1146
+ const isClaude = mappedLower.includes('claude');
934
1147
  if (isClaude) {
935
1148
  const contentsArray = Array.isArray(request.contents) ? request.contents : [];
936
1149
  const hasToolCalls = contentsArray.some((content) => {
@@ -973,14 +1186,6 @@ function buildGeminiRequestFromChat(chat, metadata) {
973
1186
  }
974
1187
  }
975
1188
  }
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
1189
  }
985
1190
  // Map OpenAI-style tool_choice to Gemini toolConfig (functionCallingConfig).
986
1191
  // This is required to reliably disable tool calling while still declaring tools
@@ -1046,9 +1251,27 @@ function buildGeminiRequestFromChat(chat, metadata) {
1046
1251
  }
1047
1252
  }
1048
1253
  // Apply claude-thinking compat at Gemini mapping time to ensure it is always active
1049
- // for Claude models, regardless of compatibilityProfile wiring. Provider层负责进一步的
1050
- // 传输层收紧(如 session_id / generationConfig),这里不做非标裁剪。
1254
+ // for Claude models, regardless of compatibilityProfile wiring.
1051
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
+ }
1052
1275
  return compatRequest;
1053
1276
  }
1054
1277
  function isPlainRecord(value) {
@@ -75,6 +75,14 @@ export function buildAdapterContext(normalized, target) {
75
75
  if (conversationId) {
76
76
  adapterContext.conversationId = conversationId;
77
77
  }
78
+ const projectIdRaw = typeof metadata.projectId === 'string'
79
+ ? metadata.projectId.trim()
80
+ : typeof metadata.project_id === 'string'
81
+ ? metadata.project_id.trim()
82
+ : '';
83
+ if (projectIdRaw) {
84
+ adapterContext.projectId = projectIdRaw;
85
+ }
78
86
  const clientConnectionState = metadata.clientConnectionState;
79
87
  if (clientConnectionState && typeof clientConnectionState === 'object' && !Array.isArray(clientConnectionState)) {
80
88
  const stateRecord = clientConnectionState;
@@ -10,6 +10,7 @@ import { runReqProcessStage1ToolGovernance } from '../stages/req_process/req_pro
10
10
  import { runReqProcessStage2RouteSelect } from '../stages/req_process/req_process_stage2_route_select/index.js';
11
11
  import { runReqOutboundStage1SemanticMap } from '../stages/req_outbound/req_outbound_stage1_semantic_map/index.js';
12
12
  import { runReqOutboundStage2FormatBuild } from '../stages/req_outbound/req_outbound_stage2_format_build/index.js';
13
+ import { runReqOutboundStage2ThoughtSignatureInject } from '../stages/req_outbound/req_outbound_stage2_thought_signature_inject/index.js';
13
14
  import { runReqOutboundStage3Compat } from '../stages/req_outbound/req_outbound_stage3_compat/index.js';
14
15
  import { extractSessionIdentifiersFromMetadata } from '../session-identifiers.js';
15
16
  import { buildAdapterContext, maybeCreateStageRecorder, resolveOutboundStreamIntent, applyOutboundStreamPreference } from './adapter-context.js';
@@ -267,6 +268,11 @@ export async function executeRequestStagePipeline(options) {
267
268
  formatAdapter: outboundFormatAdapter,
268
269
  stageRecorder: outboundRecorder
269
270
  });
271
+ formattedPayload = await runReqOutboundStage2ThoughtSignatureInject({
272
+ payload: formattedPayload,
273
+ adapterContext: outboundAdapterContext,
274
+ stageRecorder: outboundRecorder
275
+ });
270
276
  formattedPayload = await runReqOutboundStage3Compat({
271
277
  payload: formattedPayload,
272
278
  adapterContext: outboundAdapterContext,
@@ -0,0 +1,10 @@
1
+ import type { AdapterContext } from '../../../../types/chat-envelope.js';
2
+ import type { JsonObject } from '../../../../types/json.js';
3
+ import type { StageRecorder } from '../../../../format-adapters/index.js';
4
+ type ProviderPayload = JsonObject;
5
+ export declare function runReqOutboundStage2ThoughtSignatureInject(options: {
6
+ payload: ProviderPayload;
7
+ adapterContext: AdapterContext;
8
+ stageRecorder?: StageRecorder;
9
+ }): Promise<ProviderPayload>;
10
+ export {};