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,210 @@
1
+ // ============================================================================
2
+ // CodingPlanProvider - Support for Chinese Coding Plan platforms
3
+ // ============================================================================
4
+ //
5
+ // Supports multiple Coding Plan platforms with a single API key:
6
+ // - Alibaba Cloud (阿里云百炼)
7
+ // - Tencent Cloud (腾讯云)
8
+ // - Volcengine (火山引擎)
9
+ // - Baidu Qianfan (百度千帆)
10
+ // - Kimi Code
11
+ // - Zhipu AI (智谱)
12
+ // - MiniMax
13
+ //
14
+ // All platforms use OpenAI-compatible API with custom base URLs
15
+ // ============================================================================
16
+
17
+ import { OpenAICompatibleProvider, type OpenAICompatibleConfig } from './OpenAICompatibleProvider.js';
18
+
19
+ /** Coding Plan platform identifiers */
20
+ export type CodingPlanPlatform =
21
+ | 'alibaba'
22
+ | 'tencent'
23
+ | 'volcengine'
24
+ | 'baidu'
25
+ | 'kimi'
26
+ | 'zhipu'
27
+ | 'minimax'
28
+ | 'custom';
29
+
30
+ /** Coding Plan configuration */
31
+ export interface CodingPlanConfig extends Omit<OpenAICompatibleConfig, 'baseUrl'> {
32
+ platform: CodingPlanPlatform;
33
+ /** Custom base URL (required if platform is 'custom') */
34
+ customBaseUrl?: string;
35
+ }
36
+
37
+ /** Platform-specific configurations */
38
+ const PLATFORM_CONFIGS: Record<CodingPlanPlatform, {
39
+ name: string;
40
+ baseUrl: string;
41
+ models: string[];
42
+ anthropicBaseUrl?: string;
43
+ }> = {
44
+ alibaba: {
45
+ name: 'Alibaba Cloud Coding Plan',
46
+ baseUrl: 'https://coding.dashscope.aliyuncs.com/v1',
47
+ anthropicBaseUrl: 'https://coding.dashscope.aliyuncs.com/apps/anthropic',
48
+ models: ['qwen3.5-plus', 'qwen3-coder', 'glm-5', 'minimax-m2.5', 'kimi-k2.5'],
49
+ },
50
+ tencent: {
51
+ name: 'Tencent Cloud Coding Plan',
52
+ baseUrl: 'https://api.hunyuan.cloud.tencent.com/v1', // 需要从控制台获取实际URL
53
+ models: ['hy-2.0-instruct', 'glm-5', 'kimi-k2.5', 'minimax-m2.5'],
54
+ },
55
+ volcengine: {
56
+ name: 'Volcengine Coding Plan',
57
+ baseUrl: 'https://ark.cn-beijing.volces.com/api/v3', // 需要从控制台获取实际URL
58
+ models: ['doubao-seed-code', 'deepseek-v3.2', 'glm-4.7', 'kimi-k2'],
59
+ },
60
+ baidu: {
61
+ name: 'Baidu Qianfan Coding Plan',
62
+ baseUrl: 'https://qianfan.baidubce.com/v2', // 需要从控制台获取实际URL
63
+ models: ['glm-5', 'minimax-m2.5', 'kimi-k2.5', 'ernie-4.5'],
64
+ },
65
+ kimi: {
66
+ name: 'Kimi Code',
67
+ baseUrl: 'https://api.moonshot.cn/v1',
68
+ models: ['kimi-k2', 'kimi-k2.5'],
69
+ },
70
+ zhipu: {
71
+ name: 'Zhipu AI Coding Plan',
72
+ baseUrl: 'https://open.bigmodel.cn/api/paas/v4',
73
+ models: ['glm-4.7', 'glm-5'],
74
+ },
75
+ minimax: {
76
+ name: 'MiniMax Coding Plan',
77
+ baseUrl: 'https://api.minimax.chat/v1',
78
+ models: ['minimax-2.7', 'abab6.5s-chat'],
79
+ },
80
+ custom: {
81
+ name: 'Custom Coding Plan',
82
+ baseUrl: '',
83
+ models: [],
84
+ },
85
+ };
86
+
87
+ /**
88
+ * Coding Plan Provider - Access multiple AI models through Chinese Coding Plan platforms
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * // 使用阿里云百炼 Coding Plan
93
+ * const provider = new CodingPlanProvider({
94
+ * platform: 'alibaba',
95
+ * apiKey: 'your-coding-plan-api-key',
96
+ * model: 'qwen3-coder',
97
+ * });
98
+ *
99
+ * // 使用自定义 Coding Plan
100
+ * const provider = new CodingPlanProvider({
101
+ * platform: 'custom',
102
+ * customBaseUrl: 'https://your-coding-plan-endpoint.com/v1',
103
+ * apiKey: 'your-api-key',
104
+ * model: 'custom-model',
105
+ * });
106
+ * ```
107
+ */
108
+ export class CodingPlanProvider extends OpenAICompatibleProvider {
109
+ readonly name: string;
110
+ readonly platform: CodingPlanPlatform;
111
+ readonly supportedModels: string[];
112
+
113
+ constructor(config: CodingPlanConfig) {
114
+ const platformConfig = PLATFORM_CONFIGS[config.platform];
115
+
116
+ if (!platformConfig) {
117
+ throw new Error(`Unknown Coding Plan platform: ${config.platform}`);
118
+ }
119
+
120
+ if (config.platform === 'custom' && !config.customBaseUrl) {
121
+ throw new Error('customBaseUrl is required when platform is "custom"');
122
+ }
123
+
124
+ const baseUrl = config.platform === 'custom'
125
+ ? config.customBaseUrl
126
+ : platformConfig.baseUrl;
127
+
128
+ super({
129
+ apiKey: config.apiKey,
130
+ baseUrl: baseUrl,
131
+ model: config.model,
132
+ headers: config.headers,
133
+ });
134
+
135
+ this.name = platformConfig.name;
136
+ this.platform = config.platform;
137
+ this.supportedModels = platformConfig.models;
138
+
139
+ // Validate model if platform has known models
140
+ if (platformConfig.models.length > 0 && !platformConfig.models.includes(config.model)) {
141
+ console.warn(
142
+ `[CodingPlanProvider] Model "${config.model}" may not be supported by ${platformConfig.name}. ` +
143
+ `Known models: ${platformConfig.models.join(', ')}`
144
+ );
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Get list of supported models for this platform
150
+ */
151
+ getSupportedModels(): string[] {
152
+ return [...this.supportedModels];
153
+ }
154
+
155
+ /**
156
+ * Get platform info
157
+ */
158
+ getPlatformInfo(): { name: string; platform: CodingPlanPlatform; baseUrl: string } {
159
+ const config = PLATFORM_CONFIGS[this.platform];
160
+ return {
161
+ name: this.name,
162
+ platform: this.platform,
163
+ baseUrl: this.platform === 'custom' ? '' : config.baseUrl,
164
+ };
165
+ }
166
+
167
+ /**
168
+ * Get Anthropic-compatible base URL (for Alibaba Cloud only)
169
+ */
170
+ getAnthropicBaseUrl(): string | null {
171
+ const config = PLATFORM_CONFIGS[this.platform];
172
+ return config.anthropicBaseUrl || null;
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Get all supported Coding Plan platforms
178
+ */
179
+ export function getSupportedCodingPlanPlatforms(): Array<{
180
+ platform: CodingPlanPlatform;
181
+ name: string;
182
+ baseUrl: string;
183
+ models: string[];
184
+ }> {
185
+ return Object.entries(PLATFORM_CONFIGS)
186
+ .filter(([key]) => key !== 'custom')
187
+ .map(([platform, config]) => ({
188
+ platform: platform as CodingPlanPlatform,
189
+ name: config.name,
190
+ baseUrl: config.baseUrl,
191
+ models: config.models,
192
+ }));
193
+ }
194
+
195
+ /**
196
+ * Quick factory for creating Coding Plan providers
197
+ */
198
+ export function createCodingPlanProvider(
199
+ platform: CodingPlanPlatform,
200
+ apiKey: string,
201
+ model: string,
202
+ customBaseUrl?: string
203
+ ): CodingPlanProvider {
204
+ return new CodingPlanProvider({
205
+ platform,
206
+ apiKey,
207
+ model,
208
+ customBaseUrl,
209
+ });
210
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OllamaCloudProvider.d.ts","sourceRoot":"","sources":["OllamaCloudProvider.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,OAAO,EAAmD,MAAM,wBAAwB,CAAC;AAEvG,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,aAAa,EAAc,WAAW,EAAE,MAAM,aAAa,CAAC;AAI9G,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAgDD,qBAAa,mBAAoB,YAAW,aAAa;IACvD,QAAQ,CAAC,IAAI,kBAAkB;IAC/B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAS;gBAEZ,MAAM,EAAE,iBAAiB;IAK/B,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,aAAa,CAAC;IA4ClF,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,mBAAmB,GAAG,cAAc,CAAC,WAAW,CAAC;IAiHvF,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAWvD,OAAO,CAAC,UAAU;IASlB;;;OAGG;IACH,OAAO,CAAC,YAAY;IA2BpB;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAiDvB,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,sBAAsB;IA0B9B,OAAO,CAAC,iBAAiB;YAUX,OAAO;CAWtB"}
@@ -0,0 +1,405 @@
1
+ // ============================================================================
2
+ // OllamaCloudProvider - Ollama Cloud API (https://ollama.com)
3
+ // ============================================================================
4
+ //
5
+ // Uses Ollama's native REST API (/api/chat) for cloud-hosted models.
6
+ // Supports:
7
+ // - All Ollama Cloud models (deepseek-v3.2, kimi-k2.5, qwen3-coder, etc.)
8
+ // - Streaming responses with thinking/reasoning support
9
+ // - Tool/function calling (Ollama native format)
10
+ // - Model discovery via /api/tags
11
+ // - API key authentication (Bearer token)
12
+ //
13
+ // NOTE: Ollama Cloud uses native API format, NOT OpenAI-compatible.
14
+ // ============================================================================
15
+
16
+ import type { Message, ContentBlock, ToolUseContent, ToolResultContent } from '../../types/session.js';
17
+ import type { ToolDefinition } from '../../types/tools.js';
18
+ import type { ModelProvider, ModelRequestOptions, ModelResponse, TokenUsage, StreamEvent } from '../types.js';
19
+ import { ModelError, RateLimitError } from '../../types/errors.js';
20
+ import { createToolCallId } from '../../types/session.js';
21
+
22
+ export interface OllamaCloudConfig {
23
+ apiKey: string;
24
+ baseUrl?: string;
25
+ model: string;
26
+ }
27
+
28
+ /** Ollama Cloud /api/chat response format (non-streaming) */
29
+ interface OllamaChatResponse {
30
+ model: string;
31
+ created_at: string;
32
+ message: {
33
+ role: string;
34
+ content: string;
35
+ thinking?: string;
36
+ tool_calls?: Array<{
37
+ function: {
38
+ name: string;
39
+ arguments: { [key: string]: unknown };
40
+ };
41
+ }>;
42
+ };
43
+ done: boolean;
44
+ done_reason?: string;
45
+ total_duration?: number;
46
+ load_duration?: number;
47
+ prompt_eval_count?: number;
48
+ prompt_eval_duration?: number;
49
+ eval_count?: number;
50
+ eval_duration?: number;
51
+ }
52
+
53
+ /** Ollama Cloud /api/chat streaming chunk */
54
+ interface OllamaChatChunk {
55
+ model: string;
56
+ created_at: string;
57
+ message: {
58
+ role: string;
59
+ content: string;
60
+ thinking?: string;
61
+ tool_calls?: Array<{
62
+ function: {
63
+ name: string;
64
+ arguments: { [key: string]: unknown };
65
+ };
66
+ }>;
67
+ };
68
+ done: boolean;
69
+ done_reason?: string;
70
+ prompt_eval_count?: number;
71
+ eval_count?: number;
72
+ }
73
+
74
+ export class OllamaCloudProvider implements ModelProvider {
75
+ readonly name = 'Ollama Cloud';
76
+ private apiKey: string;
77
+ private baseUrl: string;
78
+
79
+ constructor(config: OllamaCloudConfig) {
80
+ this.apiKey = config.apiKey;
81
+ this.baseUrl = (config.baseUrl || 'https://ollama.com').replace(/\/+$/, '');
82
+ }
83
+
84
+ async complete(messages: Message[], options: ModelRequestOptions): Promise<ModelResponse> {
85
+ const ollamaMessages = this.convertMessages(messages, options.systemPrompt);
86
+ const tools = this.convertTools(options.tools);
87
+ const body: Record<string, unknown> = {
88
+ model: options.model,
89
+ messages: ollamaMessages,
90
+ stream: false,
91
+ ...(tools.length > 0 && { tools }),
92
+ ...(options.maxTokens && { options: { num_predict: options.maxTokens } }),
93
+ ...(options.temperature !== undefined && options.temperature !== 1 && {
94
+ options: { ...this.getOptions(options), temperature: options.temperature },
95
+ }),
96
+ };
97
+
98
+ const res = await this.request('/api/chat', body);
99
+ const data = await res.json() as OllamaChatResponse;
100
+
101
+ if (!res.ok) {
102
+ throw new ModelError(
103
+ `Ollama Cloud API error: ${res.status} ${res.statusText} - ${JSON.stringify(data)}`,
104
+ res.status,
105
+ this.name,
106
+ );
107
+ }
108
+
109
+ const content = this.convertResponseContent(data.message);
110
+ const usage: TokenUsage = {
111
+ inputTokens: data.prompt_eval_count || 0,
112
+ outputTokens: data.eval_count || 0,
113
+ };
114
+
115
+ // Ollama returns done_reason "stop" even for tool calls; detect from response content
116
+ const hasToolCalls = content.some((c): c is ToolUseContent => c.type === 'tool_use');
117
+ const stopReason = hasToolCalls ? 'tool_use' : this.convertStopReason(data.done_reason);
118
+
119
+ return {
120
+ content,
121
+ model: data.model,
122
+ stopReason,
123
+ usage,
124
+ sessionId: options.sessionId,
125
+ };
126
+ }
127
+
128
+ async *stream(messages: Message[], options: ModelRequestOptions): AsyncGenerator<StreamEvent> {
129
+ const ollamaMessages = this.convertMessages(messages, options.systemPrompt);
130
+ const tools = this.convertTools(options.tools);
131
+ const body: Record<string, unknown> = {
132
+ model: options.model,
133
+ messages: ollamaMessages,
134
+ stream: true,
135
+ ...(tools.length > 0 && { tools }),
136
+ options: this.getOptions(options),
137
+ };
138
+
139
+ let res: Response;
140
+ try {
141
+ res = await this.request('/api/chat', body);
142
+ } catch (err) {
143
+ yield { type: 'error', error: new ModelError(`Ollama Cloud request failed: ${(err as Error).message}`, undefined, this.name) };
144
+ return;
145
+ }
146
+
147
+ if (!res.ok) {
148
+ let errorText = '';
149
+ try { errorText = await res.text(); } catch { /* ignore */ }
150
+ yield { type: 'error', error: new ModelError(`Ollama Cloud API error: ${res.status} ${errorText || res.statusText}`, res.status, this.name) };
151
+ return;
152
+ }
153
+
154
+ yield { type: 'message_start', model: options.model };
155
+
156
+ const reader = res.body?.getReader();
157
+ if (!reader) {
158
+ yield { type: 'error', error: new ModelError('No response body from Ollama Cloud', undefined, this.name) };
159
+ return;
160
+ }
161
+
162
+ const decoder = new TextDecoder();
163
+ let buffer = '';
164
+ let totalInput = 0;
165
+ let totalOutput = 0;
166
+ // Track tool calls for streaming (Ollama sends them in the final chunk)
167
+ let pendingToolCalls: Array<{ name: string; arguments: string }> = [];
168
+
169
+ try {
170
+ while (true) {
171
+ const { done, value } = await reader.read();
172
+ if (done) break;
173
+
174
+ buffer += decoder.decode(value, { stream: true });
175
+ const lines = buffer.split('\n');
176
+ buffer = lines.pop() || '';
177
+
178
+ for (const line of lines) {
179
+ if (!line.trim()) continue;
180
+ try {
181
+ const chunk: OllamaChatChunk = JSON.parse(line);
182
+
183
+ // Emit thinking content
184
+ if (chunk.message?.thinking) {
185
+ yield { type: 'thinking_delta', delta: chunk.message.thinking };
186
+ }
187
+
188
+ // Emit text content
189
+ if (chunk.message?.content) {
190
+ yield { type: 'text_delta', delta: chunk.message.content };
191
+ }
192
+
193
+ // Collect tool calls (Ollama sends them in final chunk or inline)
194
+ if (chunk.message?.tool_calls) {
195
+ for (const tc of chunk.message.tool_calls) {
196
+ const argsStr = typeof tc.function.arguments === 'string'
197
+ ? tc.function.arguments
198
+ : JSON.stringify(tc.function.arguments);
199
+ pendingToolCalls.push({
200
+ name: tc.function.name,
201
+ arguments: argsStr,
202
+ });
203
+ }
204
+ }
205
+
206
+ // Track token usage
207
+ if (chunk.prompt_eval_count) totalInput = chunk.prompt_eval_count;
208
+ if (chunk.eval_count) totalOutput = chunk.eval_count;
209
+
210
+ // Stream done
211
+ if (chunk.done) {
212
+ // Emit any pending tool calls before message_complete
213
+ if (pendingToolCalls.length > 0) {
214
+ for (const tc of pendingToolCalls) {
215
+ const toolCallId = createToolCallId(`tool_${Date.now()}_${Math.random()}`);
216
+ yield { type: 'tool_call_start', toolCallId, toolName: tc.name };
217
+ yield { type: 'tool_call_delta', toolCallId, delta: tc.arguments };
218
+ }
219
+ }
220
+
221
+ // Ollama uses "stop" even for tool calls; detect from pending tool calls
222
+ const hasToolCalls = pendingToolCalls.length > 0;
223
+ const stopReason = hasToolCalls ? 'tool_use' : this.convertStopReason(chunk.done_reason);
224
+
225
+ yield {
226
+ type: 'message_complete',
227
+ stopReason,
228
+ usage: { inputTokens: totalInput, outputTokens: totalOutput },
229
+ };
230
+ }
231
+ } catch {
232
+ // Skip malformed lines
233
+ }
234
+ }
235
+ }
236
+ } catch (err) {
237
+ yield { type: 'error', error: new ModelError(`Ollama Cloud stream error: ${(err as Error).message}`, undefined, this.name) };
238
+ }
239
+ }
240
+
241
+ async countTokens(messages: Message[]): Promise<number> {
242
+ // Approximate: ~4 chars per token
243
+ const text = messages.map(m => {
244
+ if (typeof m.content === 'string') return m.content;
245
+ return m.content.map(c => c.type === 'text' ? c.text : '').join('');
246
+ }).join('');
247
+ return Math.ceil(text.length / 4);
248
+ }
249
+
250
+ // --- Private helpers ---
251
+
252
+ private getOptions(options: ModelRequestOptions): Record<string, unknown> {
253
+ const opts: Record<string, unknown> = {};
254
+ if (options.maxTokens) opts.num_predict = options.maxTokens;
255
+ if (options.temperature !== undefined && options.temperature !== 1) {
256
+ opts.temperature = options.temperature;
257
+ }
258
+ return opts;
259
+ }
260
+
261
+ /**
262
+ * Convert internal ToolDefinition[] to Ollama's native tool format.
263
+ * Ollama uses the same format as OpenAI: { type: "function", function: { name, description, parameters } }
264
+ */
265
+ private convertTools(tools: ToolDefinition[]): Array<Record<string, unknown>> {
266
+ if (!tools || tools.length === 0) return [];
267
+
268
+ return tools.map(tool => {
269
+ const parameters: Record<string, unknown> = {
270
+ type: 'object',
271
+ properties: {},
272
+ };
273
+
274
+ if (tool.inputSchema?.properties) {
275
+ parameters.properties = tool.inputSchema.properties;
276
+ }
277
+ if (tool.inputSchema?.required) {
278
+ parameters.required = tool.inputSchema.required;
279
+ }
280
+
281
+ return {
282
+ type: 'function',
283
+ function: {
284
+ name: tool.name,
285
+ description: tool.description || '',
286
+ parameters,
287
+ },
288
+ };
289
+ });
290
+ }
291
+
292
+ /**
293
+ * Convert messages to Ollama format.
294
+ * Handles: system, user, assistant (with optional tool_calls), and tool results.
295
+ * Ollama doesn't have a native tool role; tool results are sent as user messages.
296
+ */
297
+ private convertMessages(messages: Message[], systemPrompt?: string): Array<Record<string, unknown>> {
298
+ const result: Array<Record<string, unknown>> = [];
299
+
300
+ if (systemPrompt) {
301
+ result.push({ role: 'system', content: systemPrompt });
302
+ }
303
+
304
+ for (const msg of messages) {
305
+ if (msg.role === 'user') {
306
+ const text = this.extractText(msg.content);
307
+ result.push({ role: 'user', content: text });
308
+ } else if (msg.role === 'assistant') {
309
+ // Check if assistant message contains tool calls
310
+ const toolUseBlocks = msg.content.filter((c): c is ToolUseContent => c.type === 'tool_use');
311
+ const textBlocks = msg.content.filter(c => c.type === 'text');
312
+
313
+ if (toolUseBlocks.length > 0) {
314
+ // Ollama supports tool_calls in assistant messages
315
+ const msgObj: Record<string, unknown> = {
316
+ role: 'assistant',
317
+ content: textBlocks.map(c => (c as { text: string }).text).join('') || '',
318
+ tool_calls: toolUseBlocks.map(tc => ({
319
+ function: {
320
+ name: tc.name,
321
+ arguments: tc.input,
322
+ },
323
+ })),
324
+ };
325
+ result.push(msgObj);
326
+ } else {
327
+ const text = this.extractText(msg.content);
328
+ result.push({ role: 'assistant', content: text });
329
+ }
330
+ } else if (msg.role === 'tool') {
331
+ // Ollama doesn't have a native tool role; include as user message
332
+ const toolResults = msg.content.filter((c): c is ToolResultContent => c.type === 'tool_result');
333
+ const text = toolResults.map(tr => {
334
+ const content = typeof tr.content === 'string'
335
+ ? tr.content
336
+ : JSON.stringify(tr.content);
337
+ return `[Tool Result for ${(tr as any).tool_use_id}]: ${content}`;
338
+ }).join('\n');
339
+ result.push({ role: 'user', content: text });
340
+ }
341
+ }
342
+
343
+ return result;
344
+ }
345
+
346
+ private extractText(content: Message['content']): string {
347
+ if (typeof content === 'string') return content;
348
+ return content
349
+ .map(block => {
350
+ if (block.type === 'text') return block.text;
351
+ if (block.type === 'tool_use') return `[Calling tool: ${block.name}]`;
352
+ if (block.type === 'tool_result') return `[Tool result: ${typeof block.content === 'string' ? block.content : JSON.stringify(block.content)}]`;
353
+ return '';
354
+ })
355
+ .join('\n');
356
+ }
357
+
358
+ private convertResponseContent(message: OllamaChatResponse['message']): ContentBlock[] {
359
+ const content: ContentBlock[] = [];
360
+
361
+ if (message.thinking) {
362
+ content.push({ type: 'text', text: message.thinking });
363
+ }
364
+
365
+ if (message.content) {
366
+ content.push({ type: 'text', text: message.content });
367
+ }
368
+
369
+ // Handle tool calls
370
+ if (message.tool_calls) {
371
+ for (const tc of message.tool_calls) {
372
+ content.push({
373
+ type: 'tool_use' as const,
374
+ id: createToolCallId(`tool_${Date.now()}_${Math.random()}`) as any,
375
+ name: tc.function.name,
376
+ input: tc.function.arguments,
377
+ });
378
+ }
379
+ }
380
+
381
+ return content;
382
+ }
383
+
384
+ private convertStopReason(reason?: string): ModelResponse['stopReason'] {
385
+ if (!reason) return 'end_turn';
386
+ switch (reason) {
387
+ case 'stop': return 'end_turn';
388
+ case 'length': return 'max_tokens';
389
+ case 'tool_calls': return 'tool_use';
390
+ default: return 'end_turn';
391
+ }
392
+ }
393
+
394
+ private async request(path: string, body: Record<string, unknown>): Promise<Response> {
395
+ return fetch(`${this.baseUrl}${path}`, {
396
+ method: 'POST',
397
+ headers: {
398
+ 'Content-Type': 'application/json',
399
+ 'Authorization': `Bearer ${this.apiKey}`,
400
+ },
401
+ body: JSON.stringify(body),
402
+ signal: AbortSignal.timeout(120000),
403
+ });
404
+ }
405
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OllamaManager.d.ts","sourceRoot":"","sources":["OllamaManager.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEzD,4CAA4C;AAC5C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE;QACP,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,cAAc,EAAE,MAAM,CAAC;QACvB,kBAAkB,EAAE,MAAM,CAAC;KAC5B,CAAC;CACH;AAED,8CAA8C;AAC9C,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,yCAAyC;AACzC,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE;QACP,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,cAAc,EAAE,MAAM,CAAC;QACvB,kBAAkB,EAAE,MAAM,CAAC;KAC5B,CAAC;IACF,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,wCAAwC;AACxC,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,CAAC,EAAE,MAAM,EAAE,SAAS,SAAQ;IAK/C,wCAAwC;IAClC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC;IAW9B,gCAAgC;IAC1B,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC;IAKhC,wCAAwC;IAClC,UAAU,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAK1C,uCAAuC;IACjC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAIvD;;;OAGG;IACG,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAsCjG,uCAAuC;IACjC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK9C;;;OAGG;IACH,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,WAAW;YA4BhC,OAAO;CAsBtB"}