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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) 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 +217 -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 +383 -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.js +41 -1
  61. package/dist/src/parsers/TextToolCallParser.js.map +1 -1
  62. package/dist/src/prompt-config/defaults/core.md +15 -0
  63. package/dist/src/prompt-config/defaults/providers/gemini/core.md +203 -119
  64. package/dist/src/prompt-config/defaults/tool-defaults.js +2 -0
  65. package/dist/src/prompt-config/defaults/tool-defaults.js.map +1 -1
  66. package/dist/src/prompt-config/defaults/tools/list-subagents.md +7 -0
  67. package/dist/src/prompt-config/defaults/tools/task.md +8 -0
  68. package/dist/src/providers/BaseProvider.d.ts +115 -30
  69. package/dist/src/providers/BaseProvider.js +445 -109
  70. package/dist/src/providers/BaseProvider.js.map +1 -1
  71. package/dist/src/providers/IProvider.d.ts +50 -18
  72. package/dist/src/providers/LoggingProviderWrapper.d.ts +60 -16
  73. package/dist/src/providers/LoggingProviderWrapper.js +213 -60
  74. package/dist/src/providers/LoggingProviderWrapper.js.map +1 -1
  75. package/dist/src/providers/ProviderManager.d.ts +73 -2
  76. package/dist/src/providers/ProviderManager.js +492 -40
  77. package/dist/src/providers/ProviderManager.js.map +1 -1
  78. package/dist/src/providers/anthropic/AnthropicProvider.d.ts +35 -38
  79. package/dist/src/providers/anthropic/AnthropicProvider.js +222 -227
  80. package/dist/src/providers/anthropic/AnthropicProvider.js.map +1 -1
  81. package/dist/src/providers/errors.d.ts +86 -0
  82. package/dist/src/providers/errors.js +89 -0
  83. package/dist/src/providers/errors.js.map +1 -1
  84. package/dist/src/providers/gemini/GeminiProvider.d.ts +101 -41
  85. package/dist/src/providers/gemini/GeminiProvider.js +386 -311
  86. package/dist/src/providers/gemini/GeminiProvider.js.map +1 -1
  87. package/dist/src/providers/openai/ConversationCache.d.ts +5 -3
  88. package/dist/src/providers/openai/ConversationCache.js +93 -32
  89. package/dist/src/providers/openai/ConversationCache.js.map +1 -1
  90. package/dist/src/providers/openai/OpenAIProvider.d.ts +82 -42
  91. package/dist/src/providers/openai/OpenAIProvider.js +373 -532
  92. package/dist/src/providers/openai/OpenAIProvider.js.map +1 -1
  93. package/dist/src/providers/openai/getOpenAIProviderInfo.d.ts +1 -1
  94. package/dist/src/providers/openai/getOpenAIProviderInfo.js +52 -22
  95. package/dist/src/providers/openai/getOpenAIProviderInfo.js.map +1 -1
  96. package/dist/src/providers/openai/openaiRequestParams.d.ts +7 -0
  97. package/dist/src/providers/openai/openaiRequestParams.js +66 -0
  98. package/dist/src/providers/openai/openaiRequestParams.js.map +1 -0
  99. package/dist/src/providers/openai-responses/OpenAIResponsesProvider.d.ts +6 -33
  100. package/dist/src/providers/openai-responses/OpenAIResponsesProvider.js +84 -183
  101. package/dist/src/providers/openai-responses/OpenAIResponsesProvider.js.map +1 -1
  102. package/dist/src/providers/types/providerRuntime.d.ts +17 -0
  103. package/dist/src/providers/types/providerRuntime.js +7 -0
  104. package/dist/src/providers/types/providerRuntime.js.map +1 -0
  105. package/dist/src/providers/utils/authToken.d.ts +12 -0
  106. package/dist/src/providers/utils/authToken.js +17 -0
  107. package/dist/src/providers/utils/authToken.js.map +1 -0
  108. package/dist/src/providers/utils/userMemory.d.ts +8 -0
  109. package/dist/src/providers/utils/userMemory.js +34 -0
  110. package/dist/src/providers/utils/userMemory.js.map +1 -0
  111. package/dist/src/runtime/AgentRuntimeContext.d.ts +213 -0
  112. package/dist/src/runtime/AgentRuntimeContext.js +17 -0
  113. package/dist/src/runtime/AgentRuntimeContext.js.map +1 -0
  114. package/dist/src/runtime/AgentRuntimeLoader.d.ts +47 -0
  115. package/dist/src/runtime/AgentRuntimeLoader.js +122 -0
  116. package/dist/src/runtime/AgentRuntimeLoader.js.map +1 -0
  117. package/dist/src/runtime/AgentRuntimeState.d.ts +232 -0
  118. package/dist/src/runtime/AgentRuntimeState.js +439 -0
  119. package/dist/src/runtime/AgentRuntimeState.js.map +1 -0
  120. package/dist/src/runtime/RuntimeInvocationContext.d.ts +51 -0
  121. package/dist/src/runtime/RuntimeInvocationContext.js +52 -0
  122. package/dist/src/runtime/RuntimeInvocationContext.js.map +1 -0
  123. package/dist/src/runtime/createAgentRuntimeContext.d.ts +7 -0
  124. package/dist/src/runtime/createAgentRuntimeContext.js +58 -0
  125. package/dist/src/runtime/createAgentRuntimeContext.js.map +1 -0
  126. package/dist/src/runtime/index.d.ts +13 -0
  127. package/dist/src/runtime/index.js +14 -0
  128. package/dist/src/runtime/index.js.map +1 -0
  129. package/dist/src/runtime/providerRuntimeContext.d.ts +30 -0
  130. package/dist/src/runtime/providerRuntimeContext.js +70 -0
  131. package/dist/src/runtime/providerRuntimeContext.js.map +1 -0
  132. package/dist/src/runtime/runtimeAdapters.d.ts +22 -0
  133. package/dist/src/runtime/runtimeAdapters.js +81 -0
  134. package/dist/src/runtime/runtimeAdapters.js.map +1 -0
  135. package/dist/src/runtime/runtimeStateFactory.d.ts +21 -0
  136. package/dist/src/runtime/runtimeStateFactory.js +104 -0
  137. package/dist/src/runtime/runtimeStateFactory.js.map +1 -0
  138. package/dist/src/services/todo-context-tracker.d.ts +10 -8
  139. package/dist/src/services/todo-context-tracker.js +26 -10
  140. package/dist/src/services/todo-context-tracker.js.map +1 -1
  141. package/dist/src/services/tool-call-tracker-service.d.ts +11 -7
  142. package/dist/src/services/tool-call-tracker-service.js +89 -29
  143. package/dist/src/services/tool-call-tracker-service.js.map +1 -1
  144. package/dist/src/settings/SettingsService.d.ts +4 -0
  145. package/dist/src/settings/SettingsService.js +58 -2
  146. package/dist/src/settings/SettingsService.js.map +1 -1
  147. package/dist/src/settings/settingsServiceInstance.d.ts +6 -1
  148. package/dist/src/settings/settingsServiceInstance.js +28 -8
  149. package/dist/src/settings/settingsServiceInstance.js.map +1 -1
  150. package/dist/src/telemetry/loggers.d.ts +5 -1
  151. package/dist/src/telemetry/loggers.js.map +1 -1
  152. package/dist/src/telemetry/loggers.test.circular.js +4 -0
  153. package/dist/src/telemetry/loggers.test.circular.js.map +1 -1
  154. package/dist/src/telemetry/metrics.d.ts +3 -1
  155. package/dist/src/telemetry/metrics.js.map +1 -1
  156. package/dist/src/telemetry/types.d.ts +1 -0
  157. package/dist/src/telemetry/types.js +3 -0
  158. package/dist/src/telemetry/types.js.map +1 -1
  159. package/dist/src/test-utils/index.d.ts +2 -0
  160. package/dist/src/test-utils/index.js +2 -0
  161. package/dist/src/test-utils/index.js.map +1 -1
  162. package/dist/src/test-utils/mockWorkspaceContext.d.ts +0 -3
  163. package/dist/src/test-utils/mockWorkspaceContext.js +3 -4
  164. package/dist/src/test-utils/mockWorkspaceContext.js.map +1 -1
  165. package/dist/src/test-utils/providerCallOptions.d.ts +43 -0
  166. package/dist/src/test-utils/providerCallOptions.js +137 -0
  167. package/dist/src/test-utils/providerCallOptions.js.map +1 -0
  168. package/dist/src/test-utils/runtime.d.ts +92 -0
  169. package/dist/src/test-utils/runtime.js +226 -0
  170. package/dist/src/test-utils/runtime.js.map +1 -0
  171. package/dist/src/test-utils/tools.d.ts +4 -4
  172. package/dist/src/test-utils/tools.js +20 -10
  173. package/dist/src/test-utils/tools.js.map +1 -1
  174. package/dist/src/tools/list-subagents.d.ts +31 -0
  175. package/dist/src/tools/list-subagents.js +109 -0
  176. package/dist/src/tools/list-subagents.js.map +1 -0
  177. package/dist/src/tools/task.d.ts +87 -0
  178. package/dist/src/tools/task.js +427 -0
  179. package/dist/src/tools/task.js.map +1 -0
  180. package/dist/src/tools/todo-read.js +1 -1
  181. package/dist/src/tools/todo-read.js.map +1 -1
  182. package/dist/src/tools/todo-store.js +4 -2
  183. package/dist/src/tools/todo-store.js.map +1 -1
  184. package/dist/src/tools/todo-write.js +4 -2
  185. package/dist/src/tools/todo-write.js.map +1 -1
  186. package/dist/src/tools/tool-error.d.ts +1 -0
  187. package/dist/src/tools/tool-error.js +1 -0
  188. package/dist/src/tools/tool-error.js.map +1 -1
  189. package/dist/src/tools/tool-registry.d.ts +2 -0
  190. package/dist/src/tools/tool-registry.js +46 -21
  191. package/dist/src/tools/tool-registry.js.map +1 -1
  192. package/dist/src/types/modelParams.d.ts +4 -0
  193. package/dist/src/utils/gitIgnoreParser.js +15 -3
  194. package/dist/src/utils/gitIgnoreParser.js.map +1 -1
  195. package/package.json +1 -1
  196. package/dist/src/prompt-config/defaults/providers/anthropic/core.md +0 -97
  197. package/dist/src/prompt-config/defaults/providers/anthropic/tools/glob.md +0 -34
  198. package/dist/src/prompt-config/defaults/providers/anthropic/tools/list-directory.md +0 -11
  199. package/dist/src/prompt-config/defaults/providers/anthropic/tools/read-file.md +0 -14
  200. package/dist/src/prompt-config/defaults/providers/anthropic/tools/read-many-files.md +0 -31
  201. package/dist/src/prompt-config/defaults/providers/anthropic/tools/replace.md +0 -41
  202. package/dist/src/prompt-config/defaults/providers/anthropic/tools/run-shell-command.md +0 -32
  203. package/dist/src/prompt-config/defaults/providers/anthropic/tools/save-memory.md +0 -35
  204. package/dist/src/prompt-config/defaults/providers/anthropic/tools/search-file-content.md +0 -44
  205. package/dist/src/prompt-config/defaults/providers/anthropic/tools/todo-write.md +0 -45
  206. package/dist/src/prompt-config/defaults/providers/anthropic/tools/write-file.md +0 -11
  207. package/dist/src/prompt-config/defaults/providers/openai/core.md +0 -97
  208. package/dist/src/prompt-config/defaults/providers/openai/tools/todo-pause.md +0 -28
  209. package/dist/src/prompt-config/defaults/providers/openai/tools/todo-read.md +0 -5
  210. package/dist/src/prompt-config/defaults/providers/openai/tools/todo-write.md +0 -45
