@vybestack/llxprt-code-core 0.1.12 → 0.1.13
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/LICENSE +202 -0
- package/README.md +256 -0
- package/dist/src/adapters/IStreamAdapter.d.ts +18 -0
- package/dist/src/adapters/IStreamAdapter.js +7 -0
- package/dist/src/adapters/IStreamAdapter.js.map +1 -0
- package/dist/src/code_assist/oauth2.d.ts +1 -1
- package/dist/src/code_assist/oauth2.js +51 -29
- package/dist/src/code_assist/oauth2.js.map +1 -1
- package/dist/src/code_assist/oauth2.test.js +36 -7
- package/dist/src/code_assist/oauth2.test.js.map +1 -1
- package/dist/src/code_assist/server.test.js +10 -2
- package/dist/src/code_assist/server.test.js.map +1 -1
- package/dist/src/config/config.d.ts +28 -5
- package/dist/src/config/config.js +29 -4
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +2 -3
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/core/client.d.ts +4 -2
- package/dist/src/core/client.js +68 -7
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/client.test.js +8 -0
- package/dist/src/core/client.test.js.map +1 -1
- package/dist/src/core/contentGenerator.d.ts +3 -2
- package/dist/src/core/contentGenerator.js +6 -8
- package/dist/src/core/contentGenerator.js.map +1 -1
- package/dist/src/core/contentGenerator.test.js +12 -5
- package/dist/src/core/contentGenerator.test.js.map +1 -1
- package/dist/src/core/coreToolScheduler.test.js +4 -2
- package/dist/src/core/coreToolScheduler.test.js.map +1 -1
- package/dist/src/core/geminiChat.js +50 -3
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/modelCheck.d.ts +1 -1
- package/dist/src/core/modelCheck.js +10 -3
- package/dist/src/core/modelCheck.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.test.js +3 -0
- package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
- package/dist/src/core/prompts.d.ts +1 -1
- package/dist/src/core/prompts.js +14 -2
- package/dist/src/core/prompts.js.map +1 -1
- package/dist/src/core/turn.js +6 -0
- package/dist/src/core/turn.js.map +1 -1
- package/dist/src/index.d.ts +29 -1
- package/dist/src/index.js +33 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/oauth-provider.d.ts +142 -0
- package/dist/src/mcp/oauth-provider.js +446 -0
- package/dist/src/mcp/oauth-provider.js.map +1 -0
- package/dist/src/mcp/oauth-provider.test.js +520 -0
- package/dist/src/mcp/oauth-provider.test.js.map +1 -0
- package/dist/src/mcp/oauth-token-storage.d.ts +81 -0
- package/dist/src/mcp/oauth-token-storage.js +149 -0
- package/dist/src/mcp/oauth-token-storage.js.map +1 -0
- package/dist/src/mcp/oauth-token-storage.test.d.ts +6 -0
- package/dist/src/mcp/oauth-token-storage.test.js +205 -0
- package/dist/src/mcp/oauth-token-storage.test.js.map +1 -0
- package/dist/src/mcp/oauth-utils.d.ts +109 -0
- package/dist/src/mcp/oauth-utils.js +183 -0
- package/dist/src/mcp/oauth-utils.js.map +1 -0
- package/dist/src/mcp/oauth-utils.test.d.ts +6 -0
- package/dist/src/mcp/oauth-utils.test.js +144 -0
- package/dist/src/mcp/oauth-utils.test.js.map +1 -0
- package/dist/src/parsers/TextToolCallParser.d.ts +35 -0
- package/dist/src/parsers/TextToolCallParser.js +248 -0
- package/dist/src/parsers/TextToolCallParser.js.map +1 -0
- package/dist/src/parsers/TextToolCallParser.test.d.ts +1 -0
- package/dist/src/parsers/TextToolCallParser.test.js +225 -0
- package/dist/src/parsers/TextToolCallParser.test.js.map +1 -0
- package/dist/src/providers/ContentGeneratorRole.d.ts +14 -0
- package/dist/src/providers/ContentGeneratorRole.js +16 -0
- package/dist/src/providers/ContentGeneratorRole.js.map +1 -0
- package/dist/src/providers/IMessage.d.ts +38 -0
- package/dist/src/providers/IMessage.js +17 -0
- package/dist/src/providers/IMessage.js.map +1 -0
- package/dist/src/providers/IModel.d.ts +23 -0
- package/dist/src/providers/IModel.js +17 -0
- package/dist/src/providers/IModel.js.map +1 -0
- package/dist/src/providers/IProvider.d.ts +36 -0
- package/dist/src/providers/IProvider.js +17 -0
- package/dist/src/providers/IProvider.js.map +1 -0
- package/dist/src/providers/IProviderConfig.d.ts +31 -0
- package/dist/src/providers/IProviderConfig.js +7 -0
- package/dist/src/providers/IProviderConfig.js.map +1 -0
- package/dist/src/providers/IProviderManager.d.ts +53 -0
- package/dist/src/providers/IProviderManager.js +7 -0
- package/dist/src/providers/IProviderManager.js.map +1 -0
- package/dist/src/providers/ITool.d.ts +23 -0
- package/dist/src/providers/ITool.js +17 -0
- package/dist/src/providers/ITool.js.map +1 -0
- package/dist/src/providers/ProviderContentGenerator.d.ts +1 -1
- package/dist/src/providers/ProviderManager.d.ts +24 -0
- package/dist/src/providers/ProviderManager.gemini-switch.test.d.ts +6 -0
- package/dist/src/providers/ProviderManager.gemini-switch.test.js +57 -0
- package/dist/src/providers/ProviderManager.gemini-switch.test.js.map +1 -0
- package/dist/src/providers/ProviderManager.js +116 -0
- package/dist/src/providers/ProviderManager.js.map +1 -0
- package/dist/src/providers/ProviderManager.test.d.ts +6 -0
- package/dist/src/providers/ProviderManager.test.js +284 -0
- package/dist/src/providers/ProviderManager.test.js.map +1 -0
- package/dist/src/providers/adapters/GeminiCompatibleWrapper.d.ts +2 -1
- package/dist/src/providers/adapters/GeminiCompatibleWrapper.js +15 -2
- package/dist/src/providers/adapters/GeminiCompatibleWrapper.js.map +1 -1
- package/dist/src/providers/adapters/GeminiCompatibleWrapper.test.js +20 -0
- package/dist/src/providers/adapters/GeminiCompatibleWrapper.test.js.map +1 -1
- package/dist/src/providers/anthropic/AnthropicProvider.d.ts +57 -0
- package/dist/src/providers/anthropic/AnthropicProvider.js +490 -0
- package/dist/src/providers/anthropic/AnthropicProvider.js.map +1 -0
- package/dist/src/providers/anthropic/AnthropicProvider.test.d.ts +1 -0
- package/dist/src/providers/anthropic/AnthropicProvider.test.js +486 -0
- package/dist/src/providers/anthropic/AnthropicProvider.test.js.map +1 -0
- package/dist/src/providers/errors.d.ts +13 -0
- package/dist/src/providers/errors.js +19 -0
- package/dist/src/providers/errors.js.map +1 -0
- package/dist/src/providers/gemini/GeminiProvider.d.ts +97 -0
- package/dist/src/providers/gemini/GeminiProvider.integration.test.d.ts +6 -0
- package/dist/src/providers/gemini/GeminiProvider.integration.test.js +90 -0
- package/dist/src/providers/gemini/GeminiProvider.integration.test.js.map +1 -0
- package/dist/src/providers/gemini/GeminiProvider.js +937 -0
- package/dist/src/providers/gemini/GeminiProvider.js.map +1 -0
- package/dist/src/providers/gemini/GeminiProvider.test.d.ts +6 -0
- package/dist/src/providers/gemini/GeminiProvider.test.js +136 -0
- package/dist/src/providers/gemini/GeminiProvider.test.js.map +1 -0
- package/dist/src/providers/integration/TEST_INSTRUCTIONS.md +197 -0
- package/dist/src/providers/integration/multi-provider.integration.test.d.ts +6 -0
- package/dist/src/providers/integration/multi-provider.integration.test.js +292 -0
- package/dist/src/providers/integration/multi-provider.integration.test.js.map +1 -0
- package/dist/src/providers/openai/ConversationCache.accumTokens.test.d.ts +1 -0
- package/dist/src/providers/openai/ConversationCache.accumTokens.test.js +97 -0
- package/dist/src/providers/openai/ConversationCache.accumTokens.test.js.map +1 -0
- package/dist/src/providers/openai/ConversationCache.d.ts +20 -0
- package/dist/src/providers/openai/ConversationCache.js +109 -0
- package/dist/src/providers/openai/ConversationCache.js.map +1 -0
- package/dist/src/providers/openai/ConversationCache.test.d.ts +1 -0
- package/dist/src/providers/openai/ConversationCache.test.js +113 -0
- package/dist/src/providers/openai/ConversationCache.test.js.map +1 -0
- package/dist/src/providers/openai/IChatGenerateParams.d.ts +11 -0
- package/dist/src/providers/openai/IChatGenerateParams.js +2 -0
- package/dist/src/providers/openai/IChatGenerateParams.js.map +1 -0
- package/dist/src/providers/openai/OpenAIProvider.callResponses.stateless.test.d.ts +1 -0
- package/dist/src/providers/openai/OpenAIProvider.callResponses.stateless.test.js +189 -0
- package/dist/src/providers/openai/OpenAIProvider.callResponses.stateless.test.js.map +1 -0
- package/dist/src/providers/openai/OpenAIProvider.d.ts +80 -0
- package/dist/src/providers/openai/OpenAIProvider.integration.test.d.ts +6 -0
- package/dist/src/providers/openai/OpenAIProvider.integration.test.js +125 -0
- package/dist/src/providers/openai/OpenAIProvider.integration.test.js.map +1 -0
- package/dist/src/providers/openai/OpenAIProvider.js +518 -0
- package/dist/src/providers/openai/OpenAIProvider.js.map +1 -0
- package/dist/src/providers/openai/OpenAIProvider.responses.test.d.ts +1 -0
- package/dist/src/providers/openai/OpenAIProvider.responses.test.js +326 -0
- package/dist/src/providers/openai/OpenAIProvider.responses.test.js.map +1 -0
- package/dist/src/providers/openai/OpenAIProvider.responsesIntegration.test.d.ts +1 -0
- package/dist/src/providers/openai/OpenAIProvider.responsesIntegration.test.js +213 -0
- package/dist/src/providers/openai/OpenAIProvider.responsesIntegration.test.js.map +1 -0
- package/dist/src/providers/openai/OpenAIProvider.shouldUseResponses.test.d.ts +1 -0
- package/dist/src/providers/openai/OpenAIProvider.shouldUseResponses.test.js +58 -0
- package/dist/src/providers/openai/OpenAIProvider.shouldUseResponses.test.js.map +1 -0
- package/dist/src/providers/openai/OpenAIProvider.stateful.integration.test.d.ts +6 -0
- package/dist/src/providers/openai/OpenAIProvider.stateful.integration.test.js +105 -0
- package/dist/src/providers/openai/OpenAIProvider.stateful.integration.test.js.map +1 -0
- package/dist/src/providers/openai/OpenAIProvider.switch.test.d.ts +1 -0
- package/dist/src/providers/openai/OpenAIProvider.switch.test.js +256 -0
- package/dist/src/providers/openai/OpenAIProvider.switch.test.js.map +1 -0
- package/dist/src/providers/openai/OpenAIProvider.test.d.ts +16 -0
- package/dist/src/providers/openai/OpenAIProvider.test.js +214 -0
- package/dist/src/providers/openai/OpenAIProvider.test.js.map +1 -0
- package/dist/src/providers/openai/RESPONSES_API_MODELS.d.ts +2 -0
- package/dist/src/providers/openai/RESPONSES_API_MODELS.js +14 -0
- package/dist/src/providers/openai/RESPONSES_API_MODELS.js.map +1 -0
- package/dist/src/providers/openai/ResponsesContextTrim.integration.test.d.ts +1 -0
- package/dist/src/providers/openai/ResponsesContextTrim.integration.test.js +210 -0
- package/dist/src/providers/openai/ResponsesContextTrim.integration.test.js.map +1 -0
- package/dist/src/providers/openai/__tests__/formatArrayResponse.test.d.ts +1 -0
- package/dist/src/providers/openai/__tests__/formatArrayResponse.test.js +65 -0
- package/dist/src/providers/openai/__tests__/formatArrayResponse.test.js.map +1 -0
- package/dist/src/providers/openai/buildResponsesRequest.d.ts +73 -0
- package/dist/src/providers/openai/buildResponsesRequest.js +165 -0
- package/dist/src/providers/openai/buildResponsesRequest.js.map +1 -0
- package/dist/src/providers/openai/buildResponsesRequest.stripToolCalls.test.d.ts +1 -0
- package/dist/src/providers/openai/buildResponsesRequest.stripToolCalls.test.js +129 -0
- package/dist/src/providers/openai/buildResponsesRequest.stripToolCalls.test.js.map +1 -0
- package/dist/src/providers/openai/buildResponsesRequest.test.d.ts +1 -0
- package/dist/src/providers/openai/buildResponsesRequest.test.js +406 -0
- package/dist/src/providers/openai/buildResponsesRequest.test.js.map +1 -0
- package/dist/src/providers/openai/buildResponsesRequest.undefined.test.d.ts +1 -0
- package/dist/src/providers/openai/buildResponsesRequest.undefined.test.js +50 -0
- package/dist/src/providers/openai/buildResponsesRequest.undefined.test.js.map +1 -0
- package/dist/src/providers/openai/docs/accessing-provider-info.md +172 -0
- package/dist/src/providers/openai/docs/params-mapping.md +91 -0
- package/dist/src/providers/openai/docs/responses-api-tool-calls.md +96 -0
- package/dist/src/providers/openai/estimateRemoteTokens.d.ts +26 -0
- package/dist/src/providers/openai/estimateRemoteTokens.js +75 -0
- package/dist/src/providers/openai/estimateRemoteTokens.js.map +1 -0
- package/dist/src/providers/openai/estimateRemoteTokens.test.d.ts +1 -0
- package/dist/src/providers/openai/estimateRemoteTokens.test.js +125 -0
- package/dist/src/providers/openai/estimateRemoteTokens.test.js.map +1 -0
- package/dist/src/providers/openai/getOpenAIProviderInfo.d.ts +46 -0
- package/dist/src/providers/openai/getOpenAIProviderInfo.js +75 -0
- package/dist/src/providers/openai/getOpenAIProviderInfo.js.map +1 -0
- package/dist/src/providers/openai/parseResponsesStream.d.ts +3 -0
- package/dist/src/providers/openai/parseResponsesStream.js +462 -0
- package/dist/src/providers/openai/parseResponsesStream.js.map +1 -0
- package/dist/src/providers/openai/parseResponsesStream.responsesToolCalls.test.d.ts +1 -0
- package/dist/src/providers/openai/parseResponsesStream.responsesToolCalls.test.js +192 -0
- package/dist/src/providers/openai/parseResponsesStream.responsesToolCalls.test.js.map +1 -0
- package/dist/src/providers/openai/parseResponsesStream.test.d.ts +1 -0
- package/dist/src/providers/openai/parseResponsesStream.test.js +151 -0
- package/dist/src/providers/openai/parseResponsesStream.test.js.map +1 -0
- package/dist/src/providers/tokenizers/AnthropicTokenizer.d.ts +19 -0
- package/dist/src/providers/tokenizers/AnthropicTokenizer.js +37 -0
- package/dist/src/providers/tokenizers/AnthropicTokenizer.js.map +1 -0
- package/dist/src/providers/tokenizers/ITokenizer.d.ts +18 -0
- package/dist/src/providers/tokenizers/ITokenizer.js +17 -0
- package/dist/src/providers/tokenizers/ITokenizer.js.map +1 -0
- package/dist/src/providers/tokenizers/OpenAITokenizer.d.ts +24 -0
- package/dist/src/providers/tokenizers/OpenAITokenizer.js +56 -0
- package/dist/src/providers/tokenizers/OpenAITokenizer.js.map +1 -0
- package/dist/src/providers/types/IProviderConfig.d.ts +102 -0
- package/dist/src/providers/types/IProviderConfig.js +17 -0
- package/dist/src/providers/types/IProviderConfig.js.map +1 -0
- package/dist/src/providers/types.d.ts +4 -69
- package/dist/src/services/ideContext.d.ts +2 -0
- package/dist/src/services/ideContext.js +8 -0
- package/dist/src/services/ideContext.js.map +1 -1
- package/dist/src/services/ideContext.test.js +10 -0
- package/dist/src/services/ideContext.test.js.map +1 -1
- package/dist/src/services/loopDetectionService.d.ts +17 -1
- package/dist/src/services/loopDetectionService.js +117 -2
- package/dist/src/services/loopDetectionService.js.map +1 -1
- package/dist/src/services/loopDetectionService.test.js +109 -2
- package/dist/src/services/loopDetectionService.test.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +2 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +40 -2
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +2 -0
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +4 -0
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
- package/dist/src/telemetry/sdk.js +0 -2
- package/dist/src/telemetry/sdk.js.map +1 -1
- package/dist/src/telemetry/types.d.ts +2 -1
- package/dist/src/telemetry/types.js +1 -0
- package/dist/src/telemetry/types.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.d.ts +1 -0
- package/dist/src/telemetry/uiTelemetry.js +7 -0
- package/dist/src/telemetry/uiTelemetry.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.test.js +92 -0
- package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
- package/dist/src/tools/IToolFormatter.d.ts +40 -0
- package/dist/src/tools/IToolFormatter.js +17 -0
- package/dist/src/tools/IToolFormatter.js.map +1 -0
- package/dist/src/tools/ToolFormatter.d.ts +45 -0
- package/dist/src/tools/ToolFormatter.js +216 -0
- package/dist/src/tools/ToolFormatter.js.map +1 -0
- package/dist/src/tools/ToolFormatter.test.d.ts +16 -0
- package/dist/src/tools/ToolFormatter.test.js +349 -0
- package/dist/src/tools/ToolFormatter.test.js.map +1 -0
- package/dist/src/tools/ToolFormatter.toResponsesTool.test.d.ts +1 -0
- package/dist/src/tools/ToolFormatter.toResponsesTool.test.js +241 -0
- package/dist/src/tools/ToolFormatter.toResponsesTool.test.js.map +1 -0
- package/dist/src/tools/edit.d.ts +7 -1
- package/dist/src/tools/edit.js +19 -7
- package/dist/src/tools/edit.js.map +1 -1
- package/dist/src/tools/glob.js +2 -2
- package/dist/src/tools/glob.js.map +1 -1
- package/dist/src/tools/grep.js +2 -2
- package/dist/src/tools/grep.js.map +1 -1
- package/dist/src/tools/ls.js +2 -2
- package/dist/src/tools/ls.js.map +1 -1
- package/dist/src/tools/mcp-client.d.ts +0 -2
- package/dist/src/tools/mcp-client.js +8 -20
- package/dist/src/tools/mcp-client.js.map +1 -1
- package/dist/src/tools/mcp-client.test.js +1 -72
- package/dist/src/tools/mcp-client.test.js.map +1 -1
- package/dist/src/tools/mcp-tool.d.ts +11 -5
- package/dist/src/tools/mcp-tool.js +33 -9
- package/dist/src/tools/mcp-tool.js.map +1 -1
- package/dist/src/tools/mcp-tool.test.js +40 -24
- package/dist/src/tools/mcp-tool.test.js.map +1 -1
- package/dist/src/tools/memoryTool.js +2 -2
- package/dist/src/tools/memoryTool.js.map +1 -1
- package/dist/src/tools/read-file.d.ts +2 -1
- package/dist/src/tools/read-file.js +5 -2
- package/dist/src/tools/read-file.js.map +1 -1
- package/dist/src/tools/read-many-files.js +2 -2
- package/dist/src/tools/read-many-files.js.map +1 -1
- package/dist/src/tools/shell.js +2 -2
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/todo-read.js +2 -2
- package/dist/src/tools/todo-read.js.map +1 -1
- package/dist/src/tools/todo-write.js +2 -2
- package/dist/src/tools/todo-write.js.map +1 -1
- package/dist/src/tools/tool-registry.d.ts +0 -1
- package/dist/src/tools/tool-registry.js +11 -8
- package/dist/src/tools/tool-registry.js.map +1 -1
- package/dist/src/tools/tool-registry.test.js +36 -10
- package/dist/src/tools/tool-registry.test.js.map +1 -1
- package/dist/src/tools/tools.d.ts +37 -2
- package/dist/src/tools/tools.js +25 -2
- package/dist/src/tools/tools.js.map +1 -1
- package/dist/src/tools/web-fetch.integration.test.d.ts +6 -0
- package/dist/src/tools/web-fetch.integration.test.js +532 -0
- package/dist/src/tools/web-fetch.integration.test.js.map +1 -0
- package/dist/src/tools/web-fetch.js +57 -50
- package/dist/src/tools/web-fetch.js.map +1 -1
- package/dist/src/tools/web-search.js +34 -6
- package/dist/src/tools/web-search.js.map +1 -1
- package/dist/src/tools/web-search.test.d.ts +6 -0
- package/dist/src/tools/web-search.test.js +229 -0
- package/dist/src/tools/web-search.test.js.map +1 -0
- package/dist/src/tools/write-file.js +12 -5
- package/dist/src/tools/write-file.js.map +1 -1
- package/dist/src/utils/browser.d.ts +13 -0
- package/dist/src/utils/browser.js +49 -0
- package/dist/src/utils/browser.js.map +1 -0
- package/dist/src/utils/errors.js +4 -4
- package/dist/src/utils/errors.js.map +1 -1
- package/dist/src/utils/memoryDiscovery.js +5 -1
- package/dist/src/utils/memoryDiscovery.js.map +1 -1
- package/dist/src/utils/quotaErrorDetection.js +0 -2
- package/dist/src/utils/quotaErrorDetection.js.map +1 -1
- package/dist/src/utils/retry.d.ts +6 -0
- package/dist/src/utils/retry.js +1 -1
- package/dist/src/utils/retry.js.map +1 -1
- package/dist/src/utils/user_account.js +6 -1
- package/dist/src/utils/user_account.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -3
- package/dist/src/tools/web-fetch.test.js +0 -70
- package/dist/src/tools/web-fetch.test.js.map +0 -1
- package/dist/vybestack-llxprt-code-core-0.1.12.tgz +0 -0
- /package/dist/src/{tools/web-fetch.test.d.ts → mcp/oauth-provider.test.d.ts} +0 -0
|
@@ -0,0 +1,937 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Vybestack LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { AuthType, ContentGeneratorRole, AuthenticationRequiredError, getCoreSystemPrompt, createCodeAssistContentGenerator, } from '@vybestack/llxprt-code-core';
|
|
7
|
+
export class GeminiProvider {
|
|
8
|
+
name = 'gemini';
|
|
9
|
+
apiKey;
|
|
10
|
+
authMode = 'none';
|
|
11
|
+
config;
|
|
12
|
+
currentModel = 'gemini-2.5-pro';
|
|
13
|
+
modelExplicitlySet = false;
|
|
14
|
+
authDetermined = false;
|
|
15
|
+
toolSchemas;
|
|
16
|
+
constructor() {
|
|
17
|
+
// Do not determine auth mode on instantiation.
|
|
18
|
+
// This will be done lazily when a chat completion is requested.
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Determines the best available authentication method based on environment variables
|
|
22
|
+
* and existing configuration. Follows the hierarchy: Vertex AI → Gemini API key → OAuth
|
|
23
|
+
*/
|
|
24
|
+
determineBestAuth() {
|
|
25
|
+
// Skip if already determined
|
|
26
|
+
if (this.authDetermined) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// Mark as determined early to prevent concurrent determinations
|
|
30
|
+
this.authDetermined = true;
|
|
31
|
+
// Check if user explicitly selected USE_NONE via the content generator config
|
|
32
|
+
const authType = this.config?.getContentGeneratorConfig()?.authType;
|
|
33
|
+
if (authType === AuthType.USE_NONE) {
|
|
34
|
+
this.authMode = 'none';
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// If authType is USE_PROVIDER and no credentials exist, fall back to OAuth
|
|
38
|
+
if (authType === AuthType.USE_PROVIDER) {
|
|
39
|
+
if (!this.hasVertexAICredentials() && !this.hasGeminiAPIKey()) {
|
|
40
|
+
this.authMode = 'oauth';
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Check for Vertex AI credentials first
|
|
45
|
+
if (this.hasVertexAICredentials()) {
|
|
46
|
+
this.authMode = 'vertex-ai';
|
|
47
|
+
this.setupVertexAIAuth();
|
|
48
|
+
}
|
|
49
|
+
// Check for Gemini API key second
|
|
50
|
+
else if (this.hasGeminiAPIKey()) {
|
|
51
|
+
this.authMode = 'gemini-api-key';
|
|
52
|
+
// API key is already in environment, no additional setup needed
|
|
53
|
+
}
|
|
54
|
+
// Fall back to OAuth (will prompt user if needed)
|
|
55
|
+
else {
|
|
56
|
+
this.authMode = 'oauth';
|
|
57
|
+
// OAuth will be handled by the existing auth system
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Checks if Vertex AI credentials are available
|
|
62
|
+
*/
|
|
63
|
+
hasVertexAICredentials() {
|
|
64
|
+
const hasProjectAndLocation = !!process.env.GOOGLE_CLOUD_PROJECT && !!process.env.GOOGLE_CLOUD_LOCATION;
|
|
65
|
+
const hasGoogleApiKey = !!process.env.GOOGLE_API_KEY;
|
|
66
|
+
return hasProjectAndLocation || hasGoogleApiKey;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Checks if Gemini API key is available
|
|
70
|
+
*/
|
|
71
|
+
hasGeminiAPIKey() {
|
|
72
|
+
return !!this.apiKey || !!process.env.GEMINI_API_KEY;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Sets up environment variables for Vertex AI authentication
|
|
76
|
+
*/
|
|
77
|
+
setupVertexAIAuth() {
|
|
78
|
+
process.env.GOOGLE_GENAI_USE_VERTEXAI = 'true';
|
|
79
|
+
// Other Vertex AI env vars are already set, no need to duplicate
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Sets the config instance for reading OAuth credentials
|
|
83
|
+
*/
|
|
84
|
+
setConfig(config) {
|
|
85
|
+
this.config = config;
|
|
86
|
+
// Sync with config model if user hasn't explicitly set a model
|
|
87
|
+
// This ensures consistency between config and provider state
|
|
88
|
+
const configModel = config.getModel();
|
|
89
|
+
if (!this.modelExplicitlySet && configModel) {
|
|
90
|
+
this.currentModel = configModel;
|
|
91
|
+
}
|
|
92
|
+
// Clear auth cache when config changes to allow re-determination
|
|
93
|
+
this.authDetermined = false;
|
|
94
|
+
// Re-determine auth after config is set
|
|
95
|
+
this.determineBestAuth();
|
|
96
|
+
}
|
|
97
|
+
async getModels() {
|
|
98
|
+
// For OAuth mode, return fixed list of models
|
|
99
|
+
if (this.authMode === 'oauth') {
|
|
100
|
+
return [
|
|
101
|
+
{
|
|
102
|
+
id: 'gemini-2.5-pro',
|
|
103
|
+
name: 'Gemini 2.5 Pro',
|
|
104
|
+
provider: this.name,
|
|
105
|
+
supportedToolFormats: [],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 'gemini-2.5-flash',
|
|
109
|
+
name: 'Gemini 2.5 Flash',
|
|
110
|
+
provider: this.name,
|
|
111
|
+
supportedToolFormats: [],
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 'gemini-2.5-flash-exp',
|
|
115
|
+
name: 'Gemini 2.5 Flash Experimental',
|
|
116
|
+
provider: this.name,
|
|
117
|
+
supportedToolFormats: [],
|
|
118
|
+
},
|
|
119
|
+
];
|
|
120
|
+
}
|
|
121
|
+
// For API key modes (gemini-api-key or vertex-ai), try to fetch real models
|
|
122
|
+
if (this.authMode === 'gemini-api-key' || this.authMode === 'vertex-ai') {
|
|
123
|
+
const apiKey = this.apiKey || process.env.GEMINI_API_KEY;
|
|
124
|
+
if (apiKey) {
|
|
125
|
+
try {
|
|
126
|
+
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}`, {
|
|
127
|
+
method: 'GET',
|
|
128
|
+
headers: {
|
|
129
|
+
'Content-Type': 'application/json',
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
if (response.ok) {
|
|
133
|
+
const data = (await response.json());
|
|
134
|
+
if (data.models && data.models.length > 0) {
|
|
135
|
+
return data.models.map((model) => ({
|
|
136
|
+
id: model.name.replace('models/', ''), // Remove 'models/' prefix
|
|
137
|
+
name: model.displayName || model.name,
|
|
138
|
+
provider: this.name,
|
|
139
|
+
supportedToolFormats: [],
|
|
140
|
+
}));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (_error) {
|
|
145
|
+
// Fall through to default models
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Return default models as fallback
|
|
150
|
+
return [
|
|
151
|
+
{
|
|
152
|
+
id: 'gemini-2.5-pro',
|
|
153
|
+
name: 'Gemini 2.5 Pro',
|
|
154
|
+
provider: this.name,
|
|
155
|
+
supportedToolFormats: [],
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: 'gemini-2.5-flash',
|
|
159
|
+
name: 'Gemini 2.5 Flash',
|
|
160
|
+
provider: this.name,
|
|
161
|
+
supportedToolFormats: [],
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: 'gemini-2.5-flash-exp',
|
|
165
|
+
name: 'Gemini 2.5 Flash Experimental',
|
|
166
|
+
provider: this.name,
|
|
167
|
+
supportedToolFormats: [],
|
|
168
|
+
},
|
|
169
|
+
];
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Checks if OAuth authentication is still valid
|
|
173
|
+
*/
|
|
174
|
+
async isOAuthValid() {
|
|
175
|
+
if (this.authMode !== 'oauth')
|
|
176
|
+
return true;
|
|
177
|
+
// Check if we have valid OAuth tokens
|
|
178
|
+
// This would need to interact with the core auth system
|
|
179
|
+
try {
|
|
180
|
+
// For now, assume OAuth is valid if we've already determined auth
|
|
181
|
+
// A more robust check would query the auth status from the config
|
|
182
|
+
return this.authDetermined;
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async *generateChatCompletion(messages, tools, _toolFormat) {
|
|
189
|
+
// Comprehensive debug logging
|
|
190
|
+
if (process.env.DEBUG) {
|
|
191
|
+
console.log('[GEMINI] generateChatCompletion called with:');
|
|
192
|
+
console.log('[GEMINI] messages:', JSON.stringify(messages, null, 2));
|
|
193
|
+
console.log('[GEMINI] messages length:', messages.length);
|
|
194
|
+
console.log('[GEMINI] first message:', messages[0] ? JSON.stringify(messages[0], null, 2) : 'NO FIRST MESSAGE');
|
|
195
|
+
console.log('[GEMINI] tools:', tools ? JSON.stringify(tools.map((t) => t.function.name)) : 'NO TOOLS');
|
|
196
|
+
if (process.env.DEBUG) {
|
|
197
|
+
console.log('DEBUG: GeminiProvider.generateChatCompletion called with messages:', JSON.stringify(messages, null, 2));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Check if we need to re-determine auth
|
|
201
|
+
const oauthValid = await this.isOAuthValid();
|
|
202
|
+
if (!oauthValid) {
|
|
203
|
+
this.authDetermined = false;
|
|
204
|
+
}
|
|
205
|
+
// Lazily determine the best auth method now that it's needed.
|
|
206
|
+
this.determineBestAuth();
|
|
207
|
+
// Early authentication validation - check if we have the required credentials
|
|
208
|
+
// for the determined auth mode BEFORE processing messages
|
|
209
|
+
switch (this.authMode) {
|
|
210
|
+
case 'gemini-api-key':
|
|
211
|
+
if (!this.apiKey && !process.env.GEMINI_API_KEY) {
|
|
212
|
+
throw new AuthenticationRequiredError('Gemini API key required but not found. Please set GEMINI_API_KEY environment variable or use /auth to login with Google OAuth.', this.authMode, ['GEMINI_API_KEY']);
|
|
213
|
+
}
|
|
214
|
+
break;
|
|
215
|
+
case 'vertex-ai':
|
|
216
|
+
if (!process.env.GOOGLE_API_KEY) {
|
|
217
|
+
throw new AuthenticationRequiredError('Google API key required for Vertex AI. Please set GOOGLE_API_KEY environment variable or use /auth to login with Google OAuth.', this.authMode, ['GOOGLE_API_KEY', 'GOOGLE_CLOUD_PROJECT', 'GOOGLE_CLOUD_LOCATION']);
|
|
218
|
+
}
|
|
219
|
+
break;
|
|
220
|
+
case 'oauth':
|
|
221
|
+
// OAuth auth will be validated when creating the content generator
|
|
222
|
+
break;
|
|
223
|
+
case 'none':
|
|
224
|
+
// In 'none' mode, check if ANY credentials are available
|
|
225
|
+
if (!this.hasGeminiAPIKey() && !this.hasVertexAICredentials()) {
|
|
226
|
+
throw new AuthenticationRequiredError('No authentication credentials found. Please use /auth to login with Google OAuth, set GEMINI_API_KEY, or configure Vertex AI credentials.', this.authMode, ['GEMINI_API_KEY', 'GOOGLE_API_KEY']);
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
default:
|
|
230
|
+
// For any other auth mode, proceed without validation
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
// Import the necessary modules dynamically to avoid circular dependencies
|
|
234
|
+
const { GoogleGenAI } = await import('@google/genai');
|
|
235
|
+
// Create the appropriate client based on auth mode
|
|
236
|
+
let genAI;
|
|
237
|
+
const httpOptions = {
|
|
238
|
+
headers: {
|
|
239
|
+
'User-Agent': `GeminiCLI/${process.env.CLI_VERSION || process.version} (${process.platform}; ${process.arch})`,
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
switch (this.authMode) {
|
|
243
|
+
case 'gemini-api-key':
|
|
244
|
+
if (!this.apiKey && !process.env.GEMINI_API_KEY) {
|
|
245
|
+
throw new Error('Gemini API key required but not found');
|
|
246
|
+
}
|
|
247
|
+
genAI = new GoogleGenAI({
|
|
248
|
+
apiKey: this.apiKey || process.env.GEMINI_API_KEY,
|
|
249
|
+
httpOptions,
|
|
250
|
+
});
|
|
251
|
+
break;
|
|
252
|
+
case 'vertex-ai':
|
|
253
|
+
if (!process.env.GOOGLE_API_KEY) {
|
|
254
|
+
throw new Error('Google API key required for Vertex AI');
|
|
255
|
+
}
|
|
256
|
+
genAI = new GoogleGenAI({
|
|
257
|
+
apiKey: process.env.GOOGLE_API_KEY,
|
|
258
|
+
vertexai: true,
|
|
259
|
+
httpOptions,
|
|
260
|
+
});
|
|
261
|
+
break;
|
|
262
|
+
case 'oauth': {
|
|
263
|
+
// For OAuth, we need to use the code assist server
|
|
264
|
+
const contentGenerator = await createCodeAssistContentGenerator(httpOptions, AuthType.LOGIN_WITH_GOOGLE, this.config);
|
|
265
|
+
// Convert messages to Gemini request format
|
|
266
|
+
// Use config model in OAuth mode to ensure synchronization
|
|
267
|
+
const oauthModel = this.modelExplicitlySet
|
|
268
|
+
? this.currentModel
|
|
269
|
+
: this.config?.getModel() || this.currentModel;
|
|
270
|
+
// Generate systemInstruction using getCoreSystemPrompt
|
|
271
|
+
// Get user memory from config if available
|
|
272
|
+
const userMemory = this.config?.getUserMemory
|
|
273
|
+
? this.config.getUserMemory()
|
|
274
|
+
: '';
|
|
275
|
+
const systemInstruction = getCoreSystemPrompt(userMemory, oauthModel);
|
|
276
|
+
// Store tools if provided
|
|
277
|
+
if (tools && tools.length > 0) {
|
|
278
|
+
this.toolSchemas = this.convertToolsToGeminiFormat(tools);
|
|
279
|
+
}
|
|
280
|
+
// Use provided tools or stored tools
|
|
281
|
+
let geminiTools = tools
|
|
282
|
+
? this.convertToolsToGeminiFormat(tools)
|
|
283
|
+
: this.toolSchemas;
|
|
284
|
+
// For Flash models, always include tools if available
|
|
285
|
+
if (oauthModel.includes('flash') && !geminiTools && this.toolSchemas) {
|
|
286
|
+
geminiTools = this.toolSchemas;
|
|
287
|
+
}
|
|
288
|
+
const request = {
|
|
289
|
+
model: oauthModel,
|
|
290
|
+
contents: this.convertMessagesToGeminiFormat(messages),
|
|
291
|
+
systemInstruction,
|
|
292
|
+
config: {
|
|
293
|
+
tools: geminiTools,
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
// Use the content generator stream
|
|
297
|
+
const streamResult = await contentGenerator.generateContentStream(request);
|
|
298
|
+
// Convert the stream to our format
|
|
299
|
+
for await (const response of streamResult) {
|
|
300
|
+
// Extract text from the response
|
|
301
|
+
const text = response.candidates?.[0]?.content?.parts
|
|
302
|
+
?.filter((part) => 'text' in part)
|
|
303
|
+
?.map((part) => part.text)
|
|
304
|
+
?.join('') || '';
|
|
305
|
+
// Extract function calls from the response
|
|
306
|
+
const functionCalls = response.candidates?.[0]?.content?.parts
|
|
307
|
+
?.filter((part) => 'functionCall' in part)
|
|
308
|
+
?.map((part) => part.functionCall) || [];
|
|
309
|
+
// Build response message
|
|
310
|
+
const message = {
|
|
311
|
+
role: ContentGeneratorRole.ASSISTANT,
|
|
312
|
+
content: text,
|
|
313
|
+
};
|
|
314
|
+
// Add function calls if any
|
|
315
|
+
if (functionCalls && functionCalls.length > 0) {
|
|
316
|
+
message.tool_calls = functionCalls.map((call) => ({
|
|
317
|
+
id: call.id ||
|
|
318
|
+
`call_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
319
|
+
type: 'function',
|
|
320
|
+
function: {
|
|
321
|
+
name: call.name || 'unknown_function',
|
|
322
|
+
arguments: JSON.stringify(call.args || {}),
|
|
323
|
+
},
|
|
324
|
+
}));
|
|
325
|
+
}
|
|
326
|
+
// Only yield if there's content or tool calls
|
|
327
|
+
if (text || (functionCalls && functionCalls.length > 0)) {
|
|
328
|
+
yield message;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
case 'none':
|
|
334
|
+
// For 'none' mode, check what credentials are available
|
|
335
|
+
if (this.hasGeminiAPIKey()) {
|
|
336
|
+
genAI = new GoogleGenAI({
|
|
337
|
+
apiKey: this.apiKey || process.env.GEMINI_API_KEY,
|
|
338
|
+
httpOptions,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
else if (this.hasVertexAICredentials()) {
|
|
342
|
+
this.setupVertexAIAuth();
|
|
343
|
+
genAI = new GoogleGenAI({
|
|
344
|
+
apiKey: process.env.GOOGLE_API_KEY,
|
|
345
|
+
vertexai: true,
|
|
346
|
+
httpOptions,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
throw new Error('No authentication credentials found. Please use /auth to login with Google OAuth, set GEMINI_API_KEY, or configure Vertex AI credentials.');
|
|
351
|
+
}
|
|
352
|
+
break;
|
|
353
|
+
default:
|
|
354
|
+
throw new Error(`Unsupported auth mode: ${this.authMode}`);
|
|
355
|
+
}
|
|
356
|
+
// Get the models interface (which is a ContentGenerator)
|
|
357
|
+
const contentGenerator = genAI.models;
|
|
358
|
+
// Store tools if provided
|
|
359
|
+
if (tools && tools.length > 0) {
|
|
360
|
+
this.toolSchemas = this.convertToolsToGeminiFormat(tools);
|
|
361
|
+
}
|
|
362
|
+
// Convert IMessage[] to Gemini format - do this after storing tools so priming can access them
|
|
363
|
+
const contents = this.convertMessagesToGeminiFormat(messages);
|
|
364
|
+
// Use provided tools or stored tools
|
|
365
|
+
let geminiTools = tools
|
|
366
|
+
? this.convertToolsToGeminiFormat(tools)
|
|
367
|
+
: this.toolSchemas;
|
|
368
|
+
// Create the request - ContentGenerator expects model in the request
|
|
369
|
+
// Use explicit model if set, otherwise fall back to config model
|
|
370
|
+
const modelToUse = this.modelExplicitlySet
|
|
371
|
+
? this.currentModel
|
|
372
|
+
: this.config?.getModel() || this.currentModel;
|
|
373
|
+
// For Flash models, always include tools if available
|
|
374
|
+
if (modelToUse.includes('flash') && !geminiTools && this.toolSchemas) {
|
|
375
|
+
geminiTools = this.toolSchemas;
|
|
376
|
+
}
|
|
377
|
+
// Generate systemInstruction using getCoreSystemPrompt
|
|
378
|
+
// Get user memory from config if available
|
|
379
|
+
const userMemory = this.config?.getUserMemory
|
|
380
|
+
? this.config.getUserMemory()
|
|
381
|
+
: '';
|
|
382
|
+
const systemInstruction = getCoreSystemPrompt(userMemory, modelToUse);
|
|
383
|
+
const request = {
|
|
384
|
+
model: modelToUse,
|
|
385
|
+
contents,
|
|
386
|
+
systemInstruction,
|
|
387
|
+
config: {
|
|
388
|
+
tools: geminiTools,
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
// Generate content stream using the ContentGenerator interface
|
|
392
|
+
const stream = await contentGenerator.generateContentStream(request);
|
|
393
|
+
// Stream the response
|
|
394
|
+
for await (const response of stream) {
|
|
395
|
+
// Extract text from the response
|
|
396
|
+
const text = response.candidates?.[0]?.content?.parts
|
|
397
|
+
?.filter((part) => 'text' in part)
|
|
398
|
+
?.map((part) => part.text)
|
|
399
|
+
?.join('') || '';
|
|
400
|
+
// Extract function calls from the response
|
|
401
|
+
const functionCalls = response.candidates?.[0]?.content?.parts
|
|
402
|
+
?.filter((part) => 'functionCall' in part)
|
|
403
|
+
?.map((part) => part.functionCall) || [];
|
|
404
|
+
// Build response message
|
|
405
|
+
const message = {
|
|
406
|
+
role: ContentGeneratorRole.ASSISTANT,
|
|
407
|
+
content: text || '',
|
|
408
|
+
};
|
|
409
|
+
// Add function calls if any
|
|
410
|
+
if (functionCalls && functionCalls.length > 0) {
|
|
411
|
+
message.tool_calls = functionCalls.map((call) => ({
|
|
412
|
+
id: call.id ||
|
|
413
|
+
`call_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
414
|
+
type: 'function',
|
|
415
|
+
function: {
|
|
416
|
+
name: call.name || 'unknown_function',
|
|
417
|
+
arguments: JSON.stringify(call.args || {}),
|
|
418
|
+
},
|
|
419
|
+
}));
|
|
420
|
+
}
|
|
421
|
+
// Only yield if there's content or tool calls
|
|
422
|
+
if (text || (functionCalls && functionCalls.length > 0)) {
|
|
423
|
+
yield message;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
convertMessagesToGeminiFormat(messages) {
|
|
428
|
+
const contents = [];
|
|
429
|
+
// Enhanced tracking with more details
|
|
430
|
+
const functionCalls = new Map();
|
|
431
|
+
const functionResponses = new Map();
|
|
432
|
+
for (let i = 0; i < messages.length; i++) {
|
|
433
|
+
const msg = messages[i];
|
|
434
|
+
// Handle tool responses - each in its own Content object
|
|
435
|
+
if (msg.role === ContentGeneratorRole.TOOL) {
|
|
436
|
+
if (!msg.tool_call_id) {
|
|
437
|
+
if (process.env.DEBUG) {
|
|
438
|
+
console.warn(`Tool response at index ${i} missing tool_call_id, skipping:`, msg);
|
|
439
|
+
}
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
functionResponses.set(msg.tool_call_id, {
|
|
443
|
+
name: msg.tool_name || 'unknown_function',
|
|
444
|
+
contentIndex: contents.length,
|
|
445
|
+
messageIndex: i,
|
|
446
|
+
});
|
|
447
|
+
// Add each tool response as a separate content immediately
|
|
448
|
+
contents.push({
|
|
449
|
+
role: 'user',
|
|
450
|
+
parts: [
|
|
451
|
+
{
|
|
452
|
+
functionResponse: {
|
|
453
|
+
id: msg.tool_call_id,
|
|
454
|
+
name: msg.tool_name || 'unknown_function',
|
|
455
|
+
response: {
|
|
456
|
+
output: msg.content || '',
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
],
|
|
461
|
+
});
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
// For non-tool messages, convert normally
|
|
465
|
+
const parts = [];
|
|
466
|
+
// Check for parts first (for messages with PDF/image parts but no text content)
|
|
467
|
+
if (msg.parts && msg.parts.length > 0) {
|
|
468
|
+
parts.push(...msg.parts);
|
|
469
|
+
}
|
|
470
|
+
else if (msg.content) {
|
|
471
|
+
// Handle PartListUnion: string | Part | Part[]
|
|
472
|
+
// In practice, content can be PartListUnion even though IMessage types it as string
|
|
473
|
+
const content = msg.content;
|
|
474
|
+
if (typeof content === 'string') {
|
|
475
|
+
// Try to parse string in case it's a stringified Part or Part[]
|
|
476
|
+
if ((content.startsWith('{') && content.endsWith('}')) ||
|
|
477
|
+
(content.startsWith('[') && content.endsWith(']'))) {
|
|
478
|
+
try {
|
|
479
|
+
const parsed = JSON.parse(content);
|
|
480
|
+
if (Array.isArray(parsed)) {
|
|
481
|
+
parts.push(...parsed);
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
parts.push(parsed);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
catch (_e) {
|
|
488
|
+
// Not valid JSON, treat as text
|
|
489
|
+
parts.push({ text: content });
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
parts.push({ text: content });
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
else if (Array.isArray(content)) {
|
|
497
|
+
// Content is Part[]
|
|
498
|
+
parts.push(...content);
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
// Content is a single Part
|
|
502
|
+
parts.push(content);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
// Handle tool calls
|
|
506
|
+
if (msg.tool_calls && msg.tool_calls.length > 0) {
|
|
507
|
+
// Check if function calls were already added via parts
|
|
508
|
+
const existingFunctionCallIds = new Set();
|
|
509
|
+
if (msg.parts && msg.parts.length > 0) {
|
|
510
|
+
for (const part of parts) {
|
|
511
|
+
if ('functionCall' in part) {
|
|
512
|
+
const fc = part;
|
|
513
|
+
if (fc.functionCall.id) {
|
|
514
|
+
existingFunctionCallIds.add(fc.functionCall.id);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
for (const toolCall of msg.tool_calls) {
|
|
520
|
+
// Skip if this function call was already added via parts
|
|
521
|
+
if (toolCall.id && existingFunctionCallIds.has(toolCall.id)) {
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
// Ensure tool call has an ID
|
|
525
|
+
if (!toolCall.id) {
|
|
526
|
+
if (process.env.DEBUG) {
|
|
527
|
+
console.warn(`Tool call at message ${i} missing ID, generating one:`, toolCall);
|
|
528
|
+
}
|
|
529
|
+
// Generate a unique ID for the function call
|
|
530
|
+
toolCall.id = `generated_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
531
|
+
}
|
|
532
|
+
const partIndex = parts.length;
|
|
533
|
+
parts.push({
|
|
534
|
+
functionCall: {
|
|
535
|
+
id: toolCall.id,
|
|
536
|
+
name: toolCall.function.name,
|
|
537
|
+
args: JSON.parse(toolCall.function.arguments),
|
|
538
|
+
},
|
|
539
|
+
});
|
|
540
|
+
// Track this function call with its position
|
|
541
|
+
functionCalls.set(toolCall.id, {
|
|
542
|
+
name: toolCall.function.name,
|
|
543
|
+
contentIndex: contents.length,
|
|
544
|
+
partIndex,
|
|
545
|
+
messageIndex: i,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
// Map roles
|
|
550
|
+
let role = 'user';
|
|
551
|
+
if (msg.role === ContentGeneratorRole.ASSISTANT) {
|
|
552
|
+
role = 'model';
|
|
553
|
+
}
|
|
554
|
+
else if (msg.role === ContentGeneratorRole.USER) {
|
|
555
|
+
role = 'user';
|
|
556
|
+
}
|
|
557
|
+
else if (msg.role === 'system') {
|
|
558
|
+
// Gemini doesn't have system role in contents, handle separately
|
|
559
|
+
role = 'user';
|
|
560
|
+
}
|
|
561
|
+
if (parts.length > 0) {
|
|
562
|
+
contents.push({
|
|
563
|
+
role,
|
|
564
|
+
parts,
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
// Validate and add missing function responses
|
|
569
|
+
for (const [callId, callInfo] of Array.from(functionCalls.entries())) {
|
|
570
|
+
if (!functionResponses.has(callId)) {
|
|
571
|
+
// Create a placeholder response for missing function response
|
|
572
|
+
if (process.env.DEBUG) {
|
|
573
|
+
console.warn(`Function call ${callInfo.name} (id: ${callId}) has no matching response, adding placeholder`);
|
|
574
|
+
}
|
|
575
|
+
// Add each function response as a separate content object (same as regular tool responses)
|
|
576
|
+
contents.push({
|
|
577
|
+
role: 'user',
|
|
578
|
+
parts: [
|
|
579
|
+
{
|
|
580
|
+
functionResponse: {
|
|
581
|
+
id: callId,
|
|
582
|
+
name: callInfo.name,
|
|
583
|
+
response: {
|
|
584
|
+
output: JSON.stringify({
|
|
585
|
+
error: 'Function call was interrupted or no response received',
|
|
586
|
+
message: `The function "${callInfo.name}" was called but did not receive a response. This may occur if the function execution was interrupted, requires authentication, or encountered an error.`,
|
|
587
|
+
callId,
|
|
588
|
+
functionName: callInfo.name,
|
|
589
|
+
}),
|
|
590
|
+
},
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
],
|
|
594
|
+
});
|
|
595
|
+
// Mark this response as added
|
|
596
|
+
functionResponses.set(callId, {
|
|
597
|
+
name: callInfo.name,
|
|
598
|
+
contentIndex: contents.length - 1,
|
|
599
|
+
messageIndex: -1, // Placeholder response doesn't have original message index
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
// Final validation - count function calls and responses
|
|
604
|
+
let totalFunctionCalls = 0;
|
|
605
|
+
let totalFunctionResponses = 0;
|
|
606
|
+
const callsDetail = [];
|
|
607
|
+
const responsesDetail = [];
|
|
608
|
+
const unmatchedCalls = new Set();
|
|
609
|
+
const unmatchedResponses = new Set();
|
|
610
|
+
// First pass: collect all function calls and responses with their IDs
|
|
611
|
+
for (let i = 0; i < contents.length; i++) {
|
|
612
|
+
const content = contents[i];
|
|
613
|
+
for (const part of content.parts) {
|
|
614
|
+
if ('functionCall' in part) {
|
|
615
|
+
totalFunctionCalls++;
|
|
616
|
+
const fc = part;
|
|
617
|
+
callsDetail.push(`${i}: ${fc.functionCall.name} (${fc.functionCall.id})`);
|
|
618
|
+
if (fc.functionCall.id) {
|
|
619
|
+
unmatchedCalls.add(fc.functionCall.id);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
else if ('functionResponse' in part) {
|
|
623
|
+
totalFunctionResponses++;
|
|
624
|
+
const fr = part;
|
|
625
|
+
responsesDetail.push(`${i}: ${fr.functionResponse.name} (${fr.functionResponse.id})`);
|
|
626
|
+
if (fr.functionResponse.id) {
|
|
627
|
+
unmatchedResponses.add(fr.functionResponse.id);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
// Second pass: match calls with responses
|
|
633
|
+
for (const id of unmatchedCalls) {
|
|
634
|
+
if (unmatchedResponses.has(id)) {
|
|
635
|
+
unmatchedCalls.delete(id);
|
|
636
|
+
unmatchedResponses.delete(id);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
if (totalFunctionCalls !== totalFunctionResponses) {
|
|
640
|
+
if (process.env.DEBUG) {
|
|
641
|
+
console.warn(`Function parts count mismatch: ${totalFunctionCalls} calls vs ${totalFunctionResponses} responses`);
|
|
642
|
+
console.warn('Function calls:', callsDetail);
|
|
643
|
+
console.warn('Function responses:', responsesDetail);
|
|
644
|
+
console.warn('Unmatched call IDs:', Array.from(unmatchedCalls));
|
|
645
|
+
console.warn('Unmatched response IDs:', Array.from(unmatchedResponses));
|
|
646
|
+
}
|
|
647
|
+
// This is now just a warning, not an error, since we've added placeholders
|
|
648
|
+
// The Gemini API should handle this gracefully
|
|
649
|
+
}
|
|
650
|
+
return contents;
|
|
651
|
+
}
|
|
652
|
+
convertToolsToGeminiFormat(tools) {
|
|
653
|
+
const result = [
|
|
654
|
+
{
|
|
655
|
+
functionDeclarations: tools.map((tool) => ({
|
|
656
|
+
name: tool.function.name,
|
|
657
|
+
description: tool.function.description,
|
|
658
|
+
parameters: tool.function.parameters,
|
|
659
|
+
})),
|
|
660
|
+
},
|
|
661
|
+
];
|
|
662
|
+
if (process.env.DEBUG) {
|
|
663
|
+
console.log('DEBUG [GeminiProvider]: Converted tools to Gemini format:', JSON.stringify(result, null, 2));
|
|
664
|
+
}
|
|
665
|
+
return result;
|
|
666
|
+
}
|
|
667
|
+
setApiKey(apiKey) {
|
|
668
|
+
this.apiKey = apiKey;
|
|
669
|
+
// Set the API key as an environment variable so it can be used by the core library
|
|
670
|
+
process.env.GEMINI_API_KEY = apiKey;
|
|
671
|
+
// Clear auth cache when API key changes
|
|
672
|
+
this.authDetermined = false;
|
|
673
|
+
// Re-determine auth after API key is set
|
|
674
|
+
this.determineBestAuth();
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Gets the current authentication mode
|
|
678
|
+
*/
|
|
679
|
+
getAuthMode() {
|
|
680
|
+
return this.authMode;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Gets the appropriate AuthType for the core library
|
|
684
|
+
*/
|
|
685
|
+
getCoreAuthType() {
|
|
686
|
+
switch (this.authMode) {
|
|
687
|
+
case 'oauth':
|
|
688
|
+
return AuthType.LOGIN_WITH_GOOGLE;
|
|
689
|
+
case 'gemini-api-key':
|
|
690
|
+
return AuthType.USE_GEMINI;
|
|
691
|
+
case 'vertex-ai':
|
|
692
|
+
return AuthType.USE_VERTEX_AI;
|
|
693
|
+
default:
|
|
694
|
+
return AuthType.LOGIN_WITH_GOOGLE; // Default to OAuth
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Gets the current model ID
|
|
699
|
+
*/
|
|
700
|
+
getCurrentModel() {
|
|
701
|
+
return this.currentModel;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Sets the current model ID
|
|
705
|
+
*/
|
|
706
|
+
setModel(modelId) {
|
|
707
|
+
this.currentModel = modelId;
|
|
708
|
+
this.modelExplicitlySet = true;
|
|
709
|
+
// Always update config if available, not just in OAuth mode
|
|
710
|
+
// This ensures the model is properly synchronized
|
|
711
|
+
if (this.config) {
|
|
712
|
+
this.config.setModel(modelId);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Checks if the current auth mode requires payment
|
|
717
|
+
*/
|
|
718
|
+
isPaidMode() {
|
|
719
|
+
return this.authMode === 'gemini-api-key' || this.authMode === 'vertex-ai';
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Clears provider state but preserves explicitly set model
|
|
723
|
+
*/
|
|
724
|
+
clearState() {
|
|
725
|
+
// Clear auth-related state
|
|
726
|
+
this.authMode = 'none';
|
|
727
|
+
this.authDetermined = false;
|
|
728
|
+
// Only reset model if it wasn't explicitly set by user
|
|
729
|
+
if (!this.modelExplicitlySet) {
|
|
730
|
+
this.currentModel = 'gemini-2.5-pro';
|
|
731
|
+
}
|
|
732
|
+
// Note: We don't clear config or apiKey as they might be needed
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Forces re-determination of auth method
|
|
736
|
+
*/
|
|
737
|
+
clearAuthCache() {
|
|
738
|
+
this.authDetermined = false;
|
|
739
|
+
// Don't clear the auth mode itself, just the determination flag
|
|
740
|
+
// This allows for smoother transitions
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Get the list of server tools supported by this provider
|
|
744
|
+
*/
|
|
745
|
+
getServerTools() {
|
|
746
|
+
return ['web_search', 'web_fetch'];
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Invoke a server tool (native provider tool)
|
|
750
|
+
*/
|
|
751
|
+
async invokeServerTool(toolName, params, _config) {
|
|
752
|
+
if (toolName === 'web_search') {
|
|
753
|
+
// Import the necessary modules dynamically
|
|
754
|
+
const { GoogleGenAI } = await import('@google/genai');
|
|
755
|
+
// Create the appropriate client based on auth mode
|
|
756
|
+
const httpOptions = {
|
|
757
|
+
headers: {
|
|
758
|
+
'User-Agent': `GeminiCLI/${process.env.CLI_VERSION || process.version} (${process.platform}; ${process.arch})`,
|
|
759
|
+
},
|
|
760
|
+
};
|
|
761
|
+
let genAI;
|
|
762
|
+
switch (this.authMode) {
|
|
763
|
+
case 'gemini-api-key': {
|
|
764
|
+
if (!this.apiKey && !process.env.GEMINI_API_KEY) {
|
|
765
|
+
throw new Error('Gemini API key required for web search');
|
|
766
|
+
}
|
|
767
|
+
genAI = new GoogleGenAI({
|
|
768
|
+
apiKey: this.apiKey || process.env.GEMINI_API_KEY,
|
|
769
|
+
httpOptions,
|
|
770
|
+
});
|
|
771
|
+
// Get the models interface (which is a ContentGenerator)
|
|
772
|
+
const contentGenerator = genAI.models;
|
|
773
|
+
const apiKeyRequest = {
|
|
774
|
+
model: this.currentModel,
|
|
775
|
+
contents: [
|
|
776
|
+
{
|
|
777
|
+
role: 'user',
|
|
778
|
+
parts: [{ text: params.query }],
|
|
779
|
+
},
|
|
780
|
+
],
|
|
781
|
+
config: {
|
|
782
|
+
tools: [{ googleSearch: {} }],
|
|
783
|
+
},
|
|
784
|
+
};
|
|
785
|
+
const apiKeyResult = await contentGenerator.generateContent(apiKeyRequest);
|
|
786
|
+
return apiKeyResult;
|
|
787
|
+
}
|
|
788
|
+
case 'vertex-ai': {
|
|
789
|
+
if (!process.env.GOOGLE_API_KEY) {
|
|
790
|
+
throw new Error('Google API key required for web search');
|
|
791
|
+
}
|
|
792
|
+
genAI = new GoogleGenAI({
|
|
793
|
+
apiKey: process.env.GOOGLE_API_KEY,
|
|
794
|
+
vertexai: true,
|
|
795
|
+
httpOptions,
|
|
796
|
+
});
|
|
797
|
+
// Get the models interface (which is a ContentGenerator)
|
|
798
|
+
const vertexContentGenerator = genAI.models;
|
|
799
|
+
const vertexRequest = {
|
|
800
|
+
model: this.currentModel,
|
|
801
|
+
contents: [
|
|
802
|
+
{
|
|
803
|
+
role: 'user',
|
|
804
|
+
parts: [{ text: params.query }],
|
|
805
|
+
},
|
|
806
|
+
],
|
|
807
|
+
config: {
|
|
808
|
+
tools: [{ googleSearch: {} }],
|
|
809
|
+
},
|
|
810
|
+
};
|
|
811
|
+
const vertexResult = await vertexContentGenerator.generateContent(vertexRequest);
|
|
812
|
+
return vertexResult;
|
|
813
|
+
}
|
|
814
|
+
case 'oauth': {
|
|
815
|
+
// For OAuth, use the code assist content generator
|
|
816
|
+
const oauthContentGenerator = await createCodeAssistContentGenerator(httpOptions, AuthType.LOGIN_WITH_GOOGLE, this.config);
|
|
817
|
+
const oauthModel = this.modelExplicitlySet
|
|
818
|
+
? this.currentModel
|
|
819
|
+
: this.config?.getModel() || this.currentModel;
|
|
820
|
+
// For OAuth, we need to use the ContentGenerator interface
|
|
821
|
+
// which has a different API - it expects tools in the config
|
|
822
|
+
const oauthRequest = {
|
|
823
|
+
model: oauthModel,
|
|
824
|
+
contents: [
|
|
825
|
+
{
|
|
826
|
+
role: 'user',
|
|
827
|
+
parts: [{ text: params.query }],
|
|
828
|
+
},
|
|
829
|
+
],
|
|
830
|
+
config: {
|
|
831
|
+
tools: [{ googleSearch: {} }],
|
|
832
|
+
},
|
|
833
|
+
};
|
|
834
|
+
const result = await oauthContentGenerator.generateContent(oauthRequest);
|
|
835
|
+
return result;
|
|
836
|
+
}
|
|
837
|
+
default:
|
|
838
|
+
throw new Error(`Web search not supported in auth mode: ${this.authMode}`);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
else if (toolName === 'web_fetch') {
|
|
842
|
+
// Import the necessary modules dynamically
|
|
843
|
+
const { GoogleGenAI } = await import('@google/genai');
|
|
844
|
+
// Get the prompt directly without any processing
|
|
845
|
+
const prompt = params.prompt;
|
|
846
|
+
// Create the appropriate client based on auth mode
|
|
847
|
+
const httpOptions = {
|
|
848
|
+
headers: {
|
|
849
|
+
'User-Agent': `GeminiCLI/${process.env.CLI_VERSION || process.version} (${process.platform}; ${process.arch})`,
|
|
850
|
+
},
|
|
851
|
+
};
|
|
852
|
+
let genAI;
|
|
853
|
+
switch (this.authMode) {
|
|
854
|
+
case 'gemini-api-key': {
|
|
855
|
+
if (!this.apiKey && !process.env.GEMINI_API_KEY) {
|
|
856
|
+
throw new Error('Gemini API key required for web fetch');
|
|
857
|
+
}
|
|
858
|
+
genAI = new GoogleGenAI({
|
|
859
|
+
apiKey: this.apiKey || process.env.GEMINI_API_KEY,
|
|
860
|
+
httpOptions,
|
|
861
|
+
});
|
|
862
|
+
// Get the models interface (which is a ContentGenerator)
|
|
863
|
+
const contentGenerator = genAI.models;
|
|
864
|
+
const apiKeyRequest = {
|
|
865
|
+
model: this.currentModel,
|
|
866
|
+
contents: [
|
|
867
|
+
{
|
|
868
|
+
role: 'user',
|
|
869
|
+
parts: [{ text: prompt }],
|
|
870
|
+
},
|
|
871
|
+
],
|
|
872
|
+
config: {
|
|
873
|
+
tools: [{ urlContext: {} }],
|
|
874
|
+
},
|
|
875
|
+
};
|
|
876
|
+
const apiKeyResult = await contentGenerator.generateContent(apiKeyRequest);
|
|
877
|
+
return apiKeyResult;
|
|
878
|
+
}
|
|
879
|
+
case 'vertex-ai': {
|
|
880
|
+
if (!process.env.GOOGLE_API_KEY) {
|
|
881
|
+
throw new Error('Google API key required for web fetch');
|
|
882
|
+
}
|
|
883
|
+
genAI = new GoogleGenAI({
|
|
884
|
+
apiKey: process.env.GOOGLE_API_KEY,
|
|
885
|
+
vertexai: true,
|
|
886
|
+
httpOptions,
|
|
887
|
+
});
|
|
888
|
+
// Get the models interface (which is a ContentGenerator)
|
|
889
|
+
const vertexContentGenerator = genAI.models;
|
|
890
|
+
const vertexRequest = {
|
|
891
|
+
model: this.currentModel,
|
|
892
|
+
contents: [
|
|
893
|
+
{
|
|
894
|
+
role: 'user',
|
|
895
|
+
parts: [{ text: prompt }],
|
|
896
|
+
},
|
|
897
|
+
],
|
|
898
|
+
config: {
|
|
899
|
+
tools: [{ urlContext: {} }],
|
|
900
|
+
},
|
|
901
|
+
};
|
|
902
|
+
const vertexResult = await vertexContentGenerator.generateContent(vertexRequest);
|
|
903
|
+
return vertexResult;
|
|
904
|
+
}
|
|
905
|
+
case 'oauth': {
|
|
906
|
+
// For OAuth, use the code assist content generator
|
|
907
|
+
const oauthContentGenerator = await createCodeAssistContentGenerator(httpOptions, AuthType.LOGIN_WITH_GOOGLE, this.config);
|
|
908
|
+
const oauthModel = this.modelExplicitlySet
|
|
909
|
+
? this.currentModel
|
|
910
|
+
: this.config?.getModel() || this.currentModel;
|
|
911
|
+
// For OAuth, we need to use the ContentGenerator interface
|
|
912
|
+
// which has a different API - it expects tools in the config
|
|
913
|
+
const oauthRequest = {
|
|
914
|
+
model: oauthModel,
|
|
915
|
+
contents: [
|
|
916
|
+
{
|
|
917
|
+
role: 'user',
|
|
918
|
+
parts: [{ text: prompt }],
|
|
919
|
+
},
|
|
920
|
+
],
|
|
921
|
+
config: {
|
|
922
|
+
tools: [{ urlContext: {} }],
|
|
923
|
+
},
|
|
924
|
+
};
|
|
925
|
+
const result = await oauthContentGenerator.generateContent(oauthRequest);
|
|
926
|
+
return result;
|
|
927
|
+
}
|
|
928
|
+
default:
|
|
929
|
+
throw new Error(`Web fetch not supported in auth mode: ${this.authMode}`);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
else {
|
|
933
|
+
throw new Error(`Unknown server tool: ${toolName}`);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
//# sourceMappingURL=GeminiProvider.js.map
|