nova-terminal-assistant 0.1.0

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.

Potentially problematic release.


This version of nova-terminal-assistant might be problematic. Click here for more details.

Files changed (192) hide show
  1. package/README.md +358 -0
  2. package/bin/nova +38 -0
  3. package/bin/nova.js +12 -0
  4. package/package.json +67 -0
  5. package/src/cli/commands/SmartCompletion.ts +458 -0
  6. package/src/cli/index.ts +5 -0
  7. package/src/cli/startup/IFlowRepl.ts +212 -0
  8. package/src/cli/startup/InkBasedRepl.ts +1056 -0
  9. package/src/cli/startup/InteractiveRepl.ts +2833 -0
  10. package/src/cli/startup/NovaApp.ts +1861 -0
  11. package/src/cli/startup/index.ts +4 -0
  12. package/src/cli/startup/parseArgs.ts +293 -0
  13. package/src/cli/test-modules.ts +27 -0
  14. package/src/cli/ui/IFlowDropdown.ts +425 -0
  15. package/src/cli/ui/ModernReplUI.ts +276 -0
  16. package/src/cli/ui/SimpleSelector2.ts +215 -0
  17. package/src/cli/ui/components/ConfirmDialog.ts +176 -0
  18. package/src/cli/ui/components/ErrorPanel.ts +364 -0
  19. package/src/cli/ui/components/InkAppRunner.tsx +67 -0
  20. package/src/cli/ui/components/InkComponents.tsx +613 -0
  21. package/src/cli/ui/components/NovaInkApp.tsx +312 -0
  22. package/src/cli/ui/components/ProgressBar.ts +177 -0
  23. package/src/cli/ui/components/ProgressIndicator.ts +298 -0
  24. package/src/cli/ui/components/QuickActions.ts +396 -0
  25. package/src/cli/ui/components/SimpleErrorPanel.ts +231 -0
  26. package/src/cli/ui/components/StatusBar.ts +194 -0
  27. package/src/cli/ui/components/ThinkingBlockRenderer.ts +401 -0
  28. package/src/cli/ui/components/index.ts +27 -0
  29. package/src/cli/ui/ink-prototype.tsx +347 -0
  30. package/src/cli/utils/CliUI.ts +336 -0
  31. package/src/cli/utils/CompletionHelper.ts +388 -0
  32. package/src/cli/utils/EnhancedCompleter.test.ts +226 -0
  33. package/src/cli/utils/EnhancedCompleter.ts +513 -0
  34. package/src/cli/utils/ErrorEnhancer.ts +429 -0
  35. package/src/cli/utils/OutputFormatter.ts +193 -0
  36. package/src/cli/utils/index.ts +9 -0
  37. package/src/core/agents/AgentOrchestrator.ts +515 -0
  38. package/src/core/agents/index.ts +17 -0
  39. package/src/core/audit/AuditLogger.ts +509 -0
  40. package/src/core/audit/index.ts +11 -0
  41. package/src/core/auth/AuthManager.d.ts.map +1 -0
  42. package/src/core/auth/AuthManager.ts +138 -0
  43. package/src/core/auth/index.d.ts.map +1 -0
  44. package/src/core/auth/index.ts +2 -0
  45. package/src/core/config/ConfigManager.d.ts.map +1 -0
  46. package/src/core/config/ConfigManager.test.ts +183 -0
  47. package/src/core/config/ConfigManager.ts +1219 -0
  48. package/src/core/config/index.d.ts.map +1 -0
  49. package/src/core/config/index.ts +1 -0
  50. package/src/core/context/ContextBuilder.d.ts.map +1 -0
  51. package/src/core/context/ContextBuilder.ts +171 -0
  52. package/src/core/context/ContextCompressor.d.ts.map +1 -0
  53. package/src/core/context/ContextCompressor.ts +642 -0
  54. package/src/core/context/LayeredMemoryManager.ts +657 -0
  55. package/src/core/context/MemoryDiscovery.d.ts.map +1 -0
  56. package/src/core/context/MemoryDiscovery.ts +175 -0
  57. package/src/core/context/defaultSystemPrompt.d.ts.map +1 -0
  58. package/src/core/context/defaultSystemPrompt.ts +35 -0
  59. package/src/core/context/index.d.ts.map +1 -0
  60. package/src/core/context/index.ts +22 -0
  61. package/src/core/extensions/SkillGenerator.ts +421 -0
  62. package/src/core/extensions/SkillInstaller.d.ts.map +1 -0
  63. package/src/core/extensions/SkillInstaller.ts +257 -0
  64. package/src/core/extensions/SkillRegistry.d.ts.map +1 -0
  65. package/src/core/extensions/SkillRegistry.ts +361 -0
  66. package/src/core/extensions/SkillValidator.ts +525 -0
  67. package/src/core/extensions/index.ts +15 -0
  68. package/src/core/index.d.ts.map +1 -0
  69. package/src/core/index.ts +42 -0
  70. package/src/core/mcp/McpManager.d.ts.map +1 -0
  71. package/src/core/mcp/McpManager.ts +632 -0
  72. package/src/core/mcp/index.d.ts.map +1 -0
  73. package/src/core/mcp/index.ts +2 -0
  74. package/src/core/model/ModelClient.d.ts.map +1 -0
  75. package/src/core/model/ModelClient.ts +217 -0
  76. package/src/core/model/ModelConnectionTester.ts +363 -0
  77. package/src/core/model/ModelValidator.ts +348 -0
  78. package/src/core/model/index.d.ts.map +1 -0
  79. package/src/core/model/index.ts +6 -0
  80. package/src/core/model/providers/AnthropicProvider.d.ts.map +1 -0
  81. package/src/core/model/providers/AnthropicProvider.ts +279 -0
  82. package/src/core/model/providers/CodingPlanProvider.d.ts.map +1 -0
  83. package/src/core/model/providers/CodingPlanProvider.ts +210 -0
  84. package/src/core/model/providers/OllamaCloudProvider.d.ts.map +1 -0
  85. package/src/core/model/providers/OllamaCloudProvider.ts +405 -0
  86. package/src/core/model/providers/OllamaManager.d.ts.map +1 -0
  87. package/src/core/model/providers/OllamaManager.ts +201 -0
  88. package/src/core/model/providers/OllamaProvider.d.ts.map +1 -0
  89. package/src/core/model/providers/OllamaProvider.ts +73 -0
  90. package/src/core/model/providers/OpenAICompatibleProvider.d.ts.map +1 -0
  91. package/src/core/model/providers/OpenAICompatibleProvider.ts +327 -0
  92. package/src/core/model/providers/OpenAIProvider.d.ts.map +1 -0
  93. package/src/core/model/providers/OpenAIProvider.ts +29 -0
  94. package/src/core/model/providers/index.d.ts.map +1 -0
  95. package/src/core/model/providers/index.ts +12 -0
  96. package/src/core/model/types.d.ts.map +1 -0
  97. package/src/core/model/types.ts +77 -0
  98. package/src/core/security/ApprovalManager.d.ts.map +1 -0
  99. package/src/core/security/ApprovalManager.ts +174 -0
  100. package/src/core/security/FileFilter.d.ts.map +1 -0
  101. package/src/core/security/FileFilter.ts +141 -0
  102. package/src/core/security/HookExecutor.d.ts.map +1 -0
  103. package/src/core/security/HookExecutor.ts +178 -0
  104. package/src/core/security/SandboxExecutor.ts +447 -0
  105. package/src/core/security/index.d.ts.map +1 -0
  106. package/src/core/security/index.ts +8 -0
  107. package/src/core/session/AgentLoop.d.ts.map +1 -0
  108. package/src/core/session/AgentLoop.ts +501 -0
  109. package/src/core/session/SessionManager.d.ts.map +1 -0
  110. package/src/core/session/SessionManager.test.ts +183 -0
  111. package/src/core/session/SessionManager.ts +460 -0
  112. package/src/core/session/index.d.ts.map +1 -0
  113. package/src/core/session/index.ts +3 -0
  114. package/src/core/telemetry/Telemetry.d.ts.map +1 -0
  115. package/src/core/telemetry/Telemetry.ts +90 -0
  116. package/src/core/telemetry/TelemetryService.ts +531 -0
  117. package/src/core/telemetry/index.d.ts.map +1 -0
  118. package/src/core/telemetry/index.ts +12 -0
  119. package/src/core/testing/AutoFixer.ts +385 -0
  120. package/src/core/testing/ErrorAnalyzer.ts +499 -0
  121. package/src/core/testing/TestRunner.ts +265 -0
  122. package/src/core/testing/agent-cli-tests.ts +538 -0
  123. package/src/core/testing/index.ts +11 -0
  124. package/src/core/tools/ToolRegistry.d.ts.map +1 -0
  125. package/src/core/tools/ToolRegistry.test.ts +206 -0
  126. package/src/core/tools/ToolRegistry.ts +260 -0
  127. package/src/core/tools/impl/EditFileTool.d.ts.map +1 -0
  128. package/src/core/tools/impl/EditFileTool.ts +97 -0
  129. package/src/core/tools/impl/ListDirectoryTool.d.ts.map +1 -0
  130. package/src/core/tools/impl/ListDirectoryTool.ts +142 -0
  131. package/src/core/tools/impl/MemoryTool.d.ts.map +1 -0
  132. package/src/core/tools/impl/MemoryTool.ts +102 -0
  133. package/src/core/tools/impl/ReadFileTool.d.ts.map +1 -0
  134. package/src/core/tools/impl/ReadFileTool.ts +58 -0
  135. package/src/core/tools/impl/SearchContentTool.d.ts.map +1 -0
  136. package/src/core/tools/impl/SearchContentTool.ts +94 -0
  137. package/src/core/tools/impl/SearchFileTool.d.ts.map +1 -0
  138. package/src/core/tools/impl/SearchFileTool.ts +61 -0
  139. package/src/core/tools/impl/ShellTool.d.ts.map +1 -0
  140. package/src/core/tools/impl/ShellTool.ts +118 -0
  141. package/src/core/tools/impl/TaskTool.d.ts.map +1 -0
  142. package/src/core/tools/impl/TaskTool.ts +207 -0
  143. package/src/core/tools/impl/TodoTool.d.ts.map +1 -0
  144. package/src/core/tools/impl/TodoTool.ts +122 -0
  145. package/src/core/tools/impl/WebFetchTool.d.ts.map +1 -0
  146. package/src/core/tools/impl/WebFetchTool.ts +103 -0
  147. package/src/core/tools/impl/WebSearchTool.d.ts.map +1 -0
  148. package/src/core/tools/impl/WebSearchTool.ts +89 -0
  149. package/src/core/tools/impl/WriteFileTool.d.ts.map +1 -0
  150. package/src/core/tools/impl/WriteFileTool.ts +49 -0
  151. package/src/core/tools/impl/index.d.ts.map +1 -0
  152. package/src/core/tools/impl/index.ts +16 -0
  153. package/src/core/tools/index.d.ts.map +1 -0
  154. package/src/core/tools/index.ts +7 -0
  155. package/src/core/tools/schemas/execution.d.ts.map +1 -0
  156. package/src/core/tools/schemas/execution.ts +42 -0
  157. package/src/core/tools/schemas/file.d.ts.map +1 -0
  158. package/src/core/tools/schemas/file.ts +119 -0
  159. package/src/core/tools/schemas/index.d.ts.map +1 -0
  160. package/src/core/tools/schemas/index.ts +11 -0
  161. package/src/core/tools/schemas/memory.d.ts.map +1 -0
  162. package/src/core/tools/schemas/memory.ts +52 -0
  163. package/src/core/tools/schemas/orchestration.d.ts.map +1 -0
  164. package/src/core/tools/schemas/orchestration.ts +44 -0
  165. package/src/core/tools/schemas/search.d.ts.map +1 -0
  166. package/src/core/tools/schemas/search.ts +112 -0
  167. package/src/core/tools/schemas/todo.d.ts.map +1 -0
  168. package/src/core/tools/schemas/todo.ts +32 -0
  169. package/src/core/tools/schemas/web.d.ts.map +1 -0
  170. package/src/core/tools/schemas/web.ts +86 -0
  171. package/src/core/types/config.d.ts.map +1 -0
  172. package/src/core/types/config.ts +200 -0
  173. package/src/core/types/errors.d.ts.map +1 -0
  174. package/src/core/types/errors.ts +204 -0
  175. package/src/core/types/index.d.ts.map +1 -0
  176. package/src/core/types/index.ts +8 -0
  177. package/src/core/types/session.d.ts.map +1 -0
  178. package/src/core/types/session.ts +216 -0
  179. package/src/core/types/tools.d.ts.map +1 -0
  180. package/src/core/types/tools.ts +157 -0
  181. package/src/core/utils/CheckpointManager.d.ts.map +1 -0
  182. package/src/core/utils/CheckpointManager.ts +327 -0
  183. package/src/core/utils/Logger.d.ts.map +1 -0
  184. package/src/core/utils/Logger.ts +98 -0
  185. package/src/core/utils/RetryManager.ts +471 -0
  186. package/src/core/utils/TokenCounter.d.ts.map +1 -0
  187. package/src/core/utils/TokenCounter.ts +414 -0
  188. package/src/core/utils/VectorMemoryStore.ts +440 -0
  189. package/src/core/utils/helpers.d.ts.map +1 -0
  190. package/src/core/utils/helpers.ts +89 -0
  191. package/src/core/utils/index.d.ts.map +1 -0
  192. package/src/core/utils/index.ts +19 -0
