@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.
Files changed (261) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/src/agents/codebase-investigator.d.ts +46 -0
  5. package/dist/src/agents/codebase-investigator.js +136 -0
  6. package/dist/src/agents/codebase-investigator.js.map +1 -0
  7. package/dist/src/agents/executor.d.ts +92 -0
  8. package/dist/src/agents/executor.js +624 -0
  9. package/dist/src/agents/executor.js.map +1 -0
  10. package/dist/src/agents/invocation.d.ts +45 -0
  11. package/dist/src/agents/invocation.js +114 -0
  12. package/dist/src/agents/invocation.js.map +1 -0
  13. package/dist/src/agents/registry.d.ts +38 -0
  14. package/dist/src/agents/registry.js +64 -0
  15. package/dist/src/agents/registry.js.map +1 -0
  16. package/dist/src/agents/types.d.ts +145 -0
  17. package/dist/src/agents/types.js +17 -0
  18. package/dist/src/agents/types.js.map +1 -0
  19. package/dist/src/agents/utils.d.ts +15 -0
  20. package/dist/src/agents/utils.js +27 -0
  21. package/dist/src/agents/utils.js.map +1 -0
  22. package/dist/src/auth/types.d.ts +4 -4
  23. package/dist/src/code_assist/oauth-credential-storage.d.ts +27 -0
  24. package/dist/src/code_assist/oauth-credential-storage.js +115 -0
  25. package/dist/src/code_assist/oauth-credential-storage.js.map +1 -0
  26. package/dist/src/code_assist/oauth2.js +36 -9
  27. package/dist/src/code_assist/oauth2.js.map +1 -1
  28. package/dist/src/config/config.d.ts +72 -8
  29. package/dist/src/config/config.js +130 -23
  30. package/dist/src/config/config.js.map +1 -1
  31. package/dist/src/config/constants.d.ts +11 -0
  32. package/dist/src/config/constants.js +16 -0
  33. package/dist/src/config/constants.js.map +1 -0
  34. package/dist/src/config/storage.d.ts +1 -0
  35. package/dist/src/config/storage.js +2 -1
  36. package/dist/src/config/storage.js.map +1 -1
  37. package/dist/src/confirmation-bus/index.d.ts +2 -0
  38. package/dist/src/confirmation-bus/index.js +3 -0
  39. package/dist/src/confirmation-bus/index.js.map +1 -0
  40. package/dist/src/confirmation-bus/message-bus.d.ts +60 -0
  41. package/dist/src/confirmation-bus/message-bus.js +141 -0
  42. package/dist/src/confirmation-bus/message-bus.js.map +1 -0
  43. package/dist/src/confirmation-bus/types.d.ts +59 -0
  44. package/dist/src/confirmation-bus/types.js +10 -0
  45. package/dist/src/confirmation-bus/types.js.map +1 -0
  46. package/dist/src/core/baseLlmClient.d.ts +77 -0
  47. package/dist/src/core/baseLlmClient.js +175 -0
  48. package/dist/src/core/baseLlmClient.js.map +1 -0
  49. package/dist/src/core/client.d.ts +13 -1
  50. package/dist/src/core/client.js +98 -119
  51. package/dist/src/core/client.js.map +1 -1
  52. package/dist/src/core/coreToolScheduler.d.ts +20 -1
  53. package/dist/src/core/coreToolScheduler.js +160 -16
  54. package/dist/src/core/coreToolScheduler.js.map +1 -1
  55. package/dist/src/core/geminiChat.d.ts +8 -1
  56. package/dist/src/core/geminiChat.js +30 -21
  57. package/dist/src/core/geminiChat.js.map +1 -1
  58. package/dist/src/core/subagent.d.ts +16 -1
  59. package/dist/src/core/subagent.js +59 -3
  60. package/dist/src/core/subagent.js.map +1 -1
  61. package/dist/src/core/subagentOrchestrator.d.ts +2 -1
  62. package/dist/src/core/subagentOrchestrator.js +36 -6
  63. package/dist/src/core/subagentOrchestrator.js.map +1 -1
  64. package/dist/src/core/turn.d.ts +1 -4
  65. package/dist/src/core/turn.js +2 -12
  66. package/dist/src/core/turn.js.map +1 -1
  67. package/dist/src/ide/detect-ide.d.ts +44 -14
  68. package/dist/src/ide/detect-ide.js +35 -75
  69. package/dist/src/ide/detect-ide.js.map +1 -1
  70. package/dist/src/ide/ide-client.d.ts +5 -4
  71. package/dist/src/ide/ide-client.js +34 -25
  72. package/dist/src/ide/ide-client.js.map +1 -1
  73. package/dist/src/ide/ide-installer.d.ts +2 -2
  74. package/dist/src/ide/ide-installer.js +7 -9
  75. package/dist/src/ide/ide-installer.js.map +1 -1
  76. package/dist/src/index.d.ts +10 -2
  77. package/dist/src/index.js +12 -3
  78. package/dist/src/index.js.map +1 -1
  79. package/dist/src/mcp/oauth-provider.d.ts +5 -1
  80. package/dist/src/mcp/oauth-provider.js +56 -44
  81. package/dist/src/mcp/oauth-provider.js.map +1 -1
  82. package/dist/src/mcp/oauth-token-storage.d.ts +43 -40
  83. package/dist/src/mcp/oauth-token-storage.js +114 -44
  84. package/dist/src/mcp/oauth-token-storage.js.map +1 -1
  85. package/dist/src/mcp/oauth-utils.js +1 -0
  86. package/dist/src/mcp/oauth-utils.js.map +1 -1
  87. package/dist/src/mcp/sa-impersonation-provider.d.ts +33 -0
  88. package/dist/src/mcp/sa-impersonation-provider.js +130 -0
  89. package/dist/src/mcp/sa-impersonation-provider.js.map +1 -0
  90. package/dist/src/mcp/token-storage/hybrid-token-storage.js +1 -1
  91. package/dist/src/policy/config.d.ts +51 -0
  92. package/dist/src/policy/config.js +102 -0
  93. package/dist/src/policy/config.js.map +1 -0
  94. package/dist/src/policy/index.d.ts +5 -0
  95. package/dist/src/policy/index.js +6 -0
  96. package/dist/src/policy/index.js.map +1 -0
  97. package/dist/src/policy/policies/discovered.toml +9 -0
  98. package/dist/src/policy/policies/read-only.toml +68 -0
  99. package/dist/src/policy/policies/write.toml +69 -0
  100. package/dist/src/policy/policies/yolo.toml +8 -0
  101. package/dist/src/policy/policy-engine.d.ts +55 -0
  102. package/dist/src/policy/policy-engine.js +126 -0
  103. package/dist/src/policy/policy-engine.js.map +1 -0
  104. package/dist/src/policy/stable-stringify.d.ts +29 -0
  105. package/dist/src/policy/stable-stringify.js +111 -0
  106. package/dist/src/policy/stable-stringify.js.map +1 -0
  107. package/dist/src/policy/toml-loader.d.ts +37 -0
  108. package/dist/src/policy/toml-loader.js +183 -0
  109. package/dist/src/policy/toml-loader.js.map +1 -0
  110. package/dist/src/policy/types.d.ts +16 -0
  111. package/dist/src/policy/types.js +7 -0
  112. package/dist/src/policy/types.js.map +1 -0
  113. package/dist/src/providers/LoggingProviderWrapper.d.ts +2 -0
  114. package/dist/src/providers/LoggingProviderWrapper.js +27 -6
  115. package/dist/src/providers/LoggingProviderWrapper.js.map +1 -1
  116. package/dist/src/providers/ProviderManager.d.ts +18 -0
  117. package/dist/src/providers/ProviderManager.js +54 -3
  118. package/dist/src/providers/ProviderManager.js.map +1 -1
  119. package/dist/src/providers/anthropic/AnthropicProvider.d.ts +49 -0
  120. package/dist/src/providers/anthropic/AnthropicProvider.js +468 -30
  121. package/dist/src/providers/anthropic/AnthropicProvider.js.map +1 -1
  122. package/dist/src/providers/openai/OpenAIProvider.d.ts +3 -0
  123. package/dist/src/providers/openai/OpenAIProvider.js +12 -6
  124. package/dist/src/providers/openai/OpenAIProvider.js.map +1 -1
  125. package/dist/src/providers/utils/localEndpoint.d.ts +39 -0
  126. package/dist/src/providers/utils/localEndpoint.js +117 -0
  127. package/dist/src/providers/utils/localEndpoint.js.map +1 -0
  128. package/dist/src/runtime/AgentRuntimeLoader.d.ts +1 -0
  129. package/dist/src/runtime/AgentRuntimeLoader.js +6 -1
  130. package/dist/src/runtime/AgentRuntimeLoader.js.map +1 -1
  131. package/dist/src/runtime/createAgentRuntimeContext.js +8 -7
  132. package/dist/src/runtime/createAgentRuntimeContext.js.map +1 -1
  133. package/dist/src/services/fileSystemService.d.ts +9 -0
  134. package/dist/src/services/fileSystemService.js +12 -1
  135. package/dist/src/services/fileSystemService.js.map +1 -1
  136. package/dist/src/services/history/HistoryService.d.ts +4 -0
  137. package/dist/src/services/history/HistoryService.js +18 -0
  138. package/dist/src/services/history/HistoryService.js.map +1 -1
  139. package/dist/src/services/history/IContent.d.ts +6 -0
  140. package/dist/src/services/history/IContent.js.map +1 -1
  141. package/dist/src/services/shellExecutionService.js +0 -6
  142. package/dist/src/services/shellExecutionService.js.map +1 -1
  143. package/dist/src/settings/types.d.ts +7 -0
  144. package/dist/src/storage/sessionTypes.d.ts +27 -0
  145. package/dist/src/storage/sessionTypes.js +10 -0
  146. package/dist/src/storage/sessionTypes.js.map +1 -0
  147. package/dist/src/telemetry/constants.d.ts +8 -0
  148. package/dist/src/telemetry/constants.js +8 -0
  149. package/dist/src/telemetry/constants.js.map +1 -1
  150. package/dist/src/telemetry/loggers.d.ts +9 -1
  151. package/dist/src/telemetry/loggers.js +154 -2
  152. package/dist/src/telemetry/loggers.js.map +1 -1
  153. package/dist/src/telemetry/metrics.d.ts +5 -0
  154. package/dist/src/telemetry/metrics.js +4 -0
  155. package/dist/src/telemetry/metrics.js.map +1 -1
  156. package/dist/src/telemetry/types.d.ts +62 -1
  157. package/dist/src/telemetry/types.js +92 -0
  158. package/dist/src/telemetry/types.js.map +1 -1
  159. package/dist/src/telemetry/uiTelemetry.d.ts +1 -1
  160. package/dist/src/telemetry/uiTelemetry.js +2 -3
  161. package/dist/src/telemetry/uiTelemetry.js.map +1 -1
  162. package/dist/src/test-utils/config.js +14 -0
  163. package/dist/src/test-utils/config.js.map +1 -1
  164. package/dist/src/test-utils/mock-tool.d.ts +8 -4
  165. package/dist/src/test-utils/mock-tool.js +35 -18
  166. package/dist/src/test-utils/mock-tool.js.map +1 -1
  167. package/dist/src/test-utils/tools.d.ts +1 -1
  168. package/dist/src/test-utils/tools.js +4 -4
  169. package/dist/src/test-utils/tools.js.map +1 -1
  170. package/dist/src/tools/edit.d.ts +3 -2
  171. package/dist/src/tools/edit.js +29 -10
  172. package/dist/src/tools/edit.js.map +1 -1
  173. package/dist/src/tools/glob.d.ts +6 -4
  174. package/dist/src/tools/glob.js +3 -3
  175. package/dist/src/tools/glob.js.map +1 -1
  176. package/dist/src/tools/grep.d.ts +3 -2
  177. package/dist/src/tools/grep.js +2 -2
  178. package/dist/src/tools/grep.js.map +1 -1
  179. package/dist/src/tools/ls.d.ts +4 -3
  180. package/dist/src/tools/ls.js +3 -3
  181. package/dist/src/tools/ls.js.map +1 -1
  182. package/dist/src/tools/mcp-client.d.ts +9 -18
  183. package/dist/src/tools/mcp-client.js +60 -102
  184. package/dist/src/tools/mcp-client.js.map +1 -1
  185. package/dist/src/tools/mcp-tool.js +7 -1
  186. package/dist/src/tools/mcp-tool.js.map +1 -1
  187. package/dist/src/tools/memoryTool.d.ts +6 -2
  188. package/dist/src/tools/memoryTool.js +14 -4
  189. package/dist/src/tools/memoryTool.js.map +1 -1
  190. package/dist/src/tools/modifiable-tool.d.ts +1 -1
  191. package/dist/src/tools/modifiable-tool.js +9 -1
  192. package/dist/src/tools/modifiable-tool.js.map +1 -1
  193. package/dist/src/tools/read-file.d.ts +3 -2
  194. package/dist/src/tools/read-file.js +2 -2
  195. package/dist/src/tools/read-file.js.map +1 -1
  196. package/dist/src/tools/read-many-files.d.ts +3 -2
  197. package/dist/src/tools/read-many-files.js +2 -2
  198. package/dist/src/tools/read-many-files.js.map +1 -1
  199. package/dist/src/tools/ripGrep.d.ts +3 -2
  200. package/dist/src/tools/ripGrep.js +2 -2
  201. package/dist/src/tools/ripGrep.js.map +1 -1
  202. package/dist/src/tools/shell.d.ts +3 -2
  203. package/dist/src/tools/shell.js +69 -9
  204. package/dist/src/tools/shell.js.map +1 -1
  205. package/dist/src/tools/smart-edit.d.ts +22 -2
  206. package/dist/src/tools/smart-edit.js +124 -12
  207. package/dist/src/tools/smart-edit.js.map +1 -1
  208. package/dist/src/tools/task.d.ts +1 -0
  209. package/dist/src/tools/task.js +33 -16
  210. package/dist/src/tools/task.js.map +1 -1
  211. package/dist/src/tools/tool-confirmation-types.d.ts +20 -0
  212. package/dist/src/tools/tool-confirmation-types.js +15 -0
  213. package/dist/src/tools/tool-confirmation-types.js.map +1 -0
  214. package/dist/src/tools/tool-error.d.ts +2 -0
  215. package/dist/src/tools/tool-error.js +2 -0
  216. package/dist/src/tools/tool-error.js.map +1 -1
  217. package/dist/src/tools/tool-registry.d.ts +8 -1
  218. package/dist/src/tools/tool-registry.js +18 -4
  219. package/dist/src/tools/tool-registry.js.map +1 -1
  220. package/dist/src/tools/tools.d.ts +52 -14
  221. package/dist/src/tools/tools.js +71 -15
  222. package/dist/src/tools/tools.js.map +1 -1
  223. package/dist/src/tools/web-fetch.d.ts +3 -2
  224. package/dist/src/tools/web-fetch.js +11 -6
  225. package/dist/src/tools/web-fetch.js.map +1 -1
  226. package/dist/src/tools/web-search-invocation.d.ts +3 -1
  227. package/dist/src/tools/web-search-invocation.js +5 -2
  228. package/dist/src/tools/web-search-invocation.js.map +1 -1
  229. package/dist/src/tools/web-search.d.ts +3 -2
  230. package/dist/src/tools/web-search.js +6 -4
  231. package/dist/src/tools/web-search.js.map +1 -1
  232. package/dist/src/tools/write-file.d.ts +3 -2
  233. package/dist/src/tools/write-file.js +11 -6
  234. package/dist/src/tools/write-file.js.map +1 -1
  235. package/dist/src/utils/bfsFileSearch.d.ts +2 -2
  236. package/dist/src/utils/editor.js +5 -3
  237. package/dist/src/utils/editor.js.map +1 -1
  238. package/dist/src/utils/getFolderStructure.d.ts +2 -2
  239. package/dist/src/utils/getFolderStructure.js +1 -1
  240. package/dist/src/utils/getFolderStructure.js.map +1 -1
  241. package/dist/src/utils/llm-edit-fixer.js +10 -1
  242. package/dist/src/utils/llm-edit-fixer.js.map +1 -1
  243. package/dist/src/utils/memoryDiscovery.d.ts +2 -1
  244. package/dist/src/utils/memoryDiscovery.js +3 -2
  245. package/dist/src/utils/memoryDiscovery.js.map +1 -1
  246. package/dist/src/utils/memoryImportProcessor.js +13 -20
  247. package/dist/src/utils/memoryImportProcessor.js.map +1 -1
  248. package/dist/src/utils/retry.d.ts +5 -1
  249. package/dist/src/utils/retry.js +31 -16
  250. package/dist/src/utils/retry.js.map +1 -1
  251. package/dist/src/utils/schemaValidator.js +11 -1
  252. package/dist/src/utils/schemaValidator.js.map +1 -1
  253. package/dist/src/utils/shell-utils.d.ts +1 -0
  254. package/dist/src/utils/shell-utils.js +6 -2
  255. package/dist/src/utils/shell-utils.js.map +1 -1
  256. package/dist/src/utils/thoughtUtils.d.ts +21 -0
  257. package/dist/src/utils/thoughtUtils.js +39 -0
  258. package/dist/src/utils/thoughtUtils.js.map +1 -0
  259. package/dist/src/utils/tool-utils.js +2 -2
  260. package/dist/src/utils/tool-utils.js.map +1 -1
  261. 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 findIndexAfterFraction(history: Content[], fraction: number): number;
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>;
@@ -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 findIndexAfterFraction(history, fraction) {
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 contentLengths = history.map((content) => JSON.stringify(content).length);
63
- const totalCharacters = contentLengths.reduce((sum, length) => sum + length, 0);
64
- const targetCharacters = totalCharacters * fraction;
65
- let charactersSoFar = 0;
66
- for (let i = 0; i < contentLengths.length; i++) {
67
- charactersSoFar += contentLengths[i];
68
- if (charactersSoFar >= targetCharacters) {
69
- return i;
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 contentLengths.length;
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
- const requestConfig = {
1080
- abortSignal,
1081
- ...this.generateContentConfig,
1082
- ...config,
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
- let text = getResponseText(result);
1096
- if (!text) {
1097
- const error = new Error('API returned an empty response for generateJson.');
1098
- await reportError(error, 'Error in generateJson: API returned an empty response.', contents, 'generateJson-empty-response');
1099
- throw error;
1100
- }
1101
- const prefix = '```json';
1102
- const suffix = '```';
1103
- if (text.startsWith(prefix) && text.endsWith(suffix)) {
1104
- // Note: upstream added logMalformedJsonResponse here but our telemetry doesn't have it
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 new Error(`Failed to generate JSON content: ${getErrorMessage(error)}`);
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
- const embedModelParams = {
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
- // Note: chat variable used later in method
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 = findIndexAfterFraction(curatedHistory, 1 - COMPRESSION_PRESERVE_THRESHOLD);
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