@@ -7,11 +7,42 @@
7
7
  import { LoggingProviderWrapper } from './LoggingProviderWrapper.js';
8
8
  import { logProviderSwitch, logProviderCapability, } from '../telemetry/loggers.js';
9
9
  import { ProviderSwitchEvent, ProviderCapabilityEvent, } from '../telemetry/types.js';
10
- import { getSettingsService } from '../settings/settingsServiceInstance.js';
10
+ import { getActiveProviderRuntimeContext, } from '../runtime/providerRuntimeContext.js';
11
+ import { MissingProviderRuntimeError, ProviderRuntimeNormalizationError, } from './errors.js';
12
+ import { createRuntimeInvocationContext } from '../runtime/RuntimeInvocationContext.js';
13
+ const PROVIDER_CAPABILITY_HINTS = {
14
+ gemini: {
15
+ hasModelSelection: true,
16
+ hasApiKeyConfig: true,
17
+ hasBaseUrlConfig: false,
18
+ },
19
+ openai: {
20
+ hasModelSelection: true,
21
+ hasApiKeyConfig: true,
22
+ hasBaseUrlConfig: true,
23
+ },
24
+ 'openai-responses': {
25
+ hasModelSelection: false,
26
+ hasApiKeyConfig: true,
27
+ hasBaseUrlConfig: true,
28
+ },
29
+ anthropic: {
30
+ hasModelSelection: true,
31
+ hasApiKeyConfig: true,
32
+ hasBaseUrlConfig: true,
33
+ },
34
+ };
11
35
  export class ProviderManager {
12
36
  providers;
13
37
  serverToolsProvider;
14
38
  config;
39
+ /**
40
+ * @plan PLAN-20250218-STATELESSPROVIDER.P05
41
+ * @requirement REQ-SP-001
42
+ * @pseudocode provider-invocation.md lines 8-15
43
+ */
44
+ settingsService;
45
+ runtime;
15
46
  providerCapabilities = new Map();
16
47
  sessionTokenUsage = {
17
48
  input: 0,
@@ -21,19 +52,86 @@ export class ProviderManager {
21
52
  thought: 0,
22
53
  total: 0,
23
54
  };
24
- constructor() {
55
+ constructor(init) {
56
+ const resolved = this.resolveInit(init);
25
57
  this.providers = new Map();
26
58
  this.serverToolsProvider = null;
59
+ this.settingsService = resolved.settingsService;
60
+ this.config = resolved.config ?? this.config;
61
+ this.runtime = resolved.runtime;
62
+ }
63
+ /**
64
+ * @plan PLAN-20250218-STATELESSPROVIDER.P05
65
+ * @requirement REQ-SP-001
66
+ * @pseudocode provider-invocation.md lines 8-15
67
+ * @plan:PLAN-20251018-STATELESSPROVIDER2.P03
68
+ * @requirement:REQ-SP2-002
69
+ * @pseudocode multi-runtime-baseline.md lines 3-4
70
+ */
71
+ resolveInit(init) {
72
+ let fallback = null;
73
+ const ensureFallback = () => {
74
+ if (!fallback) {
75
+ fallback = getActiveProviderRuntimeContext();
76
+ }
77
+ return fallback;
78
+ };
79
+ if (!init) {
80
+ const resolved = ensureFallback();
81
+ return {
82
+ settingsService: resolved.settingsService,
83
+ config: resolved.config,
84
+ runtime: resolved,
85
+ };
86
+ }
87
+ if (typeof init === 'object' &&
88
+ init !== null &&
89
+ 'settingsService' in init &&
90
+ ('runtimeId' in init || 'metadata' in init)) {
91
+ const context = init;
92
+ return {
93
+ settingsService: context.settingsService,
94
+ config: context.config,
95
+ runtime: context,
96
+ };
97
+ }
98
+ const initObj = init;
99
+ const runtime = initObj.runtime;
100
+ let settingsService = initObj.settingsService ?? runtime?.settingsService ?? null;
101
+ let config = initObj.config ?? runtime?.config ?? undefined;
102
+ if (!settingsService || !config) {
103
+ const resolved = ensureFallback();
104
+ settingsService = settingsService ?? resolved.settingsService;
105
+ config = config ?? resolved.config;
106
+ return {
107
+ settingsService,
108
+ config,
109
+ runtime: runtime ?? resolved,
110
+ };
111
+ }
112
+ return {
113
+ settingsService,
114
+ config,
115
+ runtime,
116
+ };
27
117
  }
28
118
  setConfig(config) {
29
119
  const oldLoggingEnabled = this.config?.getConversationLoggingEnabled() ?? false;
30
120
  const newLoggingEnabled = config.getConversationLoggingEnabled();
31
121
  this.config = config;
122
+ this.runtime = this.runtime
123
+ ? { ...this.runtime, config }
124
+ : { settingsService: this.settingsService, config };
32
125
  // If logging state changed, update provider wrapping
33
126
  if (oldLoggingEnabled !== newLoggingEnabled) {
34
127
  this.updateProviderWrapping();
35
128
  }
36
129
  }
130
+ /**
131
+ * @plan PLAN-20251018-STATELESSPROVIDER2.P06
132
+ * @requirement REQ-SP2-001
133
+ * @pseudocode base-provider-call-contract.md lines 3-5
134
+ */
37
135
  updateProviderWrapping() {
38
136
  // Re-wrap all providers (ALWAYS wrap for token tracking)
39
137
  const providers = new Map(this.providers);
@@ -43,13 +141,13 @@ export class ProviderManager {
43
141
  if ('wrappedProvider' in provider && provider.wrappedProvider) {
44
142
  baseProvider = provider.wrappedProvider;
45
143
  }
144
+ this.syncProviderRuntime(baseProvider);
46
145
  // ALWAYS wrap with LoggingProviderWrapper for token tracking
47
146
  let finalProvider = baseProvider;
48
147
  if (this.config) {
49
- baseProvider.setConfig?.(this.config);
50
148
  finalProvider = new LoggingProviderWrapper(baseProvider, this.config);
51
- finalProvider.setConfig?.(this.config);
52
149
  }
150
+ this.syncProviderRuntime(finalProvider);
53
151
  this.providers.set(name, finalProvider);
54
152
  // Update server tools provider reference if needed
55
153
  if (this.serverToolsProvider && this.serverToolsProvider.name === name) {
@@ -57,17 +155,261 @@ export class ProviderManager {
57
155
  }
58
156
  }
59
157
  }
60
- registerProvider(provider) {
61
- if (this.config) {
62
- provider.setConfig?.(this.config);
158
+ /**
159
+ * @plan PLAN-20250218-STATELESSPROVIDER.P05
160
+ * @requirement REQ-SP-001
161
+ * @pseudocode provider-invocation.md lines 8-15
162
+ * @plan:PLAN-20251023-STATELESS-HARDENING.P05
163
+ * @requirement:REQ-SP4-001
164
+ * @pseudocode provider-runtime-handling.md lines 10-15
165
+ */
166
+ syncProviderRuntime(provider) {
167
+ const runtimeAware = provider;
168
+ runtimeAware.setRuntimeSettingsService?.(this.settingsService);
169
+ if (this.config && runtimeAware.setConfig) {
170
+ runtimeAware.setConfig(this.config);
171
+ }
172
+ if (runtimeAware.setRuntimeContextResolver &&
173
+ typeof runtimeAware.setRuntimeContextResolver === 'function') {
174
+ runtimeAware.setRuntimeContextResolver(() => this.snapshotRuntimeContext('ProviderManager.syncProviderRuntime'));
175
+ }
176
+ if (runtimeAware.setOptionsNormalizer &&
177
+ typeof runtimeAware.setOptionsNormalizer === 'function') {
178
+ runtimeAware.setOptionsNormalizer((options, providerName) => this.normalizeRuntimeInputs(options, providerName));
179
+ }
180
+ }
181
+ /**
182
+ * @plan:PLAN-20251023-STATELESS-HARDENING.P05
183
+ * @requirement:REQ-SP4-001
184
+ * @pseudocode provider-runtime-handling.md lines 10-15
185
+ */
186
+ snapshotRuntimeContext(source) {
187
+ const baseRuntime = this.runtime ?? {
188
+ settingsService: this.settingsService,
189
+ config: this.config,
190
+ runtimeId: 'provider-manager.default-runtime',
191
+ metadata: { source: 'ProviderManager', requirement: 'REQ-SP4-001' },
192
+ };
193
+ if (!this.runtime) {
194
+ this.runtime = baseRuntime;
195
+ }
196
+ else if (!this.runtime.config && baseRuntime.config) {
197
+ this.runtime = { ...this.runtime, config: baseRuntime.config };
198
+ }
199
+ const settingsService = baseRuntime.settingsService;
200
+ if (!settingsService) {
201
+ throw new MissingProviderRuntimeError({
202
+ providerKey: 'ProviderManager',
203
+ missingFields: ['settings'],
204
+ stage: source,
205
+ metadata: {
206
+ requirement: 'REQ-SP4-001',
207
+ hint: 'ProviderManager requires a SettingsService to construct runtime contexts.',
208
+ },
209
+ });
210
+ }
211
+ const config = baseRuntime.config ?? this.config;
212
+ if (!config) {
213
+ throw new MissingProviderRuntimeError({
214
+ providerKey: 'ProviderManager',
215
+ missingFields: ['config'],
216
+ stage: source,
217
+ metadata: {
218
+ requirement: 'REQ-SP4-001',
219
+ hint: 'Call ProviderManager.setConfig before invoking providers.',
220
+ },
221
+ });
222
+ }
223
+ const baseMetadata = baseRuntime.metadata ?? {};
224
+ const callMetadata = {
225
+ ...baseMetadata,
226
+ source,
227
+ requirement: 'REQ-SP4-001',
228
+ generatedAt: new Date().toISOString(),
229
+ };
230
+ const baseId = baseRuntime.runtimeId;
231
+ const callRuntimeId = typeof baseId === 'string' && baseId.trim() !== ''
232
+ ? `${baseId}:${Math.random().toString(36).slice(2, 10)}`
233
+ : `provider-manager:${source}:${Date.now().toString(36)}`;
234
+ return {
235
+ settingsService,
236
+ config,
237
+ runtimeId: callRuntimeId,
238
+ metadata: callMetadata,
239
+ };
240
+ }
241
+ /**
242
+ * @plan:PLAN-20251023-STATELESS-HARDENING.P08
243
+ * @requirement:REQ-SP4-002
244
+ * @requirement:REQ-SP4-003
245
+ * @requirement:REQ-SP4-004
246
+ * @requirement:REQ-SP4-005
247
+ * @pseudocode provider-runtime-handling.md lines 10-16
248
+ *
249
+ * Normalize runtime inputs per call - no stored settings/config fallbacks.
250
+ * This method enforces that all runtime context is provided per-call and that
251
+ * providers cannot rely on stored state.
252
+ */
253
+ normalizeRuntimeInputs(rawOptions, providerName) {
254
+ const runtimeId = rawOptions.runtime?.runtimeId || 'unknown';
255
+ const targetProvider = providerName || this.getActiveProviderName();
256
+ // REQ-SP4-002: Check for required settings service and config in runtime context
257
+ const settingsService = rawOptions.settings ?? rawOptions.runtime?.settingsService;
258
+ const config = rawOptions.config ?? rawOptions.runtime?.config;
259
+ if (!settingsService) {
260
+ throw new ProviderRuntimeNormalizationError({
261
+ providerKey: 'ProviderManager',
262
+ message: 'ProviderManager requires call-scoped settings; legacy provider state is disabled.',
263
+ requirement: 'REQ-SP4-002',
264
+ runtimeId,
265
+ stage: 'normalizeRuntimeInputs',
266
+ metadata: {
267
+ hint: 'SettingsService must be provided in options.settings or runtime.settingsService',
268
+ },
269
+ });
270
+ }
271
+ if (!config) {
272
+ throw new ProviderRuntimeNormalizationError({
273
+ providerKey: 'ProviderManager',
274
+ message: 'ProviderManager requires call-scoped config; legacy provider state is disabled.',
275
+ requirement: 'REQ-SP4-002',
276
+ runtimeId,
277
+ stage: 'normalizeRuntimeInputs',
278
+ metadata: {
279
+ hint: 'Config must be provided in options.config or runtime.config',
280
+ },
281
+ });
282
+ }
283
+ // REQ-SP4-003: Compose normalized.resolved with runtime helpers
284
+ const providerSettings = settingsService.getProviderSettings(targetProvider);
285
+ const providerInstance = this.providers.get(targetProvider);
286
+ const resolved = {
287
+ model: rawOptions.resolved?.model ??
288
+ config.getModel?.() ??
289
+ providerSettings.model ??
290
+ (providerInstance
291
+ ? this.getStoredModelName(providerInstance)
292
+ : undefined),
293
+ baseURL: rawOptions.resolved?.baseURL ??
294
+ providerSettings.baseURL,
295
+ authToken: rawOptions.resolved?.authToken ??
296
+ providerSettings.apiKey,
297
+ telemetry: {
298
+ ...rawOptions.resolved?.telemetry,
299
+ runtimeId,
300
+ normalizedAt: new Date().toISOString(),
301
+ provider: targetProvider,
302
+ },
303
+ };
304
+ // REQ-SP4-003: Validate required fields in resolved options
305
+ const missingFields = [];
306
+ if (!resolved.model)
307
+ missingFields.push('model');
308
+ // Note: Gemini and some providers don't require baseURL/authToken in all configurations
309
+ const baseUrlOptionalProviders = new Set([
310
+ 'gemini',
311
+ 'openai',
312
+ 'openai-responses',
313
+ 'anthropic',
314
+ ]);
315
+ if (!resolved.baseURL && !baseUrlOptionalProviders.has(targetProvider)) {
316
+ missingFields.push('baseURL');
317
+ }
318
+ if (!resolved.authToken && targetProvider !== 'gemini') {
319
+ // Check if provider can resolve auth lazily (e.g., via OAuth)
320
+ // If the provider has getAuthToken(), it can handle its own auth precedence
321
+ const providerInstance = this.providers.get(targetProvider);
322
+ let actualProvider = providerInstance;
323
+ if (providerInstance && 'wrappedProvider' in providerInstance) {
324
+ actualProvider = providerInstance
325
+ .wrappedProvider;
326
+ }
327
+ const canResolveAuth = actualProvider &&
328
+ 'getAuthToken' in actualProvider &&
329
+ typeof actualProvider.getAuthToken === 'function';
330
+ if (!canResolveAuth) {
331
+ // Only fail for providers without lazy auth resolution capability
332
+ missingFields.push('authToken');
333
+ }
334
+ // Otherwise let the provider run its multi-modal precedence chain:
335
+ // 1. Manual key (from /key)
336
+ // 2. Keyfile (from /keyfile)
337
+ // 3. Environment variables
338
+ // 4. OAuth token (if enabled)
339
+ }
340
+ if (missingFields.length > 0) {
341
+ throw new ProviderRuntimeNormalizationError({
342
+ providerKey: 'ProviderManager',
343
+ message: `Incomplete runtime resolution (${missingFields.join(', ')}) for runtimeId=${runtimeId}`,
344
+ requirement: 'REQ-SP4-003',
345
+ runtimeId,
346
+ stage: 'normalizeRuntimeInputs',
347
+ metadata: { missingFields, provider: targetProvider },
348
+ });
63
349
  }
350
+ // REQ-SP4-005: Ensure normalized.userMemory and metadata derive from runtime context
351
+ const userMemory = rawOptions.userMemory ?? config.getUserMemory?.();
352
+ const metadata = {
353
+ ...rawOptions.metadata,
354
+ ...rawOptions.runtime?.metadata,
355
+ _normalized: true,
356
+ _normalizationTime: new Date().toISOString(),
357
+ _runtimeId: runtimeId,
358
+ _provider: targetProvider,
359
+ };
360
+ const normalizedRuntime = {
361
+ ...(rawOptions.runtime ?? {}),
362
+ settingsService,
363
+ config,
364
+ runtimeId,
365
+ metadata,
366
+ };
367
+ const userMemorySnapshot = typeof userMemory === 'string' ? userMemory : config.getUserMemory?.();
368
+ const invocation = rawOptions.invocation ??
369
+ createRuntimeInvocationContext({
370
+ runtime: normalizedRuntime,
371
+ settings: settingsService,
372
+ providerName: targetProvider,
373
+ ephemeralsSnapshot: this.buildEphemeralsSnapshot(settingsService, targetProvider),
374
+ telemetry: resolved.telemetry,
375
+ metadata,
376
+ userMemory: userMemorySnapshot ?? undefined,
377
+ fallbackRuntimeId: runtimeId,
378
+ });
379
+ return {
380
+ ...rawOptions,
381
+ settings: settingsService,
382
+ config,
383
+ runtime: normalizedRuntime,
384
+ resolved,
385
+ userMemory,
386
+ metadata,
387
+ invocation,
388
+ };
389
+ }
390
+ buildEphemeralsSnapshot(settingsService, providerName) {
391
+ const globalEphemerals = settingsService.getAllGlobalSettings();
392
+ const providerEphemerals = settingsService.getProviderSettings(providerName);
393
+ const snapshot = {
394
+ ...globalEphemerals,
395
+ };
396
+ snapshot[providerName] = { ...providerEphemerals };
397
+ return snapshot;
398
+ }
399
+ /**
400
+ * @plan PLAN-20251018-STATELESSPROVIDER2.P06
401
+ * @requirement REQ-SP2-001
402
+ * @pseudocode base-provider-call-contract.md lines 3-5
403
+ */
404
+ registerProvider(provider) {
405
+ this.syncProviderRuntime(provider);
64
406
  // ALWAYS wrap provider to enable token tracking
65
407
  // (LoggingProviderWrapper handles both token tracking AND conversation logging)
66
408
  let finalProvider = provider;
67
409
  if (this.config) {
68
410
  finalProvider = new LoggingProviderWrapper(provider, this.config);
69
- finalProvider.setConfig?.(this.config);
70
411
  }
412
+ this.syncProviderRuntime(finalProvider);
71
413
  this.providers.set(provider.name, finalProvider);
72
414
  // Capture provider capabilities
73
415
  const capabilities = this.captureProviderCapabilities(provider);
@@ -78,10 +420,9 @@ export class ProviderManager {
78
420
  logProviderCapability(this.config, new ProviderCapabilityEvent(provider.name, capabilities, context));
79
421
  }
80
422
  // If this is the default provider and no provider is active, set it as active
81
- const settingsService = getSettingsService();
82
- const currentActiveProvider = settingsService.get('activeProvider');
423
+ const currentActiveProvider = this.settingsService.get('activeProvider');
83
424
  if (provider.isDefault && !currentActiveProvider) {
84
- settingsService.set('activeProvider', provider.name);
425
+ this.settingsService.set('activeProvider', provider.name);
85
426
  }
86
427
  // If registering Gemini and we don't have a serverToolsProvider, use it
87
428
  if (provider.name === 'gemini' && !this.serverToolsProvider) {
@@ -92,22 +433,26 @@ export class ProviderManager {
92
433
  this.serverToolsProvider = provider;
93
434
  }
94
435
  }
436
+ /**
437
+ * @plan PLAN-20251018-STATELESSPROVIDER2.P06
438
+ * @requirement REQ-SP2-001
439
+ * @pseudocode base-provider-call-contract.md lines 3-5
440
+ */
95
441
  setActiveProvider(name) {
96
442
  if (!this.providers.has(name)) {
97
443
  throw new Error('Provider not found');
98
444
  }
99
445
  // Store reference to the current active provider before switching
100
- const settingsService = getSettingsService();
101
- const previousProviderName = settingsService.get('activeProvider') || '';
446
+ const previousProviderName = this.settingsService.get('activeProvider') || '';
102
447
  // Only clear state from the provider we're switching FROM
103
448
  // BUT never clear the serverToolsProvider's state
104
449
  if (previousProviderName && previousProviderName !== name) {
105
450
  const previousProvider = this.providers.get(previousProviderName);
106
- if (previousProvider && previousProvider.clearState) {
107
- // Don't clear state if this provider is also the serverToolsProvider
108
- if (previousProvider !== this.serverToolsProvider) {
109
- previousProvider.clearState();
110
- }
451
+ if (previousProvider &&
452
+ previousProvider !== this.serverToolsProvider &&
453
+ 'clearState' in previousProvider) {
454
+ const candidate = previousProvider;
455
+ candidate.clearState?.();
111
456
  }
112
457
  }
113
458
  // Log provider switch if conversation logging enabled
@@ -117,7 +462,7 @@ export class ProviderManager {
117
462
  logProviderSwitch(this.config, new ProviderSwitchEvent(previousProviderName, name, this.generateConversationId(), this.isContextPreserved(previousProviderName, name)));
118
463
  }
119
464
  // Update SettingsService as the single source of truth
120
- settingsService.set('activeProvider', name);
465
+ this.settingsService.set('activeProvider', name);
121
466
  // If switching to Gemini, use it as both active and serverTools provider
122
467
  // BUT only if we don't already have a Gemini serverToolsProvider with auth state
123
468
  if (name === 'gemini') {
@@ -134,16 +479,36 @@ export class ProviderManager {
134
479
  }
135
480
  }
136
481
  clearActiveProvider() {
137
- const settingsService = getSettingsService();
138
- settingsService.set('activeProvider', '');
482
+ this.settingsService.set('activeProvider', '');
139
483
  }
140
484
  getActiveProvider() {
141
- const settingsService = getSettingsService();
142
- const activeProviderName = settingsService.get('activeProvider') || '';
143
- if (!activeProviderName) {
485
+ const activeProviderName = this.settingsService.get('activeProvider') || '';
486
+ let resolvedName = activeProviderName;
487
+ if (!resolvedName) {
488
+ const preferredFromConfig = this.config?.getProvider?.();
489
+ if (preferredFromConfig && this.providers.has(preferredFromConfig)) {
490
+ resolvedName = preferredFromConfig;
491
+ }
492
+ else if (this.providers.has('openai')) {
493
+ resolvedName = 'openai';
494
+ }
495
+ else {
496
+ const firstProvider = this.providers.keys().next();
497
+ resolvedName = firstProvider.done ? '' : firstProvider.value;
498
+ }
499
+ if (resolvedName) {
500
+ try {
501
+ this.setActiveProvider(resolvedName);
502
+ }
503
+ catch (error) {
504
+ throw new Error(`Unable to set default provider '${resolvedName}': ${String(error)}`);
505
+ }
506
+ }
507
+ }
508
+ if (!resolvedName) {
144
509
  throw new Error('No active provider set');
145
510
  }
146
- const provider = this.providers.get(activeProviderName);
511
+ const provider = this.providers.get(resolvedName);
147
512
  if (!provider) {
148
513
  throw new Error('Active provider not found');
149
514
  }
@@ -178,12 +543,10 @@ export class ProviderManager {
178
543
  return this.providers.get(name);
179
544
  }
180
545
  getActiveProviderName() {
181
- const settingsService = getSettingsService();
182
- return settingsService.get('activeProvider') || '';
546
+ return this.settingsService.get('activeProvider') || '';
183
547
  }
184
548
  hasActiveProvider() {
185
- const settingsService = getSettingsService();
186
- const activeProviderName = settingsService.get('activeProvider') || '';
549
+ const activeProviderName = this.settingsService.get('activeProvider') || '';
187
550
  return activeProviderName !== '' && this.providers.has(activeProviderName);
188
551
  }
189
552
  getServerToolsProvider() {
@@ -218,39 +581,55 @@ export class ProviderManager {
218
581
  // Context is considered preserved if compatibility is high
219
582
  return capabilityScore > 0.7;
220
583
  }
584
+ getStoredModelName(provider) {
585
+ const providerSettings = this.settingsService.getProviderSettings(provider.name);
586
+ const storedModel = providerSettings.model;
587
+ if (storedModel && typeof storedModel === 'string' && storedModel.trim()) {
588
+ return storedModel;
589
+ }
590
+ if (this.config &&
591
+ typeof this.config.getProvider === 'function' &&
592
+ this.config.getProvider() === provider.name) {
593
+ const configModel = this.config.getModel();
594
+ if (configModel) {
595
+ return configModel;
596
+ }
597
+ }
598
+ return provider.getDefaultModel?.() ?? '';
599
+ }
221
600
  captureProviderCapabilities(provider) {
601
+ const hints = PROVIDER_CAPABILITY_HINTS[provider.name] ?? {};
222
602
  return {
223
603
  supportsStreaming: true, // All current providers support streaming
224
604
  supportsTools: provider.getServerTools().length > 0,
225
605
  supportsVision: this.detectVisionSupport(provider),
226
606
  maxTokens: this.getProviderMaxTokens(provider),
227
607
  supportedFormats: this.getSupportedToolFormats(provider),
228
- hasModelSelection: typeof provider.setModel === 'function',
229
- hasApiKeyConfig: typeof provider.setApiKey === 'function',
230
- hasBaseUrlConfig: typeof provider.setBaseUrl === 'function',
608
+ hasModelSelection: hints.hasModelSelection ?? true,
609
+ hasApiKeyConfig: hints.hasApiKeyConfig ?? true,
610
+ hasBaseUrlConfig: hints.hasBaseUrlConfig ?? true,
231
611
  supportsPaidMode: typeof provider.isPaidMode === 'function',
232
612
  };
233
613
  }
234
614
  detectVisionSupport(provider) {
235
615
  // Provider-specific vision detection logic
616
+ const model = this.getStoredModelName(provider).toLowerCase();
236
617
  switch (provider.name) {
237
618
  case 'gemini': {
238
619
  return true;
239
620
  }
240
621
  case 'openai': {
241
- const model = provider.getCurrentModel?.() || '';
242
622
  return model.includes('vision') || model.includes('gpt-4');
243
623
  }
244
624
  case 'anthropic': {
245
- const claudeModel = provider.getCurrentModel?.() || '';
246
- return claudeModel.includes('claude-3');
625
+ return model.includes('claude-3');
247
626
  }
248
627
  default:
249
628
  return false;
250
629
  }
251
630
  }
252
631
  getProviderMaxTokens(provider) {
253
- const model = provider.getCurrentModel?.() || '';
632
+ const model = this.getStoredModelName(provider).toLowerCase();
254
633
  switch (provider.name) {
255
634
  case 'gemini':
256
635
  if (model.includes('pro'))
@@ -285,10 +664,12 @@ export class ProviderManager {
285
664
  }
286
665
  }
287
666
  createProviderContext(provider, capabilities) {
667
+ const providerSettings = this.settingsService.getProviderSettings(provider.name);
668
+ const toolFormatSetting = providerSettings.toolFormat ?? 'auto';
288
669
  return {
289
670
  providerName: provider.name,
290
- currentModel: provider.getCurrentModel?.() || 'unknown',
291
- toolFormat: provider.getToolFormat?.() || 'unknown',
671
+ currentModel: this.getStoredModelName(provider) || 'unknown',
672
+ toolFormat: toolFormatSetting,
292
673
  isPaidMode: provider.isPaidMode?.() || false,
293
674
  capabilities,
294
675
  sessionStartTime: Date.now(),
@@ -388,10 +769,81 @@ export class ProviderManager {
388
769
  // Conversation ID is now managed by the logging system
389
770
  }
390
771
  getProviderCapabilities(providerName) {
391
- const settingsService = getSettingsService();
392
- const name = providerName || settingsService.get('activeProvider') || '';
772
+ const name = providerName ||
773
+ this.settingsService.get('activeProvider') ||
774
+ '';
393
775
  return this.providerCapabilities.get(name);
394
776
  }
777
+ /* @plan:PLAN-20251023-STATELESS-HARDENING.P06 */
778
+ /* @requirement:REQ-SP4-004 */
779
+ prepareStatelessProviderInvocation(context) {
780
+ const stage = 'ProviderManager.prepareStatelessProviderInvocation';
781
+ const runtimeContext = context ?? this.runtime;
782
+ if (!runtimeContext) {
783
+ throw new MissingProviderRuntimeError({
784
+ providerKey: 'ProviderManager',
785
+ missingFields: ['runtime'],
786
+ requirement: 'REQ-SP4-004',
787
+ stage,
788
+ metadata: {
789
+ hint: 'Register CLI runtime context before invoking providers.',
790
+ },
791
+ });
792
+ }
793
+ if (!runtimeContext.settingsService) {
794
+ throw new MissingProviderRuntimeError({
795
+ providerKey: 'ProviderManager',
796
+ missingFields: ['settings'],
797
+ requirement: 'REQ-SP4-004',
798
+ stage,
799
+ metadata: {
800
+ runtimeId: runtimeContext.runtimeId,
801
+ hint: 'ProviderManager requires a SettingsService for stateless invocation.',
802
+ },
803
+ });
804
+ }
805
+ const resolvedConfig = runtimeContext.config ?? this.config;
806
+ if (!resolvedConfig) {
807
+ throw new MissingProviderRuntimeError({
808
+ providerKey: 'ProviderManager',
809
+ missingFields: ['config'],
810
+ requirement: 'REQ-SP4-004',
811
+ stage,
812
+ metadata: {
813
+ runtimeId: runtimeContext.runtimeId,
814
+ hint: 'ProviderManager requires Config before stateless invocation.',
815
+ },
816
+ });
817
+ }
818
+ const statelessMetadata = {
819
+ ...(runtimeContext.metadata ?? {}),
820
+ statelessHardening: 'strict',
821
+ statelessProviderMode: 'strict',
822
+ statelessGuards: true,
823
+ statelessMode: 'strict',
824
+ requirement: 'REQ-SP4-004',
825
+ source: stage,
826
+ preparedAt: new Date().toISOString(),
827
+ };
828
+ this.runtime = {
829
+ ...runtimeContext,
830
+ settingsService: runtimeContext.settingsService,
831
+ config: resolvedConfig,
832
+ metadata: statelessMetadata,
833
+ };
834
+ this.settingsService = runtimeContext.settingsService;
835
+ this.config = resolvedConfig;
836
+ for (const provider of this.providers.values()) {
837
+ this.attachStatelessRuntimeMetadata(provider, statelessMetadata);
838
+ }
839
+ }
840
+ attachStatelessRuntimeMetadata(provider, metadata) {
841
+ const statelessAware = provider;
842
+ statelessAware.attachStatelessRuntimeMetadata?.(metadata);
843
+ if (statelessAware.wrappedProvider) {
844
+ this.attachStatelessRuntimeMetadata(statelessAware.wrappedProvider, metadata);
845
+ }
846
+ }
395
847
  compareProviders(provider1, provider2) {
396
848
  const cap1 = this.providerCapabilities.get(provider1);
397
849
  const cap2 = this.providerCapabilities.get(provider2);