@vybestack/llxprt-code-core 0.5.0 → 0.6.0-nightly.251128.1049d5f2b
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/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/src/agents/codebase-investigator.d.ts +46 -0
- package/dist/src/agents/codebase-investigator.js +136 -0
- package/dist/src/agents/codebase-investigator.js.map +1 -0
- package/dist/src/agents/executor.d.ts +92 -0
- package/dist/src/agents/executor.js +624 -0
- package/dist/src/agents/executor.js.map +1 -0
- package/dist/src/agents/invocation.d.ts +45 -0
- package/dist/src/agents/invocation.js +114 -0
- package/dist/src/agents/invocation.js.map +1 -0
- package/dist/src/agents/registry.d.ts +38 -0
- package/dist/src/agents/registry.js +64 -0
- package/dist/src/agents/registry.js.map +1 -0
- package/dist/src/agents/types.d.ts +145 -0
- package/dist/src/agents/types.js +17 -0
- package/dist/src/agents/types.js.map +1 -0
- package/dist/src/agents/utils.d.ts +15 -0
- package/dist/src/agents/utils.js +27 -0
- package/dist/src/agents/utils.js.map +1 -0
- package/dist/src/auth/types.d.ts +4 -4
- package/dist/src/code_assist/oauth-credential-storage.d.ts +27 -0
- package/dist/src/code_assist/oauth-credential-storage.js +115 -0
- package/dist/src/code_assist/oauth-credential-storage.js.map +1 -0
- package/dist/src/code_assist/oauth2.js +36 -9
- package/dist/src/code_assist/oauth2.js.map +1 -1
- package/dist/src/config/config.d.ts +72 -8
- package/dist/src/config/config.js +130 -23
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/constants.d.ts +11 -0
- package/dist/src/config/constants.js +16 -0
- package/dist/src/config/constants.js.map +1 -0
- package/dist/src/config/storage.d.ts +1 -0
- package/dist/src/config/storage.js +2 -1
- package/dist/src/config/storage.js.map +1 -1
- package/dist/src/confirmation-bus/index.d.ts +2 -0
- package/dist/src/confirmation-bus/index.js +3 -0
- package/dist/src/confirmation-bus/index.js.map +1 -0
- package/dist/src/confirmation-bus/message-bus.d.ts +60 -0
- package/dist/src/confirmation-bus/message-bus.js +141 -0
- package/dist/src/confirmation-bus/message-bus.js.map +1 -0
- package/dist/src/confirmation-bus/types.d.ts +59 -0
- package/dist/src/confirmation-bus/types.js +10 -0
- package/dist/src/confirmation-bus/types.js.map +1 -0
- package/dist/src/core/baseLlmClient.d.ts +77 -0
- package/dist/src/core/baseLlmClient.js +175 -0
- package/dist/src/core/baseLlmClient.js.map +1 -0
- package/dist/src/core/client.d.ts +13 -1
- package/dist/src/core/client.js +98 -119
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/coreToolScheduler.d.ts +20 -1
- package/dist/src/core/coreToolScheduler.js +160 -16
- package/dist/src/core/coreToolScheduler.js.map +1 -1
- package/dist/src/core/geminiChat.d.ts +8 -1
- package/dist/src/core/geminiChat.js +30 -21
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/subagent.d.ts +16 -1
- package/dist/src/core/subagent.js +59 -3
- package/dist/src/core/subagent.js.map +1 -1
- package/dist/src/core/subagentOrchestrator.d.ts +2 -1
- package/dist/src/core/subagentOrchestrator.js +36 -6
- package/dist/src/core/subagentOrchestrator.js.map +1 -1
- package/dist/src/core/turn.d.ts +1 -4
- package/dist/src/core/turn.js +2 -12
- package/dist/src/core/turn.js.map +1 -1
- package/dist/src/ide/detect-ide.d.ts +44 -14
- package/dist/src/ide/detect-ide.js +35 -75
- package/dist/src/ide/detect-ide.js.map +1 -1
- package/dist/src/ide/ide-client.d.ts +5 -4
- package/dist/src/ide/ide-client.js +34 -25
- package/dist/src/ide/ide-client.js.map +1 -1
- package/dist/src/ide/ide-installer.d.ts +2 -2
- package/dist/src/ide/ide-installer.js +7 -9
- package/dist/src/ide/ide-installer.js.map +1 -1
- package/dist/src/index.d.ts +10 -2
- package/dist/src/index.js +12 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/oauth-provider.d.ts +5 -1
- package/dist/src/mcp/oauth-provider.js +56 -44
- package/dist/src/mcp/oauth-provider.js.map +1 -1
- package/dist/src/mcp/oauth-token-storage.d.ts +43 -40
- package/dist/src/mcp/oauth-token-storage.js +114 -44
- package/dist/src/mcp/oauth-token-storage.js.map +1 -1
- package/dist/src/mcp/oauth-utils.js +1 -0
- package/dist/src/mcp/oauth-utils.js.map +1 -1
- package/dist/src/mcp/sa-impersonation-provider.d.ts +33 -0
- package/dist/src/mcp/sa-impersonation-provider.js +130 -0
- package/dist/src/mcp/sa-impersonation-provider.js.map +1 -0
- package/dist/src/mcp/token-storage/hybrid-token-storage.js +1 -1
- package/dist/src/policy/config.d.ts +51 -0
- package/dist/src/policy/config.js +102 -0
- package/dist/src/policy/config.js.map +1 -0
- package/dist/src/policy/index.d.ts +5 -0
- package/dist/src/policy/index.js +6 -0
- package/dist/src/policy/index.js.map +1 -0
- package/dist/src/policy/policies/discovered.toml +9 -0
- package/dist/src/policy/policies/read-only.toml +68 -0
- package/dist/src/policy/policies/write.toml +69 -0
- package/dist/src/policy/policies/yolo.toml +8 -0
- package/dist/src/policy/policy-engine.d.ts +55 -0
- package/dist/src/policy/policy-engine.js +126 -0
- package/dist/src/policy/policy-engine.js.map +1 -0
- package/dist/src/policy/stable-stringify.d.ts +29 -0
- package/dist/src/policy/stable-stringify.js +111 -0
- package/dist/src/policy/stable-stringify.js.map +1 -0
- package/dist/src/policy/toml-loader.d.ts +37 -0
- package/dist/src/policy/toml-loader.js +183 -0
- package/dist/src/policy/toml-loader.js.map +1 -0
- package/dist/src/policy/types.d.ts +16 -0
- package/dist/src/policy/types.js +7 -0
- package/dist/src/policy/types.js.map +1 -0
- package/dist/src/providers/LoggingProviderWrapper.d.ts +2 -0
- package/dist/src/providers/LoggingProviderWrapper.js +27 -6
- package/dist/src/providers/LoggingProviderWrapper.js.map +1 -1
- package/dist/src/providers/ProviderManager.d.ts +18 -0
- package/dist/src/providers/ProviderManager.js +54 -3
- package/dist/src/providers/ProviderManager.js.map +1 -1
- package/dist/src/providers/anthropic/AnthropicProvider.d.ts +49 -0
- package/dist/src/providers/anthropic/AnthropicProvider.js +468 -30
- package/dist/src/providers/anthropic/AnthropicProvider.js.map +1 -1
- package/dist/src/providers/openai/OpenAIProvider.d.ts +3 -0
- package/dist/src/providers/openai/OpenAIProvider.js +12 -6
- package/dist/src/providers/openai/OpenAIProvider.js.map +1 -1
- package/dist/src/providers/utils/localEndpoint.d.ts +39 -0
- package/dist/src/providers/utils/localEndpoint.js +117 -0
- package/dist/src/providers/utils/localEndpoint.js.map +1 -0
- package/dist/src/runtime/AgentRuntimeLoader.d.ts +1 -0
- package/dist/src/runtime/AgentRuntimeLoader.js +6 -1
- package/dist/src/runtime/AgentRuntimeLoader.js.map +1 -1
- package/dist/src/runtime/createAgentRuntimeContext.js +8 -7
- package/dist/src/runtime/createAgentRuntimeContext.js.map +1 -1
- package/dist/src/services/fileSystemService.d.ts +9 -0
- package/dist/src/services/fileSystemService.js +12 -1
- package/dist/src/services/fileSystemService.js.map +1 -1
- package/dist/src/services/history/HistoryService.d.ts +4 -0
- package/dist/src/services/history/HistoryService.js +18 -0
- package/dist/src/services/history/HistoryService.js.map +1 -1
- package/dist/src/services/history/IContent.d.ts +6 -0
- package/dist/src/services/history/IContent.js.map +1 -1
- package/dist/src/services/shellExecutionService.js +0 -6
- package/dist/src/services/shellExecutionService.js.map +1 -1
- package/dist/src/settings/types.d.ts +7 -0
- package/dist/src/storage/sessionTypes.d.ts +27 -0
- package/dist/src/storage/sessionTypes.js +10 -0
- package/dist/src/storage/sessionTypes.js.map +1 -0
- package/dist/src/telemetry/constants.d.ts +8 -0
- package/dist/src/telemetry/constants.js +8 -0
- package/dist/src/telemetry/constants.js.map +1 -1
- package/dist/src/telemetry/loggers.d.ts +9 -1
- package/dist/src/telemetry/loggers.js +154 -2
- package/dist/src/telemetry/loggers.js.map +1 -1
- package/dist/src/telemetry/metrics.d.ts +5 -0
- package/dist/src/telemetry/metrics.js +4 -0
- package/dist/src/telemetry/metrics.js.map +1 -1
- package/dist/src/telemetry/types.d.ts +62 -1
- package/dist/src/telemetry/types.js +92 -0
- package/dist/src/telemetry/types.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.d.ts +1 -1
- package/dist/src/telemetry/uiTelemetry.js +2 -3
- package/dist/src/telemetry/uiTelemetry.js.map +1 -1
- package/dist/src/test-utils/config.js +14 -0
- package/dist/src/test-utils/config.js.map +1 -1
- package/dist/src/test-utils/mock-tool.d.ts +8 -4
- package/dist/src/test-utils/mock-tool.js +35 -18
- package/dist/src/test-utils/mock-tool.js.map +1 -1
- package/dist/src/test-utils/tools.d.ts +1 -1
- package/dist/src/test-utils/tools.js +4 -4
- package/dist/src/test-utils/tools.js.map +1 -1
- package/dist/src/tools/edit.d.ts +3 -2
- package/dist/src/tools/edit.js +29 -10
- package/dist/src/tools/edit.js.map +1 -1
- package/dist/src/tools/glob.d.ts +6 -4
- package/dist/src/tools/glob.js +3 -3
- package/dist/src/tools/glob.js.map +1 -1
- package/dist/src/tools/grep.d.ts +3 -2
- package/dist/src/tools/grep.js +2 -2
- package/dist/src/tools/grep.js.map +1 -1
- package/dist/src/tools/ls.d.ts +4 -3
- package/dist/src/tools/ls.js +3 -3
- package/dist/src/tools/ls.js.map +1 -1
- package/dist/src/tools/mcp-client.d.ts +9 -18
- package/dist/src/tools/mcp-client.js +60 -102
- package/dist/src/tools/mcp-client.js.map +1 -1
- package/dist/src/tools/mcp-tool.js +7 -1
- package/dist/src/tools/mcp-tool.js.map +1 -1
- package/dist/src/tools/memoryTool.d.ts +6 -2
- package/dist/src/tools/memoryTool.js +14 -4
- package/dist/src/tools/memoryTool.js.map +1 -1
- package/dist/src/tools/modifiable-tool.d.ts +1 -1
- package/dist/src/tools/modifiable-tool.js +9 -1
- package/dist/src/tools/modifiable-tool.js.map +1 -1
- package/dist/src/tools/read-file.d.ts +3 -2
- package/dist/src/tools/read-file.js +2 -2
- package/dist/src/tools/read-file.js.map +1 -1
- package/dist/src/tools/read-many-files.d.ts +3 -2
- package/dist/src/tools/read-many-files.js +2 -2
- package/dist/src/tools/read-many-files.js.map +1 -1
- package/dist/src/tools/ripGrep.d.ts +3 -2
- package/dist/src/tools/ripGrep.js +2 -2
- package/dist/src/tools/ripGrep.js.map +1 -1
- package/dist/src/tools/shell.d.ts +3 -2
- package/dist/src/tools/shell.js +69 -9
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/smart-edit.d.ts +22 -2
- package/dist/src/tools/smart-edit.js +124 -12
- package/dist/src/tools/smart-edit.js.map +1 -1
- package/dist/src/tools/task.d.ts +1 -0
- package/dist/src/tools/task.js +33 -16
- package/dist/src/tools/task.js.map +1 -1
- package/dist/src/tools/tool-confirmation-types.d.ts +20 -0
- package/dist/src/tools/tool-confirmation-types.js +15 -0
- package/dist/src/tools/tool-confirmation-types.js.map +1 -0
- package/dist/src/tools/tool-error.d.ts +2 -0
- package/dist/src/tools/tool-error.js +2 -0
- package/dist/src/tools/tool-error.js.map +1 -1
- package/dist/src/tools/tool-registry.d.ts +8 -1
- package/dist/src/tools/tool-registry.js +18 -4
- package/dist/src/tools/tool-registry.js.map +1 -1
- package/dist/src/tools/tools.d.ts +52 -14
- package/dist/src/tools/tools.js +71 -15
- package/dist/src/tools/tools.js.map +1 -1
- package/dist/src/tools/web-fetch.d.ts +3 -2
- package/dist/src/tools/web-fetch.js +11 -6
- package/dist/src/tools/web-fetch.js.map +1 -1
- package/dist/src/tools/web-search-invocation.d.ts +3 -1
- package/dist/src/tools/web-search-invocation.js +5 -2
- package/dist/src/tools/web-search-invocation.js.map +1 -1
- package/dist/src/tools/web-search.d.ts +3 -2
- package/dist/src/tools/web-search.js +6 -4
- package/dist/src/tools/web-search.js.map +1 -1
- package/dist/src/tools/write-file.d.ts +3 -2
- package/dist/src/tools/write-file.js +11 -6
- package/dist/src/tools/write-file.js.map +1 -1
- package/dist/src/utils/bfsFileSearch.d.ts +2 -2
- package/dist/src/utils/editor.js +5 -3
- package/dist/src/utils/editor.js.map +1 -1
- package/dist/src/utils/getFolderStructure.d.ts +2 -2
- package/dist/src/utils/getFolderStructure.js +1 -1
- package/dist/src/utils/getFolderStructure.js.map +1 -1
- package/dist/src/utils/llm-edit-fixer.js +10 -1
- package/dist/src/utils/llm-edit-fixer.js.map +1 -1
- package/dist/src/utils/memoryDiscovery.d.ts +2 -1
- package/dist/src/utils/memoryDiscovery.js +3 -2
- package/dist/src/utils/memoryDiscovery.js.map +1 -1
- package/dist/src/utils/memoryImportProcessor.js +13 -20
- package/dist/src/utils/memoryImportProcessor.js.map +1 -1
- package/dist/src/utils/retry.d.ts +5 -1
- package/dist/src/utils/retry.js +31 -16
- package/dist/src/utils/retry.js.map +1 -1
- package/dist/src/utils/schemaValidator.js +11 -1
- package/dist/src/utils/schemaValidator.js.map +1 -1
- package/dist/src/utils/shell-utils.d.ts +1 -0
- package/dist/src/utils/shell-utils.js +6 -2
- package/dist/src/utils/shell-utils.js.map +1 -1
- package/dist/src/utils/thoughtUtils.d.ts +21 -0
- package/dist/src/utils/thoughtUtils.js +39 -0
- package/dist/src/utils/thoughtUtils.js.map +1 -0
- package/dist/src/utils/tool-utils.js +2 -2
- package/dist/src/utils/tool-utils.js.map +1 -1
- package/package.json +6 -4
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { getResponseText } from '../utils/generateContentResponseUtilities.js';
|
|
7
|
+
import { getErrorMessage } from '../utils/errors.js';
|
|
8
|
+
/**
|
|
9
|
+
* Extracts JSON from a string that might be wrapped in markdown code blocks
|
|
10
|
+
* @param text - The raw text that might contain markdown-wrapped JSON
|
|
11
|
+
* @returns The extracted JSON string or the original text if no markdown found
|
|
12
|
+
*/
|
|
13
|
+
function extractJsonFromMarkdown(text) {
|
|
14
|
+
// Try to match ```json ... ``` or ``` ... ```
|
|
15
|
+
const markdownMatch = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
|
|
16
|
+
if (markdownMatch && markdownMatch[1]) {
|
|
17
|
+
return markdownMatch[1].trim();
|
|
18
|
+
}
|
|
19
|
+
// If no markdown found, return trimmed original text
|
|
20
|
+
return text.trim();
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* BaseLLMClient extracts stateless utility methods for LLM operations.
|
|
24
|
+
* Unlike the main Client class, this handles utility calls without conversation state.
|
|
25
|
+
*
|
|
26
|
+
* This implements the baseLlmClient pattern from upstream gemini-cli but adapted
|
|
27
|
+
* for llxprt's multi-provider architecture.
|
|
28
|
+
*
|
|
29
|
+
* Key features:
|
|
30
|
+
* - Multi-provider support (Anthropic, OpenAI, Gemini, Vertex AI)
|
|
31
|
+
* - Stateless operations (no conversation history)
|
|
32
|
+
* - Clean separation from GeminiClient
|
|
33
|
+
* - Dependency injection for testing
|
|
34
|
+
*/
|
|
35
|
+
export class BaseLLMClient {
|
|
36
|
+
contentGenerator;
|
|
37
|
+
constructor(contentGenerator) {
|
|
38
|
+
this.contentGenerator = contentGenerator;
|
|
39
|
+
if (!contentGenerator) {
|
|
40
|
+
throw new Error('ContentGenerator is required');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Generate structured JSON from a prompt with optional schema validation.
|
|
45
|
+
* Supports all providers through the ContentGenerator abstraction.
|
|
46
|
+
*
|
|
47
|
+
* @param options - Generation options including prompt, schema, model, etc.
|
|
48
|
+
* @returns Parsed JSON object
|
|
49
|
+
* @throws Error if generation fails or response cannot be parsed
|
|
50
|
+
*/
|
|
51
|
+
async generateJson(options) {
|
|
52
|
+
const { prompt, schema, model, temperature = 0, systemInstruction, promptId = 'baseLlmClient-generateJson', } = options;
|
|
53
|
+
try {
|
|
54
|
+
const contents = [
|
|
55
|
+
{
|
|
56
|
+
role: 'user',
|
|
57
|
+
parts: [{ text: prompt }],
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
const config = {
|
|
61
|
+
temperature,
|
|
62
|
+
topP: 1,
|
|
63
|
+
};
|
|
64
|
+
if (systemInstruction) {
|
|
65
|
+
config.systemInstruction = { text: systemInstruction };
|
|
66
|
+
}
|
|
67
|
+
if (schema) {
|
|
68
|
+
config.responseJsonSchema = schema;
|
|
69
|
+
config.responseMimeType = 'application/json';
|
|
70
|
+
}
|
|
71
|
+
const result = await this.contentGenerator.generateContent({
|
|
72
|
+
model,
|
|
73
|
+
config,
|
|
74
|
+
contents,
|
|
75
|
+
}, promptId);
|
|
76
|
+
let text = getResponseText(result);
|
|
77
|
+
if (!text) {
|
|
78
|
+
throw new Error('API returned an empty response for generateJson.');
|
|
79
|
+
}
|
|
80
|
+
// Handle markdown wrapping
|
|
81
|
+
const prefix = '```json';
|
|
82
|
+
const suffix = '```';
|
|
83
|
+
if (text.startsWith(prefix) && text.endsWith(suffix)) {
|
|
84
|
+
text = text
|
|
85
|
+
.substring(prefix.length, text.length - suffix.length)
|
|
86
|
+
.trim();
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
// Extract JSON from potential markdown wrapper
|
|
90
|
+
const cleanedText = extractJsonFromMarkdown(text);
|
|
91
|
+
return JSON.parse(cleanedText);
|
|
92
|
+
}
|
|
93
|
+
catch (parseError) {
|
|
94
|
+
throw new Error(`Failed to parse API response as JSON: ${getErrorMessage(parseError)}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
throw new Error(`Failed to generate JSON content: ${getErrorMessage(error)}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Generate embeddings for text input.
|
|
103
|
+
* Supports single text string or array of strings.
|
|
104
|
+
*
|
|
105
|
+
* @param options - Embedding options including text and model
|
|
106
|
+
* @returns Embedding vector(s) as number array(s)
|
|
107
|
+
* @throws Error if generation fails or response is invalid
|
|
108
|
+
*/
|
|
109
|
+
async generateEmbedding(options) {
|
|
110
|
+
const { text, model } = options;
|
|
111
|
+
try {
|
|
112
|
+
const texts = Array.isArray(text) ? text : [text];
|
|
113
|
+
const embedContentResponse = await this.contentGenerator.embedContent({
|
|
114
|
+
model,
|
|
115
|
+
contents: texts,
|
|
116
|
+
});
|
|
117
|
+
if (!embedContentResponse.embeddings ||
|
|
118
|
+
embedContentResponse.embeddings.length === 0) {
|
|
119
|
+
throw new Error('No embeddings found in API response.');
|
|
120
|
+
}
|
|
121
|
+
if (embedContentResponse.embeddings.length !== texts.length) {
|
|
122
|
+
throw new Error(`API returned a mismatched number of embeddings. Expected ${texts.length}, got ${embedContentResponse.embeddings.length}.`);
|
|
123
|
+
}
|
|
124
|
+
const embeddings = embedContentResponse.embeddings.map((embedding, index) => {
|
|
125
|
+
const values = embedding.values;
|
|
126
|
+
if (!values || values.length === 0) {
|
|
127
|
+
throw new Error(`API returned an empty embedding for input text at index ${index}: "${texts[index]}"`);
|
|
128
|
+
}
|
|
129
|
+
return values;
|
|
130
|
+
});
|
|
131
|
+
// Return single array if input was a single string
|
|
132
|
+
return Array.isArray(text) ? embeddings : embeddings[0];
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
throw new Error(`Failed to generate embedding: ${getErrorMessage(error)}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Count tokens in text or contents without making an API call to generate.
|
|
140
|
+
* Useful for checking context limits before generation.
|
|
141
|
+
*
|
|
142
|
+
* @param options - Options including text/contents and model
|
|
143
|
+
* @returns Token count
|
|
144
|
+
* @throws Error if counting fails
|
|
145
|
+
*/
|
|
146
|
+
async countTokens(options) {
|
|
147
|
+
const { text, contents, model } = options;
|
|
148
|
+
try {
|
|
149
|
+
let requestContents;
|
|
150
|
+
if (contents) {
|
|
151
|
+
requestContents = contents;
|
|
152
|
+
}
|
|
153
|
+
else if (text) {
|
|
154
|
+
requestContents = [
|
|
155
|
+
{
|
|
156
|
+
role: 'user',
|
|
157
|
+
parts: [{ text }],
|
|
158
|
+
},
|
|
159
|
+
];
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
throw new Error('Either text or contents must be provided');
|
|
163
|
+
}
|
|
164
|
+
const response = await this.contentGenerator.countTokens({
|
|
165
|
+
model,
|
|
166
|
+
contents: requestContents,
|
|
167
|
+
});
|
|
168
|
+
return response.totalTokens ?? 0;
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
throw new Error(`Failed to count tokens: ${getErrorMessage(error)}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=baseLlmClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseLlmClient.js","sourceRoot":"","sources":["../../../src/core/baseLlmClient.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,OAAO,EAAE,eAAe,EAAE,MAAM,8CAA8C,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AA+BrD;;;;GAIG;AACH,SAAS,uBAAuB,CAAC,IAAY;IAC3C,8CAA8C;IAC9C,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACvE,IAAI,aAAa,IAAI,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;QACtC,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACjC,CAAC;IAED,qDAAqD;IACrD,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;AACrB,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,aAAa;IACK;IAA7B,YAA6B,gBAAyC;QAAzC,qBAAgB,GAAhB,gBAAgB,CAAyB;QACpE,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,YAAY,CAAc,OAA4B;QAC1D,MAAM,EACJ,MAAM,EACN,MAAM,EACN,KAAK,EACL,WAAW,GAAG,CAAC,EACf,iBAAiB,EACjB,QAAQ,GAAG,4BAA4B,GACxC,GAAG,OAAO,CAAC;QAEZ,IAAI,CAAC;YACH,MAAM,QAAQ,GAAc;gBAC1B;oBACE,IAAI,EAAE,MAAM;oBACZ,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;iBAC1B;aACF,CAAC;YAEF,MAAM,MAAM,GAA4B;gBACtC,WAAW;gBACX,IAAI,EAAE,CAAC;aACR,CAAC;YAEF,IAAI,iBAAiB,EAAE,CAAC;gBACtB,MAAM,CAAC,iBAAiB,GAAG,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC;YACzD,CAAC;YAED,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,CAAC,kBAAkB,GAAG,MAAM,CAAC;gBACnC,MAAM,CAAC,gBAAgB,GAAG,kBAAkB,CAAC;YAC/C,CAAC;YAED,MAAM,MAAM,GACV,MAAM,IAAI,CAAC,gBAAiB,CAAC,eAAe,CAC1C;gBACE,KAAK;gBACL,MAAM;gBACN,QAAQ;aACT,EACD,QAAQ,CACT,CAAC;YAEJ,IAAI,IAAI,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;YACnC,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;YACtE,CAAC;YAED,2BAA2B;YAC3B,MAAM,MAAM,GAAG,SAAS,CAAC;YACzB,MAAM,MAAM,GAAG,KAAK,CAAC;YACrB,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;gBACrD,IAAI,GAAG,IAAI;qBACR,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;qBACrD,IAAI,EAAE,CAAC;YACZ,CAAC;YAED,IAAI,CAAC;gBACH,+CAA+C;gBAC/C,MAAM,WAAW,GAAG,uBAAuB,CAAC,IAAI,CAAC,CAAC;gBAClD,OAAO,IAAI,CAAC,KAAK,CAAC,WAAW,CAAM,CAAC;YACtC,CAAC;YAAC,OAAO,UAAU,EAAE,CAAC;gBACpB,MAAM,IAAI,KAAK,CACb,yCAAyC,eAAe,CACtD,UAAU,CACX,EAAE,CACJ,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACb,oCAAoC,eAAe,CAAC,KAAK,CAAC,EAAE,CAC7D,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,iBAAiB,CACrB,OAAiC;QAEjC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;QAEhC,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAElD,MAAM,oBAAoB,GACxB,MAAM,IAAI,CAAC,gBAAiB,CAAC,YAAY,CAAC;gBACxC,KAAK;gBACL,QAAQ,EAAE,KAAK;aAChB,CAAC,CAAC;YAEL,IACE,CAAC,oBAAoB,CAAC,UAAU;gBAChC,oBAAoB,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAC5C,CAAC;gBACD,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;YAC1D,CAAC;YAED,IAAI,oBAAoB,CAAC,UAAU,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC;gBAC5D,MAAM,IAAI,KAAK,CACb,4DAA4D,KAAK,CAAC,MAAM,SAAS,oBAAoB,CAAC,UAAU,CAAC,MAAM,GAAG,CAC3H,CAAC;YACJ,CAAC;YAED,MAAM,UAAU,GAAG,oBAAoB,CAAC,UAAU,CAAC,GAAG,CACpD,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE;gBACnB,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC;gBAChC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBACnC,MAAM,IAAI,KAAK,CACb,2DAA2D,KAAK,MAAM,KAAK,CAAC,KAAK,CAAC,GAAG,CACtF,CAAC;gBACJ,CAAC;gBACD,OAAO,MAAM,CAAC;YAChB,CAAC,CACF,CAAC;YAEF,mDAAmD;YACnD,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC1D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACb,iCAAiC,eAAe,CAAC,KAAK,CAAC,EAAE,CAC1D,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,WAAW,CAAC,OAA2B;QAC3C,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;QAE1C,IAAI,CAAC;YACH,IAAI,eAA0B,CAAC;YAE/B,IAAI,QAAQ,EAAE,CAAC;gBACb,eAAe,GAAG,QAAQ,CAAC;YAC7B,CAAC;iBAAM,IAAI,IAAI,EAAE,CAAC;gBAChB,eAAe,GAAG;oBAChB;wBACE,IAAI,EAAE,MAAM;wBACZ,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC;qBAClB;iBACF,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;YAC9D,CAAC;YAED,MAAM,QAAQ,GACZ,MAAM,IAAI,CAAC,gBAAiB,CAAC,WAAW,CAAC;gBACvC,KAAK;gBACL,QAAQ,EAAE,eAAe;aAC1B,CAAC,CAAC;YAEL,OAAO,QAAQ,CAAC,WAAW,IAAI,CAAC,CAAC;QACnC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,2BAA2B,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;CACF"}
|
|
@@ -17,7 +17,7 @@ import type { AgentRuntimeState } from '../runtime/AgentRuntimeState.js';
|
|
|
17
17
|
*
|
|
18
18
|
* Exported for testing purposes.
|
|
19
19
|
*/
|
|
20
|
-
export declare function
|
|
20
|
+
export declare function findCompressSplitPoint(contents: Content[], fraction: number): number;
|
|
21
21
|
export declare class GeminiClient {
|
|
22
22
|
private readonly config;
|
|
23
23
|
private chat?;
|
|
@@ -30,6 +30,7 @@ export declare class GeminiClient {
|
|
|
30
30
|
private _pendingConfig?;
|
|
31
31
|
private _previousHistory?;
|
|
32
32
|
private _storedHistoryService?;
|
|
33
|
+
private currentSequenceModel;
|
|
33
34
|
private readonly loopDetector;
|
|
34
35
|
private lastPromptId?;
|
|
35
36
|
private readonly complexityAnalyzer;
|
|
@@ -59,6 +60,11 @@ export declare class GeminiClient {
|
|
|
59
60
|
private readonly runtimeState;
|
|
60
61
|
private _historyService?;
|
|
61
62
|
private _unsubscribe?;
|
|
63
|
+
/**
|
|
64
|
+
* BaseLLMClient for stateless utility operations (generateJson, embeddings, etc.)
|
|
65
|
+
* Lazily initialized when needed
|
|
66
|
+
*/
|
|
67
|
+
private _baseLlmClient?;
|
|
62
68
|
/**
|
|
63
69
|
* @plan PLAN-20251027-STATELESS5.P10
|
|
64
70
|
* @requirement REQ-STAT5-003.1
|
|
@@ -74,6 +80,11 @@ export declare class GeminiClient {
|
|
|
74
80
|
private lazyInitialize;
|
|
75
81
|
getContentGenerator(): ContentGenerator;
|
|
76
82
|
getUserTier(): UserTierId | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* Get or create the BaseLLMClient for stateless utility operations.
|
|
85
|
+
* This is lazily initialized to avoid creating it when not needed.
|
|
86
|
+
*/
|
|
87
|
+
private getBaseLlmClient;
|
|
77
88
|
private processComplexityAnalysis;
|
|
78
89
|
private shouldEscalateReminder;
|
|
79
90
|
private isTodoToolCall;
|
|
@@ -113,6 +124,7 @@ export declare class GeminiClient {
|
|
|
113
124
|
setTools(): Promise<void>;
|
|
114
125
|
clearTools(): void;
|
|
115
126
|
resetChat(): Promise<void>;
|
|
127
|
+
getCurrentSequenceModel(): string | null;
|
|
116
128
|
addDirectoryContext(): Promise<void>;
|
|
117
129
|
generateDirectMessage(params: SendMessageParameters, promptId: string): Promise<GenerateContentResponse>;
|
|
118
130
|
startChat(extraHistory?: Content[]): Promise<GeminiChat>;
|
package/dist/src/core/client.js
CHANGED
|
@@ -7,7 +7,6 @@ import { getDirectoryContextString, getEnvironmentContext, } from '../utils/envi
|
|
|
7
7
|
import { Turn, GeminiEventType, DEFAULT_AGENT_ID, } from './turn.js';
|
|
8
8
|
import { CompressionStatus } from './turn.js';
|
|
9
9
|
import { getCoreSystemPromptAsync, getCompressionPrompt } from './prompts.js';
|
|
10
|
-
import { getResponseText } from '../utils/generateContentResponseUtilities.js';
|
|
11
10
|
import { reportError } from '../utils/errorReporting.js';
|
|
12
11
|
import { GeminiChat } from './geminiChat.js';
|
|
13
12
|
import { DebugLogger } from '../debug/index.js';
|
|
@@ -25,10 +24,12 @@ import { LoopDetectionService } from '../services/loopDetectionService.js';
|
|
|
25
24
|
import { ideContext } from '../ide/ideContext.js';
|
|
26
25
|
import { ComplexityAnalyzer, } from '../services/complexity-analyzer.js';
|
|
27
26
|
import { TodoReminderService } from '../services/todo-reminder-service.js';
|
|
27
|
+
import { uiTelemetryService } from '../telemetry/uiTelemetry.js';
|
|
28
28
|
import { TodoStore } from '../tools/todo-store.js';
|
|
29
29
|
import { isFunctionResponse } from '../utils/messageInspectors.js';
|
|
30
30
|
import { estimateTokens as estimateTextTokens } from '../utils/toolOutputLimiter.js';
|
|
31
31
|
import { subscribeToAgentRuntimeState } from '../runtime/AgentRuntimeState.js';
|
|
32
|
+
import { BaseLLMClient } from './baseLlmClient.js';
|
|
32
33
|
const COMPLEXITY_ESCALATION_TURN_THRESHOLD = 3;
|
|
33
34
|
const TODO_PROMPT_SUFFIX = 'Use TODO List to organize this effort.';
|
|
34
35
|
function isThinkingSupported(model) {
|
|
@@ -36,40 +37,37 @@ function isThinkingSupported(model) {
|
|
|
36
37
|
return true;
|
|
37
38
|
return false;
|
|
38
39
|
}
|
|
39
|
-
/**
|
|
40
|
-
* Extracts JSON from a string that might be wrapped in markdown code blocks
|
|
41
|
-
* @param text - The raw text that might contain markdown-wrapped JSON
|
|
42
|
-
* @returns The extracted JSON string or the original text if no markdown found
|
|
43
|
-
*/
|
|
44
|
-
function extractJsonFromMarkdown(text) {
|
|
45
|
-
// Try to match ```json ... ``` or ``` ... ```
|
|
46
|
-
const markdownMatch = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
|
|
47
|
-
if (markdownMatch && markdownMatch[1]) {
|
|
48
|
-
return markdownMatch[1].trim();
|
|
49
|
-
}
|
|
50
|
-
// If no markdown found, return trimmed original text
|
|
51
|
-
return text.trim();
|
|
52
|
-
}
|
|
53
40
|
/**
|
|
54
41
|
* Returns the index of the content after the fraction of the total characters in the history.
|
|
55
42
|
*
|
|
56
43
|
* Exported for testing purposes.
|
|
57
44
|
*/
|
|
58
|
-
export function
|
|
45
|
+
export function findCompressSplitPoint(contents, fraction) {
|
|
59
46
|
if (fraction <= 0 || fraction >= 1) {
|
|
60
47
|
throw new Error('Fraction must be between 0 and 1');
|
|
61
48
|
}
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
let
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
49
|
+
const charCounts = contents.map((content) => JSON.stringify(content).length);
|
|
50
|
+
const totalCharCount = charCounts.reduce((sum, length) => sum + length, 0);
|
|
51
|
+
const targetCharCount = totalCharCount * fraction;
|
|
52
|
+
let lastSplitPoint = 0;
|
|
53
|
+
let cumulativeCharCount = 0;
|
|
54
|
+
for (let i = 0; i < contents.length; i++) {
|
|
55
|
+
const content = contents[i];
|
|
56
|
+
const hasFunctionResponse = content.parts?.some((part) => !!part.functionResponse);
|
|
57
|
+
if (content.role === 'user' && !hasFunctionResponse) {
|
|
58
|
+
if (cumulativeCharCount >= targetCharCount) {
|
|
59
|
+
return i;
|
|
60
|
+
}
|
|
61
|
+
lastSplitPoint = i;
|
|
70
62
|
}
|
|
63
|
+
cumulativeCharCount += charCounts[i];
|
|
64
|
+
}
|
|
65
|
+
const lastContent = contents[contents.length - 1];
|
|
66
|
+
if (lastContent?.role === 'model' &&
|
|
67
|
+
!lastContent?.parts?.some((part) => part.functionCall)) {
|
|
68
|
+
return contents.length;
|
|
71
69
|
}
|
|
72
|
-
return
|
|
70
|
+
return lastSplitPoint;
|
|
73
71
|
}
|
|
74
72
|
export class GeminiClient {
|
|
75
73
|
config;
|
|
@@ -86,6 +84,7 @@ export class GeminiClient {
|
|
|
86
84
|
_pendingConfig;
|
|
87
85
|
_previousHistory;
|
|
88
86
|
_storedHistoryService;
|
|
87
|
+
currentSequenceModel = null;
|
|
89
88
|
loopDetector;
|
|
90
89
|
lastPromptId;
|
|
91
90
|
complexityAnalyzer;
|
|
@@ -115,6 +114,11 @@ export class GeminiClient {
|
|
|
115
114
|
runtimeState;
|
|
116
115
|
_historyService;
|
|
117
116
|
_unsubscribe;
|
|
117
|
+
/**
|
|
118
|
+
* BaseLLMClient for stateless utility operations (generateJson, embeddings, etc.)
|
|
119
|
+
* Lazily initialized when needed
|
|
120
|
+
*/
|
|
121
|
+
_baseLlmClient;
|
|
118
122
|
/**
|
|
119
123
|
* @plan PLAN-20251027-STATELESS5.P10
|
|
120
124
|
* @requirement REQ-STAT5-003.1
|
|
@@ -210,6 +214,16 @@ export class GeminiClient {
|
|
|
210
214
|
getUserTier() {
|
|
211
215
|
return this.contentGenerator?.userTier;
|
|
212
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Get or create the BaseLLMClient for stateless utility operations.
|
|
219
|
+
* This is lazily initialized to avoid creating it when not needed.
|
|
220
|
+
*/
|
|
221
|
+
getBaseLlmClient() {
|
|
222
|
+
if (!this._baseLlmClient) {
|
|
223
|
+
this._baseLlmClient = new BaseLLMClient(this.getContentGenerator());
|
|
224
|
+
}
|
|
225
|
+
return this._baseLlmClient;
|
|
226
|
+
}
|
|
213
227
|
processComplexityAnalysis(analysis) {
|
|
214
228
|
if (!this.todoToolsAvailable) {
|
|
215
229
|
this.consecutiveComplexTurns = 0;
|
|
@@ -514,6 +528,9 @@ export class GeminiClient {
|
|
|
514
528
|
// Clear the stored history as well
|
|
515
529
|
this._previousHistory = [];
|
|
516
530
|
}
|
|
531
|
+
getCurrentSequenceModel() {
|
|
532
|
+
return this.currentSequenceModel;
|
|
533
|
+
}
|
|
517
534
|
async addDirectoryContext() {
|
|
518
535
|
if (!this.chat) {
|
|
519
536
|
return;
|
|
@@ -827,6 +844,7 @@ export class GeminiClient {
|
|
|
827
844
|
if (this.lastPromptId !== prompt_id) {
|
|
828
845
|
this.loopDetector.reset(prompt_id);
|
|
829
846
|
this.lastPromptId = prompt_id;
|
|
847
|
+
this.currentSequenceModel = null;
|
|
830
848
|
}
|
|
831
849
|
this.sessionTurnCount++;
|
|
832
850
|
this.toolActivityCount = 0;
|
|
@@ -846,7 +864,7 @@ export class GeminiClient {
|
|
|
846
864
|
const providerName = providerManager?.getActiveProviderName() || 'backend';
|
|
847
865
|
return new Turn(this.getChat(), prompt_id, DEFAULT_AGENT_ID, providerName);
|
|
848
866
|
}
|
|
849
|
-
const compressed = await this.tryCompressChat(prompt_id);
|
|
867
|
+
const compressed = await this.tryCompressChat(prompt_id, false);
|
|
850
868
|
if (compressed.compressionStatus === CompressionStatus.COMPRESSED) {
|
|
851
869
|
yield { type: GeminiEventType.ChatCompressed, value: compressed };
|
|
852
870
|
}
|
|
@@ -1070,78 +1088,61 @@ export class GeminiClient {
|
|
|
1070
1088
|
}
|
|
1071
1089
|
async generateJson(contents, schema, abortSignal, model, config = {}) {
|
|
1072
1090
|
await this.lazyInitialize();
|
|
1073
|
-
// Use the provided model parameter directly
|
|
1074
1091
|
const modelToUse = model;
|
|
1075
1092
|
try {
|
|
1076
1093
|
const userMemory = this.config.getUserMemory();
|
|
1077
|
-
// Provider name removed from prompt call signature
|
|
1078
1094
|
const systemInstruction = await getCoreSystemPromptAsync(userMemory, modelToUse, this.getEnabledToolNamesForPrompt());
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1095
|
+
// Convert Content[] to a single prompt for BaseLLMClient
|
|
1096
|
+
// This preserves the conversation context in the prompt
|
|
1097
|
+
const prompt = contents
|
|
1098
|
+
.map((c) => c.parts
|
|
1099
|
+
?.map((p) => ('text' in p ? p.text : ''))
|
|
1100
|
+
.filter(Boolean)
|
|
1101
|
+
.join('\n'))
|
|
1102
|
+
.filter(Boolean)
|
|
1103
|
+
.join('\n\n');
|
|
1104
|
+
// Use BaseLLMClient for the core JSON generation
|
|
1105
|
+
// This delegates to the stateless utility layer
|
|
1106
|
+
const baseLlmClient = this.getBaseLlmClient();
|
|
1107
|
+
const apiCall = async () => {
|
|
1108
|
+
try {
|
|
1109
|
+
return await baseLlmClient.generateJson({
|
|
1110
|
+
prompt,
|
|
1111
|
+
schema,
|
|
1112
|
+
model: modelToUse,
|
|
1113
|
+
temperature: config.temperature ?? this.generateContentConfig.temperature,
|
|
1114
|
+
systemInstruction,
|
|
1115
|
+
promptId: this.lastPromptId || this.config.getSessionId(),
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
catch (error) {
|
|
1119
|
+
// Preserve abort signal behavior
|
|
1120
|
+
if (abortSignal.aborted) {
|
|
1121
|
+
throw error;
|
|
1122
|
+
}
|
|
1123
|
+
throw error;
|
|
1124
|
+
}
|
|
1083
1125
|
};
|
|
1084
|
-
const apiCall = () => this.getContentGenerator().generateContent({
|
|
1085
|
-
model: modelToUse,
|
|
1086
|
-
config: {
|
|
1087
|
-
...requestConfig,
|
|
1088
|
-
systemInstruction,
|
|
1089
|
-
responseJsonSchema: schema,
|
|
1090
|
-
responseMimeType: 'application/json',
|
|
1091
|
-
},
|
|
1092
|
-
contents,
|
|
1093
|
-
}, this.lastPromptId || this.config.getSessionId());
|
|
1094
1126
|
const result = await retryWithBackoff(apiCall);
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
text = text
|
|
1106
|
-
.substring(prefix.length, text.length - suffix.length)
|
|
1107
|
-
.trim();
|
|
1108
|
-
}
|
|
1109
|
-
try {
|
|
1110
|
-
// Extract JSON from potential markdown wrapper
|
|
1111
|
-
const cleanedText = extractJsonFromMarkdown(text);
|
|
1112
|
-
// Special case: Gemini sometimes returns just "user" or "model" for next speaker checks
|
|
1113
|
-
// This happens particularly with non-ASCII content in the conversation
|
|
1114
|
-
if ((cleanedText === 'user' || cleanedText === 'model') &&
|
|
1115
|
-
contents.some((c) => c.parts?.some((p) => 'text' in p && p.text?.includes('next_speaker')))) {
|
|
1116
|
-
this.logger.warn(() => `[generateJson] Gemini returned plain text "${cleanedText}" instead of JSON for next speaker check. Converting to valid response.`);
|
|
1117
|
-
return {
|
|
1118
|
-
reasoning: 'Gemini returned plain text response',
|
|
1119
|
-
next_speaker: cleanedText,
|
|
1120
|
-
};
|
|
1121
|
-
}
|
|
1122
|
-
return JSON.parse(cleanedText);
|
|
1123
|
-
}
|
|
1124
|
-
catch (parseError) {
|
|
1125
|
-
// Log both the original and cleaned text for debugging
|
|
1126
|
-
await reportError(parseError, 'Failed to parse JSON response from generateJson.', {
|
|
1127
|
-
responseTextFailedToParse: text,
|
|
1128
|
-
cleanedTextFailedToParse: extractJsonFromMarkdown(text),
|
|
1129
|
-
originalRequestContents: contents,
|
|
1130
|
-
}, 'generateJson-parse');
|
|
1131
|
-
throw new Error(`Failed to parse API response as JSON: ${getErrorMessage(parseError)}`);
|
|
1127
|
+
// Special case: Gemini sometimes returns just "user" or "model" for next speaker checks
|
|
1128
|
+
// This happens particularly with non-ASCII content in the conversation
|
|
1129
|
+
if (typeof result === 'string' &&
|
|
1130
|
+
(result === 'user' || result === 'model') &&
|
|
1131
|
+
contents.some((c) => c.parts?.some((p) => 'text' in p && p.text?.includes('next_speaker')))) {
|
|
1132
|
+
this.logger.warn(() => `[generateJson] Gemini returned plain text "${result}" instead of JSON for next speaker check. Converting to valid response.`);
|
|
1133
|
+
return {
|
|
1134
|
+
reasoning: 'Gemini returned plain text response',
|
|
1135
|
+
next_speaker: result,
|
|
1136
|
+
};
|
|
1132
1137
|
}
|
|
1138
|
+
return result;
|
|
1133
1139
|
}
|
|
1134
1140
|
catch (error) {
|
|
1135
1141
|
if (abortSignal.aborted) {
|
|
1136
1142
|
throw error;
|
|
1137
1143
|
}
|
|
1138
|
-
// Avoid double reporting for the empty response case handled above
|
|
1139
|
-
if (error instanceof Error &&
|
|
1140
|
-
error.message === 'API returned an empty response for generateJson.') {
|
|
1141
|
-
throw error;
|
|
1142
|
-
}
|
|
1143
1144
|
await reportError(error, 'Error generating JSON content via API.', contents, 'generateJson-api');
|
|
1144
|
-
throw
|
|
1145
|
+
throw error;
|
|
1145
1146
|
}
|
|
1146
1147
|
}
|
|
1147
1148
|
async generateContent(contents, generationConfig, abortSignal, model) {
|
|
@@ -1184,25 +1185,14 @@ export class GeminiClient {
|
|
|
1184
1185
|
if (!texts || texts.length === 0) {
|
|
1185
1186
|
return [];
|
|
1186
1187
|
}
|
|
1187
|
-
|
|
1188
|
+
// Delegate to BaseLLMClient for stateless embedding generation
|
|
1189
|
+
const baseLlmClient = this.getBaseLlmClient();
|
|
1190
|
+
const result = await baseLlmClient.generateEmbedding({
|
|
1191
|
+
text: texts,
|
|
1188
1192
|
model: this.embeddingModel,
|
|
1189
|
-
contents: texts,
|
|
1190
|
-
};
|
|
1191
|
-
const embedContentResponse = await this.getContentGenerator().embedContent(embedModelParams);
|
|
1192
|
-
if (!embedContentResponse.embeddings ||
|
|
1193
|
-
embedContentResponse.embeddings.length === 0) {
|
|
1194
|
-
throw new Error('No embeddings found in API response.');
|
|
1195
|
-
}
|
|
1196
|
-
if (embedContentResponse.embeddings.length !== texts.length) {
|
|
1197
|
-
throw new Error(`API returned a mismatched number of embeddings. Expected ${texts.length}, got ${embedContentResponse.embeddings.length}.`);
|
|
1198
|
-
}
|
|
1199
|
-
return embedContentResponse.embeddings.map((embedding, index) => {
|
|
1200
|
-
const values = embedding.values;
|
|
1201
|
-
if (!values || values.length === 0) {
|
|
1202
|
-
throw new Error(`API returned an empty embedding for input text at index ${index}: "${texts[index]}"`);
|
|
1203
|
-
}
|
|
1204
|
-
return values;
|
|
1205
1193
|
});
|
|
1194
|
+
// Result is already validated by BaseLLMClient
|
|
1195
|
+
return result;
|
|
1206
1196
|
}
|
|
1207
1197
|
/**
|
|
1208
1198
|
* Manually trigger chat compression
|
|
@@ -1227,24 +1217,12 @@ export class GeminiClient {
|
|
|
1227
1217
|
compressionStatus: CompressionStatus.NOOP,
|
|
1228
1218
|
};
|
|
1229
1219
|
}
|
|
1230
|
-
//
|
|
1220
|
+
// Use lastPromptTokenCount from telemetry service as the source of truth
|
|
1221
|
+
// This is more accurate than estimating from history
|
|
1222
|
+
const originalTokenCount = uiTelemetryService.getLastPromptTokenCount();
|
|
1231
1223
|
// @plan PLAN-20251027-STATELESS5.P10
|
|
1232
1224
|
// @requirement REQ-STAT5-003.1
|
|
1233
1225
|
const model = this.runtimeState.model;
|
|
1234
|
-
// Get the ACTUAL token count from the history service, not the curated subset
|
|
1235
|
-
const historyService = this.getChat().getHistoryService();
|
|
1236
|
-
const originalTokenCount = historyService
|
|
1237
|
-
? historyService.getTotalTokens()
|
|
1238
|
-
: 0;
|
|
1239
|
-
if (originalTokenCount === undefined) {
|
|
1240
|
-
console.warn(`Could not determine token count for model ${model}.`);
|
|
1241
|
-
this.hasFailedCompressionAttempt = !force && true;
|
|
1242
|
-
return {
|
|
1243
|
-
originalTokenCount: 0,
|
|
1244
|
-
newTokenCount: 0,
|
|
1245
|
-
compressionStatus: CompressionStatus.COMPRESSION_FAILED_TOKEN_COUNT_ERROR,
|
|
1246
|
-
};
|
|
1247
|
-
}
|
|
1248
1226
|
const contextPercentageThreshold = this.config.getChatCompression()?.contextPercentageThreshold;
|
|
1249
1227
|
// Don't compress if not forced and we are under the limit.
|
|
1250
1228
|
if (!force) {
|
|
@@ -1259,7 +1237,7 @@ export class GeminiClient {
|
|
|
1259
1237
|
};
|
|
1260
1238
|
}
|
|
1261
1239
|
}
|
|
1262
|
-
let compressBeforeIndex =
|
|
1240
|
+
let compressBeforeIndex = findCompressSplitPoint(curatedHistory, 1 - COMPRESSION_PRESERVE_THRESHOLD);
|
|
1263
1241
|
// Find the first user message after the index. This is the start of the next turn.
|
|
1264
1242
|
while (compressBeforeIndex < curatedHistory.length &&
|
|
1265
1243
|
(curatedHistory[compressBeforeIndex]?.role === 'model' ||
|
|
@@ -1310,7 +1288,6 @@ export class GeminiClient {
|
|
|
1310
1288
|
// TODO: Add proper telemetry logging once available
|
|
1311
1289
|
console.debug(`Chat compression: ${originalTokenCount} -> ${newTokenCount} tokens`);
|
|
1312
1290
|
if (newTokenCount > originalTokenCount) {
|
|
1313
|
-
this.getChat().setHistory(curatedHistory);
|
|
1314
1291
|
this.hasFailedCompressionAttempt = !force && true;
|
|
1315
1292
|
return {
|
|
1316
1293
|
originalTokenCount,
|
|
@@ -1320,6 +1297,8 @@ export class GeminiClient {
|
|
|
1320
1297
|
}
|
|
1321
1298
|
else {
|
|
1322
1299
|
this.chat = compressedChat; // Chat compression successful, set new state.
|
|
1300
|
+
// Update telemetry service with new token count
|
|
1301
|
+
uiTelemetryService.setLastPromptTokenCount(newTokenCount);
|
|
1323
1302
|
// Emit token update event for the new compressed chat
|
|
1324
1303
|
// This ensures the UI updates with the new token count
|
|
1325
1304
|
// Only emit if compression was successful
|