@iaforged/context-code 1.0.77 → 1.0.79

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 (121) hide show
  1. package/README.md +68 -68
  2. package/cli.js +8515 -8515
  3. package/context-bootstrap.js +27 -27
  4. package/dist/src/bootstrap/state.js +3 -0
  5. package/dist/src/bridge/bridgeMain.js +40 -40
  6. package/dist/src/cli/print.js +12 -12
  7. package/dist/src/commands/agent/agent.js +8 -0
  8. package/dist/src/commands/commit-push-pr.js +55 -55
  9. package/dist/src/commands/createMovedToPluginCommand.js +9 -9
  10. package/dist/src/commands/init-verifiers.js +238 -238
  11. package/dist/src/commands/init.js +216 -216
  12. package/dist/src/commands/install.js +2 -2
  13. package/dist/src/commands/login/login.js +24 -10
  14. package/dist/src/commands/orchestrate/index.js +1 -1
  15. package/dist/src/commands/orchestrate/orchestrate.js +110 -24
  16. package/dist/src/commands/profile/profile.js +15 -1
  17. package/dist/src/commands/provider/index.js +1 -1
  18. package/dist/src/commands/provider/provider.js +34 -1
  19. package/dist/src/commands/review.js +22 -22
  20. package/dist/src/commands/run/index.js +2 -2
  21. package/dist/src/commands/run/run.js +63 -61
  22. package/dist/src/commands/team/index.js +1 -1
  23. package/dist/src/commands/team/team.js +84 -76
  24. package/dist/src/commands/team-auto/teamAuto.js +89 -29
  25. package/dist/src/commands/terminalSetup/terminalSetup.js +24 -24
  26. package/dist/src/commands/usage/index.js +7 -0
  27. package/dist/src/commands/usage/usage.js +5 -0
  28. package/dist/src/commands/workspace/workspace.js +39 -31
  29. package/dist/src/commands.js +0 -2
  30. package/dist/src/components/ConsoleOAuthFlow.js +92 -14
  31. package/dist/src/components/ModelPicker.js +2 -0
  32. package/dist/src/components/agents/generateAgent.js +92 -92
  33. package/dist/src/components/grove/Grove.js +10 -10
  34. package/dist/src/components/permissions/AskUserQuestionPermissionRequest/AskUserQuestionPermissionRequest.js +8 -8
  35. package/dist/src/constants/geminiOAuth.js +13 -0
  36. package/dist/src/constants/github-app.js +134 -134
  37. package/dist/src/constants/prompts.js +123 -123
  38. package/dist/src/coordinator/coordinatorMode.js +252 -252
  39. package/dist/src/hooks/useTypeahead.js +7 -7
  40. package/dist/src/ink/reconciler.js +7 -7
  41. package/dist/src/main.js +5 -5
  42. package/dist/src/memdir/findRelevantMemories.js +6 -6
  43. package/dist/src/services/MagicDocs/prompts.js +56 -56
  44. package/dist/src/services/PromptSuggestion/promptSuggestion.js +29 -29
  45. package/dist/src/services/SessionMemory/prompts.js +66 -66
  46. package/dist/src/services/api/openai.js +584 -21
  47. package/dist/src/services/limits/adapters/ollama.js +3 -3
  48. package/dist/src/services/oauth/geminiCli.js +107 -0
  49. package/dist/src/services/orchestration/execution/AgentTaskExecutor.js +5 -3
  50. package/dist/src/services/orchestration/execution/OrchestrationExecutionRuntime.js +18 -18
  51. package/dist/src/services/orchestration/global/reporting.js +2 -2
  52. package/dist/src/services/toolUseSummary/toolUseSummaryGenerator.js +9 -9
  53. package/dist/src/skills/bundled/batch.js +78 -78
  54. package/dist/src/skills/bundled/claudeApi.js +34 -34
  55. package/dist/src/skills/bundled/claudeInChrome.js +4 -4
  56. package/dist/src/skills/bundled/debug.js +36 -36
  57. package/dist/src/skills/bundled/scheduleRemoteAgents.js +151 -151
  58. package/dist/src/skills/bundled/skillify.js +132 -132
  59. package/dist/src/skills/bundled/stuck.js +53 -53
  60. package/dist/src/skills/bundled/updateConfig.js +418 -418
  61. package/dist/src/tasks/RemoteAgentTask/RemoteAgentTask.js +26 -26
  62. package/dist/src/tools/AgentTool/AgentTool.js +7 -7
  63. package/dist/src/tools/AgentTool/built-in/claudeCodeGuideAgent.js +67 -67
  64. package/dist/src/tools/AgentTool/built-in/exploreAgent.js +32 -32
  65. package/dist/src/tools/AgentTool/built-in/generalPurposeAgent.js +13 -13
  66. package/dist/src/tools/AgentTool/built-in/planAgent.js +49 -49
  67. package/dist/src/tools/AgentTool/built-in/statuslineSetup.js +129 -129
  68. package/dist/src/tools/AgentTool/built-in/verificationAgent.js +119 -119
  69. package/dist/src/tools/AgentTool/prompt.js +131 -131
  70. package/dist/src/tools/AgentTool/runAgent.js +9 -9
  71. package/dist/src/tools/BashTool/BashTool.js +10 -10
  72. package/dist/src/tools/BashTool/prompt.js +94 -94
  73. package/dist/src/tools/ConfigTool/prompt.js +29 -29
  74. package/dist/src/tools/EnterWorktreeTool/prompt.js +27 -27
  75. package/dist/src/tools/FileReadTool/prompt.js +12 -12
  76. package/dist/src/tools/PowerShellTool/prompt.js +82 -82
  77. package/dist/src/tools/RemoteTriggerTool/prompt.js +9 -9
  78. package/dist/src/tools/ScheduleCronTool/prompt.js +37 -37
  79. package/dist/src/tools/TeamCreateTool/prompt.js +110 -110
  80. package/dist/src/tools/TeamDeleteTool/prompt.js +13 -13
  81. package/dist/src/utils/advisor.js +15 -15
  82. package/dist/src/utils/api.js +2 -2
  83. package/dist/src/utils/auth.js +207 -2
  84. package/dist/src/utils/autoUpdater.js +18 -18
  85. package/dist/src/utils/bash/ShellSnapshot.js +86 -86
  86. package/dist/src/utils/bash/commands.js +61 -61
  87. package/dist/src/utils/claudeInChrome/prompt.js +53 -53
  88. package/dist/src/utils/claudeInChrome/setup.js +8 -8
  89. package/dist/src/utils/databaseMcp/server/queries.js +632 -632
  90. package/dist/src/utils/deepLink/registerProtocol.js +35 -35
  91. package/dist/src/utils/deepLink/terminalLauncher.js +12 -12
  92. package/dist/src/utils/hooks/execAgentHook.js +7 -7
  93. package/dist/src/utils/hooks/execPromptHook.js +4 -4
  94. package/dist/src/utils/hooks/skillImprovement.js +36 -36
  95. package/dist/src/utils/logoV2Utils.js +1 -1
  96. package/dist/src/utils/mcp/dateTimeParser.js +9 -9
  97. package/dist/src/utils/messages.js +191 -191
  98. package/dist/src/utils/model/model.js +18 -0
  99. package/dist/src/utils/model/modelOptions.js +51 -1
  100. package/dist/src/utils/model/modelStrings.js +5 -1
  101. package/dist/src/utils/model/modelSupportOverrides.js +3 -0
  102. package/dist/src/utils/model/providerBaseUrls.js +6 -1
  103. package/dist/src/utils/model/providerCatalog.js +64 -28
  104. package/dist/src/utils/model/providerModels.js +88 -17
  105. package/dist/src/utils/model/providerProfiles.js +8 -0
  106. package/dist/src/utils/model/providerProfilesDb.js +578 -393
  107. package/dist/src/utils/model/providerSwitch.js +12 -0
  108. package/dist/src/utils/model/providerWorkspaces.js +2 -0
  109. package/dist/src/utils/model/providers.js +65 -2
  110. package/dist/src/utils/orchestration/store/providerWorkspaceStore.js +3 -1
  111. package/dist/src/utils/orchestration/store/runStore.js +47 -47
  112. package/dist/src/utils/orchestration/store/teamStore.js +61 -61
  113. package/dist/src/utils/powershell/parser.js +253 -253
  114. package/dist/src/utils/sessionTitle.js +12 -12
  115. package/dist/src/utils/sideQuestion.js +17 -17
  116. package/dist/src/utils/status.js +1 -1
  117. package/dist/src/utils/swarm/backends/registry.js +9 -9
  118. package/dist/src/utils/telemetry/instrumentation.js +9 -9
  119. package/dist/src/utils/teleport.js +15 -15
  120. package/dist/src/utils/undercover.js +28 -28
  121. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  import { APIUserAbortError } from '@anthropic-ai/sdk/error.js';
