@vybestack/llxprt-code-core 0.4.8 → 0.5.0-nightly.251102.e5b51aa3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/dist/prompt-config/defaults/default-prompts.json +4 -17
  2. package/dist/src/auth/precedence.d.ts +69 -9
  3. package/dist/src/auth/precedence.js +467 -69
  4. package/dist/src/auth/precedence.js.map +1 -1
  5. package/dist/src/auth/types.d.ts +2 -2
  6. package/dist/src/config/config.d.ts +15 -1
  7. package/dist/src/config/config.js +118 -6
  8. package/dist/src/config/config.js.map +1 -1
  9. package/dist/src/config/index.d.ts +6 -0
  10. package/dist/src/config/index.js +5 -0
  11. package/dist/src/config/index.js.map +1 -1
  12. package/dist/src/config/profileManager.d.ts +23 -3
  13. package/dist/src/config/profileManager.js +54 -7
  14. package/dist/src/config/profileManager.js.map +1 -1
  15. package/dist/src/config/subagentManager.d.ts +96 -0
  16. package/dist/src/config/subagentManager.js +371 -0
  17. package/dist/src/config/subagentManager.js.map +1 -0
  18. package/dist/src/config/types.d.ts +18 -0
  19. package/dist/src/config/types.js +3 -0
  20. package/dist/src/config/types.js.map +1 -0
  21. package/dist/src/core/client.d.ts +27 -7
  22. package/dist/src/core/client.js +231 -55
  23. package/dist/src/core/client.js.map +1 -1
  24. package/dist/src/core/contentGenerator.d.ts +3 -1
  25. package/dist/src/core/contentGenerator.js +3 -0
  26. package/dist/src/core/contentGenerator.js.map +1 -1
  27. package/dist/src/core/coreToolScheduler.d.ts +1 -5
  28. package/dist/src/core/coreToolScheduler.js +95 -23
  29. package/dist/src/core/coreToolScheduler.js.map +1 -1
  30. package/dist/src/core/geminiChat.d.ts +42 -12
  31. package/dist/src/core/geminiChat.js +405 -205
  32. package/dist/src/core/geminiChat.js.map +1 -1
  33. package/dist/src/core/nonInteractiveToolExecutor.d.ts +3 -2
  34. package/dist/src/core/nonInteractiveToolExecutor.js +94 -10
  35. package/dist/src/core/nonInteractiveToolExecutor.js.map +1 -1
  36. package/dist/src/core/subagent.d.ts +86 -7
  37. package/dist/src/core/subagent.js +809 -79
  38. package/dist/src/core/subagent.js.map +1 -1
  39. package/dist/src/core/subagentOrchestrator.d.ts +73 -0
  40. package/dist/src/core/subagentOrchestrator.js +387 -0
  41. package/dist/src/core/subagentOrchestrator.js.map +1 -0
  42. package/dist/src/core/subagentScheduler.d.ts +16 -0
  43. package/dist/src/core/subagentScheduler.js +7 -0
  44. package/dist/src/core/subagentScheduler.js.map +1 -0
  45. package/dist/src/core/turn.d.ts +5 -1
  46. package/dist/src/core/turn.js +5 -1
  47. package/dist/src/core/turn.js.map +1 -1
  48. package/dist/src/hooks/tool-render-suppression-hook.js +6 -1
  49. package/dist/src/hooks/tool-render-suppression-hook.js.map +1 -1
  50. package/dist/src/ide/ideContext.d.ts +32 -32
  51. package/dist/src/index.d.ts +19 -1
  52. package/dist/src/index.js +15 -2
  53. package/dist/src/index.js.map +1 -1
  54. package/dist/src/interfaces/index.d.ts +1 -0
  55. package/dist/src/interfaces/index.js +4 -0
  56. package/dist/src/interfaces/index.js.map +1 -0
  57. package/dist/src/interfaces/nodejs-error.interface.d.ts +4 -0
  58. package/dist/src/interfaces/nodejs-error.interface.js +2 -0
  59. package/dist/src/interfaces/nodejs-error.interface.js.map +1 -0
  60. package/dist/src/parsers/TextToolCallParser.d.ts +17 -1
  61. package/dist/src/parsers/TextToolCallParser.js +542 -148
  62. package/dist/src/parsers/TextToolCallParser.js.map +1 -1
  63. package/dist/src/prompt-config/defaults/core.md +15 -0
  64. package/dist/src/prompt-config/defaults/providers/gemini/core.md +203 -119
  65. package/dist/src/prompt-config/defaults/tool-defaults.js +2 -0
  66. package/dist/src/prompt-config/defaults/tool-defaults.js.map +1 -1
  67. package/dist/src/prompt-config/defaults/tools/list-subagents.md +7 -0
  68. package/dist/src/prompt-config/defaults/tools/task.md +8 -0
  69. package/dist/src/providers/BaseProvider.d.ts +115 -30
  70. package/dist/src/providers/BaseProvider.js +445 -109
  71. package/dist/src/providers/BaseProvider.js.map +1 -1
  72. package/dist/src/providers/IProvider.d.ts +50 -18
  73. package/dist/src/providers/LoggingProviderWrapper.d.ts +60 -16
  74. package/dist/src/providers/LoggingProviderWrapper.js +213 -60
  75. package/dist/src/providers/LoggingProviderWrapper.js.map +1 -1
  76. package/dist/src/providers/ProviderManager.d.ts +73 -2
  77. package/dist/src/providers/ProviderManager.js +492 -40
  78. package/dist/src/providers/ProviderManager.js.map +1 -1
  79. package/dist/src/providers/anthropic/AnthropicProvider.d.ts +35 -38
  80. package/dist/src/providers/anthropic/AnthropicProvider.js +222 -227
  81. package/dist/src/providers/anthropic/AnthropicProvider.js.map +1 -1
  82. package/dist/src/providers/errors.d.ts +86 -0
  83. package/dist/src/providers/errors.js +89 -0
  84. package/dist/src/providers/errors.js.map +1 -1
  85. package/dist/src/providers/gemini/GeminiProvider.d.ts +101 -41
  86. package/dist/src/providers/gemini/GeminiProvider.js +386 -311
  87. package/dist/src/providers/gemini/GeminiProvider.js.map +1 -1
  88. package/dist/src/providers/openai/ConversationCache.d.ts +5 -3
  89. package/dist/src/providers/openai/ConversationCache.js +93 -32
  90. package/dist/src/providers/openai/ConversationCache.js.map +1 -1
  91. package/dist/src/providers/openai/OpenAIProvider.d.ts +82 -42
  92. package/dist/src/providers/openai/OpenAIProvider.js +391 -536
  93. package/dist/src/providers/openai/OpenAIProvider.js.map +1 -1
  94. package/dist/src/providers/openai/getOpenAIProviderInfo.d.ts +1 -1
  95. package/dist/src/providers/openai/getOpenAIProviderInfo.js +52 -22
  96. package/dist/src/providers/openai/getOpenAIProviderInfo.js.map +1 -1
  97. package/dist/src/providers/openai/openaiRequestParams.d.ts +7 -0
  98. package/dist/src/providers/openai/openaiRequestParams.js +66 -0
  99. package/dist/src/providers/openai/openaiRequestParams.js.map +1 -0
  100. package/dist/src/providers/openai-responses/OpenAIResponsesProvider.d.ts +6 -33
  101. package/dist/src/providers/openai-responses/OpenAIResponsesProvider.js +84 -183
  102. package/dist/src/providers/openai-responses/OpenAIResponsesProvider.js.map +1 -1
  103. package/dist/src/providers/types/providerRuntime.d.ts +17 -0
  104. package/dist/src/providers/types/providerRuntime.js +7 -0
  105. package/dist/src/providers/types/providerRuntime.js.map +1 -0
  106. package/dist/src/providers/utils/authToken.d.ts +12 -0
  107. package/dist/src/providers/utils/authToken.js +17 -0
  108. package/dist/src/providers/utils/authToken.js.map +1 -0
  109. package/dist/src/providers/utils/userMemory.d.ts +8 -0
  110. package/dist/src/providers/utils/userMemory.js +34 -0
  111. package/dist/src/providers/utils/userMemory.js.map +1 -0
  112. package/dist/src/runtime/AgentRuntimeContext.d.ts +213 -0
  113. package/dist/src/runtime/AgentRuntimeContext.js +17 -0
  114. package/dist/src/runtime/AgentRuntimeContext.js.map +1 -0
  115. package/dist/src/runtime/AgentRuntimeLoader.d.ts +47 -0
  116. package/dist/src/runtime/AgentRuntimeLoader.js +122 -0
  117. package/dist/src/runtime/AgentRuntimeLoader.js.map +1 -0
  118. package/dist/src/runtime/AgentRuntimeState.d.ts +232 -0
  119. package/dist/src/runtime/AgentRuntimeState.js +439 -0
  120. package/dist/src/runtime/AgentRuntimeState.js.map +1 -0
  121. package/dist/src/runtime/RuntimeInvocationContext.d.ts +51 -0
  122. package/dist/src/runtime/RuntimeInvocationContext.js +52 -0
  123. package/dist/src/runtime/RuntimeInvocationContext.js.map +1 -0
  124. package/dist/src/runtime/createAgentRuntimeContext.d.ts +7 -0
  125. package/dist/src/runtime/createAgentRuntimeContext.js +65 -0
  126. package/dist/src/runtime/createAgentRuntimeContext.js.map +1 -0
  127. package/dist/src/runtime/index.d.ts +13 -0
  128. package/dist/src/runtime/index.js +14 -0
  129. package/dist/src/runtime/index.js.map +1 -0
  130. package/dist/src/runtime/providerRuntimeContext.d.ts +30 -0
  131. package/dist/src/runtime/providerRuntimeContext.js +70 -0
  132. package/dist/src/runtime/providerRuntimeContext.js.map +1 -0
  133. package/dist/src/runtime/runtimeAdapters.d.ts +22 -0
  134. package/dist/src/runtime/runtimeAdapters.js +81 -0
  135. package/dist/src/runtime/runtimeAdapters.js.map +1 -0
  136. package/dist/src/runtime/runtimeStateFactory.d.ts +21 -0
  137. package/dist/src/runtime/runtimeStateFactory.js +104 -0
  138. package/dist/src/runtime/runtimeStateFactory.js.map +1 -0
  139. package/dist/src/services/todo-context-tracker.d.ts +10 -8
  140. package/dist/src/services/todo-context-tracker.js +26 -10
  141. package/dist/src/services/todo-context-tracker.js.map +1 -1
  142. package/dist/src/services/tool-call-tracker-service.d.ts +11 -7
  143. package/dist/src/services/tool-call-tracker-service.js +89 -29
  144. package/dist/src/services/tool-call-tracker-service.js.map +1 -1
  145. package/dist/src/settings/SettingsService.d.ts +4 -0
  146. package/dist/src/settings/SettingsService.js +65 -2
  147. package/dist/src/settings/SettingsService.js.map +1 -1
  148. package/dist/src/settings/settingsServiceInstance.d.ts +6 -1
  149. package/dist/src/settings/settingsServiceInstance.js +28 -8
  150. package/dist/src/settings/settingsServiceInstance.js.map +1 -1
  151. package/dist/src/telemetry/loggers.d.ts +5 -1
  152. package/dist/src/telemetry/loggers.js.map +1 -1
  153. package/dist/src/telemetry/loggers.test.circular.js +4 -0
  154. package/dist/src/telemetry/loggers.test.circular.js.map +1 -1
  155. package/dist/src/telemetry/metrics.d.ts +3 -1
  156. package/dist/src/telemetry/metrics.js.map +1 -1
  157. package/dist/src/telemetry/types.d.ts +1 -0
  158. package/dist/src/telemetry/types.js +3 -0
  159. package/dist/src/telemetry/types.js.map +1 -1
  160. package/dist/src/test-utils/index.d.ts +2 -0
  161. package/dist/src/test-utils/index.js +2 -0
  162. package/dist/src/test-utils/index.js.map +1 -1
  163. package/dist/src/test-utils/mockWorkspaceContext.d.ts +0 -3
  164. package/dist/src/test-utils/mockWorkspaceContext.js +3 -4
  165. package/dist/src/test-utils/mockWorkspaceContext.js.map +1 -1
  166. package/dist/src/test-utils/providerCallOptions.d.ts +43 -0
  167. package/dist/src/test-utils/providerCallOptions.js +137 -0
  168. package/dist/src/test-utils/providerCallOptions.js.map +1 -0
  169. package/dist/src/test-utils/runtime.d.ts +92 -0
  170. package/dist/src/test-utils/runtime.js +226 -0
  171. package/dist/src/test-utils/runtime.js.map +1 -0
  172. package/dist/src/test-utils/tools.d.ts +4 -4
  173. package/dist/src/test-utils/tools.js +20 -10
  174. package/dist/src/test-utils/tools.js.map +1 -1
  175. package/dist/src/tools/list-subagents.d.ts +31 -0
  176. package/dist/src/tools/list-subagents.js +109 -0
  177. package/dist/src/tools/list-subagents.js.map +1 -0
  178. package/dist/src/tools/task.d.ts +87 -0
  179. package/dist/src/tools/task.js +427 -0
  180. package/dist/src/tools/task.js.map +1 -0
  181. package/dist/src/tools/todo-read.js +1 -1
  182. package/dist/src/tools/todo-read.js.map +1 -1
  183. package/dist/src/tools/todo-store.js +4 -2
  184. package/dist/src/tools/todo-store.js.map +1 -1
  185. package/dist/src/tools/todo-write.js +4 -2
  186. package/dist/src/tools/todo-write.js.map +1 -1
  187. package/dist/src/tools/tool-error.d.ts +1 -0
  188. package/dist/src/tools/tool-error.js +1 -0
  189. package/dist/src/tools/tool-error.js.map +1 -1
  190. package/dist/src/tools/tool-registry.d.ts +2 -0
  191. package/dist/src/tools/tool-registry.js +46 -21
  192. package/dist/src/tools/tool-registry.js.map +1 -1
  193. package/dist/src/types/modelParams.d.ts +4 -0
  194. package/dist/src/utils/editor.js +10 -8
  195. package/dist/src/utils/editor.js.map +1 -1
  196. package/dist/src/utils/gitIgnoreParser.js +15 -3
  197. package/dist/src/utils/gitIgnoreParser.js.map +1 -1
  198. package/package.json +1 -1
  199. package/dist/src/prompt-config/defaults/providers/anthropic/core.md +0 -97
  200. package/dist/src/prompt-config/defaults/providers/anthropic/tools/glob.md +0 -34
  201. package/dist/src/prompt-config/defaults/providers/anthropic/tools/list-directory.md +0 -11
  202. package/dist/src/prompt-config/defaults/providers/anthropic/tools/read-file.md +0 -14
  203. package/dist/src/prompt-config/defaults/providers/anthropic/tools/read-many-files.md +0 -31
  204. package/dist/src/prompt-config/defaults/providers/anthropic/tools/replace.md +0 -41
  205. package/dist/src/prompt-config/defaults/providers/anthropic/tools/run-shell-command.md +0 -32
  206. package/dist/src/prompt-config/defaults/providers/anthropic/tools/save-memory.md +0 -35
  207. package/dist/src/prompt-config/defaults/providers/anthropic/tools/search-file-content.md +0 -44
  208. package/dist/src/prompt-config/defaults/providers/anthropic/tools/todo-write.md +0 -45
  209. package/dist/src/prompt-config/defaults/providers/anthropic/tools/write-file.md +0 -11
  210. package/dist/src/prompt-config/defaults/providers/openai/core.md +0 -97
  211. package/dist/src/prompt-config/defaults/providers/openai/tools/todo-pause.md +0 -28
  212. package/dist/src/prompt-config/defaults/providers/openai/tools/todo-read.md +0 -5
  213. 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
