@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.
- package/README.md +68 -68
- package/cli.js +8515 -8515
- package/context-bootstrap.js +27 -27
- package/dist/src/bootstrap/state.js +3 -0
- package/dist/src/bridge/bridgeMain.js +40 -40
- package/dist/src/cli/print.js +12 -12
- package/dist/src/commands/agent/agent.js +8 -0
- package/dist/src/commands/commit-push-pr.js +55 -55
- package/dist/src/commands/createMovedToPluginCommand.js +9 -9
- package/dist/src/commands/init-verifiers.js +238 -238
- package/dist/src/commands/init.js +216 -216
- package/dist/src/commands/install.js +2 -2
- package/dist/src/commands/login/login.js +24 -10
- package/dist/src/commands/orchestrate/index.js +1 -1
- package/dist/src/commands/orchestrate/orchestrate.js +110 -24
- package/dist/src/commands/profile/profile.js +15 -1
- package/dist/src/commands/provider/index.js +1 -1
- package/dist/src/commands/provider/provider.js +34 -1
- package/dist/src/commands/review.js +22 -22
- package/dist/src/commands/run/index.js +2 -2
- package/dist/src/commands/run/run.js +63 -61
- package/dist/src/commands/team/index.js +1 -1
- package/dist/src/commands/team/team.js +84 -76
- package/dist/src/commands/team-auto/teamAuto.js +89 -29
- package/dist/src/commands/terminalSetup/terminalSetup.js +24 -24
- package/dist/src/commands/usage/index.js +7 -0
- package/dist/src/commands/usage/usage.js +5 -0
- package/dist/src/commands/workspace/workspace.js +39 -31
- package/dist/src/commands.js +0 -2
- package/dist/src/components/ConsoleOAuthFlow.js +92 -14
- package/dist/src/components/ModelPicker.js +2 -0
- package/dist/src/components/agents/generateAgent.js +92 -92
- package/dist/src/components/grove/Grove.js +10 -10
- package/dist/src/components/permissions/AskUserQuestionPermissionRequest/AskUserQuestionPermissionRequest.js +8 -8
- package/dist/src/constants/geminiOAuth.js +13 -0
- package/dist/src/constants/github-app.js +134 -134
- package/dist/src/constants/prompts.js +123 -123
- package/dist/src/coordinator/coordinatorMode.js +252 -252
- package/dist/src/hooks/useTypeahead.js +7 -7
- package/dist/src/ink/reconciler.js +7 -7
- package/dist/src/main.js +5 -5
- package/dist/src/memdir/findRelevantMemories.js +6 -6
- package/dist/src/services/MagicDocs/prompts.js +56 -56
- package/dist/src/services/PromptSuggestion/promptSuggestion.js +29 -29
- package/dist/src/services/SessionMemory/prompts.js +66 -66
- package/dist/src/services/api/openai.js +584 -21
- package/dist/src/services/limits/adapters/ollama.js +3 -3
- package/dist/src/services/oauth/geminiCli.js +107 -0
- package/dist/src/services/orchestration/execution/AgentTaskExecutor.js +5 -3
- package/dist/src/services/orchestration/execution/OrchestrationExecutionRuntime.js +18 -18
- package/dist/src/services/orchestration/global/reporting.js +2 -2
- package/dist/src/services/toolUseSummary/toolUseSummaryGenerator.js +9 -9
- package/dist/src/skills/bundled/batch.js +78 -78
- package/dist/src/skills/bundled/claudeApi.js +34 -34
- package/dist/src/skills/bundled/claudeInChrome.js +4 -4
- package/dist/src/skills/bundled/debug.js +36 -36
- package/dist/src/skills/bundled/scheduleRemoteAgents.js +151 -151
- package/dist/src/skills/bundled/skillify.js +132 -132
- package/dist/src/skills/bundled/stuck.js +53 -53
- package/dist/src/skills/bundled/updateConfig.js +418 -418
- package/dist/src/tasks/RemoteAgentTask/RemoteAgentTask.js +26 -26
- package/dist/src/tools/AgentTool/AgentTool.js +7 -7
- package/dist/src/tools/AgentTool/built-in/claudeCodeGuideAgent.js +67 -67
- package/dist/src/tools/AgentTool/built-in/exploreAgent.js +32 -32
- package/dist/src/tools/AgentTool/built-in/generalPurposeAgent.js +13 -13
- package/dist/src/tools/AgentTool/built-in/planAgent.js +49 -49
- package/dist/src/tools/AgentTool/built-in/statuslineSetup.js +129 -129
- package/dist/src/tools/AgentTool/built-in/verificationAgent.js +119 -119
- package/dist/src/tools/AgentTool/prompt.js +131 -131
- package/dist/src/tools/AgentTool/runAgent.js +9 -9
- package/dist/src/tools/BashTool/BashTool.js +10 -10
- package/dist/src/tools/BashTool/prompt.js +94 -94
- package/dist/src/tools/ConfigTool/prompt.js +29 -29
- package/dist/src/tools/EnterWorktreeTool/prompt.js +27 -27
- package/dist/src/tools/FileReadTool/prompt.js +12 -12
- package/dist/src/tools/PowerShellTool/prompt.js +82 -82
- package/dist/src/tools/RemoteTriggerTool/prompt.js +9 -9
- package/dist/src/tools/ScheduleCronTool/prompt.js +37 -37
- package/dist/src/tools/TeamCreateTool/prompt.js +110 -110
- package/dist/src/tools/TeamDeleteTool/prompt.js +13 -13
- package/dist/src/utils/advisor.js +15 -15
- package/dist/src/utils/api.js +2 -2
- package/dist/src/utils/auth.js +207 -2
- package/dist/src/utils/autoUpdater.js +18 -18
- package/dist/src/utils/bash/ShellSnapshot.js +86 -86
- package/dist/src/utils/bash/commands.js +61 -61
- package/dist/src/utils/claudeInChrome/prompt.js +53 -53
- package/dist/src/utils/claudeInChrome/setup.js +8 -8
- package/dist/src/utils/databaseMcp/server/queries.js +632 -632
- package/dist/src/utils/deepLink/registerProtocol.js +35 -35
- package/dist/src/utils/deepLink/terminalLauncher.js +12 -12
- package/dist/src/utils/hooks/execAgentHook.js +7 -7
- package/dist/src/utils/hooks/execPromptHook.js +4 -4
- package/dist/src/utils/hooks/skillImprovement.js +36 -36
- package/dist/src/utils/logoV2Utils.js +1 -1
- package/dist/src/utils/mcp/dateTimeParser.js +9 -9
- package/dist/src/utils/messages.js +191 -191
- package/dist/src/utils/model/model.js +18 -0
- package/dist/src/utils/model/modelOptions.js +51 -1
- package/dist/src/utils/model/modelStrings.js +5 -1
- package/dist/src/utils/model/modelSupportOverrides.js +3 -0
- package/dist/src/utils/model/providerBaseUrls.js +6 -1
- package/dist/src/utils/model/providerCatalog.js +64 -28
- package/dist/src/utils/model/providerModels.js +88 -17
- package/dist/src/utils/model/providerProfiles.js +8 -0
- package/dist/src/utils/model/providerProfilesDb.js +578 -393
- package/dist/src/utils/model/providerSwitch.js +12 -0
- package/dist/src/utils/model/providerWorkspaces.js +2 -0
- package/dist/src/utils/model/providers.js +65 -2
- package/dist/src/utils/orchestration/store/providerWorkspaceStore.js +3 -1
- package/dist/src/utils/orchestration/store/runStore.js +47 -47
- package/dist/src/utils/orchestration/store/teamStore.js +61 -61
- package/dist/src/utils/powershell/parser.js +253 -253
- package/dist/src/utils/sessionTitle.js +12 -12
- package/dist/src/utils/sideQuestion.js +17 -17
- package/dist/src/utils/status.js +1 -1
- package/dist/src/utils/swarm/backends/registry.js +9 -9
- package/dist/src/utils/telemetry/instrumentation.js +9 -9
- package/dist/src/utils/teleport.js +15 -15
- package/dist/src/utils/undercover.js +28 -28
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { APIUserAbortError } from '@anthropic-ai/sdk/error.js';
|
|
2
|
-
import {
|
|
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
|
|
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.
|
|
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
|
|
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')
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
1588
|
+
headers: await getOpenAIRequestHeaders(provider),
|
|
1026
1589
|
body: jsonStringify(body),
|
|
1027
1590
|
signal,
|
|
1028
1591
|
});
|