@vybestack/llxprt-code-core 0.4.8 → 0.5.0-nightly.251102.f115237d
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 +217 -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 +383 -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.js +41 -1
- 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 +373 -532
- 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 +58 -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 +58 -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/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
|
@@ -8,17 +8,12 @@
|
|
|
8
8
|
import { createUserContent, } from '@google/genai';
|
|
9
9
|
import { retryWithBackoff } from '../utils/retry.js';
|
|
10
10
|
import { isFunctionResponse } from '../utils/messageInspectors.js';
|
|
11
|
-
import { HistoryService } from '../services/history/HistoryService.js';
|
|
12
11
|
import { ContentConverters } from '../services/history/ContentConverters.js';
|
|
13
|
-
// import { estimateTokens } from '../utils/toolOutputLimiter.js'; // Unused after retry stream refactor
|
|
14
|
-
import { logApiRequest, logApiResponse, logApiError, } from '../telemetry/loggers.js';
|
|
15
|
-
import { ApiErrorEvent, ApiRequestEvent, ApiResponseEvent, } from '../telemetry/types.js';
|
|
16
12
|
import { DEFAULT_GEMINI_FLASH_MODEL } from '../config/models.js';
|
|
17
13
|
import { hasCycleInSchema } from '../tools/tools.js';
|
|
18
14
|
import { isStructuredError } from '../utils/quotaErrorDetection.js';
|
|
19
15
|
import { DebugLogger } from '../debug/index.js';
|
|
20
16
|
import { getCompressionPrompt } from './prompts.js';
|
|
21
|
-
import { COMPRESSION_TOKEN_THRESHOLD, COMPRESSION_PRESERVE_THRESHOLD, } from './compression-config.js';
|
|
22
17
|
import { estimateTokens as estimateTextTokens } from '../utils/toolOutputLimiter.js';
|
|
23
18
|
import { tokenLimit } from './tokenLimits.js';
|
|
24
19
|
export var StreamEventType;
|
|
@@ -255,8 +250,6 @@ export class EmptyStreamError extends Error {
|
|
|
255
250
|
* The session maintains all the turns between user and model.
|
|
256
251
|
*/
|
|
257
252
|
export class GeminiChat {
|
|
258
|
-
config;
|
|
259
|
-
generationConfig;
|
|
260
253
|
static TOKEN_SAFETY_MARGIN = 256;
|
|
261
254
|
static DEFAULT_COMPLETION_BUDGET = 65_536;
|
|
262
255
|
// A promise to represent the current state of the message being sent to the
|
|
@@ -264,31 +257,50 @@ export class GeminiChat {
|
|
|
264
257
|
sendPromise = Promise.resolve();
|
|
265
258
|
// A promise to represent any ongoing compression operation
|
|
266
259
|
compressionPromise = null;
|
|
267
|
-
historyService;
|
|
268
260
|
logger = new DebugLogger('llxprt:gemini:chat');
|
|
269
261
|
// Cache the compression threshold to avoid recalculating
|
|
270
262
|
cachedCompressionThreshold = null;
|
|
271
|
-
|
|
272
|
-
|
|
263
|
+
generationConfig;
|
|
264
|
+
/**
|
|
265
|
+
* Runtime state for stateless operation (Phase 6)
|
|
266
|
+
* @plan PLAN-20251028-STATELESS6.P10
|
|
267
|
+
* @requirement REQ-STAT6-001.2
|
|
268
|
+
* @pseudocode agent-runtime-context.md lines 83-91 (step 006)
|
|
269
|
+
*/
|
|
270
|
+
runtimeState;
|
|
271
|
+
historyService;
|
|
272
|
+
runtimeContext;
|
|
273
|
+
/**
|
|
274
|
+
* @plan PLAN-20251028-STATELESS6.P10
|
|
275
|
+
* @requirement REQ-STAT6-001.2, REQ-STAT6-002.2, REQ-STAT6-002.3
|
|
276
|
+
* @pseudocode agent-runtime-context.md lines 83-91 (step 006.1-006.2)
|
|
277
|
+
*
|
|
278
|
+
* Phase 6 constructor: Accept AgentRuntimeContext as first parameter
|
|
279
|
+
* Eliminates Config dependency by using runtime view adapters
|
|
280
|
+
*/
|
|
281
|
+
constructor(view, contentGenerator, generationConfig = {}, initialHistory = []) {
|
|
282
|
+
if (!view) {
|
|
283
|
+
throw new Error('AgentRuntimeContext is required for GeminiChat');
|
|
284
|
+
}
|
|
285
|
+
// Step 006.2: Extract runtime state and history from view
|
|
286
|
+
this.runtimeContext = view;
|
|
287
|
+
this.runtimeState = view.state;
|
|
288
|
+
this.historyService = view.history;
|
|
273
289
|
this.generationConfig = generationConfig;
|
|
290
|
+
void contentGenerator;
|
|
274
291
|
validateHistory(initialHistory);
|
|
275
|
-
|
|
276
|
-
this.historyService = historyService || new HistoryService();
|
|
292
|
+
const model = this.runtimeState.model;
|
|
277
293
|
this.logger.debug('GeminiChat initialized:', {
|
|
278
|
-
model
|
|
294
|
+
model,
|
|
279
295
|
initialHistoryLength: initialHistory.length,
|
|
280
|
-
hasHistoryService: !!historyService,
|
|
296
|
+
hasHistoryService: !!this.historyService,
|
|
297
|
+
hasRuntimeState: true,
|
|
281
298
|
});
|
|
282
|
-
// Convert and add initial history if provided
|
|
283
299
|
if (initialHistory.length > 0) {
|
|
284
|
-
const currentModel = this.config.getModel();
|
|
285
|
-
this.logger.debug('Adding initial history to service:', {
|
|
286
|
-
count: initialHistory.length,
|
|
287
|
-
});
|
|
288
300
|
const idGen = this.historyService.getIdGeneratorCallback();
|
|
289
301
|
for (const content of initialHistory) {
|
|
290
302
|
const matcher = this.makePositionMatcher();
|
|
291
|
-
this.historyService.add(ContentConverters.toIContent(content, idGen, matcher),
|
|
303
|
+
this.historyService.add(ContentConverters.toIContent(content, idGen, matcher), model);
|
|
292
304
|
}
|
|
293
305
|
}
|
|
294
306
|
}
|
|
@@ -314,17 +326,77 @@ export class GeminiChat {
|
|
|
314
326
|
_getRequestTextFromContents(contents) {
|
|
315
327
|
return JSON.stringify(contents);
|
|
316
328
|
}
|
|
317
|
-
|
|
329
|
+
buildProviderRuntime(source, metadata = {}) {
|
|
330
|
+
const baseRuntime = this.runtimeContext.providerRuntime;
|
|
331
|
+
const runtimeId = baseRuntime.runtimeId ?? this.runtimeState.runtimeId ?? 'geminiChat';
|
|
332
|
+
return {
|
|
333
|
+
...baseRuntime,
|
|
334
|
+
runtimeId,
|
|
335
|
+
metadata: {
|
|
336
|
+
...(baseRuntime.metadata ?? {}),
|
|
337
|
+
source,
|
|
338
|
+
...metadata,
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* @plan PLAN-20251028-STATELESS6.P10
|
|
344
|
+
* @requirement REQ-STAT6-002.3
|
|
345
|
+
* @pseudocode agent-runtime-context.md line 88 (step 006.5)
|
|
346
|
+
*/
|
|
347
|
+
async _logApiRequest(contents, model, promptId) {
|
|
318
348
|
const requestText = this._getRequestTextFromContents(contents);
|
|
319
|
-
|
|
349
|
+
// Step 006.5: Replace telemetry logging with view.telemetry adapter
|
|
350
|
+
this.runtimeContext.telemetry.logApiRequest({
|
|
351
|
+
model,
|
|
352
|
+
promptId,
|
|
353
|
+
requestText,
|
|
354
|
+
sessionId: this.runtimeState.sessionId,
|
|
355
|
+
runtimeId: this.runtimeState.runtimeId,
|
|
356
|
+
provider: this.runtimeState.provider,
|
|
357
|
+
authType: this.runtimeState.authType,
|
|
358
|
+
timestamp: Date.now(),
|
|
359
|
+
});
|
|
320
360
|
}
|
|
321
|
-
|
|
322
|
-
|
|
361
|
+
/**
|
|
362
|
+
* @plan PLAN-20251028-STATELESS6.P10
|
|
363
|
+
* @requirement REQ-STAT6-002.3
|
|
364
|
+
* @pseudocode agent-runtime-context.md line 88 (step 006.5)
|
|
365
|
+
*/
|
|
366
|
+
async _logApiResponse(durationMs, promptId, usageMetadata, responseText) {
|
|
367
|
+
// Step 006.5: Replace telemetry logging with view.telemetry adapter
|
|
368
|
+
this.runtimeContext.telemetry.logApiResponse({
|
|
369
|
+
model: this.runtimeState.model,
|
|
370
|
+
promptId,
|
|
371
|
+
durationMs,
|
|
372
|
+
authType: this.runtimeState.authType,
|
|
373
|
+
sessionId: this.runtimeState.sessionId,
|
|
374
|
+
runtimeId: this.runtimeState.runtimeId,
|
|
375
|
+
provider: this.runtimeState.provider,
|
|
376
|
+
usageMetadata,
|
|
377
|
+
responseText,
|
|
378
|
+
});
|
|
323
379
|
}
|
|
324
|
-
|
|
380
|
+
/**
|
|
381
|
+
* @plan PLAN-20251028-STATELESS6.P10
|
|
382
|
+
* @requirement REQ-STAT6-002.3
|
|
383
|
+
* @pseudocode agent-runtime-context.md line 88 (step 006.5)
|
|
384
|
+
*/
|
|
385
|
+
_logApiError(durationMs, error, promptId) {
|
|
325
386
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
326
387
|
const errorType = error instanceof Error ? error.name : 'unknown';
|
|
327
|
-
|
|
388
|
+
// Step 006.5: Replace telemetry logging with view.telemetry adapter
|
|
389
|
+
this.runtimeContext.telemetry.logApiError({
|
|
390
|
+
model: this.runtimeState.model,
|
|
391
|
+
promptId,
|
|
392
|
+
durationMs,
|
|
393
|
+
error: errorMessage,
|
|
394
|
+
errorType,
|
|
395
|
+
authType: this.runtimeState.authType,
|
|
396
|
+
sessionId: this.runtimeState.sessionId,
|
|
397
|
+
runtimeId: this.runtimeState.runtimeId,
|
|
398
|
+
provider: this.runtimeState.provider,
|
|
399
|
+
});
|
|
328
400
|
}
|
|
329
401
|
setSystemInstruction(sysInstr) {
|
|
330
402
|
this.generationConfig.systemInstruction = sysInstr;
|
|
@@ -336,6 +408,9 @@ export class GeminiChat {
|
|
|
336
408
|
getHistoryService() {
|
|
337
409
|
return this.historyService;
|
|
338
410
|
}
|
|
411
|
+
getToolsView() {
|
|
412
|
+
return this.runtimeContext.tools;
|
|
413
|
+
}
|
|
339
414
|
/**
|
|
340
415
|
* Sends a message to the model and returns the response.
|
|
341
416
|
*
|
|
@@ -368,10 +443,41 @@ export class GeminiChat {
|
|
|
368
443
|
await this.ensureCompressionBeforeSend(prompt_id, pendingTokens, 'send');
|
|
369
444
|
// DO NOT add user content to history yet - use send-then-commit pattern
|
|
370
445
|
// Get the active provider
|
|
371
|
-
|
|
446
|
+
// @plan PLAN-20251028-STATELESS6.P10
|
|
447
|
+
// @requirement REQ-STAT6-002.2
|
|
448
|
+
// @pseudocode agent-runtime-context.md line 87 (step 006.4)
|
|
449
|
+
let provider = this.getActiveProvider();
|
|
372
450
|
if (!provider) {
|
|
373
451
|
throw new Error('No active provider configured');
|
|
374
452
|
}
|
|
453
|
+
// Step 006.4: Replace providerManager access with view.provider adapter
|
|
454
|
+
const desiredProviderName = this.runtimeState.provider;
|
|
455
|
+
if (desiredProviderName && provider.name !== desiredProviderName) {
|
|
456
|
+
const previousProviderName = provider.name;
|
|
457
|
+
try {
|
|
458
|
+
this.runtimeContext.provider.setActiveProvider(desiredProviderName);
|
|
459
|
+
const updatedProvider = this.runtimeContext.provider.getActiveProvider();
|
|
460
|
+
if (updatedProvider) {
|
|
461
|
+
provider = updatedProvider;
|
|
462
|
+
}
|
|
463
|
+
this.logger.debug(() => `[GeminiChat] enforced provider switch to '${desiredProviderName}' (previous '${previousProviderName}')`);
|
|
464
|
+
}
|
|
465
|
+
catch (error) {
|
|
466
|
+
this.logger.debug(() => `[GeminiChat] provider switch skipped (read-only context): ${error instanceof Error ? error.message : String(error)}`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
const activeAuthType = this.runtimeState.authType;
|
|
470
|
+
const providerBaseUrl = this.resolveProviderBaseUrl(provider);
|
|
471
|
+
// @plan PLAN-20251027-STATELESS5.P10
|
|
472
|
+
// @requirement REQ-STAT5-004.1
|
|
473
|
+
this.logger.debug(() => '[GeminiChat] Active provider snapshot before stream/send', {
|
|
474
|
+
providerName: provider.name,
|
|
475
|
+
providerDefaultModel: provider.getDefaultModel?.(),
|
|
476
|
+
configModel: this.runtimeState.model,
|
|
477
|
+
baseUrl: providerBaseUrl,
|
|
478
|
+
authType: activeAuthType,
|
|
479
|
+
});
|
|
480
|
+
// Enforce context window limits before proceeding
|
|
375
481
|
await this.enforceContextWindow(pendingTokens, prompt_id, provider);
|
|
376
482
|
// Check if provider supports IContent interface
|
|
377
483
|
if (!this.providerSupportsIContent(provider)) {
|
|
@@ -381,12 +487,16 @@ export class GeminiChat {
|
|
|
381
487
|
const currentHistory = this.historyService.getCuratedForProvider();
|
|
382
488
|
// Build request with history + new message(s)
|
|
383
489
|
const iContents = [...currentHistory, ...userIContents];
|
|
384
|
-
|
|
490
|
+
// @plan PLAN-20251027-STATELESS5.P10
|
|
491
|
+
// @requirement REQ-STAT5-004.1
|
|
492
|
+
this._logApiRequest(ContentConverters.toGeminiContents(iContents), this.runtimeState.model, prompt_id);
|
|
385
493
|
const startTime = Date.now();
|
|
386
494
|
let response;
|
|
387
495
|
try {
|
|
388
496
|
const apiCall = async () => {
|
|
389
|
-
|
|
497
|
+
// @plan PLAN-20251027-STATELESS5.P10
|
|
498
|
+
// @requirement REQ-STAT5-004.1
|
|
499
|
+
const modelToUse = this.runtimeState.model || DEFAULT_GEMINI_FLASH_MODEL;
|
|
390
500
|
// Get tools in the format the provider expects
|
|
391
501
|
const tools = this.generationConfig.tools;
|
|
392
502
|
// Critical debug for intermittent "Tool not present" errors
|
|
@@ -426,7 +536,24 @@ export class GeminiChat {
|
|
|
426
536
|
providerName: provider.name,
|
|
427
537
|
});
|
|
428
538
|
// Call the provider directly with IContent
|
|
429
|
-
|
|
539
|
+
// @plan PLAN-20251027-STATELESS5.P10
|
|
540
|
+
// @requirement REQ-STAT5-004.1
|
|
541
|
+
this.logger.debug(() => '[GeminiChat] Calling provider.generateChatCompletion', {
|
|
542
|
+
providerName: provider.name,
|
|
543
|
+
model: this.runtimeState.model,
|
|
544
|
+
toolCount: tools?.length ?? 0,
|
|
545
|
+
baseUrl: this.resolveProviderBaseUrl(provider),
|
|
546
|
+
authType: this.runtimeState.authType,
|
|
547
|
+
});
|
|
548
|
+
const runtimeContext = this.buildProviderRuntime('GeminiChat.trySendMessage', { toolCount: tools?.length ?? 0 });
|
|
549
|
+
const streamResponse = provider.generateChatCompletion({
|
|
550
|
+
contents: iContents,
|
|
551
|
+
tools: tools,
|
|
552
|
+
config: runtimeContext.config,
|
|
553
|
+
runtime: runtimeContext,
|
|
554
|
+
settings: runtimeContext.settingsService,
|
|
555
|
+
metadata: runtimeContext.metadata,
|
|
556
|
+
});
|
|
430
557
|
// Collect all chunks from the stream
|
|
431
558
|
let lastResponse;
|
|
432
559
|
for await (const iContent of streamResponse) {
|
|
@@ -457,7 +584,9 @@ export class GeminiChat {
|
|
|
457
584
|
this.sendPromise = (async () => {
|
|
458
585
|
const outputContent = response.candidates?.[0]?.content;
|
|
459
586
|
// Send-then-commit: Now that we have a successful response, add both user and model messages
|
|
460
|
-
|
|
587
|
+
// @plan PLAN-20251027-STATELESS5.P10
|
|
588
|
+
// @requirement REQ-STAT5-004.1
|
|
589
|
+
const currentModel = this.runtimeState.model;
|
|
461
590
|
// Handle AFC history or regular history
|
|
462
591
|
const fullAutomaticFunctionCallingHistory = response.automaticFunctionCallingHistory;
|
|
463
592
|
if (fullAutomaticFunctionCallingHistory &&
|
|
@@ -546,7 +675,9 @@ export class GeminiChat {
|
|
|
546
675
|
*/
|
|
547
676
|
async sendMessageStream(params, prompt_id) {
|
|
548
677
|
this.logger.debug(() => 'DEBUG [geminiChat]: ===== SEND MESSAGE STREAM START =====');
|
|
549
|
-
|
|
678
|
+
// @plan PLAN-20251027-STATELESS5.P10
|
|
679
|
+
// @requirement REQ-STAT5-004.1
|
|
680
|
+
this.logger.debug(() => `DEBUG [geminiChat]: Model from config: ${this.runtimeState.model}`);
|
|
550
681
|
this.logger.debug(() => `DEBUG [geminiChat]: Params: ${JSON.stringify(params, null, 2)}`);
|
|
551
682
|
this.logger.debug(() => `DEBUG [geminiChat]: Message type: ${typeof params.message}`);
|
|
552
683
|
this.logger.debug(() => `DEBUG [geminiChat]: Message content: ${JSON.stringify(params.message, null, 2)}`);
|
|
@@ -576,7 +707,7 @@ export class GeminiChat {
|
|
|
576
707
|
return (async function* (instance) {
|
|
577
708
|
try {
|
|
578
709
|
let lastError = new Error('Request failed after all retries.');
|
|
579
|
-
for (let attempt = 0; attempt
|
|
710
|
+
for (let attempt = 0; attempt < INVALID_CONTENT_RETRY_OPTIONS.maxAttempts; attempt++) {
|
|
580
711
|
try {
|
|
581
712
|
if (attempt > 0) {
|
|
582
713
|
yield { type: StreamEventType.RETRY };
|
|
@@ -613,12 +744,142 @@ export class GeminiChat {
|
|
|
613
744
|
}
|
|
614
745
|
})(this);
|
|
615
746
|
}
|
|
747
|
+
async generateDirectMessage(params, prompt_id) {
|
|
748
|
+
const provider = this.getActiveProvider();
|
|
749
|
+
if (!provider) {
|
|
750
|
+
throw new Error('No active provider configured');
|
|
751
|
+
}
|
|
752
|
+
const userContent = normalizeToolInteractionInput(params.message);
|
|
753
|
+
const idGen = this.historyService.getIdGeneratorCallback();
|
|
754
|
+
const matcher = this.makePositionMatcher();
|
|
755
|
+
const userIContents = Array.isArray(userContent)
|
|
756
|
+
? userContent.map((content) => ContentConverters.toIContent(content, idGen, matcher))
|
|
757
|
+
: [ContentConverters.toIContent(userContent, idGen, matcher)];
|
|
758
|
+
const requestContents = ContentConverters.toGeminiContents(userIContents);
|
|
759
|
+
// @plan PLAN-20251027-STATELESS5.P10
|
|
760
|
+
// @requirement REQ-STAT5-004.1
|
|
761
|
+
await this._logApiRequest(requestContents, this.runtimeState.model, prompt_id);
|
|
762
|
+
const startTime = Date.now();
|
|
763
|
+
let aggregatedText = '';
|
|
764
|
+
try {
|
|
765
|
+
const response = await retryWithBackoff(async () => {
|
|
766
|
+
const toolsFromConfig = params.config?.tools && Array.isArray(params.config.tools)
|
|
767
|
+
? params.config.tools
|
|
768
|
+
: undefined;
|
|
769
|
+
const baseUrlForCall = this.resolveProviderBaseUrl(provider);
|
|
770
|
+
const activeAuthType = this.runtimeState.authType;
|
|
771
|
+
// @plan PLAN-20251027-STATELESS5.P10
|
|
772
|
+
// @requirement REQ-STAT5-004.1
|
|
773
|
+
this.logger.debug(() => '[GeminiChat] Calling provider.generateChatCompletion (non-stream retry path)', {
|
|
774
|
+
providerName: provider.name,
|
|
775
|
+
model: this.runtimeState.model,
|
|
776
|
+
toolCount: toolsFromConfig?.length ?? 0,
|
|
777
|
+
baseUrl: baseUrlForCall,
|
|
778
|
+
authType: activeAuthType,
|
|
779
|
+
});
|
|
780
|
+
const runtimeContext = this.buildProviderRuntime('GeminiChat.streamGeneration', { toolCount: toolsFromConfig?.length ?? 0 });
|
|
781
|
+
const streamResponse = provider.generateChatCompletion({
|
|
782
|
+
contents: userIContents,
|
|
783
|
+
tools: toolsFromConfig && toolsFromConfig.length > 0
|
|
784
|
+
? toolsFromConfig
|
|
785
|
+
: undefined,
|
|
786
|
+
config: runtimeContext.config,
|
|
787
|
+
runtime: runtimeContext,
|
|
788
|
+
settings: runtimeContext.settingsService,
|
|
789
|
+
metadata: runtimeContext.metadata,
|
|
790
|
+
});
|
|
791
|
+
let lastResponse;
|
|
792
|
+
for await (const iContent of streamResponse) {
|
|
793
|
+
lastResponse = iContent;
|
|
794
|
+
for (const block of iContent.blocks ?? []) {
|
|
795
|
+
if (block.type === 'text') {
|
|
796
|
+
aggregatedText += block.text;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
if (!lastResponse) {
|
|
801
|
+
throw new Error('No response from provider');
|
|
802
|
+
}
|
|
803
|
+
const directResponse = this.convertIContentToResponse(lastResponse);
|
|
804
|
+
if (aggregatedText.trim()) {
|
|
805
|
+
const candidate = directResponse.candidates?.[0];
|
|
806
|
+
if (candidate) {
|
|
807
|
+
const parts = candidate.content?.parts ?? [];
|
|
808
|
+
const hasText = parts.some((part) => 'text' in part && part.text?.trim());
|
|
809
|
+
if (!hasText) {
|
|
810
|
+
candidate.content = candidate.content || {
|
|
811
|
+
role: 'model',
|
|
812
|
+
parts: [],
|
|
813
|
+
};
|
|
814
|
+
candidate.content.parts = [
|
|
815
|
+
...(candidate.content.parts || []),
|
|
816
|
+
{ text: aggregatedText },
|
|
817
|
+
];
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
Object.defineProperty(directResponse, 'text', {
|
|
821
|
+
configurable: true,
|
|
822
|
+
get() {
|
|
823
|
+
return aggregatedText;
|
|
824
|
+
},
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
return directResponse;
|
|
828
|
+
}, {
|
|
829
|
+
shouldRetry: (error) => {
|
|
830
|
+
if (error instanceof Error && error.message) {
|
|
831
|
+
if (isSchemaDepthError(error.message))
|
|
832
|
+
return false;
|
|
833
|
+
if (error.message.includes('429'))
|
|
834
|
+
return true;
|
|
835
|
+
if (error.message.match(/5\d{2}/))
|
|
836
|
+
return true;
|
|
837
|
+
}
|
|
838
|
+
return false;
|
|
839
|
+
},
|
|
840
|
+
});
|
|
841
|
+
const durationMs = Date.now() - startTime;
|
|
842
|
+
await this._logApiResponse(durationMs, prompt_id, response.usageMetadata, JSON.stringify(response));
|
|
843
|
+
return response;
|
|
844
|
+
}
|
|
845
|
+
catch (error) {
|
|
846
|
+
const durationMs = Date.now() - startTime;
|
|
847
|
+
this._logApiError(durationMs, error, prompt_id);
|
|
848
|
+
throw error;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
616
851
|
async makeApiCallAndProcessStream(_params, promptId, pendingTokens, userContent) {
|
|
617
852
|
// Get the active provider
|
|
618
|
-
|
|
853
|
+
let provider = this.getActiveProvider();
|
|
619
854
|
if (!provider) {
|
|
620
855
|
throw new Error('No active provider configured');
|
|
621
856
|
}
|
|
857
|
+
// @plan PLAN-20251028-STATELESS6.P10
|
|
858
|
+
// @requirement REQ-STAT6-002.2
|
|
859
|
+
// @pseudocode agent-runtime-context.md line 87 (step 006.4)
|
|
860
|
+
const desiredProviderName = this.runtimeState.provider;
|
|
861
|
+
// Step 006.4: Replace providerManager access with view.provider adapter
|
|
862
|
+
if (desiredProviderName && provider.name !== desiredProviderName) {
|
|
863
|
+
const previousProviderName = provider.name;
|
|
864
|
+
try {
|
|
865
|
+
this.runtimeContext.provider.setActiveProvider(desiredProviderName);
|
|
866
|
+
provider = this.runtimeContext.provider.getActiveProvider();
|
|
867
|
+
this.logger.debug(() => `[GeminiChat] enforced provider switch (stream path) to '${desiredProviderName}' (previous '${previousProviderName}')`);
|
|
868
|
+
}
|
|
869
|
+
catch (error) {
|
|
870
|
+
this.logger.debug(() => `[GeminiChat] provider switch skipped (stream path, read-only context): ${error instanceof Error ? error.message : String(error)}`);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
const activeAuthType = this.runtimeState.authType;
|
|
874
|
+
const providerBaseUrl = this.resolveProviderBaseUrl(provider);
|
|
875
|
+
this.logger.debug(() => '[GeminiChat] Active provider snapshot before stream request', {
|
|
876
|
+
providerName: provider.name,
|
|
877
|
+
providerDefaultModel: provider.getDefaultModel?.(),
|
|
878
|
+
configModel: this.runtimeState.model,
|
|
879
|
+
baseUrl: providerBaseUrl,
|
|
880
|
+
authType: activeAuthType,
|
|
881
|
+
});
|
|
882
|
+
// Enforce context window limits before proceeding
|
|
622
883
|
await this.enforceContextWindow(pendingTokens, promptId, provider);
|
|
623
884
|
// Check if provider supports IContent interface
|
|
624
885
|
if (!this.providerSupportsIContent(provider)) {
|
|
@@ -649,7 +910,23 @@ export class GeminiChat {
|
|
|
649
910
|
// Get tools in the format the provider expects
|
|
650
911
|
const tools = this.generationConfig.tools;
|
|
651
912
|
// Call the provider directly with IContent
|
|
652
|
-
|
|
913
|
+
this.logger.debug(() => '[GeminiChat] Calling provider.generateChatCompletion (generatorRequest)', {
|
|
914
|
+
providerName: provider.name,
|
|
915
|
+
model: this.runtimeState.model,
|
|
916
|
+
historyLength: requestContents.length,
|
|
917
|
+
toolCount: tools?.length ?? 0,
|
|
918
|
+
baseUrl: providerBaseUrl,
|
|
919
|
+
authType: activeAuthType,
|
|
920
|
+
});
|
|
921
|
+
const runtimeContext = this.buildProviderRuntime('GeminiChat.generateRequest', { historyLength: requestContents.length });
|
|
922
|
+
const streamResponse = provider.generateChatCompletion({
|
|
923
|
+
contents: requestContents,
|
|
924
|
+
tools: tools,
|
|
925
|
+
config: runtimeContext.config,
|
|
926
|
+
runtime: runtimeContext,
|
|
927
|
+
settings: runtimeContext.settingsService,
|
|
928
|
+
metadata: runtimeContext.metadata,
|
|
929
|
+
});
|
|
653
930
|
// Convert the IContent stream to GenerateContentResponse stream
|
|
654
931
|
return (async function* (instance) {
|
|
655
932
|
for await (const iContent of streamResponse) {
|
|
@@ -716,11 +993,11 @@ export class GeminiChat {
|
|
|
716
993
|
* Adds a new entry to the chat history.
|
|
717
994
|
*/
|
|
718
995
|
addHistory(content) {
|
|
719
|
-
this.historyService.add(ContentConverters.toIContent(content), this.
|
|
996
|
+
this.historyService.add(ContentConverters.toIContent(content), this.runtimeState.model);
|
|
720
997
|
}
|
|
721
998
|
setHistory(history) {
|
|
722
999
|
this.historyService.clear();
|
|
723
|
-
const currentModel = this.
|
|
1000
|
+
const currentModel = this.runtimeState.model;
|
|
724
1001
|
for (const content of history) {
|
|
725
1002
|
this.historyService.add(ContentConverters.toIContent(content), currentModel);
|
|
726
1003
|
}
|
|
@@ -730,12 +1007,16 @@ export class GeminiChat {
|
|
|
730
1007
|
}
|
|
731
1008
|
/**
|
|
732
1009
|
* Check if compression is needed based on token count
|
|
1010
|
+
* @plan PLAN-20251028-STATELESS6.P10
|
|
1011
|
+
* @requirement REQ-STAT6-002.2
|
|
1012
|
+
* @pseudocode agent-runtime-context.md line 86 (step 006.3)
|
|
733
1013
|
*/
|
|
734
1014
|
shouldCompress(pendingTokens = 0) {
|
|
735
1015
|
// Calculate compression threshold only if not cached
|
|
736
1016
|
if (this.cachedCompressionThreshold === null) {
|
|
737
|
-
|
|
738
|
-
const
|
|
1017
|
+
// Step 006.3: Replace config.getEphemeralSetting with view.ephemerals
|
|
1018
|
+
const threshold = this.runtimeContext.ephemerals.compressionThreshold();
|
|
1019
|
+
const contextLimit = this.runtimeContext.ephemerals.contextLimit();
|
|
739
1020
|
this.cachedCompressionThreshold = threshold * contextLimit;
|
|
740
1021
|
this.logger.debug('Calculated compression threshold:', {
|
|
741
1022
|
threshold,
|
|
@@ -786,7 +1067,7 @@ export class GeminiChat {
|
|
|
786
1067
|
return 0;
|
|
787
1068
|
}
|
|
788
1069
|
try {
|
|
789
|
-
return await this.historyService.estimateTokensForContents(contents, this.
|
|
1070
|
+
return await this.historyService.estimateTokensForContents(contents, this.runtimeState.model);
|
|
790
1071
|
}
|
|
791
1072
|
catch (error) {
|
|
792
1073
|
this.logger.debug('Failed to estimate pending tokens with tokenizer, using fallback', error);
|
|
@@ -875,27 +1156,18 @@ export class GeminiChat {
|
|
|
875
1156
|
const generationBudget = this.asNumber(this.generationConfig.maxOutputTokens);
|
|
876
1157
|
const providerParams = provider?.getModelParams?.();
|
|
877
1158
|
const providerBudget = this.extractCompletionBudgetFromParams(providerParams);
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
if (value !== undefined) {
|
|
884
|
-
configBudget = value;
|
|
885
|
-
break;
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
return (generationBudget ??
|
|
890
|
-
providerBudget ??
|
|
891
|
-
configBudget ??
|
|
892
|
-
GeminiChat.DEFAULT_COMPLETION_BUDGET);
|
|
1159
|
+
// @plan PLAN-20251028-STATELESS6.P10
|
|
1160
|
+
// @requirement REQ-STAT6-002.2
|
|
1161
|
+
// @pseudocode agent-runtime-context.md line 86 (step 006.3)
|
|
1162
|
+
// Note: maxOutputTokens is not part of core ephemerals; removed config dependency
|
|
1163
|
+
return (generationBudget ?? providerBudget ?? GeminiChat.DEFAULT_COMPLETION_BUDGET);
|
|
893
1164
|
}
|
|
894
1165
|
async enforceContextWindow(pendingTokens, promptId, provider) {
|
|
895
1166
|
await this.historyService.waitForTokenUpdates();
|
|
896
1167
|
const completionBudget = Math.max(0, this.getCompletionBudget(provider));
|
|
897
|
-
|
|
898
|
-
const
|
|
1168
|
+
// Merged from main: Use user context limit from ephemerals
|
|
1169
|
+
const userContextLimit = this.runtimeContext.ephemerals.contextLimit();
|
|
1170
|
+
const limit = tokenLimit(this.runtimeState.model, userContextLimit);
|
|
899
1171
|
const marginAdjustedLimit = Math.max(0, limit - GeminiChat.TOKEN_SAFETY_MARGIN);
|
|
900
1172
|
const projected = this.historyService.getTotalTokens() +
|
|
901
1173
|
Math.max(0, pendingTokens) +
|
|
@@ -961,7 +1233,10 @@ export class GeminiChat {
|
|
|
961
1233
|
getCompressionSplit() {
|
|
962
1234
|
const curated = this.historyService.getCurated();
|
|
963
1235
|
// Calculate split point (keep last 30%)
|
|
964
|
-
|
|
1236
|
+
// @plan PLAN-20251028-STATELESS6.P10
|
|
1237
|
+
// @requirement REQ-STAT6-002.2
|
|
1238
|
+
// @pseudocode agent-runtime-context.md line 86 (step 006.3)
|
|
1239
|
+
const preserveThreshold = this.runtimeContext.ephemerals.preserveThreshold();
|
|
965
1240
|
let splitIndex = Math.floor(curated.length * (1 - preserveThreshold));
|
|
966
1241
|
// Adjust for tool call boundaries
|
|
967
1242
|
splitIndex = this.adjustForToolCallBoundary(curated, splitIndex);
|
|
@@ -1009,10 +1284,30 @@ export class GeminiChat {
|
|
|
1009
1284
|
* Direct API call for compression, bypassing normal message flow
|
|
1010
1285
|
*/
|
|
1011
1286
|
async directCompressionCall(historyToCompress, _prompt_id) {
|
|
1012
|
-
|
|
1013
|
-
if (!provider
|
|
1287
|
+
let provider = this.getActiveProvider();
|
|
1288
|
+
if (!provider) {
|
|
1289
|
+
throw new Error('No active provider configured');
|
|
1290
|
+
}
|
|
1291
|
+
// @plan PLAN-20251028-STATELESS6.P10
|
|
1292
|
+
// @requirement REQ-STAT6-002.2
|
|
1293
|
+
// @pseudocode agent-runtime-context.md line 87 (step 006.4)
|
|
1294
|
+
const desiredProviderName = this.runtimeState.provider;
|
|
1295
|
+
// Step 006.4: Replace providerManager access with view.provider adapter
|
|
1296
|
+
if (desiredProviderName && provider.name !== desiredProviderName) {
|
|
1297
|
+
try {
|
|
1298
|
+
this.runtimeContext.provider.setActiveProvider(desiredProviderName);
|
|
1299
|
+
provider = this.runtimeContext.provider.getActiveProvider();
|
|
1300
|
+
this.logger.debug(() => `[GeminiChat] enforced provider switch (compression) to '${desiredProviderName}'`);
|
|
1301
|
+
}
|
|
1302
|
+
catch (error) {
|
|
1303
|
+
this.logger.debug(() => `[GeminiChat] provider switch skipped (compression, read-only context): ${error instanceof Error ? error.message : String(error)}`);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
if (!this.providerSupportsIContent(provider)) {
|
|
1014
1307
|
throw new Error('Provider does not support compression');
|
|
1015
1308
|
}
|
|
1309
|
+
const activeAuthType = this.runtimeState.authType;
|
|
1310
|
+
const providerBaseUrl = this.resolveProviderBaseUrl(provider);
|
|
1016
1311
|
// Build compression request with system prompt and user history
|
|
1017
1312
|
const compressionRequest = [
|
|
1018
1313
|
// Add system instruction as the first message
|
|
@@ -1039,7 +1334,22 @@ export class GeminiChat {
|
|
|
1039
1334
|
},
|
|
1040
1335
|
];
|
|
1041
1336
|
// Direct provider call without tools for compression
|
|
1042
|
-
|
|
1337
|
+
this.logger.debug(() => '[GeminiChat] Calling provider.generateChatCompletion (directCompression)', {
|
|
1338
|
+
providerName: provider.name,
|
|
1339
|
+
model: this.runtimeState.model,
|
|
1340
|
+
historyLength: compressionRequest.length,
|
|
1341
|
+
baseUrl: providerBaseUrl,
|
|
1342
|
+
authType: activeAuthType,
|
|
1343
|
+
});
|
|
1344
|
+
const runtimeContext = this.buildProviderRuntime('GeminiChat.directCompression', { historyLength: compressionRequest.length });
|
|
1345
|
+
const stream = provider.generateChatCompletion({
|
|
1346
|
+
contents: compressionRequest,
|
|
1347
|
+
tools: undefined,
|
|
1348
|
+
config: runtimeContext.config,
|
|
1349
|
+
runtime: runtimeContext,
|
|
1350
|
+
settings: runtimeContext.settingsService,
|
|
1351
|
+
metadata: runtimeContext.metadata,
|
|
1352
|
+
});
|
|
1043
1353
|
// Collect response
|
|
1044
1354
|
let summary = '';
|
|
1045
1355
|
for await (const chunk of stream) {
|
|
@@ -1059,7 +1369,7 @@ export class GeminiChat {
|
|
|
1059
1369
|
applyCompression(summary, toKeep) {
|
|
1060
1370
|
// Clear and rebuild history atomically
|
|
1061
1371
|
this.historyService.clear();
|
|
1062
|
-
const currentModel = this.
|
|
1372
|
+
const currentModel = this.runtimeState.model;
|
|
1063
1373
|
// Add compressed summary as user message
|
|
1064
1374
|
this.historyService.add({
|
|
1065
1375
|
speaker: 'human',
|
|
@@ -1204,7 +1514,7 @@ export class GeminiChat {
|
|
|
1204
1514
|
}
|
|
1205
1515
|
}
|
|
1206
1516
|
// Part 4: Add the new turn (user and model parts) to the history service.
|
|
1207
|
-
const currentModel = this.
|
|
1517
|
+
const currentModel = this.runtimeState.model;
|
|
1208
1518
|
for (const entry of newHistoryEntries) {
|
|
1209
1519
|
this.historyService.add(entry, currentModel);
|
|
1210
1520
|
}
|
|
@@ -1244,147 +1554,25 @@ export class GeminiChat {
|
|
|
1244
1554
|
typeof content.parts[0].thought === 'boolean' &&
|
|
1245
1555
|
content.parts[0].thought === true);
|
|
1246
1556
|
}
|
|
1247
|
-
/**
|
|
1248
|
-
* Trim prompt contents to fit within token limit
|
|
1249
|
-
* Strategy: Keep the most recent user message, trim older history and tool outputs
|
|
1250
|
-
*/
|
|
1251
|
-
// private _trimPromptContents(
|
|
1252
|
-
// contents: Content[],
|
|
1253
|
-
// maxTokens: number,
|
|
1254
|
-
// ): Content[] {
|
|
1255
|
-
// if (contents.length === 0) return contents;
|
|
1256
|
-
//
|
|
1257
|
-
// // Always keep the last message (current user input)
|
|
1258
|
-
// const lastMessage = contents[contents.length - 1];
|
|
1259
|
-
// const result: Content[] = [];
|
|
1260
|
-
//
|
|
1261
|
-
// // Reserve tokens for the last message and warning
|
|
1262
|
-
// const lastMessageTokens = estimateTokens(JSON.stringify(lastMessage));
|
|
1263
|
-
// const warningTokens = 200; // Reserve for warning message
|
|
1264
|
-
// let remainingTokens = maxTokens - lastMessageTokens - warningTokens;
|
|
1265
|
-
//
|
|
1266
|
-
// if (remainingTokens <= 0) {
|
|
1267
|
-
// // Even the last message is too big, truncate it
|
|
1268
|
-
// return [this._truncateContent(lastMessage, maxTokens - warningTokens)];
|
|
1269
|
-
// }
|
|
1270
|
-
//
|
|
1271
|
-
// // Add messages from most recent to oldest, stopping when we hit the limit
|
|
1272
|
-
// for (let i = contents.length - 2; i >= 0; i--) {
|
|
1273
|
-
// const content = contents[i];
|
|
1274
|
-
// const contentTokens = estimateTokens(JSON.stringify(content));
|
|
1275
|
-
//
|
|
1276
|
-
// if (contentTokens <= remainingTokens) {
|
|
1277
|
-
// result.unshift(content);
|
|
1278
|
-
// remainingTokens -= contentTokens;
|
|
1279
|
-
// } else if (remainingTokens > 100) {
|
|
1280
|
-
// // Try to truncate this content to fit
|
|
1281
|
-
// const truncated = this._truncateContent(content, remainingTokens);
|
|
1282
|
-
// // Only add if we actually got some content back
|
|
1283
|
-
// if (truncated.parts && truncated.parts.length > 0) {
|
|
1284
|
-
// result.unshift(truncated);
|
|
1285
|
-
// }
|
|
1286
|
-
// break;
|
|
1287
|
-
// } else {
|
|
1288
|
-
// // No room left, stop
|
|
1289
|
-
// break;
|
|
1290
|
-
// }
|
|
1291
|
-
// }
|
|
1292
|
-
//
|
|
1293
|
-
// // Add the last message
|
|
1294
|
-
// result.push(lastMessage);
|
|
1295
|
-
//
|
|
1296
|
-
// return result;
|
|
1297
|
-
// }
|
|
1298
|
-
//
|
|
1299
|
-
/**
|
|
1300
|
-
* Truncate a single content to fit within token limit
|
|
1301
|
-
*/
|
|
1302
|
-
// private _truncateContent(content: Content, maxTokens: number): Content {
|
|
1303
|
-
// if (!content.parts || content.parts.length === 0) {
|
|
1304
|
-
// return content;
|
|
1305
|
-
// }
|
|
1306
|
-
//
|
|
1307
|
-
// const truncatedParts: Part[] = [];
|
|
1308
|
-
// let currentTokens = 0;
|
|
1309
|
-
//
|
|
1310
|
-
// for (const part of content.parts) {
|
|
1311
|
-
// if ('text' in part && part.text) {
|
|
1312
|
-
// const partTokens = estimateTokens(part.text);
|
|
1313
|
-
// if (currentTokens + partTokens <= maxTokens) {
|
|
1314
|
-
// truncatedParts.push(part);
|
|
1315
|
-
// currentTokens += partTokens;
|
|
1316
|
-
// } else {
|
|
1317
|
-
// // Truncate this part
|
|
1318
|
-
// const remainingTokens = maxTokens - currentTokens;
|
|
1319
|
-
// if (remainingTokens > 10) {
|
|
1320
|
-
// const remainingChars = remainingTokens * 4;
|
|
1321
|
-
// truncatedParts.push({
|
|
1322
|
-
// text:
|
|
1323
|
-
// part.text.substring(0, remainingChars) +
|
|
1324
|
-
// '\n[...content truncated due to token limit...]',
|
|
1325
|
-
// });
|
|
1326
|
-
// }
|
|
1327
|
-
// break;
|
|
1328
|
-
// }
|
|
1329
|
-
// } else {
|
|
1330
|
-
// // Non-text parts (function calls, responses, etc) - NEVER truncate these
|
|
1331
|
-
// // Either include them fully or skip them entirely to avoid breaking JSON
|
|
1332
|
-
// const partTokens = estimateTokens(JSON.stringify(part));
|
|
1333
|
-
// if (currentTokens + partTokens <= maxTokens) {
|
|
1334
|
-
// truncatedParts.push(part);
|
|
1335
|
-
// currentTokens += partTokens;
|
|
1336
|
-
// } else {
|
|
1337
|
-
// // Skip this part entirely - DO NOT truncate function calls/responses
|
|
1338
|
-
// // Log what we're skipping for debugging
|
|
1339
|
-
// if (process.env.DEBUG || process.env.VERBOSE) {
|
|
1340
|
-
// let skipInfo = 'unknown part';
|
|
1341
|
-
// if ('functionCall' in part) {
|
|
1342
|
-
// const funcPart = part as { functionCall?: { name?: string } };
|
|
1343
|
-
// skipInfo = `functionCall: ${funcPart.functionCall?.name || 'unnamed'}`;
|
|
1344
|
-
// } else if ('functionResponse' in part) {
|
|
1345
|
-
// const respPart = part as { functionResponse?: { name?: string } };
|
|
1346
|
-
// skipInfo = `functionResponse: ${respPart.functionResponse?.name || 'unnamed'}`;
|
|
1347
|
-
// }
|
|
1348
|
-
// console.warn(
|
|
1349
|
-
// `INFO: Skipping ${skipInfo} due to token limit (needs ${partTokens} tokens, only ${maxTokens - currentTokens} available)`,
|
|
1350
|
-
// );
|
|
1351
|
-
// }
|
|
1352
|
-
// // Add a marker that content was omitted
|
|
1353
|
-
// if (
|
|
1354
|
-
// truncatedParts.length > 0 &&
|
|
1355
|
-
// !truncatedParts.some(
|
|
1356
|
-
// (p) =>
|
|
1357
|
-
// 'text' in p &&
|
|
1358
|
-
// p.text?.includes(
|
|
1359
|
-
// '[...function calls omitted due to token limit...]',
|
|
1360
|
-
// ),
|
|
1361
|
-
// )
|
|
1362
|
-
// ) {
|
|
1363
|
-
// truncatedParts.push({
|
|
1364
|
-
// text: '[...function calls omitted due to token limit...]',
|
|
1365
|
-
// });
|
|
1366
|
-
// }
|
|
1367
|
-
// break;
|
|
1368
|
-
// }
|
|
1369
|
-
// }
|
|
1370
|
-
// }
|
|
1371
|
-
//
|
|
1372
|
-
// return {
|
|
1373
|
-
// role: content.role,
|
|
1374
|
-
// parts: truncatedParts,
|
|
1375
|
-
// };
|
|
1376
|
-
// }
|
|
1377
1557
|
async maybeIncludeSchemaDepthContext(error) {
|
|
1378
1558
|
// Check for potentially problematic cyclic tools with cyclic schemas
|
|
1379
1559
|
// and include a recommendation to remove potentially problematic tools.
|
|
1560
|
+
// @plan PLAN-20251028-STATELESS6.P10
|
|
1561
|
+
// @requirement REQ-STAT6-001.2
|
|
1562
|
+
// @pseudocode agent-runtime-context.md line 89 (step 006.6)
|
|
1380
1563
|
if (isStructuredError(error) && isSchemaDepthError(error.message)) {
|
|
1381
|
-
|
|
1564
|
+
// Step 006.6: Replace tool registry access with view.tools
|
|
1565
|
+
// Note: ToolRegistryView provides read-only access; getAllTools() not available
|
|
1566
|
+
// For diagnostic purposes, we can list tool names
|
|
1567
|
+
const toolNames = this.runtimeContext.tools.listToolNames();
|
|
1382
1568
|
const cyclicSchemaTools = [];
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1569
|
+
// Check each tool's metadata for cyclic schemas
|
|
1570
|
+
for (const toolName of toolNames) {
|
|
1571
|
+
const metadata = this.runtimeContext.tools.getToolMetadata(toolName);
|
|
1572
|
+
if (metadata?.parameterSchema) {
|
|
1573
|
+
if (hasCycleInSchema(metadata.parameterSchema)) {
|
|
1574
|
+
cyclicSchemaTools.push(toolName);
|
|
1575
|
+
}
|
|
1388
1576
|
}
|
|
1389
1577
|
}
|
|
1390
1578
|
if (cyclicSchemaTools.length > 0) {
|
|
@@ -1560,15 +1748,15 @@ export class GeminiChat {
|
|
|
1560
1748
|
* Get the active provider from the ProviderManager via Config
|
|
1561
1749
|
*/
|
|
1562
1750
|
getActiveProvider() {
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1751
|
+
// @plan PLAN-20251028-STATELESS6.P10
|
|
1752
|
+
// @requirement REQ-STAT6-002.2
|
|
1753
|
+
// @pseudocode agent-runtime-context.md line 87 (step 006.4)
|
|
1754
|
+
// Step 006.4: Replace providerManager access with view.provider adapter
|
|
1567
1755
|
try {
|
|
1568
|
-
return
|
|
1756
|
+
return this.runtimeContext.provider.getActiveProvider();
|
|
1569
1757
|
}
|
|
1570
1758
|
catch {
|
|
1571
|
-
// No active provider set
|
|
1759
|
+
// No active provider set or read-only context
|
|
1572
1760
|
return undefined;
|
|
1573
1761
|
}
|
|
1574
1762
|
}
|
|
@@ -1583,6 +1771,18 @@ export class GeminiChat {
|
|
|
1583
1771
|
return (typeof provider
|
|
1584
1772
|
.generateChatCompletion === 'function');
|
|
1585
1773
|
}
|
|
1774
|
+
resolveProviderBaseUrl(provider) {
|
|
1775
|
+
const candidate = provider;
|
|
1776
|
+
try {
|
|
1777
|
+
if (typeof candidate.getBaseURL === 'function') {
|
|
1778
|
+
return candidate.getBaseURL();
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
catch {
|
|
1782
|
+
// Ignore failures from provider-specific base URL accessors
|
|
1783
|
+
}
|
|
1784
|
+
return candidate.baseURL;
|
|
1785
|
+
}
|
|
1586
1786
|
}
|
|
1587
1787
|
/** Visible for Testing */
|
|
1588
1788
|
export function isSchemaDepthError(errorMessage) {
|