- constructor(config, contentGenerator, generationConfig = {}, initialHistory = [], historyService) {
272
- this.config = config;
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
- // Use provided HistoryService or create a new one
276
- this.historyService = historyService || new HistoryService();
292
+ const model = this.runtimeState.model;
277
293
  this.logger.debug('GeminiChat initialized:', {
278
- model: this.config.getModel(),
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), currentModel);
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
- async _logApiRequest(contents, model, prompt_id) {
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
- logApiRequest(this.config, new ApiRequestEvent(model, prompt_id, requestText));
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
- async _logApiResponse(durationMs, prompt_id, usageMetadata, responseText) {
322
- logApiResponse(this.config, new ApiResponseEvent(this.config.getModel(), durationMs, prompt_id, this.config.getContentGeneratorConfig()?.authType, usageMetadata, responseText));
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
- _logApiError(durationMs, error, prompt_id) {
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
- logApiError(this.config, new ApiErrorEvent(this.config.getModel(), errorMessage, durationMs, prompt_id, this.config.getContentGeneratorConfig()?.authType, errorType));
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
- const provider = this.getActiveProvider();
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
- this._logApiRequest(ContentConverters.toGeminiContents(iContents), this.config.getModel(), prompt_id);
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
- const modelToUse = this.config.getModel() || DEFAULT_GEMINI_FLASH_MODEL;
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
- const streamResponse = provider.generateChatCompletion(iContents, tools);
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
- const currentModel = this.config.getModel();
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
- this.logger.debug(() => `DEBUG [geminiChat]: Model from config: ${this.config.getModel()}`);
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 <= INVALID_CONTENT_RETRY_OPTIONS.maxAttempts; 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
- const provider = this.getActiveProvider();
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
- const streamResponse = provider.generateChatCompletion(requestContents, tools);
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.config.getModel());
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.config.getModel();
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
- const threshold = this.config.getEphemeralSetting('compression-threshold') ?? COMPRESSION_TOKEN_THRESHOLD;
738
- const contextLimit = this.config.getEphemeralSetting('context-limit') ?? 60000; // Default context limit
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.config.getModel());
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
- let configBudget;
879
- if (typeof this.config.getEphemeralSetting === 'function') {
880
- const candidateKeys = ['maxOutputTokens', 'max-output-tokens'];
881
- for (const key of candidateKeys) {
882
- const value = this.asNumber(this.config.getEphemeralSetting(key));
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
- const userContextLimit = this.config.getEphemeralSetting('context-limit');
898
- const limit = tokenLimit(this.config.getModel(), userContextLimit);
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
- const preserveThreshold = this.config.getEphemeralSetting('compression-preserve-threshold') ?? COMPRESSION_PRESERVE_THRESHOLD;
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
- const provider = this.getActiveProvider();
1013
- if (!provider || !this.providerSupportsIContent(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
- const stream = provider.generateChatCompletion(compressionRequest, undefined);
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.config.getModel();
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.config.getModel();
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
- const tools = this.config.getToolRegistry().getAllTools();
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
- for (const tool of tools) {
1384
- if ((tool.schema.parametersJsonSchema &&
1385
- hasCycleInSchema(tool.schema.parametersJsonSchema)) ||
1386
- (tool.schema.parameters && hasCycleInSchema(tool.schema.parameters))) {
1387
- cyclicSchemaTools.push(tool.displayName);
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
- const providerManager = this.config.getProviderManager();
1564
- if (!providerManager) {
1565
- return undefined;
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 providerManager.getActiveProvider();
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) {