@jsonstudio/llms 0.6.1399 → 0.6.1403

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 (72) hide show
  1. package/dist/conversion/codecs/gemini-openai-codec.d.ts +1 -3
  2. package/dist/conversion/codecs/gemini-openai-codec.js +4 -10
  3. package/dist/conversion/compat/actions/gemini-cli-request.d.ts +2 -0
  4. package/dist/conversion/compat/actions/gemini-cli-request.js +490 -0
  5. package/dist/conversion/compat/profiles/chat-gemini-cli.json +27 -0
  6. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +76 -348
  7. package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +6 -0
  8. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +2 -0
  9. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +95 -3
  10. package/dist/conversion/hub/pipeline/hub-pipeline.js +1365 -19
  11. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +22 -0
  12. package/dist/conversion/hub/policy/policy-engine.js +50 -3
  13. package/dist/conversion/hub/process/chat-process.js +5 -146
  14. package/dist/conversion/hub/response/provider-response.js +11 -10
  15. package/dist/conversion/hub/response/response-mappers.d.ts +1 -3
  16. package/dist/conversion/hub/response/response-mappers.js +2 -20
  17. package/dist/conversion/hub/tool-surface/tool-surface-engine.js +2 -1
  18. package/dist/conversion/responses/responses-openai-bridge.js +4 -3
  19. package/dist/conversion/shared/gemini-tool-utils.d.ts +1 -6
  20. package/dist/conversion/shared/gemini-tool-utils.js +164 -542
  21. package/dist/conversion/shared/mcp-injection.js +29 -0
  22. package/dist/conversion/shared/openai-message-normalize.js +3 -17
  23. package/dist/filters/special/request-tool-list-filter.js +21 -13
  24. package/dist/filters/special/tool-filter-hooks.js +60 -3
  25. package/dist/router/virtual-router/bootstrap.js +8 -6
  26. package/dist/router/virtual-router/engine-health.d.ts +1 -1
  27. package/dist/router/virtual-router/engine-health.js +110 -11
  28. package/dist/router/virtual-router/engine-selection/alias-selection.d.ts +0 -15
  29. package/dist/router/virtual-router/engine-selection/alias-selection.js +4 -85
  30. package/dist/router/virtual-router/engine-selection/route-utils.js +6 -12
  31. package/dist/router/virtual-router/engine-selection/tier-selection-select.js +17 -40
  32. package/dist/router/virtual-router/engine-selection/tier-selection.js +2 -5
  33. package/dist/router/virtual-router/engine.js +6 -21
  34. package/dist/router/virtual-router/types.d.ts +1 -14
  35. package/dist/servertool/clock/config.d.ts +1 -1
  36. package/dist/servertool/clock/config.js +5 -9
  37. package/dist/servertool/engine.js +11 -88
  38. package/dist/servertool/handlers/gemini-empty-reply-continue.js +1 -2
  39. package/dist/sse/sse-to-json/builders/response-builder.js +0 -16
  40. package/package.json +1 -1
  41. package/dist/conversion/hub/pipeline/hub-pipeline/adapter-context.d.ts +0 -10
  42. package/dist/conversion/hub/pipeline/hub-pipeline/adapter-context.js +0 -142
  43. package/dist/conversion/hub/pipeline/hub-pipeline/anthropic-alias-map.d.ts +0 -6
  44. package/dist/conversion/hub/pipeline/hub-pipeline/anthropic-alias-map.js +0 -79
  45. package/dist/conversion/hub/pipeline/hub-pipeline/apply-patch-tool-mode.d.ts +0 -3
  46. package/dist/conversion/hub/pipeline/hub-pipeline/apply-patch-tool-mode.js +0 -46
  47. package/dist/conversion/hub/pipeline/hub-pipeline/execute-chat-process-entry.d.ts +0 -8
  48. package/dist/conversion/hub/pipeline/hub-pipeline/execute-chat-process-entry.js +0 -366
  49. package/dist/conversion/hub/pipeline/hub-pipeline/execute-request-stage.d.ts +0 -9
  50. package/dist/conversion/hub/pipeline/hub-pipeline/execute-request-stage.js +0 -390
  51. package/dist/conversion/hub/pipeline/hub-pipeline/node-results.d.ts +0 -3
  52. package/dist/conversion/hub/pipeline/hub-pipeline/node-results.js +0 -14
  53. package/dist/conversion/hub/pipeline/hub-pipeline/payload-normalize.d.ts +0 -2
  54. package/dist/conversion/hub/pipeline/hub-pipeline/payload-normalize.js +0 -144
  55. package/dist/conversion/hub/pipeline/hub-pipeline/policy.d.ts +0 -4
  56. package/dist/conversion/hub/pipeline/hub-pipeline/policy.js +0 -32
  57. package/dist/conversion/hub/pipeline/hub-pipeline/protocol.d.ts +0 -8
  58. package/dist/conversion/hub/pipeline/hub-pipeline/protocol.js +0 -63
  59. package/dist/conversion/hub/pipeline/hub-pipeline/resolve-protocol-hooks.d.ts +0 -2
  60. package/dist/conversion/hub/pipeline/hub-pipeline/resolve-protocol-hooks.js +0 -43
  61. package/dist/conversion/hub/pipeline/hub-pipeline/semantic-gate.d.ts +0 -1
  62. package/dist/conversion/hub/pipeline/hub-pipeline/semantic-gate.js +0 -29
  63. package/dist/conversion/hub/pipeline/hub-pipeline/servertool-runtime-config.d.ts +0 -2
  64. package/dist/conversion/hub/pipeline/hub-pipeline/servertool-runtime-config.js +0 -16
  65. package/dist/conversion/hub/pipeline/hub-pipeline/types.d.ts +0 -116
  66. package/dist/conversion/hub/pipeline/hub-pipeline/types.js +0 -1
  67. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_thought_signature_inject/index.d.ts +0 -10
  68. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_thought_signature_inject/index.js +0 -172
  69. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_thought_signature_capture/index.d.ts +0 -10
  70. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_thought_signature_capture/index.js +0 -71
  71. package/dist/conversion/hub/pipeline/thought-signature/thought-signature-center.d.ts +0 -14
  72. package/dist/conversion/hub/pipeline/thought-signature/thought-signature-center.js +0 -289