@@ -0,0 +1,348 @@
1
+ // ============================================================================
2
+ // ModelValidator - Validates model availability and provider configuration
3
+ // ============================================================================
4
+
5
+ import fetch from 'node-fetch';
6
+ import { AuthManager } from '../auth/AuthManager.js';
7
+
8
+ export interface ProviderValidationResult {
9
+ isConfigured: boolean;
10
+ isAccessible: boolean;
11
+ error?: string;
12
+ models: string[];
13
+ }
14
+
15
+ export interface ModelValidationResult {
16
+ provider: string;
17
+ modelId: string;
18
+ isValid: boolean;
19
+ error?: string;
20
+ displayName?: string;
21
+ }
22
+
23
+ export class ModelValidator {
24
+ private authManager: any;
25
+
26
+ /**
27
+ * Validate if a provider is properly configured and accessible
28
+ */
29
+ async validateProvider(providerName: string): Promise<ProviderValidationResult> {
30
+ // Check if provider is configured
31
+ const isConfigured = this.authManager.hasCredentials(providerName);
32
+ if (!isConfigured) {
33
+ return {
34
+ isConfigured: false,
35
+ isAccessible: false,
36
+ error: `Provider "${providerName}" not configured`,
37
+ models: []
38
+ };
39
+ }
40
+
41
+ try {
42
+ switch (providerName.toLowerCase()) {
43
+ case 'ollama':
44
+ return await this.validateOllama();
45
+ case 'ollama-cloud':
46
+ return await this.validateOllamaCloud();
47
+ default:
48
+ return await this.validateApiProvider(providerName);
49
+ }
50
+ } catch (error) {
51
+ return {
52
+ isConfigured: true,
53
+ isAccessible: false,
54
+ error: `Failed to connect to ${providerName}: ${(error as Error).message}`,
55
+ models: []
56
+ };
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Validate a specific model for a provider
62
+ */
63
+ async validateModel(providerName: string, modelId: string): Promise<ModelValidationResult> {
64
+ try {
65
+ const providerResult = await this.validateProvider(providerName);
66
+
67
+ if (!providerResult.isAccessible) {
68
+ return {
69
+ provider: providerName,
70
+ modelId,
71
+ isValid: false,
72
+ error: providerResult.error
73
+ };
74
+ }
75
+
76
+ // For API providers, basic validation is enough
77
+ if (this.isApiProvider(providerName)) {
78
+ return {
79
+ provider: providerName,
80
+ modelId,
81
+ isValid: true,
82
+ displayName: modelId
83
+ };
84
+ }
85
+
86
+ // For Ollama, check if model exists locally
87
+ if (providerName === 'ollama') {
88
+ return await this.validateOllamaModel(modelId);
89
+ }
90
+
91
+ return {
92
+ provider: providerName,
93
+ modelId,
94
+ isValid: true,
95
+ displayName: modelId
96
+ };
97
+
98
+ } catch (error) {
99
+ return {
100
+ provider: providerName,
101
+ modelId,
102
+ isValid: false,
103
+ error: `Validation failed: ${(error as Error).message}`
104
+ };
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Get all available and validated models for all configured providers
110
+ */
111
+ async getValidatedModels(): Promise<Map<string, ModelValidationResult[]>> {
112
+ const result = new Map<string, ModelValidationResult[]>();
113
+
114
+ try {
115
+ // Get all providers from config
116
+ const { ConfigManager } = await import('../../../core/src/config/ConfigManager.js');
117
+ const config = ConfigManager.getInstance ? ConfigManager.getInstance() : new ConfigManager();
118
+ const providers = Object.keys(config.getConfig().models.providers);
119
+ } catch (error) {
120
+ console.warn(`⚠️ Could not load config: ${(error as Error).message}`);
121
+ return result; // Return empty map on error
122
+ }
123
+
124
+ for (const providerName of providers) {
125
+ const providerResult = await this.validateProvider(providerName);
126
+
127
+ if (providerResult.isAccessible && providerResult.models.length > 0) {
128
+ const models: ModelValidationResult[] = [];
129
+
130
+ // Validate each model in the provider
131
+ for (const modelId of providerResult.models) {
132
+ const modelResult = await this.validateModel(providerName, modelId);
133
+ if (modelResult.isValid) {
134
+ models.push(modelResult);
135
+ }
136
+ }
137
+
138
+ if (models.length > 0) {
139
+ result.set(providerName, models);
140
+ }
141
+ }
142
+ }
143
+
144
+ return result;
145
+ }
146
+
147
+ /**
148
+ * Quick check if a provider is ready for use
149
+ */
150
+ isProviderReady(providerName: string): boolean {
151
+ if (!this.authManager.hasCredentials(providerName)) {
152
+ return false;
153
+ }
154
+
155
+ // Basic checks for common providers
156
+ switch (providerName.toLowerCase()) {
157
+ case 'ollama':
158
+ const ollamaCreds = this.authManager.getCredentials('ollama');
159
+ return !!ollamaCreds?.baseUrl || process.env.OLLAMA_HOST !== undefined;
160
+ case 'custom':
161
+ const customCreds = this.authManager.getCredentials('custom');
162
+ return !!(customCreds?.apiKey && customCreds?.baseUrl);
163
+ default:
164
+ // API providers just need an API key
165
+ return true;
166
+ }
167
+ }
168
+
169
+ private async validateOllama(): Promise<ProviderValidationResult> {
170
+ const baseUrl = this.authManager.getCredentials('ollama')?.baseUrl ||
171
+ process.env.OLLAMA_HOST ||
172
+ 'http://localhost:11434';
173
+
174
+ try {
175
+ const response = await fetch(`${baseUrl}/api/tags`, {
176
+ method: 'GET',
177
+ timeout: 3000
178
+ });
179
+
180
+ if (!response.ok) {
181
+ return {
182
+ isConfigured: true,
183
+ isAccessible: false,
184
+ error: `Ollama server returned ${response.status}: ${response.statusText}`,
185
+ models: []
186
+ };
187
+ }
188
+
189
+ const data = await response.json() as { models: Array<{ name: string }> };
190
+ const modelNames = data.models.map(m => m.name);
191
+
192
+ return {
193
+ isConfigured: true,
194
+ isAccessible: true,
195
+ models: modelNames
196
+ };
197
+
198
+ } catch (error) {
199
+ return {
200
+ isConfigured: true,
201
+ isAccessible: false,
202
+ error: `Cannot connect to Ollama at ${baseUrl}: ${(error as Error).message}`,
203
+ models: []
204
+ };
205
+ }
206
+ }
207
+
208
+ private async validateOllamaCloud(): Promise<ProviderValidationResult> {
209
+ const apiKey = this.authManager.getCredentials('ollama-cloud')?.apiKey ||
210
+ process.env.OLLAMA_API_KEY;
211
+
212
+ if (!apiKey) {
213
+ return {
214
+ isConfigured: false,
215
+ isAccessible: false,
216
+ error: 'Ollama Cloud API key not found',
217
+ models: []
218
+ };
219
+ }
220
+
221
+ try {
222
+ const baseUrl = this.authManager.getCredentials('ollama-cloud')?.baseUrl ||
223
+ 'https://ollama.com';
224
+
225
+ const response = await fetch(`${baseUrl}/api/tags`, {
226
+ headers: { 'Authorization': `Bearer ${apiKey}` },
227
+ timeout: 5000
228
+ });
229
+
230
+ if (!response.ok) {
231
+ return {
232
+ isConfigured: true,
233
+ isAccessible: false,
234
+ error: `Ollama Cloud returned ${response.status}: ${response.statusText}`,
235
+ models: []
236
+ };
237
+ }
238
+
239
+ const data = await response.json() as { models: Array<{ name: string }> };
240
+ const modelNames = data.models.map(m => m.name);
241
+
242
+ return {
243
+ isConfigured: true,
244
+ isAccessible: true,
245
+ models: modelNames
246
+ };
247
+
248
+ } catch (error) {
249
+ return {
250
+ isConfigured: true,
251
+ isAccessible: false,
252
+ error: `Cannot connect to Ollama Cloud: ${(error as Error).message}`,
253
+ models: []
254
+ };
255
+ }
256
+ }
257
+
258
+ private async validateApiProvider(providerName: string): Promise<ProviderValidationResult> {
259
+ // For API providers, we can't list models without making expensive calls
260
+ // So we return basic info and let the user try to use it
261
+ const creds = this.authManager.getCredentials(providerName);
262
+
263
+ if (!creds?.apiKey) {
264
+ return {
265
+ isConfigured: false,
266
+ isAccessible: false,
267
+ error: `API key not found for ${providerName}`,
268
+ models: []
269
+ };
270
+ }
271
+
272
+ // For now, return empty models array - actual model validation happens on use
273
+ return {
274
+ isConfigured: true,
275
+ isAccessible: true,
276
+ models: [] // Will be populated when actually used
277
+ };
278
+ }
279
+
280
+ private async validateOllamaModel(modelId: string): Promise<ModelValidationResult> {
281
+ try {
282
+ const validator = new ModelValidator();
283
+ const ollamaResult = await validator.validateOllama();
284
+
285
+ if (!ollamaResult.isAccessible) {
286
+ return {
287
+ provider: 'ollama',
288
+ modelId,
289
+ isValid: false,
290
+ error: ollamaResult.error
291
+ };
292
+ }
293
+
294
+ const modelExists = ollamaResult.models.includes(modelId);
295
+
296
+ return {
297
+ provider: 'ollama',
298
+ modelId,
299
+ isValid: modelExists,
300
+ displayName: modelExists ? modelId : undefined,
301
+ error: modelExists ? undefined : `Model "${modelId}" not found locally`
302
+ };
303
+
304
+ } catch (error) {
305
+ return {
306
+ provider: 'ollama',
307
+ modelId,
308
+ isValid: false,
309
+ error: `Failed to validate Ollama model: ${(error as Error).message}`
310
+ };
311
+ }
312
+ }
313
+
314
+ private isApiProvider(providerName: string): boolean {
315
+ const apiProviders = new Set([
316
+ 'anthropic', 'openai', 'azure', 'google', 'deepseek',
317
+ 'qwen', 'glm', 'moonshot', 'baichuan', 'minimax',
318
+ 'yi', 'groq', 'mistral', 'together', 'perplexity',
319
+ 'coding-plan-alibaba', 'coding-plan-tencent',
320
+ 'coding-plan-volcengine', 'coding-plan-baidu',
321
+ 'coding-plan-kimi', 'coding-plan-zhipu', 'coding-plan-minimax'
322
+ ]);
323
+
324
+ return apiProviders.has(providerName.toLowerCase());
325
+ }
326
+
327
+ private getEnvKeyName(provider: string): string {
328
+ const envMap: Record<string, string> = {
329
+ anthropic: 'ANTHROPIC_API_KEY',
330
+ openai: 'OPENAI_API_KEY',
331
+ google: 'GOOGLE_API_KEY',
332
+ deepseek: 'DEEPSEEK_API_KEY',
333
+ qwen: 'QWEN_API_KEY',
334
+ glm: 'GLM_API_KEY',
335
+ moonshot: 'MOONSHOT_API_KEY',
336
+ baichuan: 'BAICHUAN_API_KEY',
337
+ minimax: 'MINIMAX_API_KEY',
338
+ yi: 'YI_API_KEY',
339
+ groq: 'GROQ_API_KEY',
340
+ mistral: 'MISTRAL_API_KEY',
341
+ together: 'TOGETHER_API_KEY',
342
+ perplexity: 'PERPLEXITY_API_KEY',
343
+ 'ollama-cloud': 'OLLAMA_API_KEY'
344
+ };
345
+
346
+ return envMap[provider] || `${provider.toUpperCase()}_API_KEY`;
347
+ }
348
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AACjE,cAAc,YAAY,CAAC;AAC3B,cAAc,sBAAsB,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { ModelClient } from './ModelClient.js';
2
+ export type { CreateModelClientOptions } from './ModelClient.js';
3
+ export { ModelConnectionTester } from './ModelConnectionTester.js';
4
+ export type { ModelConnectionStatus, ProviderConnectionStatus } from './ModelConnectionTester.js';
5
+ export * from './types.js';
6
+ export * from './providers/index.js';
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnthropicProvider.d.ts","sourceRoot":"","sources":["AnthropicProvider.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAA8D,MAAM,wBAAwB,CAAC;AAElH,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,aAAa,EAAc,WAAW,EAAE,MAAM,aAAa,CAAC;AAK9G,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,iBAAkB,YAAW,aAAa;IACrD,QAAQ,CAAC,IAAI,eAAe;IAC5B,OAAO,CAAC,MAAM,CAAY;gBAEd,MAAM,EAAE,uBAAuB;IAQrC,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,aAAa,CAAC;IAsClF,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,mBAAmB,GAAG,cAAc,CAAC,WAAW,CAAC;IAoEvF,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAWvD,OAAO,CAAC,eAAe;IAsCvB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,sBAAsB;IAa9B,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,iBAAiB;CAS1B"}
@@ -0,0 +1,279 @@
1
+ // ============================================================================
2
+ // Anthropic Provider - Claude API integration
3
+ // ============================================================================
4
+
5
+ import Anthropic from '@anthropic-ai/sdk';
6
+ import type { Message, ContentBlock, ToolUseContent, ToolResultContent, SessionId } from '../../types/session.js';
7
+ import type { ToolDefinition } from '../../types/tools.js';
8
+ import type { ModelProvider, ModelRequestOptions, ModelResponse, TokenUsage, StreamEvent } from '../types.js';
9
+ import { ModelError, RateLimitError } from '../../types/errors.js';
10
+ import { createToolCallId, createMessageId } from '../../types/session.js';
11
+ import { tokenCounter } from '../../utils/TokenCounter.js';
12
+
13
+ export interface AnthropicProviderConfig {
14
+ apiKey: string;
15
+ baseUrl?: string;
16
+ model: string;
17
+ apiVersion?: string;
18
+ }
19
+
20
+ export class AnthropicProvider implements ModelProvider {
21
+ readonly name = 'Anthropic';
22
+ private client: Anthropic;
23
+ private enableCache: boolean;
24
+
25
+ constructor(config: AnthropicProviderConfig & { enableCache?: boolean }) {
26
+ this.client = new Anthropic({
27
+ apiKey: config.apiKey,
28
+ baseURL: config.baseUrl,
29
+ ...(config.apiVersion && { defaultHeaders: { 'anthropic-version': config.apiVersion } }),
30
+ });
31
+ this.enableCache = config.enableCache !== false; // Default to true
32
+ }
33
+
34
+ async complete(messages: Message[], options: ModelRequestOptions): Promise<ModelResponse> {
35
+ try {
36
+ const anthropicMessages = this.convertMessages(messages);
37
+
38
+ // Convert tools with cache control if caching is enabled
39
+ const anthropicTools = this.convertTools(options.tools);
40
+
41
+ // Prepare params
42
+ const params: any = {
43
+ model: options.model,
44
+ max_tokens: options.maxTokens,
45
+ messages: anthropicMessages,
46
+ ...(anthropicTools.length > 0 && { tools: anthropicTools }),
47
+ ...(options.stopSequences && options.stopSequences.length > 0 && { stop_sequences: options.stopSequences }),
48
+ };
49
+
50
+ // Add system prompt with cache control if enabled
51
+ if (options.systemPrompt) {
52
+ if (this.enableCache && options.thinking !== 'disabled') {
53
+ params.system = [
54
+ {
55
+ type: 'text',
56
+ text: options.systemPrompt,
57
+ cache_control: { type: 'ephemeral' }
58
+ }
59
+ ];
60
+ } else {
61
+ params.system = options.systemPrompt;
62
+ }
63
+ }
64
+
65
+ // Add beta header for caching
66
+ const requestOptions: any = {};
67
+ if (this.enableCache && options.thinking !== 'disabled') {
68
+ requestOptions.headers = {
69
+ 'anthropic-beta': 'prompt-caching-2024-07-31'
70
+ };
71
+ }
72
+
73
+ const response = await this.client.messages.create(params, requestOptions);
74
+
75
+ const content = this.convertResponseContent(response.content);
76
+ const usage = this.convertUsage(response.usage);
77
+
78
+ return {
79
+ content,
80
+ model: response.model,
81
+ stopReason: this.convertStopReason(response.stop_reason),
82
+ usage,
83
+ sessionId: options.sessionId,
84
+ };
85
+ } catch (err) {
86
+ if (err instanceof Anthropic.APIError) {
87
+ if (err.status === 429) {
88
+ const retryAfter = parseInt(err.headers?.['retry-after'] || '60', 10) * 1000;
89
+ throw new RateLimitError(err.message, retryAfter, 'anthropic');
90
+ }
91
+ throw new ModelError(err.message, err.status, 'anthropic', err);
92
+ }
93
+ throw err;
94
+ }
95
+ }
96
+
97
+ async *stream(messages: Message[], options: ModelRequestOptions): AsyncGenerator<StreamEvent> {
98
+ try {
99
+ const anthropicMessages = this.convertMessages(messages);
100
+
101
+ // Convert tools with cache control if caching is enabled
102
+ const anthropicTools = this.convertTools(options.tools);
103
+
104
+ // Prepare params
105
+ const params: any = {
106
+ model: options.model,
107
+ max_tokens: options.maxTokens,
108
+ messages: anthropicMessages,
109
+ ...(anthropicTools.length > 0 && { tools: anthropicTools }),
110
+ stream: true,
111
+ };
112
+
113
+ // Add system prompt with cache control if enabled
114
+ if (options.systemPrompt) {
115
+ if (this.enableCache && options.thinking !== 'disabled') {
116
+ params.system = [
117
+ {
118
+ type: 'text',
119
+ text: options.systemPrompt,
120
+ cache_control: { type: 'ephemeral' }
121
+ }
122
+ ];
123
+ } else {
124
+ params.system = options.systemPrompt;
125
+ }
126
+ }
127
+
128
+ // Add beta header for caching
129
+ const requestOptions: any = {};
130
+ if (this.enableCache && options.thinking !== 'disabled') {
131
+ requestOptions.headers = {
132
+ 'anthropic-beta': 'prompt-caching-2024-07-31'
133
+ };
134
+ }
135
+
136
+ const stream = await this.client.messages.create(params, requestOptions) as unknown as AsyncIterable<Anthropic.Messages.MessageStreamEvent>;
137
+
138
+ for await (const event of stream) {
139
+ if (event.type === 'message_start') {
140
+ yield { type: 'message_start', model: event.message.model };
141
+ } else if (event.type === 'content_block_start') {
142
+ if (event.content_block.type === 'text') {
143
+ // noop, deltas follow
144
+ } else if (event.content_block.type === 'tool_use') {
145
+ yield {
146
+ type: 'tool_call_start',
147
+ toolCallId: event.content_block.id,
148
+ toolName: event.content_block.name,
149
+ };
150
+ }
151
+ } else if (event.type === 'content_block_delta') {
152
+ if (event.delta.type === 'text_delta') {
153
+ yield { type: 'text_delta', delta: event.delta.text };
154
+ } else if ((event.delta as any).type === 'thinking_delta') {
155
+ yield { type: 'thinking_delta', delta: (event.delta as any).thinking };
156
+ } else if (event.delta.type === 'input_json_delta') {
157
+ yield {
158
+ type: 'tool_call_delta',
159
+ toolCallId: (event as Anthropic.ContentBlockDeltaEvent).index?.toString() || '',
160
+ delta: event.delta.partial_json,
161
+ };
162
+ }
163
+ } else if (event.type === 'content_block_stop') {
164
+ // noop
165
+ } else if (event.type === 'message_delta') {
166
+ if (event.delta.stop_reason) {
167
+ const usage: TokenUsage = {
168
+ inputTokens: (event.usage as any)?.input_tokens ?? 0,
169
+ outputTokens: (event.usage as any)?.output_tokens ?? 0,
170
+ };
171
+ yield {
172
+ type: 'message_complete',
173
+ stopReason: this.convertStopReason(event.delta.stop_reason),
174
+ usage,
175
+ };
176
+ }
177
+ } else if (event.type === 'message_stop') {
178
+ // Stream complete
179
+ }
180
+ }
181
+ } catch (err) {
182
+ if (err instanceof Anthropic.APIError) {
183
+ yield { type: 'error', error: new ModelError(err.message, err.status, 'anthropic', err) };
184
+ } else {
185
+ yield { type: 'error', error: err };
186
+ }
187
+ }
188
+ }
189
+
190
+ async countTokens(messages: Message[]): Promise<number> {
191
+ // Approximate: ~4 chars per token
192
+ const text = messages.map(m => {
193
+ if (typeof m.content === 'string') return m.content;
194
+ return m.content.map(c => c.type === 'text' ? (c as any).text || '' : '').join('');
195
+ }).join('');
196
+ return Math.ceil(text.length / 4);
197
+ }
198
+
199
+ // --- Conversion helpers ---
200
+
201
+ private convertMessages(messages: Message[]): Anthropic.MessageParam[] {
202
+ const result: Anthropic.MessageParam[] = [];
203
+
204
+ for (const msg of messages) {
205
+ if (msg.role === 'system') continue; // System messages handled separately
206
+
207
+ if (msg.role === 'tool') {
208
+ const toolResults = msg.content
209
+ .filter((c): c is ToolResultContent => c.type === 'tool_result')
210
+ .map((c): Anthropic.ToolResultBlockParam => ({
211
+ type: 'tool_result',
212
+ tool_use_id: c.tool_use_id,
213
+ content: typeof c.content === 'string' ? c.content : c.content.map((cc) => 'text' in cc ? (cc as any).text || '' : '').join(''),
214
+ is_error: c.is_error,
215
+ }));
216
+
217
+ result.push({ role: 'user', content: toolResults });
218
+ continue;
219
+ }
220
+
221
+ const content = msg.content
222
+ .map((c) => {
223
+ if (c.type === 'text') return { type: 'text' as const, text: c.text };
224
+ if (c.type === 'tool_use') return { type: 'tool_use' as const, id: c.id, name: c.name, input: c.input };
225
+ if (c.type === 'image') return {
226
+ type: 'image' as const,
227
+ source: { type: c.source.type === 'url' ? 'url' : 'base64', media_type: c.source.media_type, data: c.source.data || c.source.url || '' },
228
+ };
229
+ return null;
230
+ })
231
+ .filter((c): c is NonNullable<typeof c> => c !== null) as Anthropic.ContentBlock[];
232
+
233
+ result.push({ role: msg.role as 'user' | 'assistant', content });
234
+ }
235
+
236
+ return result;
237
+ }
238
+
239
+ private convertTools(tools: ToolDefinition[]): Anthropic.Tool[] {
240
+ return tools.map((t) => ({
241
+ name: t.name,
242
+ description: t.description,
243
+ input_schema: t.inputSchema as Anthropic.Tool.InputSchema,
244
+ }));
245
+ }
246
+
247
+ private convertResponseContent(content: Anthropic.Message['content']): ContentBlock[] {
248
+ return content.map((block) => {
249
+ if (block.type === 'text') return { type: 'text' as const, text: block.text };
250
+ if (block.type === 'tool_use') return {
251
+ type: 'tool_use' as const,
252
+ id: createToolCallId(block.id),
253
+ name: block.name,
254
+ input: block.input as Record<string, unknown>,
255
+ };
256
+ return { type: 'text', text: JSON.stringify(block) };
257
+ });
258
+ }
259
+
260
+ private convertUsage(usage: Anthropic.Usage): TokenUsage {
261
+ const usageAny = usage as any;
262
+ return {
263
+ inputTokens: usage.input_tokens,
264
+ outputTokens: usage.output_tokens,
265
+ cacheReadTokens: usageAny.cache_read_input_tokens as number | undefined,
266
+ cacheWriteTokens: usageAny.cache_creation_input_tokens as number | undefined,
267
+ };
268
+ }
269
+
270
+ private convertStopReason(reason: string | null): ModelResponse['stopReason'] {
271
+ switch (reason) {
272
+ case 'end_turn': return 'end_turn';
273
+ case 'tool_use': return 'tool_use';
274
+ case 'max_tokens': return 'max_tokens';
275
+ case 'stop_sequence': return 'stop_sequence';
276
+ default: return 'end_turn';
277
+ }
278
+ }
279
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CodingPlanProvider.d.ts","sourceRoot":"","sources":["CodingPlanProvider.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,wBAAwB,EAAE,KAAK,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAEtG,uCAAuC;AACvC,MAAM,MAAM,kBAAkB,GAC1B,SAAS,GACT,SAAS,GACT,YAAY,GACZ,OAAO,GACP,MAAM,GACN,OAAO,GACP,SAAS,GACT,QAAQ,CAAC;AAEb,gCAAgC;AAChC,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,sBAAsB,EAAE,SAAS,CAAC;IAC/E,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,yDAAyD;IACzD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAoDD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,kBAAmB,SAAQ,wBAAwB;IAC9D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IACtC,QAAQ,CAAC,eAAe,EAAE,MAAM,EAAE,CAAC;gBAEvB,MAAM,EAAE,gBAAgB;IAmCpC;;OAEG;IACH,kBAAkB,IAAI,MAAM,EAAE;IAI9B;;OAEG;IACH,eAAe,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,kBAAkB,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;IASlF;;OAEG;IACH,mBAAmB,IAAI,MAAM,GAAG,IAAI;CAIrC;AAED;;GAEG;AACH,wBAAgB,+BAA+B,IAAI,KAAK,CAAC;IACvD,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC,CASD;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,kBAAkB,EAC5B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,aAAa,CAAC,EAAE,MAAM,GACrB,kBAAkB,CAOpB"}