@vybestack/llxprt-code-core 0.4.8 → 0.5.0-nightly.251102.e5b51aa3
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/dist/prompt-config/defaults/default-prompts.json +4 -17
- package/dist/src/auth/precedence.d.ts +69 -9
- package/dist/src/auth/precedence.js +467 -69
- package/dist/src/auth/precedence.js.map +1 -1
- package/dist/src/auth/types.d.ts +2 -2
- package/dist/src/config/config.d.ts +15 -1
- package/dist/src/config/config.js +118 -6
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/index.d.ts +6 -0
- package/dist/src/config/index.js +5 -0
- package/dist/src/config/index.js.map +1 -1
- package/dist/src/config/profileManager.d.ts +23 -3
- package/dist/src/config/profileManager.js +54 -7
- package/dist/src/config/profileManager.js.map +1 -1
- package/dist/src/config/subagentManager.d.ts +96 -0
- package/dist/src/config/subagentManager.js +371 -0
- package/dist/src/config/subagentManager.js.map +1 -0
- package/dist/src/config/types.d.ts +18 -0
- package/dist/src/config/types.js +3 -0
- package/dist/src/config/types.js.map +1 -0
- package/dist/src/core/client.d.ts +27 -7
- package/dist/src/core/client.js +231 -55
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/contentGenerator.d.ts +3 -1
- package/dist/src/core/contentGenerator.js +3 -0
- package/dist/src/core/contentGenerator.js.map +1 -1
- package/dist/src/core/coreToolScheduler.d.ts +1 -5
- package/dist/src/core/coreToolScheduler.js +95 -23
- package/dist/src/core/coreToolScheduler.js.map +1 -1
- package/dist/src/core/geminiChat.d.ts +42 -12
- package/dist/src/core/geminiChat.js +405 -205
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.d.ts +3 -2
- package/dist/src/core/nonInteractiveToolExecutor.js +94 -10
- package/dist/src/core/nonInteractiveToolExecutor.js.map +1 -1
- package/dist/src/core/subagent.d.ts +86 -7
- package/dist/src/core/subagent.js +809 -79
- package/dist/src/core/subagent.js.map +1 -1
- package/dist/src/core/subagentOrchestrator.d.ts +73 -0
- package/dist/src/core/subagentOrchestrator.js +387 -0
- package/dist/src/core/subagentOrchestrator.js.map +1 -0
- package/dist/src/core/subagentScheduler.d.ts +16 -0
- package/dist/src/core/subagentScheduler.js +7 -0
- package/dist/src/core/subagentScheduler.js.map +1 -0
- package/dist/src/core/turn.d.ts +5 -1
- package/dist/src/core/turn.js +5 -1
- package/dist/src/core/turn.js.map +1 -1
- package/dist/src/hooks/tool-render-suppression-hook.js +6 -1
- package/dist/src/hooks/tool-render-suppression-hook.js.map +1 -1
- package/dist/src/ide/ideContext.d.ts +32 -32
- package/dist/src/index.d.ts +19 -1
- package/dist/src/index.js +15 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/interfaces/index.d.ts +1 -0
- package/dist/src/interfaces/index.js +4 -0
- package/dist/src/interfaces/index.js.map +1 -0
- package/dist/src/interfaces/nodejs-error.interface.d.ts +4 -0
- package/dist/src/interfaces/nodejs-error.interface.js +2 -0
- package/dist/src/interfaces/nodejs-error.interface.js.map +1 -0
- package/dist/src/parsers/TextToolCallParser.d.ts +17 -1
- package/dist/src/parsers/TextToolCallParser.js +542 -148
- package/dist/src/parsers/TextToolCallParser.js.map +1 -1
- package/dist/src/prompt-config/defaults/core.md +15 -0
- package/dist/src/prompt-config/defaults/providers/gemini/core.md +203 -119
- package/dist/src/prompt-config/defaults/tool-defaults.js +2 -0
- package/dist/src/prompt-config/defaults/tool-defaults.js.map +1 -1
- package/dist/src/prompt-config/defaults/tools/list-subagents.md +7 -0
- package/dist/src/prompt-config/defaults/tools/task.md +8 -0
- package/dist/src/providers/BaseProvider.d.ts +115 -30
- package/dist/src/providers/BaseProvider.js +445 -109
- package/dist/src/providers/BaseProvider.js.map +1 -1
- package/dist/src/providers/IProvider.d.ts +50 -18
- package/dist/src/providers/LoggingProviderWrapper.d.ts +60 -16
- package/dist/src/providers/LoggingProviderWrapper.js +213 -60
- package/dist/src/providers/LoggingProviderWrapper.js.map +1 -1
- package/dist/src/providers/ProviderManager.d.ts +73 -2
- package/dist/src/providers/ProviderManager.js +492 -40
- package/dist/src/providers/ProviderManager.js.map +1 -1
- package/dist/src/providers/anthropic/AnthropicProvider.d.ts +35 -38
- package/dist/src/providers/anthropic/AnthropicProvider.js +222 -227
- package/dist/src/providers/anthropic/AnthropicProvider.js.map +1 -1
- package/dist/src/providers/errors.d.ts +86 -0
- package/dist/src/providers/errors.js +89 -0
- package/dist/src/providers/errors.js.map +1 -1
- package/dist/src/providers/gemini/GeminiProvider.d.ts +101 -41
- package/dist/src/providers/gemini/GeminiProvider.js +386 -311
- package/dist/src/providers/gemini/GeminiProvider.js.map +1 -1
- package/dist/src/providers/openai/ConversationCache.d.ts +5 -3
- package/dist/src/providers/openai/ConversationCache.js +93 -32
- package/dist/src/providers/openai/ConversationCache.js.map +1 -1
- package/dist/src/providers/openai/OpenAIProvider.d.ts +82 -42
- package/dist/src/providers/openai/OpenAIProvider.js +391 -536
- package/dist/src/providers/openai/OpenAIProvider.js.map +1 -1
- package/dist/src/providers/openai/getOpenAIProviderInfo.d.ts +1 -1
- package/dist/src/providers/openai/getOpenAIProviderInfo.js +52 -22
- package/dist/src/providers/openai/getOpenAIProviderInfo.js.map +1 -1
- package/dist/src/providers/openai/openaiRequestParams.d.ts +7 -0
- package/dist/src/providers/openai/openaiRequestParams.js +66 -0
- package/dist/src/providers/openai/openaiRequestParams.js.map +1 -0
- package/dist/src/providers/openai-responses/OpenAIResponsesProvider.d.ts +6 -33
- package/dist/src/providers/openai-responses/OpenAIResponsesProvider.js +84 -183
- package/dist/src/providers/openai-responses/OpenAIResponsesProvider.js.map +1 -1
- package/dist/src/providers/types/providerRuntime.d.ts +17 -0
- package/dist/src/providers/types/providerRuntime.js +7 -0
- package/dist/src/providers/types/providerRuntime.js.map +1 -0
- package/dist/src/providers/utils/authToken.d.ts +12 -0
- package/dist/src/providers/utils/authToken.js +17 -0
- package/dist/src/providers/utils/authToken.js.map +1 -0
- package/dist/src/providers/utils/userMemory.d.ts +8 -0
- package/dist/src/providers/utils/userMemory.js +34 -0
- package/dist/src/providers/utils/userMemory.js.map +1 -0
- package/dist/src/runtime/AgentRuntimeContext.d.ts +213 -0
- package/dist/src/runtime/AgentRuntimeContext.js +17 -0
- package/dist/src/runtime/AgentRuntimeContext.js.map +1 -0
- package/dist/src/runtime/AgentRuntimeLoader.d.ts +47 -0
- package/dist/src/runtime/AgentRuntimeLoader.js +122 -0
- package/dist/src/runtime/AgentRuntimeLoader.js.map +1 -0
- package/dist/src/runtime/AgentRuntimeState.d.ts +232 -0
- package/dist/src/runtime/AgentRuntimeState.js +439 -0
- package/dist/src/runtime/AgentRuntimeState.js.map +1 -0
- package/dist/src/runtime/RuntimeInvocationContext.d.ts +51 -0
- package/dist/src/runtime/RuntimeInvocationContext.js +52 -0
- package/dist/src/runtime/RuntimeInvocationContext.js.map +1 -0
- package/dist/src/runtime/createAgentRuntimeContext.d.ts +7 -0
- package/dist/src/runtime/createAgentRuntimeContext.js +65 -0
- package/dist/src/runtime/createAgentRuntimeContext.js.map +1 -0
- package/dist/src/runtime/index.d.ts +13 -0
- package/dist/src/runtime/index.js +14 -0
- package/dist/src/runtime/index.js.map +1 -0
- package/dist/src/runtime/providerRuntimeContext.d.ts +30 -0
- package/dist/src/runtime/providerRuntimeContext.js +70 -0
- package/dist/src/runtime/providerRuntimeContext.js.map +1 -0
- package/dist/src/runtime/runtimeAdapters.d.ts +22 -0
- package/dist/src/runtime/runtimeAdapters.js +81 -0
- package/dist/src/runtime/runtimeAdapters.js.map +1 -0
- package/dist/src/runtime/runtimeStateFactory.d.ts +21 -0
- package/dist/src/runtime/runtimeStateFactory.js +104 -0
- package/dist/src/runtime/runtimeStateFactory.js.map +1 -0
- package/dist/src/services/todo-context-tracker.d.ts +10 -8
- package/dist/src/services/todo-context-tracker.js +26 -10
- package/dist/src/services/todo-context-tracker.js.map +1 -1
- package/dist/src/services/tool-call-tracker-service.d.ts +11 -7
- package/dist/src/services/tool-call-tracker-service.js +89 -29
- package/dist/src/services/tool-call-tracker-service.js.map +1 -1
- package/dist/src/settings/SettingsService.d.ts +4 -0
- package/dist/src/settings/SettingsService.js +65 -2
- package/dist/src/settings/SettingsService.js.map +1 -1
- package/dist/src/settings/settingsServiceInstance.d.ts +6 -1
- package/dist/src/settings/settingsServiceInstance.js +28 -8
- package/dist/src/settings/settingsServiceInstance.js.map +1 -1
- package/dist/src/telemetry/loggers.d.ts +5 -1
- package/dist/src/telemetry/loggers.js.map +1 -1
- package/dist/src/telemetry/loggers.test.circular.js +4 -0
- package/dist/src/telemetry/loggers.test.circular.js.map +1 -1
- package/dist/src/telemetry/metrics.d.ts +3 -1
- package/dist/src/telemetry/metrics.js.map +1 -1
- package/dist/src/telemetry/types.d.ts +1 -0
- package/dist/src/telemetry/types.js +3 -0
- package/dist/src/telemetry/types.js.map +1 -1
- package/dist/src/test-utils/index.d.ts +2 -0
- package/dist/src/test-utils/index.js +2 -0
- package/dist/src/test-utils/index.js.map +1 -1
- package/dist/src/test-utils/mockWorkspaceContext.d.ts +0 -3
- package/dist/src/test-utils/mockWorkspaceContext.js +3 -4
- package/dist/src/test-utils/mockWorkspaceContext.js.map +1 -1
- package/dist/src/test-utils/providerCallOptions.d.ts +43 -0
- package/dist/src/test-utils/providerCallOptions.js +137 -0
- package/dist/src/test-utils/providerCallOptions.js.map +1 -0
- package/dist/src/test-utils/runtime.d.ts +92 -0
- package/dist/src/test-utils/runtime.js +226 -0
- package/dist/src/test-utils/runtime.js.map +1 -0
- package/dist/src/test-utils/tools.d.ts +4 -4
- package/dist/src/test-utils/tools.js +20 -10
- package/dist/src/test-utils/tools.js.map +1 -1
- package/dist/src/tools/list-subagents.d.ts +31 -0
- package/dist/src/tools/list-subagents.js +109 -0
- package/dist/src/tools/list-subagents.js.map +1 -0
- package/dist/src/tools/task.d.ts +87 -0
- package/dist/src/tools/task.js +427 -0
- package/dist/src/tools/task.js.map +1 -0
- package/dist/src/tools/todo-read.js +1 -1
- package/dist/src/tools/todo-read.js.map +1 -1
- package/dist/src/tools/todo-store.js +4 -2
- package/dist/src/tools/todo-store.js.map +1 -1
- package/dist/src/tools/todo-write.js +4 -2
- package/dist/src/tools/todo-write.js.map +1 -1
- package/dist/src/tools/tool-error.d.ts +1 -0
- package/dist/src/tools/tool-error.js +1 -0
- package/dist/src/tools/tool-error.js.map +1 -1
- package/dist/src/tools/tool-registry.d.ts +2 -0
- package/dist/src/tools/tool-registry.js +46 -21
- package/dist/src/tools/tool-registry.js.map +1 -1
- package/dist/src/types/modelParams.d.ts +4 -0
- package/dist/src/utils/editor.js +10 -8
- package/dist/src/utils/editor.js.map +1 -1
- package/dist/src/utils/gitIgnoreParser.js +15 -3
- package/dist/src/utils/gitIgnoreParser.js.map +1 -1
- package/package.json +1 -1
- package/dist/src/prompt-config/defaults/providers/anthropic/core.md +0 -97
- package/dist/src/prompt-config/defaults/providers/anthropic/tools/glob.md +0 -34
- package/dist/src/prompt-config/defaults/providers/anthropic/tools/list-directory.md +0 -11
- package/dist/src/prompt-config/defaults/providers/anthropic/tools/read-file.md +0 -14
- package/dist/src/prompt-config/defaults/providers/anthropic/tools/read-many-files.md +0 -31
- package/dist/src/prompt-config/defaults/providers/anthropic/tools/replace.md +0 -41
- package/dist/src/prompt-config/defaults/providers/anthropic/tools/run-shell-command.md +0 -32
- package/dist/src/prompt-config/defaults/providers/anthropic/tools/save-memory.md +0 -35
- package/dist/src/prompt-config/defaults/providers/anthropic/tools/search-file-content.md +0 -44
- package/dist/src/prompt-config/defaults/providers/anthropic/tools/todo-write.md +0 -45
- package/dist/src/prompt-config/defaults/providers/anthropic/tools/write-file.md +0 -11
- package/dist/src/prompt-config/defaults/providers/openai/core.md +0 -97
- package/dist/src/prompt-config/defaults/providers/openai/tools/todo-pause.md +0 -28
- package/dist/src/prompt-config/defaults/providers/openai/tools/todo-read.md +0 -5
- package/dist/src/prompt-config/defaults/providers/openai/tools/todo-write.md +0 -45
|
@@ -3,17 +3,49 @@
|
|
|
3
3
|
* Copyright 2025 Vybestack LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
+
// @plan:PLAN-20251023-STATELESS-HARDENING.P08 @requirement:REQ-SP4-002
|
|
7
|
+
// createHash import removed - no longer needed without client caching
|
|
6
8
|
import { DebugLogger } from '../../debug/index.js';
|
|
7
9
|
import { Config } from '../../config/config.js';
|
|
8
10
|
import { AuthType } from '../../core/contentGenerator.js';
|
|
9
11
|
import { AuthenticationRequiredError } from '../errors.js';
|
|
10
12
|
import { getCoreSystemPromptAsync } from '../../core/prompts.js';
|
|
11
|
-
import { createCodeAssistContentGenerator } from '../../code_assist/codeAssist.js';
|
|
12
13
|
import { Type, } from '@google/genai';
|
|
13
|
-
import { BaseProvider } from '../BaseProvider.js';
|
|
14
|
-
import { getSettingsService } from '../../settings/settingsServiceInstance.js';
|
|
15
|
-
import { retryWithBackoff, getErrorStatus, isNetworkTransientError, } from '../../utils/retry.js';
|
|
14
|
+
import { BaseProvider, } from '../BaseProvider.js';
|
|
16
15
|
export class GeminiProvider extends BaseProvider {
|
|
16
|
+
/**
|
|
17
|
+
* @plan:PLAN-20251023-STATELESS-HARDENING.P08
|
|
18
|
+
* @requirement:REQ-SP4-002
|
|
19
|
+
* @pseudocode lines 10-14
|
|
20
|
+
*
|
|
21
|
+
* Removed cached state variables to ensure stateless behavior.
|
|
22
|
+
* Provider now resolves model, auth, and parameters per call.
|
|
23
|
+
* No instance state: authMode, model overrides, modelExplicitlySet, currentModel removed.
|
|
24
|
+
* @requirement:REQ-SP4-003: Auth/model/params come from NormalizedGenerateChatOptions
|
|
25
|
+
*/
|
|
26
|
+
geminiOAuthManager;
|
|
27
|
+
/**
|
|
28
|
+
* @plan:PLAN-20251023-STATELESS-HARDENING.P08
|
|
29
|
+
* @requirement:REQ-SP4-002, REQ-SP4-003
|
|
30
|
+
* @pseudocode lines 10-14
|
|
31
|
+
*
|
|
32
|
+
* Simplified constructor that only stores readonly OAuth manager.
|
|
33
|
+
* All other state is now resolved per call from NormalizedGenerateChatOptions.
|
|
34
|
+
* No loggers cached - created on demand like AnthropicProvider.
|
|
35
|
+
*/
|
|
36
|
+
constructor(apiKey, baseURL, config, oauthManager) {
|
|
37
|
+
const baseConfig = {
|
|
38
|
+
name: 'gemini',
|
|
39
|
+
apiKey,
|
|
40
|
+
baseURL,
|
|
41
|
+
envKeyNames: ['GEMINI_API_KEY', 'GOOGLE_API_KEY'],
|
|
42
|
+
isOAuthEnabled: !!oauthManager,
|
|
43
|
+
oauthProvider: oauthManager ? 'gemini' : undefined,
|
|
44
|
+
oauthManager,
|
|
45
|
+
};
|
|
46
|
+
super(baseConfig, config);
|
|
47
|
+
this.geminiOAuthManager = oauthManager;
|
|
48
|
+
}
|
|
17
49
|
/**
|
|
18
50
|
* @description Cleans a JSON Schema object to ensure it strictly conforms to the Gemini API's supported Schema definition.
|
|
19
51
|
* This function acts as a whitelist, removing any properties not explicitly defined in the OpenAPI 3.03 Schema Object
|
|
@@ -85,28 +117,42 @@ export class GeminiProvider extends BaseProvider {
|
|
|
85
117
|
}
|
|
86
118
|
return cleanedSchema;
|
|
87
119
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
120
|
+
/**
|
|
121
|
+
* @plan:PLAN-20251023-STATELESS-HARDENING.P08
|
|
122
|
+
* @requirement:REQ-SP4-002
|
|
123
|
+
* Create loggers on-demand to avoid instance state, like AnthropicProvider
|
|
124
|
+
*/
|
|
125
|
+
getLogger() {
|
|
126
|
+
return new DebugLogger('llxprt:gemini:provider');
|
|
127
|
+
}
|
|
128
|
+
getToolsLogger() {
|
|
129
|
+
return new DebugLogger('llxprt:gemini:tools');
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* @plan PLAN-20251018-STATELESSPROVIDER2.P12
|
|
133
|
+
* @requirement REQ-SP2-001
|
|
134
|
+
* @pseudocode anthropic-gemini-stateless.md lines 1-2
|
|
135
|
+
*/
|
|
136
|
+
/**
|
|
137
|
+
* @plan:PLAN-20251023-STATELESS-HARDENING.P08
|
|
138
|
+
* @requirement:REQ-SP4-003
|
|
139
|
+
* Determine streaming preference from config settings per call
|
|
140
|
+
*/
|
|
141
|
+
getStreamingPreference(_options) {
|
|
142
|
+
const streamingSetting = this.providerConfig?.getEphemeralSettings?.()?.['streaming'];
|
|
143
|
+
return streamingSetting !== 'disabled';
|
|
144
|
+
}
|
|
145
|
+
async createOAuthContentGenerator(httpOptions, config, baseURL) {
|
|
146
|
+
const { createCodeAssistContentGenerator } = await import('../../code_assist/codeAssist.js');
|
|
147
|
+
return createCodeAssistContentGenerator(httpOptions, AuthType.LOGIN_WITH_GOOGLE, config, baseURL);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* @plan:PLAN-20251023-STATELESS-HARDENING.P08
|
|
151
|
+
* @requirement:REQ-SP4-002
|
|
152
|
+
* No operation - stateless provider has no cache to clear
|
|
153
|
+
*/
|
|
154
|
+
clearClientCache(_runtimeKey) {
|
|
155
|
+
this.getLogger().debug(() => 'Cache clear called on stateless provider - no operation');
|
|
110
156
|
}
|
|
111
157
|
/**
|
|
112
158
|
* Updates OAuth configuration based on current OAuth manager state
|
|
@@ -123,21 +169,21 @@ export class GeminiProvider extends BaseProvider {
|
|
|
123
169
|
}
|
|
124
170
|
}
|
|
125
171
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
172
|
+
* @plan:PLAN-20251023-STATELESS-HARDENING.P08
|
|
173
|
+
* @requirement:REQ-SP4-002, REQ-SP4-003
|
|
174
|
+
* Determines the best available authentication method per call without storing state.
|
|
175
|
+
* Returns both authMode and token - caller must handle both values.
|
|
128
176
|
*/
|
|
129
177
|
async determineBestAuth() {
|
|
130
178
|
// Re-check OAuth enablement state before determining auth
|
|
131
179
|
this.updateOAuthState();
|
|
132
180
|
// First check if we have Gemini-specific credentials
|
|
133
181
|
if (this.hasVertexAICredentials()) {
|
|
134
|
-
this.authMode = 'vertex-ai';
|
|
135
182
|
this.setupVertexAIAuth();
|
|
136
|
-
return 'USE_VERTEX_AI';
|
|
183
|
+
return { authMode: 'vertex-ai', token: 'USE_VERTEX_AI' };
|
|
137
184
|
}
|
|
138
185
|
if (this.hasGeminiAPIKey()) {
|
|
139
|
-
|
|
140
|
-
return process.env.GEMINI_API_KEY;
|
|
186
|
+
return { authMode: 'gemini-api-key', token: process.env.GEMINI_API_KEY };
|
|
141
187
|
}
|
|
142
188
|
// No Gemini-specific credentials, check OAuth availability
|
|
143
189
|
try {
|
|
@@ -151,51 +197,42 @@ export class GeminiProvider extends BaseProvider {
|
|
|
151
197
|
if (isOAuthEnabled &&
|
|
152
198
|
(authMethodName?.startsWith('oauth-') ||
|
|
153
199
|
(this.geminiOAuthManager && !token))) {
|
|
154
|
-
|
|
155
|
-
return 'USE_LOGIN_WITH_GOOGLE';
|
|
200
|
+
return { authMode: 'oauth', token: 'USE_LOGIN_WITH_GOOGLE' };
|
|
156
201
|
}
|
|
157
202
|
// If we have a token but it's not for Gemini (e.g., from another provider),
|
|
158
203
|
// we should still fall back to OAuth for Gemini web search - BUT ONLY IF OAUTH IS ENABLED
|
|
159
204
|
if (!this.hasGeminiAPIKey() && !this.hasVertexAICredentials()) {
|
|
160
205
|
if (isOAuthEnabled) {
|
|
161
|
-
|
|
162
|
-
return 'USE_LOGIN_WITH_GOOGLE';
|
|
206
|
+
return { authMode: 'oauth', token: 'USE_LOGIN_WITH_GOOGLE' };
|
|
163
207
|
}
|
|
164
208
|
else {
|
|
165
209
|
// OAuth is disabled and no other auth method available
|
|
166
210
|
throw new AuthenticationRequiredError('Web search requires Gemini authentication, but no API key is set and OAuth is disabled. Hint: call /auth gemini enable', 'none', ['GEMINI_API_KEY', 'GOOGLE_API_KEY']);
|
|
167
211
|
}
|
|
168
212
|
}
|
|
169
|
-
|
|
170
|
-
return token || '';
|
|
213
|
+
return { authMode: 'none', token: token || '' };
|
|
171
214
|
}
|
|
172
215
|
catch (error) {
|
|
173
216
|
// CRITICAL FIX: Only fall back to LOGIN_WITH_GOOGLE if OAuth is actually enabled
|
|
174
|
-
// Don't use it when OAuth has been disabled
|
|
175
217
|
const manager = this.geminiOAuthManager;
|
|
176
218
|
const isOAuthEnabled = this.geminiOAuthManager &&
|
|
177
219
|
manager.isOAuthEnabled &&
|
|
178
220
|
typeof manager.isOAuthEnabled === 'function' &&
|
|
179
221
|
manager.isOAuthEnabled('gemini');
|
|
180
222
|
if (isOAuthEnabled) {
|
|
181
|
-
|
|
182
|
-
return 'USE_LOGIN_WITH_GOOGLE';
|
|
223
|
+
return { authMode: 'oauth', token: 'USE_LOGIN_WITH_GOOGLE' };
|
|
183
224
|
}
|
|
184
225
|
// Handle case where no auth is available
|
|
185
226
|
const authType = this.globalConfig?.getContentGeneratorConfig()?.authType;
|
|
186
227
|
if (authType === AuthType.USE_NONE) {
|
|
187
|
-
|
|
188
|
-
throw new AuthenticationRequiredError('Authentication is set to USE_NONE but no credentials are available', this.authMode, ['GEMINI_API_KEY', 'GOOGLE_API_KEY']);
|
|
228
|
+
throw new AuthenticationRequiredError('Authentication is set to USE_NONE but no credentials are available', 'none', ['GEMINI_API_KEY', 'GOOGLE_API_KEY']);
|
|
189
229
|
}
|
|
190
230
|
// When used as serverToolsProvider without API key, fall back to OAuth ONLY if enabled
|
|
191
|
-
// This handles the case where Gemini is used for server tools but not as main provider
|
|
192
231
|
if (!this.hasGeminiAPIKey() && !this.hasVertexAICredentials()) {
|
|
193
232
|
if (isOAuthEnabled) {
|
|
194
|
-
|
|
195
|
-
return 'USE_LOGIN_WITH_GOOGLE';
|
|
233
|
+
return { authMode: 'oauth', token: 'USE_LOGIN_WITH_GOOGLE' };
|
|
196
234
|
}
|
|
197
235
|
else {
|
|
198
|
-
// OAuth is disabled and no other auth method available
|
|
199
236
|
throw new AuthenticationRequiredError('Web search requires Gemini authentication, but no API key is set and OAuth is disabled. Hint: call /auth gemini enable', 'none', ['GEMINI_API_KEY', 'GOOGLE_API_KEY']);
|
|
200
237
|
}
|
|
201
238
|
}
|
|
@@ -253,20 +290,30 @@ export class GeminiProvider extends BaseProvider {
|
|
|
253
290
|
setConfig(config) {
|
|
254
291
|
// Call base provider implementation
|
|
255
292
|
super.setConfig?.(config);
|
|
293
|
+
this.refreshCachedSettings();
|
|
256
294
|
// Sync with config model if user hasn't explicitly set a model
|
|
257
295
|
// This ensures consistency between config and provider state
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
296
|
+
// @plan:PLAN-20251023-STATELESS-HARDENING.P08 @requirement:REQ-SP4-002
|
|
297
|
+
// Removed model caching in stateless implementation
|
|
298
|
+
// const configModel = config.getModel();
|
|
299
|
+
// if (!this.modelExplicitlySet && configModel) {
|
|
300
|
+
// this.currentModel = configModel;
|
|
301
|
+
// }
|
|
262
302
|
// Update OAuth configuration based on OAuth manager state, not config authType
|
|
263
303
|
// This ensures that if OAuth is disabled via /auth gemini disable, it stays disabled
|
|
264
304
|
this.updateOAuthState();
|
|
265
305
|
// Clear auth cache when config changes to allow re-determination
|
|
266
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* @plan:PLAN-20251023-STATELESS-HARDENING.P08
|
|
309
|
+
* @requirement:REQ-SP4-002
|
|
310
|
+
* Determine auth mode per call instead of using cached state
|
|
311
|
+
*/
|
|
267
312
|
async getModels() {
|
|
313
|
+
// Determine auth mode for this call
|
|
314
|
+
const { authMode } = await this.determineBestAuth();
|
|
268
315
|
// For OAuth mode, return fixed list of models
|
|
269
|
-
if (
|
|
316
|
+
if (authMode === 'oauth') {
|
|
270
317
|
return [
|
|
271
318
|
{
|
|
272
319
|
id: 'gemini-2.5-pro',
|
|
@@ -289,7 +336,7 @@ export class GeminiProvider extends BaseProvider {
|
|
|
289
336
|
];
|
|
290
337
|
}
|
|
291
338
|
// For API key modes (gemini-api-key or vertex-ai), try to fetch real models
|
|
292
|
-
if (
|
|
339
|
+
if (authMode === 'gemini-api-key' || authMode === 'vertex-ai') {
|
|
293
340
|
const apiKey = (await this.getAuthToken()) || process.env.GEMINI_API_KEY;
|
|
294
341
|
if (apiKey) {
|
|
295
342
|
try {
|
|
@@ -342,35 +389,23 @@ export class GeminiProvider extends BaseProvider {
|
|
|
342
389
|
},
|
|
343
390
|
];
|
|
344
391
|
}
|
|
345
|
-
setApiKey(apiKey) {
|
|
346
|
-
// Call base provider implementation
|
|
347
|
-
super.setApiKey(apiKey);
|
|
348
|
-
// Set the API key as an environment variable so it can be used by the core library
|
|
349
|
-
// CRITICAL FIX: When clearing the key (empty string), delete the env var instead of setting to empty
|
|
350
|
-
if (apiKey && apiKey.trim() !== '') {
|
|
351
|
-
process.env.GEMINI_API_KEY = apiKey;
|
|
352
|
-
}
|
|
353
|
-
else {
|
|
354
|
-
delete process.env.GEMINI_API_KEY;
|
|
355
|
-
}
|
|
356
|
-
// Clear auth cache when API key changes
|
|
357
|
-
this.clearAuthCache();
|
|
358
|
-
}
|
|
359
|
-
setBaseUrl(baseUrl) {
|
|
360
|
-
// Call base provider implementation which stores in ephemeral settings
|
|
361
|
-
super.setBaseUrl?.(baseUrl);
|
|
362
|
-
}
|
|
363
392
|
/**
|
|
364
|
-
*
|
|
393
|
+
* @plan:PLAN-20251023-STATELESS-HARDENING.P08
|
|
394
|
+
* @requirement:REQ-SP4-002
|
|
395
|
+
* Gets the current authentication mode per call without caching
|
|
365
396
|
*/
|
|
366
|
-
getAuthMode() {
|
|
367
|
-
|
|
397
|
+
async getAuthMode() {
|
|
398
|
+
const { authMode } = await this.determineBestAuth();
|
|
399
|
+
return authMode;
|
|
368
400
|
}
|
|
369
401
|
/**
|
|
370
|
-
*
|
|
402
|
+
* @plan:PLAN-20251023-STATELESS-HARDENING.P08
|
|
403
|
+
* @requirement:REQ-SP4-002
|
|
404
|
+
* Gets the appropriate AuthType for the core library per call
|
|
371
405
|
*/
|
|
372
|
-
getCoreAuthType() {
|
|
373
|
-
|
|
406
|
+
async getCoreAuthType() {
|
|
407
|
+
const { authMode } = await this.determineBestAuth();
|
|
408
|
+
switch (authMode) {
|
|
374
409
|
case 'oauth':
|
|
375
410
|
return AuthType.LOGIN_WITH_GOOGLE;
|
|
376
411
|
case 'gemini-api-key':
|
|
@@ -382,22 +417,32 @@ export class GeminiProvider extends BaseProvider {
|
|
|
382
417
|
}
|
|
383
418
|
}
|
|
384
419
|
/**
|
|
385
|
-
*
|
|
420
|
+
* @plan:PLAN-20251023-STATELESS-HARDENING.P08
|
|
421
|
+
* @requirement:REQ-SP4-002
|
|
422
|
+
* No caching - this method is now a no-op. Settings read on demand.
|
|
423
|
+
*/
|
|
424
|
+
refreshCachedSettings() {
|
|
425
|
+
// No operation - stateless provider doesn't cache settings
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* @plan:PLAN-20251023-STATELESS-HARDENING.P08
|
|
429
|
+
* @requirement:REQ-SP4-003
|
|
430
|
+
* Gets the current model ID from SettingsService per call
|
|
386
431
|
*/
|
|
387
432
|
getCurrentModel() {
|
|
388
433
|
// Try to get from SettingsService first (source of truth)
|
|
389
434
|
try {
|
|
390
|
-
const settingsService =
|
|
435
|
+
const settingsService = this.resolveSettingsService();
|
|
391
436
|
const providerSettings = settingsService.getProviderSettings(this.name);
|
|
392
437
|
if (providerSettings.model) {
|
|
393
438
|
return providerSettings.model;
|
|
394
439
|
}
|
|
395
440
|
}
|
|
396
441
|
catch (error) {
|
|
397
|
-
this.
|
|
442
|
+
this.getLogger().debug(() => `Failed to get model from SettingsService: ${error}`);
|
|
398
443
|
}
|
|
399
|
-
//
|
|
400
|
-
return this.
|
|
444
|
+
// In stateless mode, always return default since model should come from options
|
|
445
|
+
return this.getDefaultModel();
|
|
401
446
|
}
|
|
402
447
|
/**
|
|
403
448
|
* Gets the default model for Gemini
|
|
@@ -406,61 +451,61 @@ export class GeminiProvider extends BaseProvider {
|
|
|
406
451
|
return 'gemini-2.5-pro';
|
|
407
452
|
}
|
|
408
453
|
/**
|
|
409
|
-
*
|
|
454
|
+
* @plan:PLAN-20251023-STATELESS-HARDENING.P08
|
|
455
|
+
* @requirement:REQ-SP4-003
|
|
456
|
+
* Gets model parameters from SettingsService per call
|
|
410
457
|
*/
|
|
411
|
-
|
|
412
|
-
// Update SettingsService as the source of truth
|
|
458
|
+
getModelParams() {
|
|
413
459
|
try {
|
|
414
|
-
const settingsService =
|
|
415
|
-
settingsService.
|
|
460
|
+
const settingsService = this.resolveSettingsService();
|
|
461
|
+
const providerSettings = settingsService.getProviderSettings(this.name);
|
|
462
|
+
const reservedKeys = new Set([
|
|
463
|
+
'enabled',
|
|
464
|
+
'apiKey',
|
|
465
|
+
'api-key',
|
|
466
|
+
'apiKeyfile',
|
|
467
|
+
'api-keyfile',
|
|
468
|
+
'baseUrl',
|
|
469
|
+
'base-url',
|
|
470
|
+
'model',
|
|
471
|
+
'toolFormat',
|
|
472
|
+
'tool-format',
|
|
473
|
+
'toolFormatOverride',
|
|
474
|
+
'tool-format-override',
|
|
475
|
+
'defaultModel',
|
|
476
|
+
]);
|
|
477
|
+
const params = {};
|
|
478
|
+
if (providerSettings) {
|
|
479
|
+
for (const [key, value] of Object.entries(providerSettings)) {
|
|
480
|
+
if (reservedKeys.has(key) || value === undefined || value === null) {
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
params[key] = value;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return Object.keys(params).length > 0 ? params : undefined;
|
|
416
487
|
}
|
|
417
488
|
catch (error) {
|
|
418
|
-
this.
|
|
419
|
-
|
|
420
|
-
// Keep local cache for performance
|
|
421
|
-
this.currentModel = modelId;
|
|
422
|
-
this.modelExplicitlySet = true;
|
|
423
|
-
// Always update config if available, not just in OAuth mode
|
|
424
|
-
// This ensures the model is properly synchronized
|
|
425
|
-
if (this.globalConfig) {
|
|
426
|
-
this.globalConfig.setModel(modelId);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
/**
|
|
430
|
-
* Sets additional model parameters to include in requests
|
|
431
|
-
*/
|
|
432
|
-
setModelParams(params) {
|
|
433
|
-
if (params === undefined) {
|
|
434
|
-
this.modelParams = undefined;
|
|
435
|
-
}
|
|
436
|
-
else {
|
|
437
|
-
this.modelParams = { ...this.modelParams, ...params };
|
|
489
|
+
this.getLogger().debug(() => `Failed to get Gemini provider settings from SettingsService: ${error}`);
|
|
490
|
+
return undefined;
|
|
438
491
|
}
|
|
439
492
|
}
|
|
440
493
|
/**
|
|
441
|
-
*
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
return this.modelParams;
|
|
445
|
-
}
|
|
446
|
-
/**
|
|
447
|
-
* Checks if the current auth mode requires payment
|
|
494
|
+
* @plan:PLAN-20251023-STATELESS-HARDENING.P08
|
|
495
|
+
* @requirement:REQ-SP4-002
|
|
496
|
+
* Checks if using paid mode synchronously via env vars (stateless check)
|
|
448
497
|
*/
|
|
449
498
|
isPaidMode() {
|
|
450
|
-
|
|
499
|
+
// Synchronous check based on environment variables only
|
|
500
|
+
return this.hasGeminiAPIKey() || this.hasVertexAICredentials();
|
|
451
501
|
}
|
|
452
502
|
/**
|
|
453
|
-
*
|
|
503
|
+
* @plan:PLAN-20251023-STATELESS-HARDENING.P08
|
|
504
|
+
* @requirement:REQ-SP4-002
|
|
505
|
+
* No state to clear in stateless implementation
|
|
454
506
|
*/
|
|
455
507
|
clearState() {
|
|
456
|
-
//
|
|
457
|
-
this.authMode = 'none';
|
|
458
|
-
// Only reset model if it wasn't explicitly set by user
|
|
459
|
-
if (!this.modelExplicitlySet) {
|
|
460
|
-
this.currentModel = 'gemini-2.5-pro';
|
|
461
|
-
}
|
|
462
|
-
// Note: We don't clear config or apiKey as they might be needed
|
|
463
|
-
// Clear auth cache
|
|
508
|
+
// No operation - stateless provider has no state to clear
|
|
464
509
|
this.clearAuthCache();
|
|
465
510
|
}
|
|
466
511
|
/**
|
|
@@ -478,6 +523,7 @@ export class GeminiProvider extends BaseProvider {
|
|
|
478
523
|
clearAuthCache() {
|
|
479
524
|
// Call the base implementation to clear the cached token
|
|
480
525
|
super.clearAuthCache();
|
|
526
|
+
this.clearClientCache();
|
|
481
527
|
// Don't clear the auth mode itself, just allow re-determination next time
|
|
482
528
|
}
|
|
483
529
|
/**
|
|
@@ -487,30 +533,39 @@ export class GeminiProvider extends BaseProvider {
|
|
|
487
533
|
return ['web_search', 'web_fetch'];
|
|
488
534
|
}
|
|
489
535
|
/**
|
|
490
|
-
*
|
|
536
|
+
* @plan:PLAN-20251023-STATELESS-HARDENING.P08
|
|
537
|
+
* @requirement:REQ-SP4-002
|
|
538
|
+
* Invoke a server tool using per-call auth resolution
|
|
491
539
|
*/
|
|
492
|
-
async invokeServerTool(toolName, params, _config) {
|
|
540
|
+
async invokeServerTool(toolName, params, _config, _signal) {
|
|
493
541
|
if (toolName === 'web_search') {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
542
|
+
const logger = this.getToolsLogger();
|
|
543
|
+
logger.debug(() => `invokeServerTool: web_search called with params: ${JSON.stringify(params)}`);
|
|
544
|
+
logger.debug(() => `invokeServerTool: globalConfig is ${this.globalConfig ? 'set' : 'null/undefined'}`);
|
|
545
|
+
// Check for abort before auth determination
|
|
546
|
+
if (_signal?.aborted) {
|
|
547
|
+
const error = new Error('Operation was aborted');
|
|
548
|
+
error.name = 'AbortError';
|
|
549
|
+
throw error;
|
|
550
|
+
}
|
|
497
551
|
// Import the necessary modules dynamically
|
|
498
552
|
const { GoogleGenAI } = await import('@google/genai');
|
|
499
553
|
// Create the appropriate client based on auth mode
|
|
500
554
|
const httpOptions = this.createHttpOptions();
|
|
501
555
|
let genAI;
|
|
502
|
-
// Get authentication token lazily
|
|
503
|
-
|
|
504
|
-
const authToken = await this.determineBestAuth();
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
556
|
+
// Get authentication token and mode lazily per call
|
|
557
|
+
logger.debug(() => `invokeServerTool: about to call determineBestAuth()`);
|
|
558
|
+
const { authMode, token: authToken } = await this.determineBestAuth();
|
|
559
|
+
// Check for abort after auth determination
|
|
560
|
+
if (_signal?.aborted) {
|
|
561
|
+
const error = new Error('Operation was aborted');
|
|
562
|
+
error.name = 'AbortError';
|
|
563
|
+
throw error;
|
|
509
564
|
}
|
|
510
|
-
|
|
565
|
+
logger.debug(() => `invokeServerTool: determineBestAuth returned authMode=${authMode}`);
|
|
566
|
+
switch (authMode) {
|
|
511
567
|
case 'gemini-api-key': {
|
|
512
|
-
//
|
|
513
|
-
// but add safety check
|
|
568
|
+
// Validate auth token
|
|
514
569
|
if (!authToken ||
|
|
515
570
|
authToken === 'USE_LOGIN_WITH_GOOGLE' ||
|
|
516
571
|
authToken === '') {
|
|
@@ -572,12 +627,12 @@ export class GeminiProvider extends BaseProvider {
|
|
|
572
627
|
}
|
|
573
628
|
case 'oauth': {
|
|
574
629
|
try {
|
|
575
|
-
|
|
630
|
+
logger.debug(() => `invokeServerTool: OAuth case - creating content generator`);
|
|
576
631
|
// If globalConfig is not set (e.g., when using non-Gemini provider),
|
|
577
632
|
// create a minimal config for OAuth
|
|
578
633
|
let configForOAuth = this.globalConfig;
|
|
579
634
|
if (!configForOAuth) {
|
|
580
|
-
|
|
635
|
+
logger.debug(() => `invokeServerTool: globalConfig is null, creating minimal config for OAuth`);
|
|
581
636
|
// Use crypto for UUID generation
|
|
582
637
|
const { randomUUID } = await import('crypto');
|
|
583
638
|
configForOAuth = new Config({
|
|
@@ -591,8 +646,8 @@ export class GeminiProvider extends BaseProvider {
|
|
|
591
646
|
}
|
|
592
647
|
// For OAuth, use the code assist content generator
|
|
593
648
|
// Note: Detailed logging is now handled by DebugLogger in codeAssist.ts with namespace llxprt:code:assist
|
|
594
|
-
const oauthContentGenerator = await
|
|
595
|
-
|
|
649
|
+
const oauthContentGenerator = await this.createOAuthContentGenerator(httpOptions, configForOAuth);
|
|
650
|
+
logger.debug(() => `invokeServerTool: OAuth content generator created successfully`);
|
|
596
651
|
// For web search, always use gemini-2.5-flash regardless of the active model
|
|
597
652
|
const oauthRequest = {
|
|
598
653
|
model: 'gemini-2.5-flash',
|
|
@@ -606,23 +661,29 @@ export class GeminiProvider extends BaseProvider {
|
|
|
606
661
|
tools: [{ googleSearch: {} }],
|
|
607
662
|
},
|
|
608
663
|
};
|
|
609
|
-
|
|
664
|
+
logger.debug(() => `invokeServerTool: making OAuth generateContent request with query: ${params.query}`);
|
|
610
665
|
// PRIVACY FIX: Removed sessionId to prevent transmission to Google servers
|
|
611
666
|
const result = await oauthContentGenerator.generateContent(oauthRequest, 'web-search-oauth');
|
|
612
|
-
|
|
667
|
+
logger.debug(() => `invokeServerTool: OAuth generateContent completed successfully`);
|
|
613
668
|
return result;
|
|
614
669
|
}
|
|
615
670
|
catch (error) {
|
|
616
|
-
|
|
617
|
-
|
|
671
|
+
logger.debug(() => `invokeServerTool: ERROR in OAuth case: ${error}`);
|
|
672
|
+
logger.debug(() => `invokeServerTool: Error details:`, error);
|
|
618
673
|
throw error;
|
|
619
674
|
}
|
|
620
675
|
}
|
|
621
676
|
default:
|
|
622
|
-
throw new Error(`Web search not supported in auth mode: ${
|
|
677
|
+
throw new Error(`Web search not supported in auth mode: ${authMode}`);
|
|
623
678
|
}
|
|
624
679
|
}
|
|
625
680
|
else if (toolName === 'web_fetch') {
|
|
681
|
+
// Check for abort before auth determination
|
|
682
|
+
if (_signal?.aborted) {
|
|
683
|
+
const error = new Error('Operation was aborted');
|
|
684
|
+
error.name = 'AbortError';
|
|
685
|
+
throw error;
|
|
686
|
+
}
|
|
626
687
|
// Import the necessary modules dynamically
|
|
627
688
|
const { GoogleGenAI } = await import('@google/genai');
|
|
628
689
|
// Get the prompt directly without any processing
|
|
@@ -630,9 +691,15 @@ export class GeminiProvider extends BaseProvider {
|
|
|
630
691
|
// Create the appropriate client based on auth mode
|
|
631
692
|
const httpOptions = this.createHttpOptions();
|
|
632
693
|
let genAI;
|
|
633
|
-
// Get authentication token lazily
|
|
634
|
-
const authToken = await this.determineBestAuth();
|
|
635
|
-
|
|
694
|
+
// Get authentication token and mode lazily per call
|
|
695
|
+
const { authMode, token: authToken } = await this.determineBestAuth();
|
|
696
|
+
// Check for abort after auth determination
|
|
697
|
+
if (_signal?.aborted) {
|
|
698
|
+
const error = new Error('Operation was aborted');
|
|
699
|
+
error.name = 'AbortError';
|
|
700
|
+
throw error;
|
|
701
|
+
}
|
|
702
|
+
switch (authMode) {
|
|
636
703
|
case 'gemini-api-key': {
|
|
637
704
|
genAI = new GoogleGenAI({
|
|
638
705
|
apiKey: authToken,
|
|
@@ -690,7 +757,7 @@ export class GeminiProvider extends BaseProvider {
|
|
|
690
757
|
}
|
|
691
758
|
case 'oauth': {
|
|
692
759
|
// For OAuth, use the code assist content generator
|
|
693
|
-
const oauthContentGenerator = await
|
|
760
|
+
const oauthContentGenerator = await this.createOAuthContentGenerator(httpOptions, this.globalConfig, this.getBaseURL());
|
|
694
761
|
// For web fetch, always use gemini-2.5-flash regardless of the active model
|
|
695
762
|
const oauthRequest = {
|
|
696
763
|
model: 'gemini-2.5-flash',
|
|
@@ -709,7 +776,7 @@ export class GeminiProvider extends BaseProvider {
|
|
|
709
776
|
return result;
|
|
710
777
|
}
|
|
711
778
|
default:
|
|
712
|
-
throw new Error(`Web fetch not supported in auth mode: ${
|
|
779
|
+
throw new Error(`Web fetch not supported in auth mode: ${authMode}`);
|
|
713
780
|
}
|
|
714
781
|
}
|
|
715
782
|
else {
|
|
@@ -717,32 +784,18 @@ export class GeminiProvider extends BaseProvider {
|
|
|
717
784
|
}
|
|
718
785
|
}
|
|
719
786
|
/**
|
|
720
|
-
*
|
|
721
|
-
*
|
|
787
|
+
* @plan:PLAN-20251023-STATELESS-HARDENING.P08
|
|
788
|
+
* @requirement:REQ-SP4-002, REQ-SP4-003
|
|
789
|
+
* Generate chat completion using per-call resolution of auth, model, and params
|
|
790
|
+
* All state comes from NormalizedGenerateChatOptions, no instance caching
|
|
722
791
|
*/
|
|
723
|
-
async *
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
// AbortSignal check before auth
|
|
731
|
-
if (signal?.aborted) {
|
|
732
|
-
const error = new Error('Operation was aborted');
|
|
733
|
-
error.name = 'AbortError';
|
|
734
|
-
throw error;
|
|
735
|
-
}
|
|
736
|
-
// Determine best auth method
|
|
737
|
-
const authToken = await this.determineBestAuth();
|
|
738
|
-
// AbortSignal check after auth
|
|
739
|
-
if (signal?.aborted) {
|
|
740
|
-
const error = new Error('Operation was aborted');
|
|
741
|
-
error.name = 'AbortError';
|
|
742
|
-
throw error;
|
|
743
|
-
}
|
|
744
|
-
// Import necessary modules
|
|
745
|
-
const { GoogleGenAI } = await import('@google/genai');
|
|
792
|
+
async *generateChatCompletionWithOptions(options) {
|
|
793
|
+
const streamingEnabled = this.getStreamingPreference(options);
|
|
794
|
+
const { contents: content, tools } = options;
|
|
795
|
+
// Determine best auth method per call - no state storage
|
|
796
|
+
const { authMode, token: authToken } = await this.determineBestAuth();
|
|
797
|
+
// Model is resolved from options.resolved.model - no instance caching
|
|
798
|
+
const currentModel = options.resolved.model;
|
|
746
799
|
// Convert IContent directly to Gemini format
|
|
747
800
|
const contents = [];
|
|
748
801
|
for (const c of content) {
|
|
@@ -824,116 +877,27 @@ export class GeminiProvider extends BaseProvider {
|
|
|
824
877
|
}),
|
|
825
878
|
}))
|
|
826
879
|
: undefined;
|
|
827
|
-
const
|
|
828
|
-
|
|
829
|
-
.
|
|
830
|
-
|
|
880
|
+
const toolNamesForPrompt = tools === undefined
|
|
881
|
+
? undefined
|
|
882
|
+
: Array.from(new Set(tools.flatMap((group) => group.functionDeclarations
|
|
883
|
+
.map((decl) => decl.name)
|
|
884
|
+
.filter((name) => Boolean(name)))));
|
|
885
|
+
const serverTools = ['web_search', 'web_fetch'];
|
|
886
|
+
// @plan:PLAN-20251023-STATELESS-HARDENING.P08 @requirement:REQ-SP4-003
|
|
887
|
+
// Get model params per call from ephemeral settings, not cached instance state
|
|
888
|
+
const requestOverrides = options.invocation?.ephemerals ?? {};
|
|
889
|
+
const requestConfig = {
|
|
890
|
+
serverTools,
|
|
891
|
+
...(requestOverrides ?? {}),
|
|
892
|
+
};
|
|
893
|
+
if (geminiTools) {
|
|
894
|
+
requestConfig.tools = geminiTools;
|
|
895
|
+
}
|
|
831
896
|
// Create appropriate client and generate content
|
|
897
|
+
const baseURL = options.resolved.baseURL ?? this.getBaseURL();
|
|
832
898
|
const httpOptions = this.createHttpOptions();
|
|
833
|
-
const
|
|
834
|
-
|
|
835
|
-
// AbortSignal check before client creation
|
|
836
|
-
if (signal?.aborted) {
|
|
837
|
-
const error = new Error('Operation was aborted');
|
|
838
|
-
error.name = 'AbortError';
|
|
839
|
-
throw error;
|
|
840
|
-
}
|
|
841
|
-
if (this.authMode === 'oauth') {
|
|
842
|
-
// OAuth mode
|
|
843
|
-
const configForOAuth = this.globalConfig || {
|
|
844
|
-
getProxy: () => undefined,
|
|
845
|
-
isBrowserLaunchSuppressed: () => false,
|
|
846
|
-
getNoBrowser: () => false,
|
|
847
|
-
getUserMemory: () => '',
|
|
848
|
-
};
|
|
849
|
-
const contentGenerator = await createCodeAssistContentGenerator(httpOptions, AuthType.LOGIN_WITH_GOOGLE, configForOAuth, this.getBaseURL());
|
|
850
|
-
const userMemory = this.globalConfig?.getUserMemory
|
|
851
|
-
? this.globalConfig.getUserMemory()
|
|
852
|
-
: '';
|
|
853
|
-
const systemInstruction = await getCoreSystemPromptAsync(userMemory, this.currentModel, toolNamesArg);
|
|
854
|
-
// For OAuth/CodeAssist mode, inject system prompt as first user message
|
|
855
|
-
// This ensures the CodeAssist endpoint receives the full context
|
|
856
|
-
// Similar to how AnthropicProvider handles OAuth mode
|
|
857
|
-
const contentsWithSystemPrompt = [
|
|
858
|
-
{
|
|
859
|
-
role: 'user',
|
|
860
|
-
parts: [
|
|
861
|
-
{
|
|
862
|
-
text: `<system>\n${systemInstruction}\n</system>\n\nUser provided conversation begins here:`,
|
|
863
|
-
},
|
|
864
|
-
],
|
|
865
|
-
},
|
|
866
|
-
...contents,
|
|
867
|
-
];
|
|
868
|
-
// Streaming request abort check
|
|
869
|
-
if (signal?.aborted) {
|
|
870
|
-
const error = new Error('Operation was aborted');
|
|
871
|
-
error.name = 'AbortError';
|
|
872
|
-
throw error;
|
|
873
|
-
}
|
|
874
|
-
const request = {
|
|
875
|
-
model: this.currentModel,
|
|
876
|
-
contents: contentsWithSystemPrompt,
|
|
877
|
-
// Still pass systemInstruction for SDK compatibility
|
|
878
|
-
systemInstruction,
|
|
879
|
-
config: {
|
|
880
|
-
tools: geminiTools,
|
|
881
|
-
...this.modelParams,
|
|
882
|
-
},
|
|
883
|
-
};
|
|
884
|
-
return await contentGenerator.generateContentStream(request, 'oauth-session');
|
|
885
|
-
}
|
|
886
|
-
else {
|
|
887
|
-
// API key mode
|
|
888
|
-
// AbortSignal check before client creation
|
|
889
|
-
if (signal?.aborted) {
|
|
890
|
-
const error = new Error('Operation was aborted');
|
|
891
|
-
error.name = 'AbortError';
|
|
892
|
-
throw error;
|
|
893
|
-
}
|
|
894
|
-
const genAI = new GoogleGenAI({
|
|
895
|
-
apiKey: authToken,
|
|
896
|
-
vertexai: this.authMode === 'vertex-ai',
|
|
897
|
-
httpOptions: this.getBaseURL()
|
|
898
|
-
? { ...httpOptions, baseUrl: this.getBaseURL() }
|
|
899
|
-
: httpOptions,
|
|
900
|
-
});
|
|
901
|
-
const contentGenerator = genAI.models;
|
|
902
|
-
const userMemory = this.globalConfig?.getUserMemory
|
|
903
|
-
? this.globalConfig.getUserMemory()
|
|
904
|
-
: '';
|
|
905
|
-
const systemInstruction = await getCoreSystemPromptAsync(userMemory, this.currentModel, toolNamesArg);
|
|
906
|
-
// Streaming request abort check
|
|
907
|
-
if (signal?.aborted) {
|
|
908
|
-
const error = new Error('Operation was aborted');
|
|
909
|
-
error.name = 'AbortError';
|
|
910
|
-
throw error;
|
|
911
|
-
}
|
|
912
|
-
const request = {
|
|
913
|
-
model: this.currentModel,
|
|
914
|
-
contents,
|
|
915
|
-
systemInstruction,
|
|
916
|
-
config: {
|
|
917
|
-
tools: geminiTools,
|
|
918
|
-
...this.modelParams,
|
|
919
|
-
},
|
|
920
|
-
};
|
|
921
|
-
return await contentGenerator.generateContentStream(request);
|
|
922
|
-
}
|
|
923
|
-
}, {
|
|
924
|
-
maxAttempts,
|
|
925
|
-
initialDelayMs,
|
|
926
|
-
shouldRetry: this.shouldRetryGeminiResponse.bind(this),
|
|
927
|
-
trackThrottleWaitTime: this.throttleTracker,
|
|
928
|
-
});
|
|
929
|
-
// Stream responses as IContent
|
|
930
|
-
for await (const response of stream) {
|
|
931
|
-
// Critical streaming loop abort check
|
|
932
|
-
if (signal?.aborted) {
|
|
933
|
-
const error = new Error('Operation was aborted');
|
|
934
|
-
error.name = 'AbortError';
|
|
935
|
-
throw error;
|
|
936
|
-
}
|
|
899
|
+
const mapResponseToChunks = (response) => {
|
|
900
|
+
const chunks = [];
|
|
937
901
|
const text = response.candidates?.[0]?.content?.parts
|
|
938
902
|
?.filter((part) => 'text' in part)
|
|
939
903
|
?.map((part) => part.text)
|
|
@@ -941,15 +905,12 @@ export class GeminiProvider extends BaseProvider {
|
|
|
941
905
|
const functionCalls = response.candidates?.[0]?.content?.parts
|
|
942
906
|
?.filter((part) => 'functionCall' in part)
|
|
943
907
|
?.map((part) => part.functionCall) || [];
|
|
944
|
-
// Extract usage metadata from response
|
|
945
908
|
const usageMetadata = response.usageMetadata;
|
|
946
|
-
// Yield text if present
|
|
947
909
|
if (text) {
|
|
948
910
|
const textContent = {
|
|
949
911
|
speaker: 'ai',
|
|
950
912
|
blocks: [{ type: 'text', text }],
|
|
951
913
|
};
|
|
952
|
-
// Add usage metadata if present
|
|
953
914
|
if (usageMetadata) {
|
|
954
915
|
textContent.metadata = {
|
|
955
916
|
usage: {
|
|
@@ -961,9 +922,8 @@ export class GeminiProvider extends BaseProvider {
|
|
|
961
922
|
},
|
|
962
923
|
};
|
|
963
924
|
}
|
|
964
|
-
|
|
925
|
+
chunks.push(textContent);
|
|
965
926
|
}
|
|
966
|
-
// Yield tool calls if present
|
|
967
927
|
if (functionCalls.length > 0) {
|
|
968
928
|
const blocks = functionCalls.map((call) => ({
|
|
969
929
|
type: 'tool_call',
|
|
@@ -976,7 +936,6 @@ export class GeminiProvider extends BaseProvider {
|
|
|
976
936
|
speaker: 'ai',
|
|
977
937
|
blocks,
|
|
978
938
|
};
|
|
979
|
-
// Add usage metadata if present
|
|
980
939
|
if (usageMetadata) {
|
|
981
940
|
toolCallContent.metadata = {
|
|
982
941
|
usage: {
|
|
@@ -988,11 +947,10 @@ export class GeminiProvider extends BaseProvider {
|
|
|
988
947
|
},
|
|
989
948
|
};
|
|
990
949
|
}
|
|
991
|
-
|
|
950
|
+
chunks.push(toolCallContent);
|
|
992
951
|
}
|
|
993
|
-
// If we have usage metadata but no content blocks, emit a metadata-only response
|
|
994
952
|
if (usageMetadata && !text && functionCalls.length === 0) {
|
|
995
|
-
|
|
953
|
+
chunks.push({
|
|
996
954
|
speaker: 'ai',
|
|
997
955
|
blocks: [],
|
|
998
956
|
metadata: {
|
|
@@ -1004,27 +962,144 @@ export class GeminiProvider extends BaseProvider {
|
|
|
1004
962
|
(usageMetadata.candidatesTokenCount || 0),
|
|
1005
963
|
},
|
|
1006
964
|
},
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
if (!usageMetadata && !text && functionCalls.length === 0) {
|
|
968
|
+
chunks.push({
|
|
969
|
+
speaker: 'ai',
|
|
970
|
+
blocks: [],
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
return chunks;
|
|
974
|
+
};
|
|
975
|
+
let stream = null;
|
|
976
|
+
let emitted = false;
|
|
977
|
+
// @plan:PLAN-20251023-STATELESS-HARDENING.P08 @requirement:REQ-SP4-002
|
|
978
|
+
// No caching - create client per call based on resolved authMode
|
|
979
|
+
if (authMode === 'oauth') {
|
|
980
|
+
const configForOAuth = this.globalConfig || {
|
|
981
|
+
getProxy: () => undefined,
|
|
982
|
+
isBrowserLaunchSuppressed: () => false,
|
|
983
|
+
getNoBrowser: () => false,
|
|
984
|
+
getUserMemory: () => '',
|
|
985
|
+
};
|
|
986
|
+
// Create OAuth content generator per call - no caching
|
|
987
|
+
const contentGenerator = await this.createOAuthContentGenerator(httpOptions, configForOAuth, baseURL);
|
|
988
|
+
const userMemory = this.globalConfig?.getUserMemory
|
|
989
|
+
? this.globalConfig.getUserMemory()
|
|
990
|
+
: '';
|
|
991
|
+
const systemInstruction = await getCoreSystemPromptAsync(userMemory, currentModel, toolNamesForPrompt);
|
|
992
|
+
const contentsWithSystemPrompt = [
|
|
993
|
+
{
|
|
994
|
+
role: 'user',
|
|
995
|
+
parts: [
|
|
996
|
+
{
|
|
997
|
+
text: `<system>\n${systemInstruction}\n</system>\n\nUser provided conversation begins here:`,
|
|
998
|
+
},
|
|
999
|
+
],
|
|
1000
|
+
},
|
|
1001
|
+
...contents,
|
|
1002
|
+
];
|
|
1003
|
+
const oauthConfig = { ...requestConfig };
|
|
1004
|
+
const oauthRequest = {
|
|
1005
|
+
model: currentModel,
|
|
1006
|
+
contents: contentsWithSystemPrompt,
|
|
1007
|
+
systemInstruction,
|
|
1008
|
+
config: oauthConfig,
|
|
1009
|
+
};
|
|
1010
|
+
// Use runtime metadata from options for session ID
|
|
1011
|
+
const runtimeId = options.runtime?.runtimeId || 'default';
|
|
1012
|
+
const sessionId = `oauth-session:${runtimeId}:${Math.random()
|
|
1013
|
+
.toString(36)
|
|
1014
|
+
.slice(2)}`;
|
|
1015
|
+
const generatorWithStream = contentGenerator;
|
|
1016
|
+
if (!streamingEnabled && generatorWithStream.generateContent) {
|
|
1017
|
+
const response = await generatorWithStream.generateContent(oauthRequest, sessionId);
|
|
1018
|
+
let yielded = false;
|
|
1019
|
+
for (const chunk of mapResponseToChunks(response)) {
|
|
1020
|
+
yielded = true;
|
|
1021
|
+
yield chunk;
|
|
1022
|
+
}
|
|
1023
|
+
if (!yielded) {
|
|
1024
|
+
yield { speaker: 'ai', blocks: [] };
|
|
1025
|
+
}
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
const oauthStream = await generatorWithStream.generateContentStream(oauthRequest, sessionId);
|
|
1029
|
+
stream = oauthStream;
|
|
1030
|
+
if (streamingEnabled) {
|
|
1031
|
+
emitted = true;
|
|
1032
|
+
yield {
|
|
1033
|
+
speaker: 'ai',
|
|
1034
|
+
blocks: [],
|
|
1035
|
+
metadata: {
|
|
1036
|
+
session: sessionId,
|
|
1037
|
+
runtime: runtimeId,
|
|
1038
|
+
authMode: 'oauth',
|
|
1039
|
+
},
|
|
1007
1040
|
};
|
|
1008
1041
|
}
|
|
1009
1042
|
}
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1043
|
+
else {
|
|
1044
|
+
// @plan:PLAN-20251023-STATELESS-HARDENING.P08 @requirement:REQ-SP4-002
|
|
1045
|
+
// Create Google GenAI client per call - no caching
|
|
1046
|
+
const { GoogleGenAI } = await import('@google/genai');
|
|
1047
|
+
const genAI = new GoogleGenAI({
|
|
1048
|
+
apiKey: authToken,
|
|
1049
|
+
vertexai: authMode === 'vertex-ai',
|
|
1050
|
+
httpOptions: baseURL
|
|
1051
|
+
? { ...httpOptions, baseUrl: baseURL }
|
|
1052
|
+
: httpOptions,
|
|
1053
|
+
});
|
|
1054
|
+
const contentGenerator = genAI.models;
|
|
1055
|
+
const userMemory = this.globalConfig?.getUserMemory
|
|
1056
|
+
? this.globalConfig.getUserMemory()
|
|
1057
|
+
: '';
|
|
1058
|
+
const systemInstruction = await getCoreSystemPromptAsync(userMemory, currentModel, toolNamesForPrompt);
|
|
1059
|
+
const apiRequest = {
|
|
1060
|
+
model: currentModel,
|
|
1061
|
+
contents,
|
|
1062
|
+
systemInstruction,
|
|
1063
|
+
config: { ...requestConfig },
|
|
1064
|
+
};
|
|
1065
|
+
if (streamingEnabled) {
|
|
1066
|
+
stream = await contentGenerator.generateContentStream(apiRequest);
|
|
1067
|
+
}
|
|
1068
|
+
else {
|
|
1069
|
+
const response = await contentGenerator.generateContent(apiRequest);
|
|
1070
|
+
let yielded = false;
|
|
1071
|
+
for (const chunk of mapResponseToChunks(response)) {
|
|
1072
|
+
yielded = true;
|
|
1073
|
+
yield chunk;
|
|
1074
|
+
}
|
|
1075
|
+
if (!yielded) {
|
|
1076
|
+
yield { speaker: 'ai', blocks: [] };
|
|
1077
|
+
}
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1022
1080
|
}
|
|
1023
|
-
if (
|
|
1024
|
-
|
|
1025
|
-
|
|
1081
|
+
if (stream) {
|
|
1082
|
+
const iterator = typeof stream[Symbol.asyncIterator] === 'function'
|
|
1083
|
+
? stream[Symbol.asyncIterator]()
|
|
1084
|
+
: stream;
|
|
1085
|
+
while (true) {
|
|
1086
|
+
const { value, done } = await iterator.next();
|
|
1087
|
+
if (done) {
|
|
1088
|
+
break;
|
|
1089
|
+
}
|
|
1090
|
+
const mapped = mapResponseToChunks(value);
|
|
1091
|
+
if (mapped.length === 0) {
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
emitted = true;
|
|
1095
|
+
for (const chunk of mapped) {
|
|
1096
|
+
yield chunk;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
if (!emitted) {
|
|
1101
|
+
yield { speaker: 'ai', blocks: [] };
|
|
1026
1102
|
}
|
|
1027
|
-
return false;
|
|
1028
1103
|
}
|
|
1029
1104
|
}
|
|
1030
1105
|
//# sourceMappingURL=GeminiProvider.js.map
|