2
- import { checkAndRefreshOpenAIOAuthTokenIfNeeded, getOpenAICompatibleAccessToken, getOpenAIAccessToken, handleOpenAIOAuth401Error, } from '../../utils/auth.js';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { checkAndRefreshOpenAIOAuthTokenIfNeeded, getGeminiGoogleAuthHeaders, getOpenAICompatibleAccessToken, getOpenAIAccessToken, handleOpenAIOAuth401Error, } from '../../utils/auth.js';
3
4
  import { getAPIProvider, } from '../../utils/model/providers.js';
4
5
  import { getConfiguredProviderBaseUrl } from '../../utils/model/providerBaseUrls.js';
5
6
  import { errorMessage } from '../../utils/errors.js';
@@ -48,6 +49,8 @@ function getOpenAICompatibleProvider() {
48
49
  return provider === 'openrouter' ||
49
50
  provider === 'ollama' ||
50
51
  provider === 'ollama-cloud' ||
52
+ provider === 'gemini-api' ||
53
+ provider === 'gemini-google' ||
51
54
  provider === 'zai'
52
55
  ? provider
53
56
  : 'openai';
@@ -57,9 +60,13 @@ function getOpenAICompatibleProviderLabel(provider = getOpenAICompatibleProvider
57
60
  case 'openrouter':
58
61
  return 'OpenRouter';
59
62
  case 'ollama':
60
- return 'Ollama Local';
63
+ return 'Ollama';
61
64
  case 'ollama-cloud':
62
65
  return 'Ollama Cloud';
66
+ case 'gemini-api':
67
+ return 'Gemini API';
68
+ case 'gemini-google':
69
+ return 'Gemini Google';
63
70
  case 'zai':
64
71
  return 'Z.AI';
65
72
  default:
@@ -88,7 +95,8 @@ function getOpenAIBaseUrl(provider = getOpenAICompatibleProvider()) {
88
95
  : `${raw}/v1`;
89
96
  }
90
97
  if (provider === 'ollama-cloud') {
91
- const raw = process.env.OLLAMA_BASE_URL ||
98
+ const raw = process.env.OLLAMA_CLOUD_BASE_URL ||
99
+ process.env.OLLAMA_BASE_URL ||
92
100
  getConfiguredProviderBaseUrl('ollama-cloud') ||
93
101
  'http://localhost:11434/v1';
94
102
  return raw.endsWith('/v1')
@@ -105,6 +113,17 @@ function getOpenAIBaseUrl(provider = getOpenAICompatibleProvider()) {
105
113
  ? raw.slice(0, -1)
106
114
  : raw;
107
115
  }
116
+ if (provider === 'gemini-api' || provider === 'gemini-google') {
117
+ const envBaseUrl = provider === 'gemini-api'
118
+ ? process.env.GEMINI_BASE_URL || process.env.GEMINI_API_BASE_URL
119
+ : process.env.GEMINI_GOOGLE_BASE_URL || process.env.GEMINI_BASE_URL;
120
+ const raw = envBaseUrl ||
121
+ getConfiguredProviderBaseUrl(provider) ||
122
+ 'https://generativelanguage.googleapis.com/v1beta/openai';
123
+ return raw.endsWith('/')
124
+ ? raw.slice(0, -1)
125
+ : raw;
126
+ }
108
127
  const raw = process.env.OPENAI_API_BASE_URL ||
109
128
  process.env.OPENAI_BASE_URL ||
110
129
  'https://api.openai.com/v1';
@@ -131,6 +150,12 @@ function getOpenAIHeaders(provider = getOpenAICompatibleProvider()) {
131
150
  'Content-Type': 'application/json',
132
151
  };
133
152
  }
153
+ async function getOpenAIRequestHeaders(provider = getOpenAICompatibleProvider()) {
154
+ if (provider === 'gemini-google') {
155
+ return getGeminiGoogleAuthHeaders();
156
+ }
157
+ return getOpenAIHeaders(provider);
158
+ }
134
159
  function isLocalOllamaProviderHost(provider, baseUrl = getOpenAIBaseUrl(provider)) {
135
160
  return ((provider === 'ollama' || provider === 'ollama-cloud') &&
136
161
  (baseUrl.includes('localhost') || baseUrl.includes('127.0.0.1')));
@@ -139,6 +164,10 @@ function getOpenAIAccessTokenOrThrow(provider = getOpenAICompatibleProvider()) {
139
164
  if (isLocalOllamaProviderHost(provider)) {
140
165
  return 'ollama';
141
166
  }
167
+ if (provider === 'gemini-google') {
168
+ return (getOpenAICompatibleAccessToken(provider) ||
169
+ 'google-application-default-credentials');
170
+ }
142
171
  const token = getOpenAICompatibleAccessToken(provider);
143
172
  if (!token) {
144
173
  throw new Error(`${getOpenAICompatibleProviderLabel(provider)} is selected but no credentials were found.`);
@@ -484,6 +513,13 @@ async function readOpenAIResponseBody(response) {
484
513
  }
485
514
  try {
486
515
  const parsed = JSON.parse(rawBody);
516
+ if (isRecord(parsed) && typeof parsed.error === 'string') {
517
+ return {
518
+ ...parsed,
519
+ error: { message: parsed.error },
520
+ raw_error_text: rawBody,
521
+ };
522
+ }
487
523
  return isRecord(parsed)
488
524
  ? { ...parsed, raw_error_text: rawBody }
489
525
  : { error: { message: rawBody }, raw_error_text: rawBody };
@@ -589,11 +625,14 @@ function parseOpenAISSEBody(rawBody) {
589
625
  };
590
626
  }
591
627
  function buildOpenAIErrorMessage(params) {
592
- const primary = params.data.error?.message?.trim() ||
628
+ const primaryRaw = params.data.error?.message?.trim() ||
593
629
  params.data.raw_error_text?.trim() ||
594
630
  `OpenAI request failed (${params.status} ${params.statusText})`;
631
+ const primary = /requires a subscription|upgrade for access/i.test(primaryRaw)
632
+ ? `El modelo ${params.model ?? 'seleccionado'} requiere una suscripcion de Ollama Cloud. Cambia a otro modelo en /model o actualiza tu plan de Ollama.`
633
+ : primaryRaw;
595
634
  const rawSnippet = params.data.raw_error_text?.trim();
596
- const compactRaw = rawSnippet && rawSnippet !== primary
635
+ const compactRaw = rawSnippet && rawSnippet !== primaryRaw && rawSnippet !== primary
597
636
  ? rawSnippet.replace(/\s+/g, ' ').slice(0, 400)
598
637
  : null;
599
638
  return compactRaw
@@ -660,7 +699,7 @@ function messageToChatMessages(message) {
660
699
  textParts.push(block.text);
661
700
  }
662
701
  }
663
- const msg = { role: 'assistant', content: textParts.join('\n') || null };
702
+ const msg = { role: 'assistant', content: textParts.join('\n') };
664
703
  if (toolCalls.length > 0)
665
704
  msg.tool_calls = toolCalls;
666
705
  return [msg];
@@ -748,6 +787,416 @@ function buildChatToolChoice(options) {
748
787
  default: return 'auto';
749
788
  }
750
789
  }
790
+ function chatContentToGeminiParts(content) {
791
+ if (typeof content === 'string') {
792
+ return content ? [{ text: content }] : [];
793
+ }
794
+ if (!Array.isArray(content)) {
795
+ return [];
796
+ }
797
+ return content
798
+ .map(part => {
799
+ if (part.type === 'text') {
800
+ return part.text ? { text: part.text } : null;
801
+ }
802
+ if (part.type === 'image_url') {
803
+ return {
804
+ text: `[Image input: ${part.image_url.url}]`,
805
+ };
806
+ }
807
+ return null;
808
+ })
809
+ .filter((part) => part !== null);
810
+ }
811
+ function buildGeminiContents(chatMessages) {
812
+ const toolNameById = new Map();
813
+ const contents = [];
814
+ for (const message of chatMessages) {
815
+ if (message.role === 'system') {
816
+ continue;
817
+ }
818
+ if (message.role === 'assistant') {
819
+ const parts = chatContentToGeminiParts(message.content);
820
+ for (const toolCall of message.tool_calls ?? []) {
821
+ toolNameById.set(toolCall.id, toolCall.function.name);
822
+ const parsedArgs = parseFunctionCallArguments(toolCall.function.arguments);
823
+ parts.push({
824
+ functionCall: {
825
+ name: toolCall.function.name,
826
+ args: isPlainObject(parsedArgs) ? parsedArgs : { raw: parsedArgs },
827
+ },
828
+ });
829
+ }
830
+ if (parts.length > 0) {
831
+ contents.push({ role: 'model', parts });
832
+ }
833
+ continue;
834
+ }
835
+ if (message.role === 'tool') {
836
+ const name = message.tool_call_id
837
+ ? toolNameById.get(message.tool_call_id) ?? 'tool'
838
+ : 'tool';
839
+ contents.push({
840
+ role: 'function',
841
+ parts: [
842
+ {
843
+ functionResponse: {
844
+ name,
845
+ response: { result: message.content ?? '' },
846
+ },
847
+ },
848
+ ],
849
+ });
850
+ continue;
851
+ }
852
+ const parts = chatContentToGeminiParts(message.content);
853
+ if (parts.length > 0) {
854
+ contents.push({ role: 'user', parts });
855
+ }
856
+ }
857
+ return contents.length > 0
858
+ ? contents
859
+ : [{ role: 'user', parts: [{ text: '' }] }];
860
+ }
861
+ function buildGeminiTools(chatTools) {
862
+ if (chatTools.length === 0) {
863
+ return undefined;
864
+ }
865
+ return [
866
+ {
867
+ functionDeclarations: chatTools.map(tool => ({
868
+ name: tool.function.name,
869
+ description: tool.function.description,
870
+ parameters: sanitizeGeminiSchema(tool.function.parameters ?? { type: 'object', properties: {} }),
871
+ })),
872
+ },
873
+ ];
874
+ }
875
+ function getGeminiEnumType(value) {
876
+ switch (typeof value) {
877
+ case 'string':
878
+ return 'string';
879
+ case 'number':
880
+ return Number.isInteger(value) ? 'integer' : 'number';
881
+ case 'boolean':
882
+ return 'boolean';
883
+ default:
884
+ return undefined;
885
+ }
886
+ }
887
+ function collapseSimpleGeminiAnyOf(schemas) {
888
+ const enumValues = [];
889
+ let nullable = false;
890
+ let enumType;
891
+ for (const item of schemas) {
892
+ if (!isRecord(item)) {
893
+ return null;
894
+ }
895
+ if (item.type === 'null') {
896
+ nullable = true;
897
+ continue;
898
+ }
899
+ const values = item.const !== undefined
900
+ ? [item.const]
901
+ : Array.isArray(item.enum)
902
+ ? item.enum
903
+ : null;
904
+ if (!values) {
905
+ return null;
906
+ }
907
+ for (const value of values) {
908
+ const valueType = getGeminiEnumType(value);
909
+ if (!valueType) {
910
+ return null;
911
+ }
912
+ if (enumType && enumType !== valueType) {
913
+ return null;
914
+ }
915
+ enumType = valueType;
916
+ enumValues.push(value);
917
+ }
918
+ }
919
+ if (!enumType || enumValues.length === 0) {
920
+ return null;
921
+ }
922
+ return {
923
+ type: enumType,
924
+ enum: Array.from(new Set(enumValues)),
925
+ ...(nullable ? { nullable: true } : {}),
926
+ };
927
+ }
928
+ function sanitizeGeminiSchema(schema) {
929
+ if (!isRecord(schema)) {
930
+ return { type: 'object', properties: {} };
931
+ }
932
+ const anyOf = Array.isArray(schema.anyOf)
933
+ ? schema.anyOf
934
+ : Array.isArray(schema.any_of)
935
+ ? schema.any_of
936
+ : null;
937
+ const oneOf = Array.isArray(schema.oneOf)
938
+ ? schema.oneOf
939
+ : Array.isArray(schema.one_of)
940
+ ? schema.one_of
941
+ : null;
942
+ if (anyOf || oneOf) {
943
+ const collapsed = collapseSimpleGeminiAnyOf(anyOf ?? oneOf ?? []);
944
+ if (collapsed) {
945
+ return collapsed;
946
+ }
947
+ return {
948
+ anyOf: (anyOf ?? oneOf ?? []).map(sanitizeGeminiSchema),
949
+ };
950
+ }
951
+ const out = {};
952
+ const typeValue = schema.type;
953
+ if (Array.isArray(typeValue)) {
954
+ const nonNullType = typeValue.find(value => value !== 'null');
955
+ if (typeof nonNullType === 'string') {
956
+ out.type = nonNullType;
957
+ }
958
+ if (typeValue.includes('null')) {
959
+ out.nullable = true;
960
+ }
961
+ }
962
+ else if (typeof typeValue === 'string') {
963
+ out.type = typeValue;
964
+ }
965
+ for (const key of [
966
+ 'title',
967
+ 'description',
968
+ 'format',
969
+ 'nullable',
970
+ 'minimum',
971
+ 'maximum',
972
+ 'minItems',
973
+ 'maxItems',
974
+ 'minLength',
975
+ 'maxLength',
976
+ 'pattern',
977
+ ]) {
978
+ if (schema[key] !== undefined) {
979
+ out[key] = schema[key];
980
+ }
981
+ }
982
+ if (Array.isArray(schema.enum)) {
983
+ out.enum = schema.enum;
984
+ }
985
+ else if (schema.const !== undefined) {
986
+ out.enum = [schema.const];
987
+ }
988
+ if (!out.type && Array.isArray(out.enum) && out.enum.length > 0) {
989
+ out.type = getGeminiEnumType(out.enum[0]);
990
+ }
991
+ if (schema.exclusiveMinimum !== undefined && out.minimum === undefined) {
992
+ out.minimum =
993
+ typeof schema.exclusiveMinimum === 'number'
994
+ ? schema.exclusiveMinimum
995
+ : schema.minimum;
996
+ }
997
+ if (schema.exclusiveMaximum !== undefined && out.maximum === undefined) {
998
+ out.maximum =
999
+ typeof schema.exclusiveMaximum === 'number'
1000
+ ? schema.exclusiveMaximum
1001
+ : schema.maximum;
1002
+ }
1003
+ if (isRecord(schema.items)) {
1004
+ out.items = sanitizeGeminiSchema(schema.items);
1005
+ }
1006
+ else if (Array.isArray(schema.items) && schema.items.length > 0) {
1007
+ out.items = sanitizeGeminiSchema(schema.items[0]);
1008
+ }
1009
+ if (isRecord(schema.properties)) {
1010
+ const properties = {};
1011
+ for (const [name, value] of Object.entries(schema.properties)) {
1012
+ properties[name] = sanitizeGeminiSchema(value);
1013
+ }
1014
+ out.properties = properties;
1015
+ }
1016
+ if (Array.isArray(schema.required)) {
1017
+ out.required = schema.required.filter((name) => typeof name === 'string');
1018
+ }
1019
+ if (!out.type) {
1020
+ out.type = out.properties ? 'object' : out.items ? 'array' : 'object';
1021
+ }
1022
+ if (out.type === 'object' && !out.properties) {
1023
+ out.properties = {};
1024
+ }
1025
+ return out;
1026
+ }
1027
+ function buildGeminiToolConfig(toolChoice) {
1028
+ if (toolChoice === 'auto') {
1029
+ return undefined;
1030
+ }
1031
+ if (toolChoice === 'none') {
1032
+ return { functionCallingConfig: { mode: 'NONE' } };
1033
+ }
1034
+ if (toolChoice === 'required') {
1035
+ return { functionCallingConfig: { mode: 'ANY' } };
1036
+ }
1037
+ return {
1038
+ functionCallingConfig: {
1039
+ mode: 'ANY',
1040
+ allowedFunctionNames: [toolChoice.function.name],
1041
+ },
1042
+ };
1043
+ }
1044
+ function parseGeminiCodeAssistResponse(response) {
1045
+ const candidates = response.response?.candidates ?? [];
1046
+ return {
1047
+ id: response.traceId,
1048
+ choices: candidates.map(candidate => {
1049
+ const contentParts = [];
1050
+ const toolCalls = [];
1051
+ for (const part of candidate.content?.parts ?? []) {
1052
+ if ('text' in part && typeof part.text === 'string') {
1053
+ contentParts.push(part.text);
1054
+ }
1055
+ if ('functionCall' in part && part.functionCall?.name) {
1056
+ toolCalls.push({
1057
+ id: `call_${randomUUID().replace(/-/g, '')}`,
1058
+ type: 'function',
1059
+ function: {
1060
+ name: part.functionCall.name,
1061
+ arguments: jsonStringify(part.functionCall.args ?? {}),
1062
+ },
1063
+ });
1064
+ }
1065
+ }
1066
+ return {
1067
+ message: {
1068
+ content: contentParts.join('') || null,
1069
+ tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
1070
+ },
1071
+ finish_reason: candidate.finishReason,
1072
+ };
1073
+ }),
1074
+ usage: {
1075
+ prompt_tokens: response.response?.usageMetadata?.promptTokenCount,
1076
+ completion_tokens: response.response?.usageMetadata?.candidatesTokenCount,
1077
+ },
1078
+ };
1079
+ }
1080
+ function getExplicitGeminiGoogleProjectId() {
1081
+ return process.env.GEMINI_GOOGLE_PROJECT_ID?.trim() || undefined;
1082
+ }
1083
+ async function postGeminiCodeAssist(method, headers, body, signal) {
1084
+ const response = await fetch(`https://cloudcode-pa.googleapis.com/v1internal:${method}`, {
1085
+ method: 'POST',
1086
+ headers,
1087
+ body: jsonStringify(body),
1088
+ signal,
1089
+ });
1090
+ const data = (await readOpenAIResponseBody(response));
1091
+ return { response, data };
1092
+ }
1093
+ function getGeminiCodeAssistMetadata(projectId) {
1094
+ return {
1095
+ ideType: 'IDE_UNSPECIFIED',
1096
+ platform: 'PLATFORM_UNSPECIFIED',
1097
+ pluginType: 'GEMINI',
1098
+ ...(projectId ? { duetProject: projectId } : {}),
1099
+ };
1100
+ }
1101
+ async function resolveGeminiCodeAssistProject(headers, signal) {
1102
+ const explicitProjectId = getExplicitGeminiGoogleProjectId();
1103
+ const loadBody = {
1104
+ ...(explicitProjectId ? { cloudaicompanionProject: explicitProjectId } : {}),
1105
+ metadata: getGeminiCodeAssistMetadata(explicitProjectId),
1106
+ };
1107
+ const { response, data } = await postGeminiCodeAssist('loadCodeAssist', headers, loadBody, signal);
1108
+ if (!response.ok) {
1109
+ return explicitProjectId;
1110
+ }
1111
+ if (typeof data.cloudaicompanionProject === 'string') {
1112
+ return data.cloudaicompanionProject;
1113
+ }
1114
+ if (explicitProjectId) {
1115
+ return explicitProjectId;
1116
+ }
1117
+ const allowedTiers = Array.isArray(data.allowedTiers)
1118
+ ? data.allowedTiers.filter(isRecord)
1119
+ : [];
1120
+ const tier = allowedTiers.find(tierInfo => tierInfo.isDefault === true) ??
1121
+ allowedTiers[0];
1122
+ const tierId = typeof tier?.id === 'string' ? tier.id : undefined;
1123
+ if (!tierId) {
1124
+ return undefined;
1125
+ }
1126
+ const { response: onboardResponse, data: onboardData } = await postGeminiCodeAssist('onboardUser', headers, {
1127
+ tierId,
1128
+ metadata: getGeminiCodeAssistMetadata(),
1129
+ }, signal);
1130
+ if (!onboardResponse.ok) {
1131
+ return undefined;
1132
+ }
1133
+ const responseData = isRecord(onboardData.response)
1134
+ ? onboardData.response
1135
+ : onboardData;
1136
+ const companionProject = isRecord(responseData.cloudaicompanionProject)
1137
+ ? responseData.cloudaicompanionProject
1138
+ : null;
1139
+ return typeof companionProject?.id === 'string'
1140
+ ? companionProject.id
1141
+ : undefined;
1142
+ }
1143
+ async function createGeminiGoogleChatCompletionResponse(params) {
1144
+ const { model, chatMessages, chatTools, toolChoice, signal, maxOutputTokens, temperature, } = params;
1145
+ const headers = await getGeminiGoogleAuthHeaders();
1146
+ const project = await resolveGeminiCodeAssistProject(headers, signal);
1147
+ const generationConfig = {};
1148
+ if (maxOutputTokens) {
1149
+ generationConfig.maxOutputTokens = maxOutputTokens;
1150
+ }
1151
+ if (temperature !== undefined) {
1152
+ generationConfig.temperature = temperature;
1153
+ }
1154
+ const request = {
1155
+ contents: buildGeminiContents(chatMessages),
1156
+ session_id: randomUUID(),
1157
+ };
1158
+ const systemText = chatMessages
1159
+ .filter(message => message.role === 'system' && typeof message.content === 'string')
1160
+ .map(message => message.content)
1161
+ .join('\n\n');
1162
+ if (systemText) {
1163
+ request.systemInstruction = {
1164
+ role: 'user',
1165
+ parts: [{ text: systemText }],
1166
+ };
1167
+ }
1168
+ const geminiTools = buildGeminiTools(chatTools);
1169
+ if (geminiTools) {
1170
+ request.tools = geminiTools;
1171
+ }
1172
+ const geminiToolConfig = buildGeminiToolConfig(toolChoice);
1173
+ if (geminiToolConfig) {
1174
+ request.toolConfig = geminiToolConfig;
1175
+ }
1176
+ if (Object.keys(generationConfig).length > 0) {
1177
+ request.generationConfig = generationConfig;
1178
+ }
1179
+ const body = {
1180
+ model,
1181
+ ...(project ? { project } : {}),
1182
+ user_prompt_id: randomUUID(),
1183
+ request,
1184
+ };
1185
+ const url = 'https://cloudcode-pa.googleapis.com/v1internal:generateContent';
1186
+ logForDebugging(`[Gemini Google] codeassist generateContent request model=${model} messages=${chatMessages.length} tools=${chatTools.length} endpoint=${url}`);
1187
+ const { response, data } = (await postGeminiCodeAssist('generateContent', headers, body, signal));
1188
+ if (!response.ok) {
1189
+ logForDebugging(`[Gemini Google] codeassist generateContent error status=${response.status} model=${model} endpoint=${url} body=${(data.error?.message ?? '').replace(/\s+/g, ' ').slice(0, 500)}`, { level: 'error' });
1190
+ throw new Error(buildOpenAIErrorMessage({
1191
+ status: response.status,
1192
+ statusText: response.statusText,
1193
+ url,
1194
+ model,
1195
+ data: data,
1196
+ }));
1197
+ }
1198
+ return parseGeminiCodeAssistResponse(data);
1199
+ }
751
1200
  async function createChatCompletionResponse({ messages, systemPrompt, tools, signal, options, }) {
752
1201
  const provider = getOpenAICompatibleProvider();
753
1202
  await maybeRefreshOpenAICompatibleAuth(provider);
@@ -779,15 +1228,23 @@ async function createChatCompletionResponse({ messages, systemPrompt, tools, sig
779
1228
  if (options.effortValue) {
780
1229
  body.reasoning_effort = options.effortValue;
781
1230
  }
782
- const sendRequest = async (bearerToken) => {
1231
+ if (provider === 'gemini-google') {
1232
+ return createGeminiGoogleChatCompletionResponse({
1233
+ model: String(options.model),
1234
+ chatMessages,
1235
+ chatTools,
1236
+ toolChoice: buildChatToolChoice(options),
1237
+ signal,
1238
+ maxOutputTokens: options.maxOutputTokensOverride,
1239
+ temperature: options.temperatureOverride,
1240
+ });
1241
+ }
1242
+ const sendRequest = async (bearerToken = getOpenAICompatibleAccessToken(provider) ?? '') => {
783
1243
  const url = `${getOpenAIBaseUrl(provider)}/chat/completions`;
784
1244
  logForDebugging(`[${getOpenAICompatibleProviderLabel(provider)}] chat.completions request model=${options.model} messages=${chatMessages.length} tools=${chatTools.length} endpoint=${url}`);
785
1245
  const response = await fetch(url, {
786
1246
  method: 'POST',
787
- headers: {
788
- Authorization: `Bearer ${bearerToken}`,
789
- 'Content-Type': 'application/json',
790
- },
1247
+ headers: await getOpenAIRequestHeaders(provider),
791
1248
  body: jsonStringify(body),
792
1249
  signal,
793
1250
  });
@@ -810,7 +1267,8 @@ async function createChatCompletionResponse({ messages, systemPrompt, tools, sig
810
1267
  }
811
1268
  return data;
812
1269
  };
813
- return sendRequest(getOpenAIAccessTokenOrThrow(provider));
1270
+ getOpenAIAccessTokenOrThrow(provider);
1271
+ return sendRequest();
814
1272
  }
815
1273
  async function buildOpenAITools(tools, options) {
816
1274
  const schemas = await Promise.all(tools.map(tool => toolToAPISchema(tool, {
@@ -981,9 +1439,121 @@ function buildSideQueryToolChoice(toolChoice) {
981
1439
  }
982
1440
  return 'auto';
983
1441
  }
1442
+ function sideQueryMessageToChatMessage(message) {
1443
+ const content = Array.isArray(message.content)
1444
+ ? message.content
1445
+ .filter((block) => block?.type === 'text' && typeof block.text === 'string')
1446
+ .map(block => block.text)
1447
+ .join('\n')
1448
+ : String(message.content ?? '');
1449
+ return {
1450
+ role: message.role,
1451
+ content,
1452
+ };
1453
+ }
1454
+ function sideQueryToolToChatTool(tool) {
1455
+ return {
1456
+ type: 'function',
1457
+ function: {
1458
+ name: tool.name,
1459
+ description: tool.description,
1460
+ parameters: tool.input_schema ?? { type: 'object', properties: {} },
1461
+ },
1462
+ };
1463
+ }
1464
+ function buildSideQueryChatToolChoice(toolChoice) {
1465
+ const responsesChoice = buildSideQueryToolChoice(toolChoice);
1466
+ if (responsesChoice === 'auto' || responsesChoice === 'none') {
1467
+ return responsesChoice;
1468
+ }
1469
+ if (responsesChoice === 'required') {
1470
+ return 'required';
1471
+ }
1472
+ return {
1473
+ type: 'function',
1474
+ function: { name: responsesChoice.name },
1475
+ };
1476
+ }
984
1477
  export async function queryOpenAISideQuery({ model, systemPrompt, messages, tools, toolChoice, outputFormat, signal, maxOutputTokens, temperature, }) {
985
1478
  const provider = getOpenAICompatibleProvider();
986
1479
  await maybeRefreshOpenAICompatibleAuth(provider);
1480
+ const baseUrl = getOpenAIBaseUrl(provider);
1481
+ const bearerToken = getOpenAICompatibleAccessToken(provider);
1482
+ const isCodexOAuth = provider === 'openai' && bearerToken && isJwtToken(bearerToken);
1483
+ if (!isCodexOAuth) {
1484
+ const chatMessages = [
1485
+ ...(systemPrompt
1486
+ ? [{ role: 'system', content: systemPrompt }]
1487
+ : []),
1488
+ ...messages.map(sideQueryMessageToChatMessage),
1489
+ ];
1490
+ const body = {
1491
+ model,
1492
+ messages: chatMessages,
1493
+ stream: false,
1494
+ };
1495
+ const chatTools = tools?.map(sideQueryToolToChatTool) ?? [];
1496
+ const chatToolChoice = buildSideQueryChatToolChoice(toolChoice);
1497
+ if (chatTools.length > 0) {
1498
+ body.tools = chatTools;
1499
+ body.tool_choice = chatToolChoice;
1500
+ }
1501
+ if (outputFormat?.type === 'json_schema') {
1502
+ body.response_format = {
1503
+ type: 'json_schema',
1504
+ json_schema: {
1505
+ name: 'side_query_output',
1506
+ strict: true,
1507
+ schema: outputFormat.schema,
1508
+ },
1509
+ };
1510
+ }
1511
+ if (maxOutputTokens) {
1512
+ body.max_tokens = maxOutputTokens;
1513
+ }
1514
+ if (temperature !== undefined) {
1515
+ body.temperature = temperature;
1516
+ }
1517
+ if (provider === 'gemini-google') {
1518
+ const data = await createGeminiGoogleChatCompletionResponse({
1519
+ model,
1520
+ chatMessages,
1521
+ chatTools,
1522
+ toolChoice: chatToolChoice,
1523
+ signal,
1524
+ maxOutputTokens,
1525
+ temperature,
1526
+ });
1527
+ return {
1528
+ id: data.id,
1529
+ content: parseChatCompletionResponse(data).content,
1530
+ usage: getChatUsage(data),
1531
+ };
1532
+ }
1533
+ const url = `${baseUrl}/chat/completions`;
1534
+ const response = await fetch(url, {
1535
+ method: 'POST',
1536
+ headers: await getOpenAIRequestHeaders(provider),
1537
+ body: jsonStringify(body),
1538
+ signal,
1539
+ });
1540
+ const data = await readOpenAIResponseBody(response);
1541
+ if (!response.ok) {
1542
+ logForDebugging(`[${getOpenAICompatibleProviderLabel(provider)}] sideQuery chat.completions error status=${response.status} model=${model} endpoint=${url} body=${(data.error?.message ?? '').replace(/\s+/g, ' ').slice(0, 500)}`, { level: 'error' });
1543
+ throw new Error(buildOpenAIErrorMessage({
1544
+ status: response.status,
1545
+ statusText: response.statusText,
1546
+ url,
1547
+ model,
1548
+ data,
1549
+ }));
1550
+ }
1551
+ return {
1552
+ id: data.id,
1553
+ content: parseChatCompletionResponse(data).content,
1554
+ usage: getChatUsage(data),
1555
+ };
1556
+ }
987
1557
  const input = messages.flatMap(sideQueryMessageToOpenAIInput);
988
1558
  const body = {
989
1559
  model,
@@ -1012,17 +1582,10 @@ export async function queryOpenAISideQuery({ model, systemPrompt, messages, tool
1012
1582
  if (temperature !== undefined) {
1013
1583
  body.temperature = temperature;
1014
1584
  }
1015
- const baseUrl = getOpenAIBaseUrl(provider);
1016
- const bearerToken = getOpenAICompatibleAccessToken(provider);
1017
- const isCodexOAuth = provider === 'openai' && bearerToken && isJwtToken(bearerToken);
1018
- const endpoint = isCodexOAuth ? 'responses' : 'chat/completions';
1019
- const url = `${baseUrl}/${endpoint}`;
1020
- // Si no es un proveedor que soporte la Responses API, deberíamos ideally transformar el body.
1021
- // Pero para SideQuery a menudo el body ya es compatible o se ignora si falla.
1022
- // Por ahora arreglamos el 404 cambiando el endpoint.
1585
+ const url = `${baseUrl}/responses`;
1023
1586
  const response = await fetch(url, {
1024
1587
  method: 'POST',
1025
- headers: getOpenAIHeaders(provider),
1588
+ headers: await getOpenAIRequestHeaders(provider),
1026
1589
  body: jsonStringify(body),
1027
1590
  signal,
1028
1591
  });