wogiflow 1.0.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.
Files changed (221) hide show
  1. package/.workflow/agents/reviewer.md +81 -0
  2. package/.workflow/agents/security.md +94 -0
  3. package/.workflow/agents/story-writer.md +58 -0
  4. package/.workflow/bridges/base-bridge.js +395 -0
  5. package/.workflow/bridges/claude-bridge.js +434 -0
  6. package/.workflow/bridges/index.js +130 -0
  7. package/.workflow/lib/assumption-detector.js +481 -0
  8. package/.workflow/lib/config-substitution.js +371 -0
  9. package/.workflow/lib/failure-categories.js +478 -0
  10. package/.workflow/state/app-map.md.template +15 -0
  11. package/.workflow/state/architecture.md.template +24 -0
  12. package/.workflow/state/component-index.json.template +5 -0
  13. package/.workflow/state/decisions.md.template +15 -0
  14. package/.workflow/state/feedback-patterns.md.template +9 -0
  15. package/.workflow/state/knowledge-sync.json.template +6 -0
  16. package/.workflow/state/progress.md.template +14 -0
  17. package/.workflow/state/ready.json.template +7 -0
  18. package/.workflow/state/request-log.md.template +14 -0
  19. package/.workflow/state/session-state.json.template +11 -0
  20. package/.workflow/state/stack.md.template +33 -0
  21. package/.workflow/state/testing.md.template +36 -0
  22. package/.workflow/templates/claude-md.hbs +257 -0
  23. package/.workflow/templates/correction-report.md +67 -0
  24. package/.workflow/templates/gemini-md.hbs +52 -0
  25. package/README.md +1802 -0
  26. package/bin/flow +205 -0
  27. package/lib/index.js +33 -0
  28. package/lib/installer.js +467 -0
  29. package/lib/release-channel.js +269 -0
  30. package/lib/skill-registry.js +526 -0
  31. package/lib/upgrader.js +401 -0
  32. package/lib/utils.js +305 -0
  33. package/package.json +64 -0
  34. package/scripts/flow +985 -0
  35. package/scripts/flow-adaptive-learning.js +1259 -0
  36. package/scripts/flow-aggregate.js +488 -0
  37. package/scripts/flow-archive +133 -0
  38. package/scripts/flow-auto-context.js +1015 -0
  39. package/scripts/flow-auto-learn.js +615 -0
  40. package/scripts/flow-bridge.js +223 -0
  41. package/scripts/flow-browser-suggest.js +316 -0
  42. package/scripts/flow-bug.js +247 -0
  43. package/scripts/flow-cascade.js +711 -0
  44. package/scripts/flow-changelog +85 -0
  45. package/scripts/flow-checkpoint.js +483 -0
  46. package/scripts/flow-cli.js +403 -0
  47. package/scripts/flow-code-intelligence.js +760 -0
  48. package/scripts/flow-complexity.js +502 -0
  49. package/scripts/flow-config-set.js +152 -0
  50. package/scripts/flow-constants.js +157 -0
  51. package/scripts/flow-context +152 -0
  52. package/scripts/flow-context-init.js +482 -0
  53. package/scripts/flow-context-monitor.js +384 -0
  54. package/scripts/flow-context-scoring.js +886 -0
  55. package/scripts/flow-correct.js +458 -0
  56. package/scripts/flow-damage-control.js +985 -0
  57. package/scripts/flow-deps +101 -0
  58. package/scripts/flow-diff.js +700 -0
  59. package/scripts/flow-done +151 -0
  60. package/scripts/flow-done.js +489 -0
  61. package/scripts/flow-durable-session.js +1541 -0
  62. package/scripts/flow-entropy-monitor.js +345 -0
  63. package/scripts/flow-export-profile +349 -0
  64. package/scripts/flow-export-scanner.js +1046 -0
  65. package/scripts/flow-figma-confirm.js +400 -0
  66. package/scripts/flow-figma-extract.js +496 -0
  67. package/scripts/flow-figma-generate.js +683 -0
  68. package/scripts/flow-figma-index.js +909 -0
  69. package/scripts/flow-figma-match.js +617 -0
  70. package/scripts/flow-figma-mcp-server.js +518 -0
  71. package/scripts/flow-figma-pipeline.js +414 -0
  72. package/scripts/flow-file-ops.js +301 -0
  73. package/scripts/flow-gate-confidence.js +825 -0
  74. package/scripts/flow-guided-edit.js +659 -0
  75. package/scripts/flow-health +185 -0
  76. package/scripts/flow-health.js +413 -0
  77. package/scripts/flow-hooks.js +556 -0
  78. package/scripts/flow-http-client.js +249 -0
  79. package/scripts/flow-hybrid-detect.js +167 -0
  80. package/scripts/flow-hybrid-interactive.js +591 -0
  81. package/scripts/flow-hybrid-test.js +152 -0
  82. package/scripts/flow-import-profile +439 -0
  83. package/scripts/flow-init +253 -0
  84. package/scripts/flow-instruction-richness.js +827 -0
  85. package/scripts/flow-jira-integration.js +579 -0
  86. package/scripts/flow-knowledge-router.js +522 -0
  87. package/scripts/flow-knowledge-sync.js +589 -0
  88. package/scripts/flow-linear-integration.js +631 -0
  89. package/scripts/flow-links.js +774 -0
  90. package/scripts/flow-log-manager.js +559 -0
  91. package/scripts/flow-loop-enforcer.js +1246 -0
  92. package/scripts/flow-loop-retry-learning.js +630 -0
  93. package/scripts/flow-lsp.js +923 -0
  94. package/scripts/flow-map-index +348 -0
  95. package/scripts/flow-map-sync +201 -0
  96. package/scripts/flow-memory-blocks.js +668 -0
  97. package/scripts/flow-memory-compactor.js +350 -0
  98. package/scripts/flow-memory-db.js +1110 -0
  99. package/scripts/flow-memory-sync.js +484 -0
  100. package/scripts/flow-metrics.js +353 -0
  101. package/scripts/flow-migrate-ids.js +370 -0
  102. package/scripts/flow-model-adapter.js +802 -0
  103. package/scripts/flow-model-router.js +884 -0
  104. package/scripts/flow-models.js +1231 -0
  105. package/scripts/flow-morning.js +517 -0
  106. package/scripts/flow-multi-approach.js +660 -0
  107. package/scripts/flow-new-feature +86 -0
  108. package/scripts/flow-onboard +1042 -0
  109. package/scripts/flow-orchestrate-llm.js +459 -0
  110. package/scripts/flow-orchestrate.js +3592 -0
  111. package/scripts/flow-output.js +123 -0
  112. package/scripts/flow-parallel-detector.js +399 -0
  113. package/scripts/flow-parallel-dispatch.js +987 -0
  114. package/scripts/flow-parallel.js +428 -0
  115. package/scripts/flow-pattern-enforcer.js +600 -0
  116. package/scripts/flow-prd-manager.js +282 -0
  117. package/scripts/flow-progress.js +323 -0
  118. package/scripts/flow-project-analyzer.js +975 -0
  119. package/scripts/flow-prompt-composer.js +487 -0
  120. package/scripts/flow-providers.js +1381 -0
  121. package/scripts/flow-queue.js +308 -0
  122. package/scripts/flow-ready +82 -0
  123. package/scripts/flow-ready.js +189 -0
  124. package/scripts/flow-regression.js +396 -0
  125. package/scripts/flow-response-parser.js +450 -0
  126. package/scripts/flow-resume.js +284 -0
  127. package/scripts/flow-rules-sync.js +439 -0
  128. package/scripts/flow-run-trace.js +718 -0
  129. package/scripts/flow-safety.js +587 -0
  130. package/scripts/flow-search +104 -0
  131. package/scripts/flow-security.js +481 -0
  132. package/scripts/flow-session-end +106 -0
  133. package/scripts/flow-session-end.js +437 -0
  134. package/scripts/flow-session-state.js +671 -0
  135. package/scripts/flow-setup-hooks +216 -0
  136. package/scripts/flow-setup-hooks.js +377 -0
  137. package/scripts/flow-skill-create.js +329 -0
  138. package/scripts/flow-skill-creator.js +572 -0
  139. package/scripts/flow-skill-generator.js +1046 -0
  140. package/scripts/flow-skill-learn.js +880 -0
  141. package/scripts/flow-skill-matcher.js +578 -0
  142. package/scripts/flow-spec-generator.js +820 -0
  143. package/scripts/flow-stack-wizard.js +895 -0
  144. package/scripts/flow-standup +162 -0
  145. package/scripts/flow-start +74 -0
  146. package/scripts/flow-start.js +235 -0
  147. package/scripts/flow-status +110 -0
  148. package/scripts/flow-status.js +301 -0
  149. package/scripts/flow-step-browser.js +83 -0
  150. package/scripts/flow-step-changelog.js +217 -0
  151. package/scripts/flow-step-comments.js +306 -0
  152. package/scripts/flow-step-complexity.js +234 -0
  153. package/scripts/flow-step-coverage.js +218 -0
  154. package/scripts/flow-step-knowledge.js +193 -0
  155. package/scripts/flow-step-pr-tests.js +364 -0
  156. package/scripts/flow-step-regression.js +89 -0
  157. package/scripts/flow-step-review.js +516 -0
  158. package/scripts/flow-step-security.js +162 -0
  159. package/scripts/flow-step-silent-failures.js +290 -0
  160. package/scripts/flow-step-simplifier.js +346 -0
  161. package/scripts/flow-story +105 -0
  162. package/scripts/flow-story.js +500 -0
  163. package/scripts/flow-suspend.js +252 -0
  164. package/scripts/flow-sync-daemon.js +654 -0
  165. package/scripts/flow-task-analyzer.js +606 -0
  166. package/scripts/flow-team-dashboard.js +748 -0
  167. package/scripts/flow-team-sync.js +752 -0
  168. package/scripts/flow-team.js +977 -0
  169. package/scripts/flow-tech-options.js +528 -0
  170. package/scripts/flow-templates.js +812 -0
  171. package/scripts/flow-tiered-learning.js +728 -0
  172. package/scripts/flow-trace +204 -0
  173. package/scripts/flow-transcript-chunking.js +1106 -0
  174. package/scripts/flow-transcript-digest.js +7918 -0
  175. package/scripts/flow-transcript-language.js +465 -0
  176. package/scripts/flow-transcript-parsing.js +1085 -0
  177. package/scripts/flow-transcript-stories.js +2194 -0
  178. package/scripts/flow-update-map +224 -0
  179. package/scripts/flow-utils.js +2242 -0
  180. package/scripts/flow-verification.js +644 -0
  181. package/scripts/flow-verify.js +1177 -0
  182. package/scripts/flow-voice-input.js +638 -0
  183. package/scripts/flow-watch +168 -0
  184. package/scripts/flow-workflow-steps.js +521 -0
  185. package/scripts/flow-workflow.js +1029 -0
  186. package/scripts/flow-worktree.js +489 -0
  187. package/scripts/hooks/adapters/base-adapter.js +102 -0
  188. package/scripts/hooks/adapters/claude-code.js +359 -0
  189. package/scripts/hooks/adapters/index.js +79 -0
  190. package/scripts/hooks/core/component-check.js +341 -0
  191. package/scripts/hooks/core/index.js +35 -0
  192. package/scripts/hooks/core/loop-check.js +241 -0
  193. package/scripts/hooks/core/session-context.js +294 -0
  194. package/scripts/hooks/core/task-gate.js +177 -0
  195. package/scripts/hooks/core/validation.js +230 -0
  196. package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
  197. package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
  198. package/scripts/hooks/entry/claude-code/session-end.js +87 -0
  199. package/scripts/hooks/entry/claude-code/session-start.js +46 -0
  200. package/scripts/hooks/entry/claude-code/stop.js +43 -0
  201. package/scripts/postinstall.js +139 -0
  202. package/templates/browser-test-flow.json +56 -0
  203. package/templates/bug-report.md +43 -0
  204. package/templates/component-detail.md +42 -0
  205. package/templates/component.stories.tsx +49 -0
  206. package/templates/context/constraints.md +83 -0
  207. package/templates/context/conventions.md +177 -0
  208. package/templates/context/stack.md +60 -0
  209. package/templates/correction-report.md +90 -0
  210. package/templates/feature-proposal.md +35 -0
  211. package/templates/hybrid/_base.md +254 -0
  212. package/templates/hybrid/_patterns.md +45 -0
  213. package/templates/hybrid/create-component.md +127 -0
  214. package/templates/hybrid/create-file.md +56 -0
  215. package/templates/hybrid/create-hook.md +145 -0
  216. package/templates/hybrid/create-service.md +70 -0
  217. package/templates/hybrid/fix-bug.md +33 -0
  218. package/templates/hybrid/modify-file.md +55 -0
  219. package/templates/story.md +68 -0
  220. package/templates/task.json +56 -0
  221. package/templates/trace.md +69 -0
