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.
- package/.workflow/agents/reviewer.md +81 -0
- package/.workflow/agents/security.md +94 -0
- package/.workflow/agents/story-writer.md +58 -0
- package/.workflow/bridges/base-bridge.js +395 -0
- package/.workflow/bridges/claude-bridge.js +434 -0
- package/.workflow/bridges/index.js +130 -0
- package/.workflow/lib/assumption-detector.js +481 -0
- package/.workflow/lib/config-substitution.js +371 -0
- package/.workflow/lib/failure-categories.js +478 -0
- package/.workflow/state/app-map.md.template +15 -0
- package/.workflow/state/architecture.md.template +24 -0
- package/.workflow/state/component-index.json.template +5 -0
- package/.workflow/state/decisions.md.template +15 -0
- package/.workflow/state/feedback-patterns.md.template +9 -0
- package/.workflow/state/knowledge-sync.json.template +6 -0
- package/.workflow/state/progress.md.template +14 -0
- package/.workflow/state/ready.json.template +7 -0
- package/.workflow/state/request-log.md.template +14 -0
- package/.workflow/state/session-state.json.template +11 -0
- package/.workflow/state/stack.md.template +33 -0
- package/.workflow/state/testing.md.template +36 -0
- package/.workflow/templates/claude-md.hbs +257 -0
- package/.workflow/templates/correction-report.md +67 -0
- package/.workflow/templates/gemini-md.hbs +52 -0
- package/README.md +1802 -0
- package/bin/flow +205 -0
- package/lib/index.js +33 -0
- package/lib/installer.js +467 -0
- package/lib/release-channel.js +269 -0
- package/lib/skill-registry.js +526 -0
- package/lib/upgrader.js +401 -0
- package/lib/utils.js +305 -0
- package/package.json +64 -0
- package/scripts/flow +985 -0
- package/scripts/flow-adaptive-learning.js +1259 -0
- package/scripts/flow-aggregate.js +488 -0
- package/scripts/flow-archive +133 -0
- package/scripts/flow-auto-context.js +1015 -0
- package/scripts/flow-auto-learn.js +615 -0
- package/scripts/flow-bridge.js +223 -0
- package/scripts/flow-browser-suggest.js +316 -0
- package/scripts/flow-bug.js +247 -0
- package/scripts/flow-cascade.js +711 -0
- package/scripts/flow-changelog +85 -0
- package/scripts/flow-checkpoint.js +483 -0
- package/scripts/flow-cli.js +403 -0
- package/scripts/flow-code-intelligence.js +760 -0
- package/scripts/flow-complexity.js +502 -0
- package/scripts/flow-config-set.js +152 -0
- package/scripts/flow-constants.js +157 -0
- package/scripts/flow-context +152 -0
- package/scripts/flow-context-init.js +482 -0
- package/scripts/flow-context-monitor.js +384 -0
- package/scripts/flow-context-scoring.js +886 -0
- package/scripts/flow-correct.js +458 -0
- package/scripts/flow-damage-control.js +985 -0
- package/scripts/flow-deps +101 -0
- package/scripts/flow-diff.js +700 -0
- package/scripts/flow-done +151 -0
- package/scripts/flow-done.js +489 -0
- package/scripts/flow-durable-session.js +1541 -0
- package/scripts/flow-entropy-monitor.js +345 -0
- package/scripts/flow-export-profile +349 -0
- package/scripts/flow-export-scanner.js +1046 -0
- package/scripts/flow-figma-confirm.js +400 -0
- package/scripts/flow-figma-extract.js +496 -0
- package/scripts/flow-figma-generate.js +683 -0
- package/scripts/flow-figma-index.js +909 -0
- package/scripts/flow-figma-match.js +617 -0
- package/scripts/flow-figma-mcp-server.js +518 -0
- package/scripts/flow-figma-pipeline.js +414 -0
- package/scripts/flow-file-ops.js +301 -0
- package/scripts/flow-gate-confidence.js +825 -0
- package/scripts/flow-guided-edit.js +659 -0
- package/scripts/flow-health +185 -0
- package/scripts/flow-health.js +413 -0
- package/scripts/flow-hooks.js +556 -0
- package/scripts/flow-http-client.js +249 -0
- package/scripts/flow-hybrid-detect.js +167 -0
- package/scripts/flow-hybrid-interactive.js +591 -0
- package/scripts/flow-hybrid-test.js +152 -0
- package/scripts/flow-import-profile +439 -0
- package/scripts/flow-init +253 -0
- package/scripts/flow-instruction-richness.js +827 -0
- package/scripts/flow-jira-integration.js +579 -0
- package/scripts/flow-knowledge-router.js +522 -0
- package/scripts/flow-knowledge-sync.js +589 -0
- package/scripts/flow-linear-integration.js +631 -0
- package/scripts/flow-links.js +774 -0
- package/scripts/flow-log-manager.js +559 -0
- package/scripts/flow-loop-enforcer.js +1246 -0
- package/scripts/flow-loop-retry-learning.js +630 -0
- package/scripts/flow-lsp.js +923 -0
- package/scripts/flow-map-index +348 -0
- package/scripts/flow-map-sync +201 -0
- package/scripts/flow-memory-blocks.js +668 -0
- package/scripts/flow-memory-compactor.js +350 -0
- package/scripts/flow-memory-db.js +1110 -0
- package/scripts/flow-memory-sync.js +484 -0
- package/scripts/flow-metrics.js +353 -0
- package/scripts/flow-migrate-ids.js +370 -0
- package/scripts/flow-model-adapter.js +802 -0
- package/scripts/flow-model-router.js +884 -0
- package/scripts/flow-models.js +1231 -0
- package/scripts/flow-morning.js +517 -0
- package/scripts/flow-multi-approach.js +660 -0
- package/scripts/flow-new-feature +86 -0
- package/scripts/flow-onboard +1042 -0
- package/scripts/flow-orchestrate-llm.js +459 -0
- package/scripts/flow-orchestrate.js +3592 -0
- package/scripts/flow-output.js +123 -0
- package/scripts/flow-parallel-detector.js +399 -0
- package/scripts/flow-parallel-dispatch.js +987 -0
- package/scripts/flow-parallel.js +428 -0
- package/scripts/flow-pattern-enforcer.js +600 -0
- package/scripts/flow-prd-manager.js +282 -0
- package/scripts/flow-progress.js +323 -0
- package/scripts/flow-project-analyzer.js +975 -0
- package/scripts/flow-prompt-composer.js +487 -0
- package/scripts/flow-providers.js +1381 -0
- package/scripts/flow-queue.js +308 -0
- package/scripts/flow-ready +82 -0
- package/scripts/flow-ready.js +189 -0
- package/scripts/flow-regression.js +396 -0
- package/scripts/flow-response-parser.js +450 -0
- package/scripts/flow-resume.js +284 -0
- package/scripts/flow-rules-sync.js +439 -0
- package/scripts/flow-run-trace.js +718 -0
- package/scripts/flow-safety.js +587 -0
- package/scripts/flow-search +104 -0
- package/scripts/flow-security.js +481 -0
- package/scripts/flow-session-end +106 -0
- package/scripts/flow-session-end.js +437 -0
- package/scripts/flow-session-state.js +671 -0
- package/scripts/flow-setup-hooks +216 -0
- package/scripts/flow-setup-hooks.js +377 -0
- package/scripts/flow-skill-create.js +329 -0
- package/scripts/flow-skill-creator.js +572 -0
- package/scripts/flow-skill-generator.js +1046 -0
- package/scripts/flow-skill-learn.js +880 -0
- package/scripts/flow-skill-matcher.js +578 -0
- package/scripts/flow-spec-generator.js +820 -0
- package/scripts/flow-stack-wizard.js +895 -0
- package/scripts/flow-standup +162 -0
- package/scripts/flow-start +74 -0
- package/scripts/flow-start.js +235 -0
- package/scripts/flow-status +110 -0
- package/scripts/flow-status.js +301 -0
- package/scripts/flow-step-browser.js +83 -0
- package/scripts/flow-step-changelog.js +217 -0
- package/scripts/flow-step-comments.js +306 -0
- package/scripts/flow-step-complexity.js +234 -0
- package/scripts/flow-step-coverage.js +218 -0
- package/scripts/flow-step-knowledge.js +193 -0
- package/scripts/flow-step-pr-tests.js +364 -0
- package/scripts/flow-step-regression.js +89 -0
- package/scripts/flow-step-review.js +516 -0
- package/scripts/flow-step-security.js +162 -0
- package/scripts/flow-step-silent-failures.js +290 -0
- package/scripts/flow-step-simplifier.js +346 -0
- package/scripts/flow-story +105 -0
- package/scripts/flow-story.js +500 -0
- package/scripts/flow-suspend.js +252 -0
- package/scripts/flow-sync-daemon.js +654 -0
- package/scripts/flow-task-analyzer.js +606 -0
- package/scripts/flow-team-dashboard.js +748 -0
- package/scripts/flow-team-sync.js +752 -0
- package/scripts/flow-team.js +977 -0
- package/scripts/flow-tech-options.js +528 -0
- package/scripts/flow-templates.js +812 -0
- package/scripts/flow-tiered-learning.js +728 -0
- package/scripts/flow-trace +204 -0
- package/scripts/flow-transcript-chunking.js +1106 -0
- package/scripts/flow-transcript-digest.js +7918 -0
- package/scripts/flow-transcript-language.js +465 -0
- package/scripts/flow-transcript-parsing.js +1085 -0
- package/scripts/flow-transcript-stories.js +2194 -0
- package/scripts/flow-update-map +224 -0
- package/scripts/flow-utils.js +2242 -0
- package/scripts/flow-verification.js +644 -0
- package/scripts/flow-verify.js +1177 -0
- package/scripts/flow-voice-input.js +638 -0
- package/scripts/flow-watch +168 -0
- package/scripts/flow-workflow-steps.js +521 -0
- package/scripts/flow-workflow.js +1029 -0
- package/scripts/flow-worktree.js +489 -0
- package/scripts/hooks/adapters/base-adapter.js +102 -0
- package/scripts/hooks/adapters/claude-code.js +359 -0
- package/scripts/hooks/adapters/index.js +79 -0
- package/scripts/hooks/core/component-check.js +341 -0
- package/scripts/hooks/core/index.js +35 -0
- package/scripts/hooks/core/loop-check.js +241 -0
- package/scripts/hooks/core/session-context.js +294 -0
- package/scripts/hooks/core/task-gate.js +177 -0
- package/scripts/hooks/core/validation.js +230 -0
- package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
- package/scripts/hooks/entry/claude-code/session-end.js +87 -0
- package/scripts/hooks/entry/claude-code/session-start.js +46 -0
- package/scripts/hooks/entry/claude-code/stop.js +43 -0
- package/scripts/postinstall.js +139 -0
- package/templates/browser-test-flow.json +56 -0
- package/templates/bug-report.md +43 -0
- package/templates/component-detail.md +42 -0
- package/templates/component.stories.tsx +49 -0
- package/templates/context/constraints.md +83 -0
- package/templates/context/conventions.md +177 -0
- package/templates/context/stack.md +60 -0
- package/templates/correction-report.md +90 -0
- package/templates/feature-proposal.md +35 -0
- package/templates/hybrid/_base.md +254 -0
- package/templates/hybrid/_patterns.md +45 -0
- package/templates/hybrid/create-component.md +127 -0
- package/templates/hybrid/create-file.md +56 -0
- package/templates/hybrid/create-hook.md +145 -0
- package/templates/hybrid/create-service.md +70 -0
- package/templates/hybrid/fix-bug.md +33 -0
- package/templates/hybrid/modify-file.md +55 -0
- package/templates/story.md +68 -0
- package/templates/task.json +56 -0
- 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
|
+
};
|