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,1219 @@
1
+ // ============================================================================
2
+ // ConfigManager - Loads and manages application configuration
3
+ // ============================================================================
4
+
5
+ import fs from 'node:fs/promises';
6
+ import path from 'node:path';
7
+ import os from 'node:os';
8
+ import type { NovaConfig, CoreConfig, ModelProviderConfig, ModelConfig } from '../types/config.js';
9
+ import { ConfigError } from '../types/errors.js';
10
+
11
+ const DEFAULT_CONFIG: NovaConfig = {
12
+ core: {
13
+ defaultModel: 'glm-5',
14
+ defaultApprovalMode: 'default',
15
+ maxTurns: 100,
16
+ maxTokens: 8192,
17
+ temperature: 0.7,
18
+ contextWindowTarget: 128000,
19
+ streaming: true,
20
+ logLevel: 'info',
21
+ timeout: 300000,
22
+ },
23
+ models: {
24
+ providers: {
25
+ anthropic: {
26
+ type: 'anthropic',
27
+ models: {
28
+ // Claude 4 Series
29
+ 'claude-sonnet-4-20250514': {
30
+ name: 'Claude Sonnet 4',
31
+ maxContextTokens: 200000,
32
+ maxOutputTokens: 64000,
33
+ supportsVision: true,
34
+ supportsTools: true,
35
+ supportsStreaming: true,
36
+ supportsThinking: true,
37
+ inputCostPerMToken: 3,
38
+ outputCostPerMToken: 15,
39
+ },
40
+ 'claude-opus-4-20250514': {
41
+ name: 'Claude Opus 4',
42
+ maxContextTokens: 200000,
43
+ maxOutputTokens: 32000,
44
+ supportsVision: true,
45
+ supportsTools: true,
46
+ supportsStreaming: true,
47
+ supportsThinking: true,
48
+ inputCostPerMToken: 15,
49
+ outputCostPerMToken: 75,
50
+ },
51
+ // Claude 3.5 Series
52
+ 'claude-3-5-sonnet-20241022': {
53
+ name: 'Claude 3.5 Sonnet',
54
+ maxContextTokens: 200000,
55
+ maxOutputTokens: 8192,
56
+ supportsVision: true,
57
+ supportsTools: true,
58
+ supportsStreaming: true,
59
+ supportsThinking: false,
60
+ inputCostPerMToken: 3,
61
+ outputCostPerMToken: 15,
62
+ },
63
+ 'claude-3-5-sonnet-20240620': {
64
+ name: 'Claude 3.5 Sonnet (Jun 2024)',
65
+ maxContextTokens: 200000,
66
+ maxOutputTokens: 8192,
67
+ supportsVision: true,
68
+ supportsTools: true,
69
+ supportsStreaming: true,
70
+ supportsThinking: false,
71
+ inputCostPerMToken: 3,
72
+ outputCostPerMToken: 15,
73
+ },
74
+ 'claude-3-5-haiku-20241022': {
75
+ name: 'Claude 3.5 Haiku',
76
+ maxContextTokens: 200000,
77
+ maxOutputTokens: 8192,
78
+ supportsVision: true,
79
+ supportsTools: true,
80
+ supportsStreaming: true,
81
+ supportsThinking: false,
82
+ inputCostPerMToken: 1,
83
+ outputCostPerMToken: 5,
84
+ },
85
+ // Claude 3 Series
86
+ 'claude-3-opus-20240229': {
87
+ name: 'Claude 3 Opus',
88
+ maxContextTokens: 200000,
89
+ maxOutputTokens: 4096,
90
+ supportsVision: true,
91
+ supportsTools: true,
92
+ supportsStreaming: true,
93
+ supportsThinking: false,
94
+ inputCostPerMToken: 15,
95
+ outputCostPerMToken: 75,
96
+ },
97
+ 'claude-3-sonnet-20240229': {
98
+ name: 'Claude 3 Sonnet',
99
+ maxContextTokens: 200000,
100
+ maxOutputTokens: 4096,
101
+ supportsVision: true,
102
+ supportsTools: true,
103
+ supportsStreaming: true,
104
+ supportsThinking: false,
105
+ inputCostPerMToken: 3,
106
+ outputCostPerMToken: 15,
107
+ },
108
+ 'claude-3-haiku-20240307': {
109
+ name: 'Claude 3 Haiku',
110
+ maxContextTokens: 200000,
111
+ maxOutputTokens: 4096,
112
+ supportsVision: true,
113
+ supportsTools: true,
114
+ supportsStreaming: true,
115
+ supportsThinking: false,
116
+ inputCostPerMToken: 0.25,
117
+ outputCostPerMToken: 1.25,
118
+ },
119
+ },
120
+ defaultModel: 'claude-3-5-sonnet-20241022',
121
+ },
122
+ openai: {
123
+ type: 'openai',
124
+ models: {
125
+ // GPT-4o Series
126
+ 'gpt-4o': {
127
+ name: 'GPT-4o',
128
+ maxContextTokens: 128000,
129
+ maxOutputTokens: 16384,
130
+ supportsVision: true,
131
+ supportsTools: true,
132
+ supportsStreaming: true,
133
+ supportsThinking: false,
134
+ inputCostPerMToken: 2.5,
135
+ outputCostPerMToken: 10,
136
+ },
137
+ 'gpt-4o-2024-11-20': {
138
+ name: 'GPT-4o (Nov 2024)',
139
+ maxContextTokens: 128000,
140
+ maxOutputTokens: 16384,
141
+ supportsVision: true,
142
+ supportsTools: true,
143
+ supportsStreaming: true,
144
+ supportsThinking: false,
145
+ inputCostPerMToken: 2.5,
146
+ outputCostPerMToken: 10,
147
+ },
148
+ 'gpt-4o-mini': {
149
+ name: 'GPT-4o Mini',
150
+ maxContextTokens: 128000,
151
+ maxOutputTokens: 16384,
152
+ supportsVision: true,
153
+ supportsTools: true,
154
+ supportsStreaming: true,
155
+ supportsThinking: false,
156
+ inputCostPerMToken: 0.15,
157
+ outputCostPerMToken: 0.6,
158
+ },
159
+ // o1 Series (reasoning)
160
+ 'o1': {
161
+ name: 'o1',
162
+ maxContextTokens: 200000,
163
+ maxOutputTokens: 100000,
164
+ supportsVision: true,
165
+ supportsTools: false,
166
+ supportsStreaming: false,
167
+ supportsThinking: true,
168
+ inputCostPerMToken: 15,
169
+ outputCostPerMToken: 60,
170
+ },
171
+ 'o1-mini': {
172
+ name: 'o1-mini',
173
+ maxContextTokens: 128000,
174
+ maxOutputTokens: 65536,
175
+ supportsVision: false,
176
+ supportsTools: false,
177
+ supportsStreaming: false,
178
+ supportsThinking: true,
179
+ inputCostPerMToken: 1.1,
180
+ outputCostPerMToken: 4.4,
181
+ },
182
+ 'o3-mini': {
183
+ name: 'o3-mini',
184
+ maxContextTokens: 200000,
185
+ maxOutputTokens: 100000,
186
+ supportsVision: false,
187
+ supportsTools: false,
188
+ supportsStreaming: true,
189
+ supportsThinking: true,
190
+ inputCostPerMToken: 1.1,
191
+ outputCostPerMToken: 4.4,
192
+ },
193
+ // GPT-4 Turbo
194
+ 'gpt-4-turbo': {
195
+ name: 'GPT-4 Turbo',
196
+ maxContextTokens: 128000,
197
+ maxOutputTokens: 4096,
198
+ supportsVision: true,
199
+ supportsTools: true,
200
+ supportsStreaming: true,
201
+ supportsThinking: false,
202
+ inputCostPerMToken: 10,
203
+ outputCostPerMToken: 30,
204
+ },
205
+ },
206
+ defaultModel: 'gpt-4o',
207
+ },
208
+ google: {
209
+ type: 'custom',
210
+ baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai',
211
+ models: {
212
+ 'gemini-2.0-flash': {
213
+ name: 'Gemini 2.0 Flash',
214
+ maxContextTokens: 1048576,
215
+ maxOutputTokens: 8192,
216
+ supportsVision: true,
217
+ supportsTools: true,
218
+ supportsStreaming: true,
219
+ supportsThinking: false,
220
+ inputCostPerMToken: 0.1,
221
+ outputCostPerMToken: 0.4,
222
+ },
223
+ 'gemini-2.0-flash-lite': {
224
+ name: 'Gemini 2.0 Flash Lite',
225
+ maxContextTokens: 1048576,
226
+ maxOutputTokens: 8192,
227
+ supportsVision: true,
228
+ supportsTools: true,
229
+ supportsStreaming: true,
230
+ supportsThinking: false,
231
+ inputCostPerMToken: 0.05,
232
+ outputCostPerMToken: 0.15,
233
+ },
234
+ 'gemini-2.5-pro-preview-05-06': {
235
+ name: 'Gemini 2.5 Pro Preview',
236
+ maxContextTokens: 1048576,
237
+ maxOutputTokens: 65536,
238
+ supportsVision: true,
239
+ supportsTools: true,
240
+ supportsStreaming: true,
241
+ supportsThinking: true,
242
+ inputCostPerMToken: 1.25,
243
+ outputCostPerMToken: 10,
244
+ },
245
+ 'gemini-2.5-flash-preview-05-20': {
246
+ name: 'Gemini 2.5 Flash Preview',
247
+ maxContextTokens: 1048576,
248
+ maxOutputTokens: 65536,
249
+ supportsVision: true,
250
+ supportsTools: true,
251
+ supportsStreaming: true,
252
+ supportsThinking: true,
253
+ inputCostPerMToken: 0.15,
254
+ outputCostPerMToken: 0.6,
255
+ },
256
+ },
257
+ defaultModel: 'gemini-2.0-flash',
258
+ },
259
+ deepseek: {
260
+ type: 'custom',
261
+ baseUrl: 'https://api.deepseek.com',
262
+ models: {
263
+ 'deepseek-chat': {
264
+ name: 'DeepSeek V3',
265
+ maxContextTokens: 65536,
266
+ maxOutputTokens: 8192,
267
+ supportsVision: false,
268
+ supportsTools: true,
269
+ supportsStreaming: true,
270
+ supportsThinking: false,
271
+ inputCostPerMToken: 0.27,
272
+ outputCostPerMToken: 1.1,
273
+ },
274
+ 'deepseek-reasoner': {
275
+ name: 'DeepSeek R1',
276
+ maxContextTokens: 65536,
277
+ maxOutputTokens: 8192,
278
+ supportsVision: false,
279
+ supportsTools: false,
280
+ supportsStreaming: true,
281
+ supportsThinking: true,
282
+ inputCostPerMToken: 0.55,
283
+ outputCostPerMToken: 2.19,
284
+ },
285
+ },
286
+ defaultModel: 'deepseek-chat',
287
+ },
288
+ ollama: {
289
+ type: 'ollama',
290
+ baseUrl: 'http://localhost:11434',
291
+ models: {
292
+ 'llama3.1': {
293
+ name: 'Llama 3.1 8B',
294
+ maxContextTokens: 128000,
295
+ maxOutputTokens: 4096,
296
+ supportsVision: false,
297
+ supportsTools: true,
298
+ supportsStreaming: true,
299
+ supportsThinking: false,
300
+ },
301
+ 'llama3.1:70b': {
302
+ name: 'Llama 3.1 70B',
303
+ maxContextTokens: 128000,
304
+ maxOutputTokens: 4096,
305
+ supportsVision: false,
306
+ supportsTools: true,
307
+ supportsStreaming: true,
308
+ supportsThinking: false,
309
+ },
310
+ 'codellama': {
311
+ name: 'Code Llama',
312
+ maxContextTokens: 16384,
313
+ maxOutputTokens: 4096,
314
+ supportsVision: false,
315
+ supportsTools: true,
316
+ supportsStreaming: true,
317
+ supportsThinking: false,
318
+ },
319
+ 'mistral': {
320
+ name: 'Mistral 7B',
321
+ maxContextTokens: 32768,
322
+ maxOutputTokens: 4096,
323
+ supportsVision: false,
324
+ supportsTools: true,
325
+ supportsStreaming: true,
326
+ supportsThinking: false,
327
+ },
328
+ 'qwen2.5-coder': {
329
+ name: 'Qwen 2.5 Coder',
330
+ maxContextTokens: 131072,
331
+ maxOutputTokens: 8192,
332
+ supportsVision: false,
333
+ supportsTools: true,
334
+ supportsStreaming: true,
335
+ supportsThinking: false,
336
+ },
337
+ 'deepseek-coder-v2': {
338
+ name: 'DeepSeek Coder V2',
339
+ maxContextTokens: 128000,
340
+ maxOutputTokens: 4096,
341
+ supportsVision: false,
342
+ supportsTools: true,
343
+ supportsStreaming: true,
344
+ supportsThinking: false,
345
+ },
346
+ 'gemma2': {
347
+ name: 'Gemma 2 9B',
348
+ maxContextTokens: 8192,
349
+ maxOutputTokens: 4096,
350
+ supportsVision: false,
351
+ supportsTools: true,
352
+ supportsStreaming: true,
353
+ supportsThinking: false,
354
+ },
355
+ },
356
+ defaultModel: 'llama3.1',
357
+ },
358
+ // ==================== Ollama Cloud ====================
359
+ 'ollama-cloud': {
360
+ type: 'ollama-cloud',
361
+ baseUrl: 'https://ollama.com',
362
+ models: {
363
+ 'deepseek-v3.2': {
364
+ name: 'DeepSeek V3.2 (Cloud)',
365
+ maxContextTokens: 131072,
366
+ maxOutputTokens: 8192,
367
+ supportsVision: false,
368
+ supportsTools: true,
369
+ supportsStreaming: true,
370
+ supportsThinking: true,
371
+ },
372
+ 'deepseek-v3.1:671b': {
373
+ name: 'DeepSeek V3.1 671B (Cloud)',
374
+ maxContextTokens: 131072,
375
+ maxOutputTokens: 8192,
376
+ supportsVision: false,
377
+ supportsTools: true,
378
+ supportsStreaming: true,
379
+ supportsThinking: true,
380
+ },
381
+ 'kimi-k2.5': {
382
+ name: 'Kimi K2.5 (Cloud)',
383
+ maxContextTokens: 131072,
384
+ maxOutputTokens: 8192,
385
+ supportsVision: false,
386
+ supportsTools: true,
387
+ supportsStreaming: true,
388
+ supportsThinking: true,
389
+ },
390
+ 'kimi-k2:1t': {
391
+ name: 'Kimi K2 1T (Cloud)',
392
+ maxContextTokens: 131072,
393
+ maxOutputTokens: 8192,
394
+ supportsVision: false,
395
+ supportsTools: true,
396
+ supportsStreaming: true,
397
+ supportsThinking: true,
398
+ },
399
+ 'qwen3-coder:480b': {
400
+ name: 'Qwen3 Coder 480B (Cloud)',
401
+ maxContextTokens: 131072,
402
+ maxOutputTokens: 8192,
403
+ supportsVision: false,
404
+ supportsTools: true,
405
+ supportsStreaming: true,
406
+ supportsThinking: false,
407
+ },
408
+ 'qwen3.5:397b': {
409
+ name: 'Qwen3.5 397B (Cloud)',
410
+ maxContextTokens: 131072,
411
+ maxOutputTokens: 8192,
412
+ supportsVision: false,
413
+ supportsTools: true,
414
+ supportsStreaming: true,
415
+ supportsThinking: false,
416
+ },
417
+ 'glm-5': {
418
+ name: 'GLM-5 (Cloud)',
419
+ maxContextTokens: 128000,
420
+ maxOutputTokens: 8192,
421
+ supportsVision: false,
422
+ supportsTools: true,
423
+ supportsStreaming: true,
424
+ supportsThinking: true,
425
+ },
426
+ 'glm-4.7': {
427
+ name: 'GLM-4.7 (Cloud)',
428
+ maxContextTokens: 128000,
429
+ maxOutputTokens: 8192,
430
+ supportsVision: false,
431
+ supportsTools: true,
432
+ supportsStreaming: true,
433
+ supportsThinking: false,
434
+ },
435
+ 'mistral-large-3:675b': {
436
+ name: 'Mistral Large 3 675B (Cloud)',
437
+ maxContextTokens: 131072,
438
+ maxOutputTokens: 8192,
439
+ supportsVision: false,
440
+ supportsTools: false,
441
+ supportsStreaming: true,
442
+ supportsThinking: false,
443
+ },
444
+ 'gpt-oss:120b': {
445
+ name: 'GPT-OSS 120B (Cloud)',
446
+ maxContextTokens: 131072,
447
+ maxOutputTokens: 8192,
448
+ supportsVision: false,
449
+ supportsTools: false,
450
+ supportsStreaming: true,
451
+ supportsThinking: false,
452
+ },
453
+ 'minimax-m2.7': {
454
+ name: 'MiniMax M2.7 (Cloud)',
455
+ maxContextTokens: 131072,
456
+ maxOutputTokens: 8192,
457
+ supportsVision: false,
458
+ supportsTools: false,
459
+ supportsStreaming: true,
460
+ supportsThinking: false,
461
+ },
462
+ },
463
+ defaultModel: 'deepseek-v3.2',
464
+ },
465
+ // ==================== Chinese Providers ====================
466
+ qwen: {
467
+ type: 'custom',
468
+ baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
469
+ models: {
470
+ 'qwen-max': {
471
+ name: 'Qwen Max',
472
+ maxContextTokens: 32768,
473
+ maxOutputTokens: 8192,
474
+ supportsVision: true,
475
+ supportsTools: true,
476
+ supportsStreaming: true,
477
+ supportsThinking: false,
478
+ inputCostPerMToken: 2,
479
+ outputCostPerMToken: 6,
480
+ },
481
+ 'qwen-plus': {
482
+ name: 'Qwen Plus',
483
+ maxContextTokens: 131072,
484
+ maxOutputTokens: 8192,
485
+ supportsVision: false,
486
+ supportsTools: true,
487
+ supportsStreaming: true,
488
+ supportsThinking: false,
489
+ inputCostPerMToken: 0.8,
490
+ outputCostPerMToken: 2,
491
+ },
492
+ 'qwen-turbo': {
493
+ name: 'Qwen Turbo',
494
+ maxContextTokens: 131072,
495
+ maxOutputTokens: 8192,
496
+ supportsVision: false,
497
+ supportsTools: true,
498
+ supportsStreaming: true,
499
+ supportsThinking: false,
500
+ inputCostPerMToken: 0.3,
501
+ outputCostPerMToken: 0.6,
502
+ },
503
+ 'qwen-coder-plus': {
504
+ name: 'Qwen Coder Plus',
505
+ maxContextTokens: 131072,
506
+ maxOutputTokens: 8192,
507
+ supportsVision: false,
508
+ supportsTools: true,
509
+ supportsStreaming: true,
510
+ supportsThinking: false,
511
+ inputCostPerMToken: 1,
512
+ outputCostPerMToken: 3,
513
+ },
514
+ 'qwen-vl-max': {
515
+ name: 'Qwen VL Max',
516
+ maxContextTokens: 32768,
517
+ maxOutputTokens: 8192,
518
+ supportsVision: true,
519
+ supportsTools: false,
520
+ supportsStreaming: true,
521
+ supportsThinking: false,
522
+ inputCostPerMToken: 2,
523
+ outputCostPerMToken: 6,
524
+ },
525
+ },
526
+ defaultModel: 'qwen-max',
527
+ },
528
+ glm: {
529
+ type: 'custom',
530
+ baseUrl: 'https://open.bigmodel.cn/api/paas/v4',
531
+ models: {
532
+ 'glm-4-plus': {
533
+ name: 'GLM-4 Plus',
534
+ maxContextTokens: 128000,
535
+ maxOutputTokens: 4096,
536
+ supportsVision: false,
537
+ supportsTools: true,
538
+ supportsStreaming: true,
539
+ supportsThinking: false,
540
+ inputCostPerMToken: 0.5,
541
+ outputCostPerMToken: 0.5,
542
+ },
543
+ 'glm-4-flash': {
544
+ name: 'GLM-4 Flash',
545
+ maxContextTokens: 128000,
546
+ maxOutputTokens: 4096,
547
+ supportsVision: false,
548
+ supportsTools: true,
549
+ supportsStreaming: true,
550
+ supportsThinking: false,
551
+ inputCostPerMToken: 0.1,
552
+ outputCostPerMToken: 0.1,
553
+ },
554
+ 'glm-4v-plus': {
555
+ name: 'GLM-4V Plus',
556
+ maxContextTokens: 8192,
557
+ maxOutputTokens: 4096,
558
+ supportsVision: true,
559
+ supportsTools: false,
560
+ supportsStreaming: true,
561
+ supportsThinking: false,
562
+ inputCostPerMToken: 0.5,
563
+ outputCostPerMToken: 0.5,
564
+ },
565
+ 'glm-4-long': {
566
+ name: 'GLM-4 Long',
567
+ maxContextTokens: 1000000,
568
+ maxOutputTokens: 4096,
569
+ supportsVision: false,
570
+ supportsTools: false,
571
+ supportsStreaming: true,
572
+ supportsThinking: false,
573
+ inputCostPerMToken: 0.1,
574
+ outputCostPerMToken: 0.1,
575
+ },
576
+ },
577
+ defaultModel: 'glm-4-plus',
578
+ },
579
+ moonshot: {
580
+ type: 'custom',
581
+ baseUrl: 'https://api.moonshot.cn/v1',
582
+ models: {
583
+ 'moonshot-v1-128k': {
584
+ name: 'Moonshot V1 128K',
585
+ maxContextTokens: 131072,
586
+ maxOutputTokens: 4096,
587
+ supportsVision: false,
588
+ supportsTools: true,
589
+ supportsStreaming: true,
590
+ supportsThinking: false,
591
+ inputCostPerMToken: 0.6,
592
+ outputCostPerMToken: 1.6,
593
+ },
594
+ 'moonshot-v1-32k': {
595
+ name: 'Moonshot V1 32K',
596
+ maxContextTokens: 32768,
597
+ maxOutputTokens: 4096,
598
+ supportsVision: false,
599
+ supportsTools: true,
600
+ supportsStreaming: true,
601
+ supportsThinking: false,
602
+ inputCostPerMToken: 0.24,
603
+ outputCostPerMToken: 0.6,
604
+ },
605
+ 'moonshot-v1-8k': {
606
+ name: 'Moonshot V1 8K',
607
+ maxContextTokens: 8192,
608
+ maxOutputTokens: 4096,
609
+ supportsVision: false,
610
+ supportsTools: true,
611
+ supportsStreaming: true,
612
+ supportsThinking: false,
613
+ inputCostPerMToken: 0.12,
614
+ outputCostPerMToken: 0.3,
615
+ },
616
+ },
617
+ defaultModel: 'moonshot-v1-128k',
618
+ },
619
+ baichuan: {
620
+ type: 'custom',
621
+ baseUrl: 'https://api.baichuan-ai.com/v1',
622
+ models: {
623
+ 'baichuan4': {
624
+ name: 'Baichuan 4',
625
+ maxContextTokens: 131072,
626
+ maxOutputTokens: 4096,
627
+ supportsVision: false,
628
+ supportsTools: true,
629
+ supportsStreaming: true,
630
+ supportsThinking: false,
631
+ inputCostPerMToken: 4,
632
+ outputCostPerMToken: 4,
633
+ },
634
+ 'baichuan3-turbo': {
635
+ name: 'Baichuan 3 Turbo',
636
+ maxContextTokens: 32768,
637
+ maxOutputTokens: 4096,
638
+ supportsVision: false,
639
+ supportsTools: true,
640
+ supportsStreaming: true,
641
+ supportsThinking: false,
642
+ inputCostPerMToken: 0.4,
643
+ outputCostPerMToken: 0.4,
644
+ },
645
+ },
646
+ defaultModel: 'baichuan4',
647
+ },
648
+ minimax: {
649
+ type: 'custom',
650
+ baseUrl: 'https://api.minimax.chat/v1',
651
+ models: {
652
+ 'abab6.5s': {
653
+ name: 'MiniMax abab6.5s',
654
+ maxContextTokens: 32768,
655
+ maxOutputTokens: 8192,
656
+ supportsVision: false,
657
+ supportsTools: true,
658
+ supportsStreaming: true,
659
+ supportsThinking: false,
660
+ inputCostPerMToken: 1,
661
+ outputCostPerMToken: 1,
662
+ },
663
+ },
664
+ defaultModel: 'abab6.5s',
665
+ },
666
+ yi: {
667
+ type: 'custom',
668
+ baseUrl: 'https://api.lingyiwanwu.com/v1',
669
+ models: {
670
+ 'yi-large': {
671
+ name: 'Yi Large',
672
+ maxContextTokens: 16384,
673
+ maxOutputTokens: 4096,
674
+ supportsVision: false,
675
+ supportsTools: true,
676
+ supportsStreaming: true,
677
+ supportsThinking: false,
678
+ inputCostPerMToken: 2,
679
+ outputCostPerMToken: 2,
680
+ },
681
+ 'yi-medium': {
682
+ name: 'Yi Medium',
683
+ maxContextTokens: 16384,
684
+ maxOutputTokens: 4096,
685
+ supportsVision: false,
686
+ supportsTools: true,
687
+ supportsStreaming: true,
688
+ supportsThinking: false,
689
+ inputCostPerMToken: 0.35,
690
+ outputCostPerMToken: 0.35,
691
+ },
692
+ 'yi-coder': {
693
+ name: 'Yi Coder',
694
+ maxContextTokens: 16384,
695
+ maxOutputTokens: 4096,
696
+ supportsVision: false,
697
+ supportsTools: true,
698
+ supportsStreaming: true,
699
+ supportsThinking: false,
700
+ inputCostPerMToken: 1.5,
701
+ outputCostPerMToken: 1.5,
702
+ },
703
+ },
704
+ defaultModel: 'yi-large',
705
+ },
706
+ siliconflow: {
707
+ type: 'custom',
708
+ baseUrl: 'https://api.siliconflow.cn/v1',
709
+ models: {
710
+ 'deepseek-ai/DeepSeek-V3': {
711
+ name: 'DeepSeek V3 (SiliconFlow)',
712
+ maxContextTokens: 65536,
713
+ maxOutputTokens: 8192,
714
+ supportsVision: false,
715
+ supportsTools: true,
716
+ supportsStreaming: true,
717
+ supportsThinking: false,
718
+ inputCostPerMToken: 0.27,
719
+ outputCostPerMToken: 0.11,
720
+ },
721
+ 'Qwen/Qwen2.5-72B-Instruct': {
722
+ name: 'Qwen 2.5 72B (SiliconFlow)',
723
+ maxContextTokens: 131072,
724
+ maxOutputTokens: 8192,
725
+ supportsVision: false,
726
+ supportsTools: true,
727
+ supportsStreaming: true,
728
+ supportsThinking: false,
729
+ inputCostPerMToken: 0.7,
730
+ outputCostPerMToken: 0.7,
731
+ },
732
+ 'THUDM/glm-4-9b-chat': {
733
+ name: 'GLM-4 9B (SiliconFlow)',
734
+ maxContextTokens: 128000,
735
+ maxOutputTokens: 4096,
736
+ supportsVision: false,
737
+ supportsTools: true,
738
+ supportsStreaming: true,
739
+ supportsThinking: false,
740
+ inputCostPerMToken: 0,
741
+ outputCostPerMToken: 0,
742
+ },
743
+ },
744
+ defaultModel: 'deepseek-ai/DeepSeek-V3',
745
+ },
746
+ // ==================== International Providers ====================
747
+ groq: {
748
+ type: 'custom',
749
+ baseUrl: 'https://api.groq.com/openai/v1',
750
+ models: {
751
+ 'llama-3.3-70b-versatile': {
752
+ name: 'Llama 3.3 70B (Groq)',
753
+ maxContextTokens: 131072,
754
+ maxOutputTokens: 32768,
755
+ supportsVision: false,
756
+ supportsTools: true,
757
+ supportsStreaming: true,
758
+ supportsThinking: false,
759
+ inputCostPerMToken: 0.59,
760
+ outputCostPerMToken: 0.79,
761
+ },
762
+ 'llama-3.1-8b-instant': {
763
+ name: 'Llama 3.1 8B (Groq)',
764
+ maxContextTokens: 131072,
765
+ maxOutputTokens: 8192,
766
+ supportsVision: false,
767
+ supportsTools: true,
768
+ supportsStreaming: true,
769
+ supportsThinking: false,
770
+ inputCostPerMToken: 0.05,
771
+ outputCostPerMToken: 0.08,
772
+ },
773
+ 'mixtral-8x7b-32768': {
774
+ name: 'Mixtral 8x7B (Groq)',
775
+ maxContextTokens: 32768,
776
+ maxOutputTokens: 8192,
777
+ supportsVision: false,
778
+ supportsTools: true,
779
+ supportsStreaming: true,
780
+ supportsThinking: false,
781
+ inputCostPerMToken: 0.24,
782
+ outputCostPerMToken: 0.24,
783
+ },
784
+ 'gemma2-9b-it': {
785
+ name: 'Gemma 2 9B (Groq)',
786
+ maxContextTokens: 8192,
787
+ maxOutputTokens: 4096,
788
+ supportsVision: false,
789
+ supportsTools: true,
790
+ supportsStreaming: true,
791
+ supportsThinking: false,
792
+ inputCostPerMToken: 0.05,
793
+ outputCostPerMToken: 0.08,
794
+ },
795
+ },
796
+ defaultModel: 'llama-3.3-70b-versatile',
797
+ },
798
+ mistral: {
799
+ type: 'custom',
800
+ baseUrl: 'https://api.mistral.ai/v1',
801
+ models: {
802
+ 'mistral-large-latest': {
803
+ name: 'Mistral Large',
804
+ maxContextTokens: 131072,
805
+ maxOutputTokens: 8192,
806
+ supportsVision: false,
807
+ supportsTools: true,
808
+ supportsStreaming: true,
809
+ supportsThinking: false,
810
+ inputCostPerMToken: 2,
811
+ outputCostPerMToken: 6,
812
+ },
813
+ 'codestral-latest': {
814
+ name: 'Codestral',
815
+ maxContextTokens: 32768,
816
+ maxOutputTokens: 8192,
817
+ supportsVision: false,
818
+ supportsTools: true,
819
+ supportsStreaming: true,
820
+ supportsThinking: false,
821
+ inputCostPerMToken: 0.3,
822
+ outputCostPerMToken: 0.9,
823
+ },
824
+ 'mistral-small-latest': {
825
+ name: 'Mistral Small',
826
+ maxContextTokens: 131072,
827
+ maxOutputTokens: 8192,
828
+ supportsVision: false,
829
+ supportsTools: true,
830
+ supportsStreaming: true,
831
+ supportsThinking: false,
832
+ inputCostPerMToken: 0.2,
833
+ outputCostPerMToken: 0.6,
834
+ },
835
+ },
836
+ defaultModel: 'mistral-large-latest',
837
+ },
838
+ together: {
839
+ type: 'custom',
840
+ baseUrl: 'https://api.together.xyz/v1',
841
+ models: {
842
+ 'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo': {
843
+ name: 'Llama 3.1 70B (Together)',
844
+ maxContextTokens: 131072,
845
+ maxOutputTokens: 8192,
846
+ supportsVision: false,
847
+ supportsTools: true,
848
+ supportsStreaming: true,
849
+ supportsThinking: false,
850
+ inputCostPerMToken: 0.88,
851
+ outputCostPerMToken: 0.88,
852
+ },
853
+ 'mistralai/Mixtral-8x7B-Instruct-v0.1': {
854
+ name: 'Mixtral 8x7B (Together)',
855
+ maxContextTokens: 32768,
856
+ maxOutputTokens: 8192,
857
+ supportsVision: false,
858
+ supportsTools: true,
859
+ supportsStreaming: true,
860
+ supportsThinking: false,
861
+ inputCostPerMToken: 0.24,
862
+ outputCostPerMToken: 0.24,
863
+ },
864
+ },
865
+ defaultModel: 'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo',
866
+ },
867
+ perplexity: {
868
+ type: 'custom',
869
+ baseUrl: 'https://api.perplexity.ai',
870
+ models: {
871
+ 'sonar-pro': {
872
+ name: 'Sonar Pro',
873
+ maxContextTokens: 200000,
874
+ maxOutputTokens: 8192,
875
+ supportsVision: false,
876
+ supportsTools: false,
877
+ supportsStreaming: true,
878
+ supportsThinking: false,
879
+ inputCostPerMToken: 3,
880
+ outputCostPerMToken: 15,
881
+ },
882
+ 'sonar': {
883
+ name: 'Sonar',
884
+ maxContextTokens: 127000,
885
+ maxOutputTokens: 8192,
886
+ supportsVision: false,
887
+ supportsTools: false,
888
+ supportsStreaming: true,
889
+ supportsThinking: false,
890
+ inputCostPerMToken: 1,
891
+ outputCostPerMToken: 1,
892
+ },
893
+ },
894
+ defaultModel: 'sonar-pro',
895
+ },
896
+ },
897
+ aliases: {
898
+ // Claude aliases
899
+ 'claude': 'claude-3-5-sonnet-20241022',
900
+ 'sonnet': 'claude-3-5-sonnet-20241022',
901
+ 'sonnet4': 'claude-sonnet-4-20250514',
902
+ 'opus': 'claude-3-opus-20240229',
903
+ 'opus4': 'claude-opus-4-20250514',
904
+ 'haiku': 'claude-3-5-haiku-20241022',
905
+ // OpenAI aliases
906
+ 'gpt4': 'gpt-4o',
907
+ 'gpt4mini': 'gpt-4o-mini',
908
+ 'o1': 'o1',
909
+ 'o1mini': 'o1-mini',
910
+ 'o3mini': 'o3-mini',
911
+ // Google aliases
912
+ 'gemini': 'gemini-2.0-flash',
913
+ 'gemflash': 'gemini-2.0-flash',
914
+ 'gempro': 'gemini-2.5-pro-preview-05-06',
915
+ // DeepSeek aliases
916
+ 'deepseek': 'deepseek-chat',
917
+ 'dsr1': 'deepseek-reasoner',
918
+ // Chinese provider aliases
919
+ 'qwen': 'qwen-max',
920
+ 'qwenplus': 'qwen-plus',
921
+ 'qwen-turbo': 'qwen-turbo',
922
+ 'qwen-coder': 'qwen-coder-plus',
923
+ 'glm': 'glm-4-plus',
924
+ 'glm4': 'glm-4-plus',
925
+ 'moonshot': 'moonshot-v1-128k',
926
+ 'baichuan': 'baichuan4',
927
+ 'yi': 'yi-large',
928
+ // International provider aliases
929
+ 'groq': 'llama-3.3-70b-versatile',
930
+ 'mistral': 'mistral-large-latest',
931
+ 'codestral': 'codestral-latest',
932
+ 'sonar': 'sonar-pro',
933
+ // Ollama aliases
934
+ 'local': 'llama3.1',
935
+ // Ollama Cloud aliases
936
+ 'cloud': 'deepseek-v3.2',
937
+ 'deepseek-cloud': 'deepseek-v3.2',
938
+ 'kimi': 'kimi-k2.5',
939
+ 'qwen-cloud': 'qwen3-coder:480b',
940
+ 'glm-cloud': 'glm-5',
941
+ },
942
+ },
943
+ security: {
944
+ sandbox: 'restricted',
945
+ checkpoints: true,
946
+ blockedCommands: ['rm -rf /', 'mkfs', 'dd if=', ':(){ :|:& };:'],
947
+ allowedCommands: ['ls', 'cat', 'pwd', 'echo', 'which', 'node', 'npm', 'pnpm'],
948
+ },
949
+ fileFilter: {
950
+ ignorePatterns: ['node_modules/**', '.git/**', 'dist/**', 'build/**', '*.log', '.env', '.env.local'],
951
+ maxFileSize: 10 * 1024 * 1024,
952
+ maxBatchSize: 100,
953
+ },
954
+ telemetry: {
955
+ enabled: false,
956
+ track: { usage: false, errors: true, performance: false },
957
+ },
958
+ extensions: {
959
+ enabled: [],
960
+ },
961
+ preferences: {
962
+ theme: 'auto',
963
+ language: 'en',
964
+ },
965
+ };
966
+
967
+ export class ConfigManager {
968
+ private config: NovaConfig | null = null;
969
+ private configPath: string;
970
+ private projectConfigPath: string | null = null;
971
+
972
+ constructor() {
973
+ this.configPath = path.join(os.homedir(), '.nova', 'config.yaml');
974
+ }
975
+
976
+ /** Load configuration from file, merging with defaults */
977
+ async load(projectDir?: string): Promise<NovaConfig> {
978
+ this.config = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
979
+
980
+ // Load global config
981
+ try {
982
+ const globalContent = await fs.readFile(this.configPath, 'utf-8');
983
+ const globalConfig = this.parseConfig(globalContent);
984
+ this.config = this.mergeConfig(this.config, globalConfig);
985
+ } catch {
986
+ // Global config doesn't exist yet, that's fine
987
+ }
988
+
989
+ // Load project config
990
+ if (projectDir) {
991
+ const projectPaths = [
992
+ path.join(projectDir, '.nova', 'config.yaml'),
993
+ path.join(projectDir, '.nova', 'config.json'),
994
+ path.join(projectDir, '.nova.json'),
995
+ ];
996
+
997
+ for (const p of projectPaths) {
998
+ try {
999
+ const content = await fs.readFile(p, 'utf-8');
1000
+ const projectConfig = p.endsWith('.json') ? JSON.parse(content) : this.parseConfig(content);
1001
+ this.config = this.mergeConfig(this.config, projectConfig);
1002
+ this.projectConfigPath = p;
1003
+ break;
1004
+ } catch {
1005
+ continue;
1006
+ }
1007
+ }
1008
+ }
1009
+
1010
+ this.applyEnvOverrides();
1011
+ return this.config;
1012
+ }
1013
+
1014
+ /** Get the loaded configuration */
1015
+ getConfig(): NovaConfig {
1016
+ if (!this.config) {
1017
+ throw new ConfigError('Configuration not loaded. Call load() first.');
1018
+ }
1019
+ return this.config;
1020
+ }
1021
+
1022
+ /** Get core config */
1023
+ getCoreConfig(): CoreConfig {
1024
+ return this.getConfig().core;
1025
+ }
1026
+
1027
+ /** Get model configuration */
1028
+ getModelConfig(modelId: string): { provider: ModelProviderConfig; model: ModelConfig } | null {
1029
+ const config = this.getConfig();
1030
+
1031
+ // Handle provider/model format (e.g., "coding-plan-alibaba/glm-5")
1032
+ let targetProvider: string | null = null;
1033
+ let targetModelId: string = modelId;
1034
+
1035
+ if (modelId.includes('/')) {
1036
+ const [providerKey, modelKey] = modelId.split('/');
1037
+ targetProvider = providerKey;
1038
+ targetModelId = modelKey;
1039
+ }
1040
+
1041
+ // Resolve alias for the model ID (not for provider/model format)
1042
+ const resolvedId = targetProvider ? targetModelId : (config.models.aliases?.[modelId] || modelId);
1043
+
1044
+ // If provider was specified, look only in that provider
1045
+ if (targetProvider) {
1046
+ const provider = config.models.providers[targetProvider];
1047
+ if (provider) {
1048
+ const model = provider.models[targetModelId];
1049
+ if (model) {
1050
+ return {
1051
+ provider: { ...provider, name: targetProvider },
1052
+ model,
1053
+ };
1054
+ }
1055
+ }
1056
+ return null; // Provider specified but not found or model not in provider
1057
+ }
1058
+
1059
+ // Otherwise, search across all providers
1060
+ for (const [providerName, provider] of Object.entries(config.models.providers)) {
1061
+ const model = provider.models[resolvedId];
1062
+ if (model) {
1063
+ return {
1064
+ provider: { ...provider, name: providerName },
1065
+ model,
1066
+ };
1067
+ }
1068
+ }
1069
+
1070
+ return null;
1071
+ }
1072
+
1073
+ /**
1074
+ * Dynamically register a model to an existing provider.
1075
+ * Useful for Ollama runtime model discovery and custom provider model addition.
1076
+ * Returns true if the model was newly registered, false if it already existed.
1077
+ */
1078
+ registerModel(
1079
+ providerName: string,
1080
+ modelId: string,
1081
+ modelConfig: ModelConfig,
1082
+ providerOverrides?: Partial<ModelProviderConfig>
1083
+ ): boolean {
1084
+ if (!this.config) return false;
1085
+
1086
+ if (!this.config.models.providers[providerName]) {
1087
+ // Create a new provider entry
1088
+ this.config.models.providers[providerName] = {
1089
+ type: providerOverrides?.type || 'custom',
1090
+ models: {},
1091
+ ...(providerOverrides?.baseUrl && { baseUrl: providerOverrides.baseUrl }),
1092
+ ...(providerOverrides?.defaultModel && { defaultModel: providerOverrides.defaultModel }),
1093
+ };
1094
+ }
1095
+
1096
+ const provider = this.config.models.providers[providerName];
1097
+ if (provider.models[modelId]) return false; // Already exists
1098
+
1099
+ provider.models[modelId] = modelConfig;
1100
+ if (!provider.defaultModel) {
1101
+ provider.defaultModel = modelId;
1102
+ }
1103
+ return true;
1104
+ }
1105
+
1106
+ /**
1107
+ * Dynamically register a complete provider with models.
1108
+ */
1109
+ registerProvider(providerName: string, providerConfig: ModelProviderConfig): boolean {
1110
+ if (!this.config) return false;
1111
+ this.config.models.providers[providerName] = providerConfig;
1112
+ return true;
1113
+ }
1114
+
1115
+ /**
1116
+ * Add a model alias.
1117
+ */
1118
+ registerAlias(alias: string, targetModelId: string): void {
1119
+ if (!this.config) return;
1120
+ if (!this.config.models.aliases) {
1121
+ this.config.models.aliases = {};
1122
+ }
1123
+ this.config.models.aliases[alias] = targetModelId;
1124
+ }
1125
+
1126
+ /** Get config file path */
1127
+ getConfigPath(): string {
1128
+ return this.configPath;
1129
+ }
1130
+
1131
+ /** Set the default model */
1132
+ setDefaultModel(modelId: string): void {
1133
+ if (!this.config) return;
1134
+ this.config.core.defaultModel = modelId;
1135
+ }
1136
+
1137
+ /** Save configuration */
1138
+ async save(config: Partial<NovaConfig>): Promise<void> {
1139
+ const dir = path.dirname(this.configPath);
1140
+ await fs.mkdir(dir, { recursive: true });
1141
+ const content = JSON.stringify(config, null, 2);
1142
+ await fs.writeFile(this.configPath, content, 'utf-8');
1143
+ }
1144
+
1145
+ /** Deep merge two configurations */
1146
+ private mergeConfig(base: NovaConfig, source: Partial<NovaConfig>): NovaConfig {
1147
+ const result = { ...base };
1148
+ for (const [key, value] of Object.entries(source)) {
1149
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
1150
+ (result as any)[key] = { ...((base as any)[key] || {}), ...value };
1151
+ } else if (value !== undefined) {
1152
+ (result as any)[key] = value;
1153
+ }
1154
+ }
1155
+ return result;
1156
+ }
1157
+
1158
+ /** Apply environment variable overrides */
1159
+ private applyEnvOverrides(): void {
1160
+ if (!this.config) return;
1161
+
1162
+ const overrides: Record<string, (v: string) => void> = {
1163
+ NOVA_MODEL: (v) => { this.config!.core.defaultModel = v; },
1164
+ NOVA_LOG_LEVEL: (v) => { this.config!.core.logLevel = v as any; },
1165
+ NOVA_MAX_TURNS: (v) => { this.config!.core.maxTurns = parseInt(v, 10); },
1166
+ NOVA_MAX_TOKENS: (v) => { this.config!.core.maxTokens = parseInt(v, 10); },
1167
+ NOVA_TEMPERATURE: (v) => { this.config!.core.temperature = parseFloat(v); },
1168
+ NOVA_APPROVAL_MODE: (v) => { this.config!.core.defaultApprovalMode = v as any; },
1169
+ };
1170
+
1171
+ for (const [envKey, setter] of Object.entries(overrides)) {
1172
+ const value = process.env[envKey];
1173
+ if (value) setter(value);
1174
+ }
1175
+ }
1176
+
1177
+ /** Simple config parser (YAML-like or JSON) */
1178
+ private parseConfig(content: string): Record<string, unknown> {
1179
+ try {
1180
+ // Try JSON first
1181
+ return JSON.parse(content);
1182
+ } catch {
1183
+ // Fallback: simple line-based parsing
1184
+ const result: Record<string, unknown> = {};
1185
+ const lines = content.split('\n');
1186
+ let currentKey = '';
1187
+
1188
+ for (const line of lines) {
1189
+ if (line.trim().startsWith('#') || !line.trim()) continue;
1190
+ const indent = line.search(/\S/);
1191
+
1192
+ if (indent === 0) {
1193
+ const [key, ...valueParts] = line.trim().split(':');
1194
+ const value = valueParts.join(':').trim();
1195
+ currentKey = key;
1196
+ result[key] = value ? this.parseValue(value) : {};
1197
+ } else if (currentKey && typeof result[currentKey] === 'object') {
1198
+ const [key, ...valueParts] = line.trim().split(':');
1199
+ const value = valueParts.join(':').trim();
1200
+ (result[currentKey] as Record<string, unknown>)[key] = this.parseValue(value);
1201
+ }
1202
+ }
1203
+ return result;
1204
+ }
1205
+ }
1206
+
1207
+ /** Parse a scalar value */
1208
+ private parseValue(value: string): unknown {
1209
+ if (value === 'true') return true;
1210
+ if (value === 'false') return false;
1211
+ if (value === 'null' || value === '~') return null;
1212
+ if (/^-?\d+$/.test(value)) return parseInt(value, 10);
1213
+ if (/^-?\d+\.\d+$/.test(value)) return parseFloat(value);
1214
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
1215
+ return value.slice(1, -1);
1216
+ }
1217
+ return value;
1218
+ }
1219
+ }