@@ -0,0 +1,459 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Orchestrator LLM Clients
5
+ *
6
+ * LLM client implementations for the hybrid mode orchestrator.
7
+ * Supports both local LLMs (Ollama, LM Studio) and cloud providers.
8
+ *
9
+ * Extracted from flow-orchestrate.js for modularity.
10
+ */
11
+
12
+ const http = require('http');
13
+ const https = require('https');
14
+
15
+ const {
16
+ createExecutorFromConfig,
17
+ MODEL_CAPABILITIES
18
+ } = require('./flow-providers');
19
+
20
+ // ============================================================
21
+ // Logging Helper
22
+ // ============================================================
23
+
24
+ const colors = {
25
+ reset: '\x1b[0m',
26
+ cyan: '\x1b[36m',
27
+ dim: '\x1b[2m'
28
+ };
29
+
30
+ function log(color, ...args) {
31
+ console.log(colors[color] + args.join(' ') + colors.reset);
32
+ }
33
+
34
+ // ============================================================
35
+ // Model Defaults
36
+ // ============================================================
37
+
38
+ // Model-specific context window defaults for popular models
39
+ const MODEL_DEFAULTS = {
40
+ 'qwen/qwen3-coder-30b': { contextWindow: 32768 },
41
+ 'qwen/qwen3-coder': { contextWindow: 32768 },
42
+ 'qwen3-coder': { contextWindow: 32768 },
43
+ 'nvidia/nemotron-3-nano': { contextWindow: 8192 },
44
+ 'nemotron': { contextWindow: 8192 },
45
+ 'meta/llama-3.3-70b': { contextWindow: 131072 },
46
+ 'llama-3.3': { contextWindow: 131072 },
47
+ 'llama-3.1': { contextWindow: 131072 },
48
+ 'deepseek-coder': { contextWindow: 16384 },
49
+ 'codellama': { contextWindow: 16384 },
50
+ 'mistral': { contextWindow: 32768 },
51
+ 'mixtral': { contextWindow: 32768 },
52
+ };
53
+
54
+ /**
55
+ * Gets default settings for a model by name
56
+ * @param {string} modelName - The model name from config
57
+ * @returns {Object} - Default settings including contextWindow
58
+ */
59
+ function getModelDefaults(modelName) {
60
+ if (!modelName) return { contextWindow: 4096 };
61
+
62
+ const lowerName = modelName.toLowerCase();
63
+
64
+ // Try exact match first
65
+ if (MODEL_DEFAULTS[modelName]) {
66
+ return MODEL_DEFAULTS[modelName];
67
+ }
68
+
69
+ // Try partial match
70
+ for (const [key, defaults] of Object.entries(MODEL_DEFAULTS)) {
71
+ if (lowerName.includes(key.toLowerCase())) {
72
+ return defaults;
73
+ }
74
+ }
75
+
76
+ return { contextWindow: 4096 }; // Conservative fallback
77
+ }
78
+
79
+ // ============================================================
80
+ // Local LLM Client
81
+ // ============================================================
82
+
83
+ class LocalLLM {
84
+ constructor(config) {
85
+ this.config = config;
86
+ this.contextWindow = config.contextWindow || null; // Will be auto-detected or use defaults
87
+ this.modelInfoFetched = false;
88
+ }
89
+
90
+ /**
91
+ * Fetches model info including context window from the provider.
92
+ * Called once on first generate() call.
93
+ *
94
+ * Priority order:
95
+ * 1. Config override (hybrid.settings.contextWindow)
96
+ * 2. Auto-detection from provider API
97
+ * 3. Model-specific defaults
98
+ * 4. Conservative fallback (4096)
99
+ */
100
+ async fetchModelInfo() {
101
+ if (this.modelInfoFetched) return;
102
+ this.modelInfoFetched = true;
103
+
104
+ // Priority 1: Config override
105
+ if (this.config.contextWindow) {
106
+ this.contextWindow = this.config.contextWindow;
107
+ log('dim', ` 📊 Using configured context window: ${this.contextWindow.toLocaleString()} tokens`);
108
+ return;
109
+ }
110
+
111
+ // Get model defaults for fallback
112
+ const modelDefaults = getModelDefaults(this.config.model);
113
+
114
+ try {
115
+ // Priority 2: Auto-detection from provider
116
+ if (this.config.provider === 'ollama') {
117
+ const info = await this.ollamaShowModel();
118
+ if (info.contextLength) {
119
+ this.contextWindow = info.contextLength;
120
+ log('dim', ` 📊 Model context window (detected): ${this.contextWindow.toLocaleString()} tokens`);
121
+ return;
122
+ }
123
+ } else {
124
+ // LM Studio / OpenAI-compatible
125
+ const info = await this.lmStudioGetModelInfo();
126
+ if (info.contextLength) {
127
+ this.contextWindow = info.contextLength;
128
+ log('dim', ` 📊 Model context window (detected): ${this.contextWindow.toLocaleString()} tokens`);
129
+ return;
130
+ }
131
+ }
132
+
133
+ // Priority 3: Model-specific defaults
134
+ this.contextWindow = modelDefaults.contextWindow;
135
+ log('dim', ` 📊 Using model default context window: ${this.contextWindow.toLocaleString()} tokens`);
136
+ } catch (err) {
137
+ log('dim', ` ⚠️ Could not fetch model info: ${err.message}`);
138
+ // Priority 3/4: Model-specific defaults or conservative fallback
139
+ this.contextWindow = modelDefaults.contextWindow;
140
+ log('dim', ` 📊 Using model default context window: ${this.contextWindow.toLocaleString()} tokens`);
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Ollama: GET /api/show to get model parameters
146
+ */
147
+ async ollamaShowModel() {
148
+ return new Promise((resolve, reject) => {
149
+ const url = new URL('/api/show', this.config.endpoint);
150
+ const postData = JSON.stringify({ name: this.config.model });
151
+
152
+ const req = http.request(url, {
153
+ method: 'POST',
154
+ headers: {
155
+ 'Content-Type': 'application/json',
156
+ 'Content-Length': Buffer.byteLength(postData)
157
+ },
158
+ timeout: 10000
159
+ }, (res) => {
160
+ let data = '';
161
+ res.on('data', chunk => data += chunk);
162
+ res.on('end', () => {
163
+ try {
164
+ const parsed = JSON.parse(data);
165
+ // Ollama returns model_info with context_length or parameters.num_ctx
166
+ const contextLength =
167
+ parsed.model_info?.['context_length'] ||
168
+ parsed.model_info?.context_length ||
169
+ parsed.parameters?.num_ctx ||
170
+ parsed.details?.parameter_size && 4096; // fallback
171
+ resolve({ contextLength: contextLength || 4096 });
172
+ } catch (err) {
173
+ reject(new Error('Invalid response from Ollama /api/show'));
174
+ }
175
+ });
176
+ });
177
+
178
+ req.on('error', reject);
179
+ req.on('timeout', () => {
180
+ req.destroy();
181
+ reject(new Error('Timeout fetching model info'));
182
+ });
183
+
184
+ req.write(postData);
185
+ req.end();
186
+ });
187
+ }
188
+
189
+ /**
190
+ * LM Studio: GET /v1/models to get model info
191
+ */
192
+ async lmStudioGetModelInfo() {
193
+ return new Promise((resolve, reject) => {
194
+ const url = new URL('/v1/models', this.config.endpoint);
195
+ const client = url.protocol === 'https:' ? https : http;
196
+
197
+ const req = client.request(url, {
198
+ method: 'GET',
199
+ headers: { 'Content-Type': 'application/json' },
200
+ timeout: 10000
201
+ }, (res) => {
202
+ let data = '';
203
+ res.on('data', chunk => data += chunk);
204
+ res.on('end', () => {
205
+ try {
206
+ const parsed = JSON.parse(data);
207
+ // Find our model in the list
208
+ const model = parsed.data?.find(m =>
209
+ m.id === this.config.model ||
210
+ m.id?.includes(this.config.model)
211
+ );
212
+ // LM Studio may include context_length in model object
213
+ const contextLength = model?.context_length || model?.max_tokens || 4096;
214
+ resolve({ contextLength });
215
+ } catch (err) {
216
+ reject(new Error('Invalid response from /v1/models'));
217
+ }
218
+ });
219
+ });
220
+
221
+ req.on('error', reject);
222
+ req.on('timeout', () => {
223
+ req.destroy();
224
+ reject(new Error('Timeout fetching model info'));
225
+ });
226
+
227
+ req.end();
228
+ });
229
+ }
230
+
231
+ async generate(prompt) {
232
+ // Fetch model info on first call
233
+ await this.fetchModelInfo();
234
+
235
+ if (this.config.provider === 'ollama') {
236
+ return this.ollamaGenerate(prompt);
237
+ } else {
238
+ return this.openaiCompatibleGenerate(prompt);
239
+ }
240
+ }
241
+
242
+ async ollamaGenerate(prompt) {
243
+ return new Promise((resolve, reject) => {
244
+ const url = new URL('/api/generate', this.config.endpoint);
245
+ const postData = JSON.stringify({
246
+ model: this.config.model,
247
+ prompt: prompt,
248
+ stream: false,
249
+ options: {
250
+ temperature: this.config.temperature,
251
+ num_predict: this.config.maxTokens
252
+ }
253
+ });
254
+
255
+ const req = http.request(url, {
256
+ method: 'POST',
257
+ headers: {
258
+ 'Content-Type': 'application/json',
259
+ 'Content-Length': Buffer.byteLength(postData)
260
+ },
261
+ timeout: this.config.timeout
262
+ }, (res) => {
263
+ let data = '';
264
+ res.on('data', chunk => data += chunk);
265
+ res.on('end', () => {
266
+ try {
267
+ const parsed = JSON.parse(data);
268
+ resolve(parsed.response || '');
269
+ } catch (err) {
270
+ reject(new Error('Invalid response from Ollama'));
271
+ }
272
+ });
273
+ });
274
+
275
+ req.on('error', reject);
276
+ req.on('timeout', () => {
277
+ req.destroy();
278
+ reject(new Error('Request timeout'));
279
+ });
280
+
281
+ req.write(postData);
282
+ req.end();
283
+ });
284
+ }
285
+
286
+ async openaiCompatibleGenerate(prompt) {
287
+ return new Promise((resolve, reject) => {
288
+ const url = new URL('/v1/chat/completions', this.config.endpoint);
289
+ const postData = JSON.stringify({
290
+ model: this.config.model,
291
+ messages: [{ role: 'user', content: prompt }],
292
+ temperature: this.config.temperature,
293
+ max_tokens: this.config.maxTokens
294
+ });
295
+
296
+ const client = url.protocol === 'https:' ? https : http;
297
+
298
+ const req = client.request(url, {
299
+ method: 'POST',
300
+ headers: {
301
+ 'Content-Type': 'application/json',
302
+ 'Content-Length': Buffer.byteLength(postData)
303
+ },
304
+ timeout: this.config.timeout
305
+ }, (res) => {
306
+ let data = '';
307
+ res.on('data', chunk => data += chunk);
308
+ res.on('end', () => {
309
+ try {
310
+ const parsed = JSON.parse(data);
311
+ resolve(parsed.choices?.[0]?.message?.content || '');
312
+ } catch (err) {
313
+ reject(new Error('Invalid response from LLM'));
314
+ }
315
+ });
316
+ });
317
+
318
+ req.on('error', reject);
319
+ req.on('timeout', () => {
320
+ req.destroy();
321
+ reject(new Error('Request timeout'));
322
+ });
323
+
324
+ req.write(postData);
325
+ req.end();
326
+ });
327
+ }
328
+ }
329
+
330
+ // ============================================================
331
+ // Cloud Executor Client
332
+ // ============================================================
333
+
334
+ /**
335
+ * CloudExecutor wraps cloud providers from flow-providers.js
336
+ * and exposes the same interface as LocalLLM (generate, contextWindow)
337
+ * for seamless integration with the Orchestrator.
338
+ */
339
+ class CloudExecutor {
340
+ constructor(config) {
341
+ this.config = config;
342
+ this.provider = createExecutorFromConfig({ executor: config });
343
+ this.contextWindow = null;
344
+ this.modelInfoFetched = false;
345
+
346
+ if (!this.provider) {
347
+ throw new Error(`Failed to create cloud executor for provider: ${config.provider}`);
348
+ }
349
+
350
+ log('cyan', ` ☁️ Cloud executor: ${config.provider} / ${config.model}`);
351
+ }
352
+
353
+ /**
354
+ * Fetches model info including context window from MODEL_CAPABILITIES.
355
+ * Called once on first generate() call.
356
+ */
357
+ async fetchModelInfo() {
358
+ if (this.modelInfoFetched) return;
359
+ this.modelInfoFetched = true;
360
+
361
+ // Priority 1: Config override
362
+ if (this.config.contextWindow) {
363
+ this.contextWindow = this.config.contextWindow;
364
+ log('dim', ` 📊 Using configured context window: ${this.contextWindow.toLocaleString()} tokens`);
365
+ return;
366
+ }
367
+
368
+ // Priority 2: Look up in MODEL_CAPABILITIES
369
+ const modelName = this.config.model || '';
370
+ const lowerModel = modelName.toLowerCase();
371
+
372
+ // Try exact match first
373
+ if (MODEL_CAPABILITIES[modelName]) {
374
+ this.contextWindow = MODEL_CAPABILITIES[modelName].contextWindow;
375
+ log('dim', ` 📊 Model context window: ${this.contextWindow.toLocaleString()} tokens`);
376
+ return;
377
+ }
378
+
379
+ // Try partial match
380
+ for (const [key, caps] of Object.entries(MODEL_CAPABILITIES)) {
381
+ if (lowerModel.includes(key.toLowerCase()) || key.toLowerCase().includes(lowerModel)) {
382
+ this.contextWindow = caps.contextWindow;
383
+ log('dim', ` 📊 Model context window (matched ${key}): ${this.contextWindow.toLocaleString()} tokens`);
384
+ return;
385
+ }
386
+ }
387
+
388
+ // Priority 3: Provider-specific defaults
389
+ const providerDefaults = {
390
+ 'openai': 128000, // GPT-4o-mini
391
+ 'anthropic': 200000, // Claude Haiku
392
+ 'google': 1000000 // Gemini Flash
393
+ };
394
+
395
+ this.contextWindow = providerDefaults[this.config.provider] || 128000;
396
+ log('dim', ` 📊 Using provider default context window: ${this.contextWindow.toLocaleString()} tokens`);
397
+ }
398
+
399
+ /**
400
+ * Generate a response from the cloud LLM.
401
+ * Matches the LocalLLM interface.
402
+ */
403
+ async generate(prompt) {
404
+ // Fetch model info on first call
405
+ await this.fetchModelInfo();
406
+
407
+ try {
408
+ const response = await this.provider.complete(prompt, {
409
+ model: this.config.model,
410
+ temperature: this.config.temperature,
411
+ maxTokens: this.config.maxTokens
412
+ });
413
+ return response;
414
+ } catch (error) {
415
+ // Enhance error message with cloud-specific context
416
+ const enhancedError = new Error(
417
+ `Cloud executor error (${this.config.provider}/${this.config.model}): ${error.message}`
418
+ );
419
+ enhancedError.originalError = error;
420
+ throw enhancedError;
421
+ }
422
+ }
423
+ }
424
+
425
+ // ============================================================
426
+ // Factory Function
427
+ // ============================================================
428
+
429
+ /**
430
+ * Factory function to create the appropriate executor based on config.
431
+ * Returns either LocalLLM or CloudExecutor.
432
+ */
433
+ function createExecutor(config) {
434
+ const executorType = config.executorType || 'local';
435
+
436
+ if (executorType === 'cloud') {
437
+ // Validate cloud config
438
+ const cloudProviders = ['openai', 'anthropic', 'google'];
439
+ if (!cloudProviders.includes(config.provider)) {
440
+ throw new Error(
441
+ `Invalid cloud provider: ${config.provider}. ` +
442
+ `Supported: ${cloudProviders.join(', ')}`
443
+ );
444
+ }
445
+
446
+ return new CloudExecutor(config);
447
+ }
448
+
449
+ // Default to local LLM (ollama, lm-studio)
450
+ return new LocalLLM(config);
451
+ }
452
+
453
+ module.exports = {
454
+ LocalLLM,
455
+ CloudExecutor,
456
+ createExecutor,
457
+ getModelDefaults,
458
+ MODEL_DEFAULTS
459
+ };