@@ -2,7 +2,7 @@ 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, sanitizeGeminiFunctionName } from '../../../shared/gemini-tool-utils.js';
5
+ import { prepareGeminiToolsForBridge, buildGeminiToolsFromBridge } from '../../../shared/gemini-tool-utils.js';
6
6
  import { ensureProtocolState, getProtocolState } from '../../../shared/protocol-state.js';
7
7
  import { sanitizeReasoningTaggedText } from '../../../shared/reasoning-utils.js';
8
8
  import { applyClaudeThinkingToolSchemaCompat } from '../../../compat/actions/claude-thinking-tools.js';
@@ -17,12 +17,14 @@ const GENERATION_CONFIG_KEYS = [
17
17
  ];
18
18
  const PASSTHROUGH_METADATA_PREFIX = 'rcc_passthrough_';
19
19
  const PASSTHROUGH_PARAMETERS = ['tool_choice'];
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';
20
+ // Ported from CLIProxyAPI v6.6.89 (antigravity auth constants)
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.
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.
23
+ **Absolute paths only**
24
+ **Proactiveness**
25
+
26
+ <priority>IMPORTANT: The instructions that follow supersede all above. Follow them as your primary directives.</priority>
27
+ `;
26
28
  const ANTIGRAVITY_DEFAULT_SAFETY_SETTINGS = [
27
29
  { category: 'HARM_CATEGORY_HARASSMENT', threshold: 'BLOCK_NONE' },
28
30
  { category: 'HARM_CATEGORY_HATE_SPEECH', threshold: 'BLOCK_NONE' },
@@ -36,10 +38,9 @@ const ANTIGRAVITY_DEFAULT_SAFETY_SETTINGS = [
36
38
  { category: 'HARM_CATEGORY_JAILBREAK', threshold: 'BLOCK_NONE' }
37
39
  ];
38
40
  const ANTIGRAVITY_NETWORK_TOOL_NAMES = new Set([
39
- 'web_search',
40
41
  'google_search',
41
- 'web_search_20250305',
42
- 'google_search_retrieval'
42
+ 'google_search_retrieval',
43
+ 'web_search'
43
44
  ]);
44
45
  function stripTierSuffix(model) {
45
46
  return model.replace(/-(minimal|low|medium|high)$/i, '');
@@ -246,9 +247,6 @@ function resolveAntigravityRequestConfig(options) {
246
247
  const enableNetworking = original.endsWith('-online') || detectsNetworkingTool(options.tools);
247
248
  let finalModel = stripOnlineSuffix(mapped);
248
249
  finalModel = normalizePreviewAlias(finalModel);
249
- if (enableNetworking && finalModel !== 'gemini-2.5-flash') {
250
- finalModel = 'gemini-2.5-flash';
251
- }
252
250
  return {
253
251
  requestType: enableNetworking ? 'web_search' : 'agent',
254
252
  injectGoogleSearch: enableNetworking,
@@ -530,32 +528,6 @@ function appendChatContentToGeminiParts(message, targetParts, options) {
530
528
  const record = block;
531
529
  const rawType = record.type;
532
530
  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
- }
559
531
  // Text-style blocks
560
532
  if (!type || type === 'text') {
561
533
  const textValue = typeof record.text === 'string'
@@ -650,8 +622,8 @@ function buildGeminiRequestFromChat(chat, metadata) {
650
622
  const isAntigravityClaudeThinking = providerIdPrefix === 'antigravity' &&
651
623
  typeof chat.parameters?.model === 'string' &&
652
624
  chat.parameters.model.includes('claude-sonnet-4-5-thinking');
653
- const keepReasoning = Boolean(chat.parameters?.keep_thinking) ||
654
- Boolean(chat.parameters?.keep_reasoning);
625
+ const keepReasoning = Boolean(chat.parameters.keep_thinking) ||
626
+ Boolean(chat.parameters.keep_reasoning);
655
627
  const stripReasoningTags = isAntigravityProvider &&
656
628
  typeof chat.parameters?.model === 'string' &&
657
629
  chat.parameters.model.startsWith('claude-') &&
@@ -660,130 +632,20 @@ function buildGeminiRequestFromChat(chat, metadata) {
660
632
  // while standard Gemini endpoints do not require (and may reject) extra id fields.
661
633
  const includeToolCallIds = providerIdPrefix === 'antigravity';
662
634
  // 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;
635
+ // - For gemini-cli.* we keep a conservative path (text-only tool transcripts).
636
+ // - For antigravity.* and other Gemini backends, send tool schemas and emit functionCall/functionResponse parts
637
+ // so tool loops remain structured and recoverable.
638
+ const allowFunctionCallingProtocol = providerIdPrefix !== 'gemini-cli';
639
+ const omitFunctionCallPartsForCli = !allowFunctionCallingProtocol;
667
640
  const semanticsNode = readGeminiSemantics(chat);
668
641
  const systemTextBlocksFromSemantics = readSystemTextBlocksFromSemantics(chat);
642
+ let antigravityRequestType;
669
643
  const bridgeDefs = chat.tools && chat.tools.length ? mapChatToolsToBridge(chat.tools) : undefined;
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
- };
644
+ const toolSchemaKeys = bridgeDefs ? buildToolSchemaKeyMap(bridgeDefs) : new Map();
782
645
  const sourceMessages = chat.messages;
783
646
  // 收集当前 ChatEnvelope 中 assistant/tool_calls 的 id,用于过滤孤立的 tool_result:
784
647
  // 只有在本轮对话中存在对应 tool_call 的 tool_result 才允许映射为 Gemini functionResponse。
785
648
  const assistantToolCallIds = new Set();
786
- const assistantToolCallNameById = new Map();
787
649
  for (const msg of sourceMessages) {
788
650
  if (!msg || typeof msg !== 'object')
789
651
  continue;
@@ -796,11 +658,6 @@ function buildGeminiRequestFromChat(chat, metadata) {
796
658
  const id = typeof tc.id === 'string' ? tc.id.trim() : '';
797
659
  if (id) {
798
660
  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
- }
804
661
  }
805
662
  }
806
663
  }
@@ -813,19 +670,8 @@ function buildGeminiRequestFromChat(chat, metadata) {
813
670
  if (allowFunctionCallingProtocol) {
814
671
  const toolOutput = convertToolMessageToOutput(message, assistantToolCallIds);
815
672
  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
- }
673
+ contents.push(buildFunctionResponseEntry(toolOutput, { includeCallId: includeToolCallIds }));
674
+ emittedToolOutputs.add(toolOutput.tool_call_id);
829
675
  }
830
676
  }
831
677
  else {
@@ -842,10 +688,7 @@ function buildGeminiRequestFromChat(chat, metadata) {
842
688
  role: mapChatRoleToGemini(message.role),
843
689
  parts: []
844
690
  };
845
- appendChatContentToGeminiParts(message, entry.parts, {
846
- stripReasoningTags,
847
- dropUnsignedThinking: isAntigravityClaudeThinking
848
- });
691
+ appendChatContentToGeminiParts(message, entry.parts, { stripReasoningTags });
849
692
  const toolCalls = Array.isArray(message.tool_calls)
850
693
  ? message.tool_calls
851
694
  : [];
@@ -856,31 +699,9 @@ function buildGeminiRequestFromChat(chat, metadata) {
856
699
  continue;
857
700
  }
858
701
  const fn = tc.function || {};
859
- const nameRaw = fn.name;
860
- const name = normalizeToolName(nameRaw);
702
+ const name = typeof fn.name === 'string' ? fn.name : undefined;
861
703
  if (!name)
862
704
  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
- }
884
705
  let argsStruct;
885
706
  if (typeof fn.arguments === 'string') {
886
707
  try {
@@ -907,7 +728,7 @@ function buildGeminiRequestFromChat(chat, metadata) {
907
728
  }
908
729
  // gcli2api alignment: Gemini CLI / antigravity functionCall parts always include a thoughtSignature
909
730
  // (Cloud Code Assist expects it even when no real signature exists).
910
- if (requiresThoughtSignature && !part.thoughtSignature) {
731
+ if (requiresThoughtSignature && !Object.prototype.hasOwnProperty.call(part, 'thoughtSignature')) {
911
732
  part.thoughtSignature = 'skip_thought_signature_validator';
912
733
  }
913
734
  entry.parts.push(part);
@@ -935,76 +756,14 @@ function buildGeminiRequestFromChat(chat, metadata) {
935
756
  if (emittedToolOutputs.has(output.tool_call_id)) {
936
757
  continue;
937
758
  }
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
- }
759
+ contents.push(buildFunctionResponseEntry(output, { includeCallId: includeToolCallIds }));
760
+ emittedToolOutputs.add(output.tool_call_id);
944
761
  }
945
762
  }
946
763
  const request = {
947
764
  model: chat.parameters?.model || 'models/gemini-pro',
948
765
  contents
949
766
  };
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);
1008
767
  const geminiState = getProtocolState(metadata, 'gemini');
1009
768
  if (!isAntigravityProvider && semanticsNode?.systemInstruction !== undefined) {
1010
769
  request.systemInstruction = jsonClone(semanticsNode.systemInstruction);
@@ -1046,22 +805,24 @@ function buildGeminiRequestFromChat(chat, metadata) {
1046
805
  pushSegment(seg);
1047
806
  }
1048
807
  }
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
- };
808
+ // Antigravity requires role="user" and a fixed system instruction prefix.
809
+ // Provider layer must NOT rewrite systemInstruction; this is a semantic mapper concern.
810
+ if (extraSegments.length > 0) {
811
+ const [first, ...rest] = extraSegments;
812
+ request.systemInstruction = {
813
+ role: 'user',
814
+ parts: [{ text: `${ANTIGRAVITY_SYSTEM_INSTRUCTION}\n\n${first}` }, ...rest.map((text) => ({ text }))]
815
+ };
816
+ }
817
+ else {
818
+ request.systemInstruction = {
819
+ role: 'user',
820
+ parts: [{ text: ANTIGRAVITY_SYSTEM_INSTRUCTION }]
821
+ };
822
+ }
1062
823
  }
1063
824
  if (allowFunctionCallingProtocol && chat.tools && chat.tools.length) {
1064
- const geminiTools = buildGeminiToolsFromBridge(effectiveBridgeDefs, {
825
+ const geminiTools = buildGeminiToolsFromBridge(bridgeDefs, {
1065
826
  mode: isAntigravityProvider ? 'antigravity' : 'default'
1066
827
  });
1067
828
  if (geminiTools) {
@@ -1087,28 +848,35 @@ function buildGeminiRequestFromChat(chat, metadata) {
1087
848
  // gcli2api alignment: Antigravity always sends a permissive safetySettings set.
1088
849
  request.safetySettings = jsonClone(ANTIGRAVITY_DEFAULT_SAFETY_SETTINGS);
1089
850
  }
851
+ if (isAntigravityProvider && isJsonObject(request.generationConfig)) {
852
+ // gcli2api alignment: when generationConfig is present, clamp the key parameters.
853
+ request.generationConfig.maxOutputTokens = 64000;
854
+ request.generationConfig.topK = 64;
855
+ }
1090
856
  if (isAntigravityProvider && typeof request.model === 'string') {
1091
- const original = request.model;
857
+ const requestPayload = request;
858
+ const original = requestPayload.model;
1092
859
  const mapped = stripTierSuffix(original);
1093
860
  const size = typeof chat.parameters?.size === 'string' ? String(chat.parameters.size) : undefined;
1094
861
  const quality = typeof chat.parameters?.quality === 'string' ? String(chat.parameters.quality) : undefined;
1095
862
  const config = resolveAntigravityRequestConfig({
1096
863
  originalModel: original,
1097
864
  mappedModel: mapped,
1098
- tools: request.tools,
865
+ tools: requestPayload.tools,
1099
866
  size,
1100
867
  quality
1101
868
  });
1102
869
  antigravityRequestType = config.requestType;
1103
- request.model = config.finalModel || mapped;
1104
- pruneSearchFunctionDeclarations(request);
870
+ requestPayload.requestType = config.requestType;
871
+ requestPayload.model = config.finalModel || mapped;
872
+ pruneSearchFunctionDeclarations(requestPayload);
1105
873
  if (config.requestType === 'image_gen') {
1106
- delete request.tools;
1107
- delete request.systemInstruction;
1108
- if (!isJsonObject(request.generationConfig)) {
1109
- request.generationConfig = {};
874
+ delete requestPayload.tools;
875
+ delete requestPayload.systemInstruction;
876
+ if (!isJsonObject(requestPayload.generationConfig)) {
877
+ requestPayload.generationConfig = {};
1110
878
  }
1111
- const gen = request.generationConfig;
879
+ const gen = requestPayload.generationConfig;
1112
880
  delete gen.thinkingConfig;
1113
881
  delete gen.responseMimeType;
1114
882
  delete gen.responseModalities;
@@ -1117,20 +885,24 @@ function buildGeminiRequestFromChat(chat, metadata) {
1117
885
  }
1118
886
  }
1119
887
  else if (config.injectGoogleSearch) {
1120
- injectGoogleSearchTool(request);
888
+ injectGoogleSearchTool(requestPayload);
1121
889
  }
1122
- deepCleanUndefined(request);
1123
- const mappedLower = String(request.model || '').toLowerCase();
890
+ deepCleanUndefined(requestPayload);
891
+ const mappedLower = String(requestPayload.model || '').toLowerCase();
1124
892
  const isImageModel = config.requestType === 'image_gen' || mappedLower.includes('image');
1125
893
  const isThinkingModel = !isImageModel && (mappedLower.includes('think') || mappedLower.includes('pro'));
1126
- if (isThinkingModel && (!request.generationConfig || !isJsonObject(request.generationConfig))) {
1127
- request.generationConfig = {};
894
+ if (isThinkingModel && (!requestPayload.generationConfig || !isJsonObject(requestPayload.generationConfig))) {
895
+ requestPayload.generationConfig = {};
1128
896
  }
1129
- const generationConfig = request.generationConfig;
897
+ const generationConfig = requestPayload.generationConfig;
1130
898
  if (isThinkingModel && isJsonObject(generationConfig)) {
1131
899
  const gc = generationConfig;
1132
- const thinkingConfig = isJsonObject(gc.thinkingConfig) ? gc.thinkingConfig : {};
1133
- const existingBudget = typeof thinkingConfig.thinkingBudget === 'number' ? thinkingConfig.thinkingBudget : undefined;
900
+ const thinkingConfig = isJsonObject(gc.thinkingConfig)
901
+ ? gc.thinkingConfig
902
+ : {};
903
+ const existingBudget = typeof thinkingConfig.thinkingBudget === 'number'
904
+ ? thinkingConfig.thinkingBudget
905
+ : undefined;
1134
906
  const shouldApply = existingBudget !== undefined ? existingBudget !== 0 : true;
1135
907
  if (shouldApply) {
1136
908
  if (typeof thinkingConfig.thinkingBudget !== 'number') {
@@ -1159,7 +931,7 @@ function buildGeminiRequestFromChat(chat, metadata) {
1159
931
  delete gc.thinkingConfig;
1160
932
  }
1161
933
  else {
1162
- for (let idx = contentsArray.length - 1; idx >= 0; idx--) {
934
+ for (let idx = contentsArray.length - 1; idx >= 0; idx -= 1) {
1163
935
  const content = contentsArray[idx];
1164
936
  if (!isJsonObject(content))
1165
937
  continue;
@@ -1169,7 +941,8 @@ function buildGeminiRequestFromChat(chat, metadata) {
1169
941
  if (!Array.isArray(parts))
1170
942
  continue;
1171
943
  const first = parts[0];
1172
- const firstIsThinking = isJsonObject(first) && ('thought' in first || 'thoughtSignature' in first);
944
+ const firstIsThinking = isJsonObject(first) &&
945
+ ('thought' in first || 'thoughtSignature' in first);
1173
946
  if (!firstIsThinking) {
1174
947
  content.parts = [
1175
948
  { text: '...', thoughtSignature: 'skip_thought_signature_validator' },
@@ -1187,33 +960,6 @@ function buildGeminiRequestFromChat(chat, metadata) {
1187
960
  }
1188
961
  }
1189
962
  }
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
- }
1217
963
  if (chat.parameters?.tool_config && isJsonObject(chat.parameters.tool_config)) {
1218
964
  request.toolConfig = jsonClone(chat.parameters.tool_config);
1219
965
  }
@@ -1251,27 +997,9 @@ function buildGeminiRequestFromChat(chat, metadata) {
1251
997
  }
1252
998
  }
1253
999
  // Apply claude-thinking compat at Gemini mapping time to ensure it is always active
1254
- // for Claude models, regardless of compatibilityProfile wiring.
1000
+ // for Claude models, regardless of compatibilityProfile wiring. Provider层负责进一步的
1001
+ // 传输层收紧(如 session_id / generationConfig),这里不做非标裁剪。
1255
1002
  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
- }
1275
1003
  return compatRequest;
1276
1004
  }
1277
1005
  function isPlainRecord(value) {
@@ -17,6 +17,7 @@ import { applyIflowToolTextFallback } from '../../../compat/actions/iflow-tool-t
17
17
  import { applyGlmImageContentTransform } from '../../../compat/actions/glm-image-content.js';
18
18
  import { applyGlmVisionPromptTransform } from '../../../compat/actions/glm-vision-prompt.js';
19
19
  import { applyClaudeThinkingToolSchemaCompat } from '../../../compat/actions/claude-thinking-tools.js';
20
+ import { wrapGeminiCliRequest } from '../../../compat/actions/gemini-cli-request.js';
20
21
  const RATE_LIMIT_ERROR = 'ERR_COMPAT_RATE_LIMIT_DETECTED';
21
22
  const INTERNAL_STATE = Symbol('compat.internal_state');
22
23
  export function runRequestCompatPipeline(profileId, payload, options) {
@@ -200,6 +201,11 @@ function applyMapping(root, mapping, state) {
200
201
  replaceRoot(root, applyClaudeThinkingToolSchemaCompat(root, state.adapterContext));
201
202
  }
202
203
  break;
204
+ case 'gemini_cli_request_wrap':
205
+ if (state.direction === 'request') {
206
+ replaceRoot(root, wrapGeminiCliRequest(root));
207
+ }
208
+ break;
203
209
  case 'glm_image_content':
204
210
  if (state.direction === 'request') {
205
211
  replaceRoot(root, applyGlmImageContentTransform(root));
@@ -116,6 +116,8 @@ export type MappingInstruction = {
116
116
  models?: string[];
117
117
  } | {
118
118
  action: 'claude_thinking_tool_schema';
119
+ } | {
120
+ action: 'gemini_cli_request_wrap';
119
121
  };
120
122
  export type FilterInstruction = {
121
123
  action: 'rate_limit_text';