tlc-claude-code 1.4.1 → 1.4.4
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/dashboard/dist/App.js +229 -35
- package/dashboard/dist/components/AgentRegistryPane.d.ts +35 -0
- package/dashboard/dist/components/AgentRegistryPane.js +89 -0
- package/dashboard/dist/components/AgentRegistryPane.test.d.ts +1 -0
- package/dashboard/dist/components/AgentRegistryPane.test.js +200 -0
- package/dashboard/dist/components/RouterPane.d.ts +5 -0
- package/dashboard/dist/components/RouterPane.js +65 -0
- package/dashboard/dist/components/RouterPane.test.d.ts +1 -0
- package/dashboard/dist/components/RouterPane.test.js +176 -0
- package/dashboard/dist/components/accessibility.test.d.ts +1 -0
- package/dashboard/dist/components/accessibility.test.js +116 -0
- package/dashboard/dist/components/layout/MobileNav.d.ts +16 -0
- package/dashboard/dist/components/layout/MobileNav.js +31 -0
- package/dashboard/dist/components/layout/MobileNav.test.d.ts +1 -0
- package/dashboard/dist/components/layout/MobileNav.test.js +111 -0
- package/dashboard/dist/components/performance.test.d.ts +1 -0
- package/dashboard/dist/components/performance.test.js +114 -0
- package/dashboard/dist/components/responsive.test.d.ts +1 -0
- package/dashboard/dist/components/responsive.test.js +114 -0
- package/dashboard/dist/components/ui/Dropdown.d.ts +22 -0
- package/dashboard/dist/components/ui/Dropdown.js +109 -0
- package/dashboard/dist/components/ui/Dropdown.test.d.ts +1 -0
- package/dashboard/dist/components/ui/Dropdown.test.js +105 -0
- package/dashboard/dist/components/ui/Modal.d.ts +13 -0
- package/dashboard/dist/components/ui/Modal.js +25 -0
- package/dashboard/dist/components/ui/Modal.test.d.ts +1 -0
- package/dashboard/dist/components/ui/Modal.test.js +91 -0
- package/dashboard/dist/components/ui/Skeleton.d.ts +32 -0
- package/dashboard/dist/components/ui/Skeleton.js +48 -0
- package/dashboard/dist/components/ui/Skeleton.test.d.ts +1 -0
- package/dashboard/dist/components/ui/Skeleton.test.js +125 -0
- package/dashboard/dist/components/ui/Toast.d.ts +32 -0
- package/dashboard/dist/components/ui/Toast.js +21 -0
- package/dashboard/dist/components/ui/Toast.test.d.ts +1 -0
- package/dashboard/dist/components/ui/Toast.test.js +118 -0
- package/dashboard/dist/hooks/useTheme.d.ts +37 -0
- package/dashboard/dist/hooks/useTheme.js +96 -0
- package/dashboard/dist/hooks/useTheme.test.d.ts +1 -0
- package/dashboard/dist/hooks/useTheme.test.js +94 -0
- package/dashboard/dist/hooks/useWebSocket.d.ts +17 -0
- package/dashboard/dist/hooks/useWebSocket.js +100 -0
- package/dashboard/dist/hooks/useWebSocket.test.d.ts +1 -0
- package/dashboard/dist/hooks/useWebSocket.test.js +115 -0
- package/dashboard/dist/stores/projectStore.d.ts +44 -0
- package/dashboard/dist/stores/projectStore.js +76 -0
- package/dashboard/dist/stores/projectStore.test.d.ts +1 -0
- package/dashboard/dist/stores/projectStore.test.js +114 -0
- package/dashboard/dist/stores/uiStore.d.ts +29 -0
- package/dashboard/dist/stores/uiStore.js +72 -0
- package/dashboard/dist/stores/uiStore.test.d.ts +1 -0
- package/dashboard/dist/stores/uiStore.test.js +93 -0
- package/dashboard/package.json +3 -3
- package/docker-compose.dev.yml +6 -1
- package/package.json +5 -2
- package/server/dashboard/index.html +1336 -779
- package/server/index.js +178 -0
- package/server/lib/agent-cleanup.js +177 -0
- package/server/lib/agent-cleanup.test.js +359 -0
- package/server/lib/agent-hooks.js +126 -0
- package/server/lib/agent-hooks.test.js +303 -0
- package/server/lib/agent-metadata.js +179 -0
- package/server/lib/agent-metadata.test.js +383 -0
- package/server/lib/agent-persistence.js +191 -0
- package/server/lib/agent-persistence.test.js +475 -0
- package/server/lib/agent-registry-command.js +340 -0
- package/server/lib/agent-registry-command.test.js +334 -0
- package/server/lib/agent-registry.js +155 -0
- package/server/lib/agent-registry.test.js +239 -0
- package/server/lib/agent-state.js +236 -0
- package/server/lib/agent-state.test.js +375 -0
- package/server/lib/api-provider.js +186 -0
- package/server/lib/api-provider.test.js +336 -0
- package/server/lib/cli-detector.js +166 -0
- package/server/lib/cli-detector.test.js +269 -0
- package/server/lib/cli-provider.js +212 -0
- package/server/lib/cli-provider.test.js +349 -0
- package/server/lib/debug.test.js +62 -0
- package/server/lib/devserver-router-api.js +249 -0
- package/server/lib/devserver-router-api.test.js +426 -0
- package/server/lib/model-router.js +245 -0
- package/server/lib/model-router.test.js +313 -0
- package/server/lib/output-schemas.js +269 -0
- package/server/lib/output-schemas.test.js +307 -0
- package/server/lib/provider-interface.js +153 -0
- package/server/lib/provider-interface.test.js +394 -0
- package/server/lib/provider-queue.js +158 -0
- package/server/lib/provider-queue.test.js +315 -0
- package/server/lib/router-config.js +221 -0
- package/server/lib/router-config.test.js +237 -0
- package/server/lib/router-setup-command.js +419 -0
- package/server/lib/router-setup-command.test.js +375 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
detectCLI,
|
|
4
|
+
detectAllCLIs,
|
|
5
|
+
clearCache,
|
|
6
|
+
getCapabilities,
|
|
7
|
+
CLI_TOOLS,
|
|
8
|
+
} from './cli-detector.js';
|
|
9
|
+
|
|
10
|
+
// Mock child_process
|
|
11
|
+
vi.mock('child_process', () => ({
|
|
12
|
+
execSync: vi.fn(),
|
|
13
|
+
exec: vi.fn(),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
import { execSync } from 'child_process';
|
|
17
|
+
|
|
18
|
+
describe('cli-detector', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
vi.clearAllMocks();
|
|
21
|
+
clearCache();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('detectCLI', () => {
|
|
25
|
+
it('finds claude when installed', async () => {
|
|
26
|
+
execSync.mockImplementation((cmd) => {
|
|
27
|
+
if (cmd.includes('which claude') || cmd.includes('where claude')) {
|
|
28
|
+
return Buffer.from('/usr/local/bin/claude\n');
|
|
29
|
+
}
|
|
30
|
+
if (cmd.includes('--version')) {
|
|
31
|
+
return Buffer.from('claude v4.2.1\n');
|
|
32
|
+
}
|
|
33
|
+
throw new Error('Command not found');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const result = await detectCLI('claude');
|
|
37
|
+
|
|
38
|
+
expect(result).not.toBeNull();
|
|
39
|
+
expect(result.name).toBe('claude');
|
|
40
|
+
expect(result.path).toContain('claude');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('returns null when CLI not installed', async () => {
|
|
44
|
+
execSync.mockImplementation(() => {
|
|
45
|
+
throw new Error('Command not found');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const result = await detectCLI('claude');
|
|
49
|
+
|
|
50
|
+
expect(result).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('gets version string', async () => {
|
|
54
|
+
execSync.mockImplementation((cmd) => {
|
|
55
|
+
if (cmd.includes('which') || cmd.includes('where')) {
|
|
56
|
+
return Buffer.from('/usr/local/bin/claude\n');
|
|
57
|
+
}
|
|
58
|
+
if (cmd.includes('--version')) {
|
|
59
|
+
return Buffer.from('claude v4.2.1\n');
|
|
60
|
+
}
|
|
61
|
+
return Buffer.from('');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const result = await detectCLI('claude');
|
|
65
|
+
|
|
66
|
+
expect(result.version).toBe('v4.2.1');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('detects codex CLI', async () => {
|
|
70
|
+
execSync.mockImplementation((cmd) => {
|
|
71
|
+
if (cmd.includes('which codex') || cmd.includes('where codex')) {
|
|
72
|
+
return Buffer.from('/usr/local/bin/codex\n');
|
|
73
|
+
}
|
|
74
|
+
if (cmd.includes('--version')) {
|
|
75
|
+
return Buffer.from('codex 1.3.0\n');
|
|
76
|
+
}
|
|
77
|
+
throw new Error('not found');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const result = await detectCLI('codex');
|
|
81
|
+
|
|
82
|
+
expect(result).not.toBeNull();
|
|
83
|
+
expect(result.name).toBe('codex');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('detects gemini CLI', async () => {
|
|
87
|
+
execSync.mockImplementation((cmd) => {
|
|
88
|
+
if (cmd.includes('which gemini') || cmd.includes('where gemini')) {
|
|
89
|
+
return Buffer.from('/usr/local/bin/gemini\n');
|
|
90
|
+
}
|
|
91
|
+
if (cmd.includes('--version')) {
|
|
92
|
+
return Buffer.from('gemini 0.9.2\n');
|
|
93
|
+
}
|
|
94
|
+
throw new Error('not found');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const result = await detectCLI('gemini');
|
|
98
|
+
|
|
99
|
+
expect(result).not.toBeNull();
|
|
100
|
+
expect(result.name).toBe('gemini');
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('detectAllCLIs', () => {
|
|
105
|
+
it('returns map of detected CLIs', async () => {
|
|
106
|
+
execSync.mockImplementation((cmd) => {
|
|
107
|
+
if (cmd.includes('claude')) {
|
|
108
|
+
if (cmd.includes('which') || cmd.includes('where')) {
|
|
109
|
+
return Buffer.from('/usr/local/bin/claude\n');
|
|
110
|
+
}
|
|
111
|
+
return Buffer.from('v4.2.1\n');
|
|
112
|
+
}
|
|
113
|
+
throw new Error('not found');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const result = await detectAllCLIs();
|
|
117
|
+
|
|
118
|
+
expect(result).toBeInstanceOf(Map);
|
|
119
|
+
expect(result.has('claude')).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('caches results', async () => {
|
|
123
|
+
let callCount = 0;
|
|
124
|
+
execSync.mockImplementation((cmd) => {
|
|
125
|
+
callCount++;
|
|
126
|
+
if (cmd.includes('which') || cmd.includes('where')) {
|
|
127
|
+
return Buffer.from('/path/to/cli\n');
|
|
128
|
+
}
|
|
129
|
+
return Buffer.from('v1.0.0\n');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
await detectAllCLIs();
|
|
133
|
+
const initialCount = callCount;
|
|
134
|
+
|
|
135
|
+
await detectAllCLIs();
|
|
136
|
+
|
|
137
|
+
// Should not have called execSync again
|
|
138
|
+
expect(callCount).toBe(initialCount);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('clearCache', () => {
|
|
143
|
+
it('forces re-detection', async () => {
|
|
144
|
+
let callCount = 0;
|
|
145
|
+
execSync.mockImplementation((cmd) => {
|
|
146
|
+
callCount++;
|
|
147
|
+
if (cmd.includes('which') || cmd.includes('where')) {
|
|
148
|
+
return Buffer.from('/path/to/cli\n');
|
|
149
|
+
}
|
|
150
|
+
return Buffer.from('v1.0.0\n');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
await detectAllCLIs();
|
|
154
|
+
const countAfterFirst = callCount;
|
|
155
|
+
|
|
156
|
+
clearCache();
|
|
157
|
+
await detectAllCLIs();
|
|
158
|
+
|
|
159
|
+
// Should have called again after cache clear
|
|
160
|
+
expect(callCount).toBeGreaterThan(countAfterFirst);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('getCapabilities', () => {
|
|
165
|
+
it('returns CLI capabilities for claude', () => {
|
|
166
|
+
const caps = getCapabilities('claude');
|
|
167
|
+
|
|
168
|
+
expect(caps).toContain('review');
|
|
169
|
+
expect(caps).toContain('code-gen');
|
|
170
|
+
expect(caps).toContain('refactor');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('returns CLI capabilities for codex', () => {
|
|
174
|
+
const caps = getCapabilities('codex');
|
|
175
|
+
|
|
176
|
+
expect(caps).toContain('review');
|
|
177
|
+
expect(caps).toContain('code-gen');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('returns CLI capabilities for gemini', () => {
|
|
181
|
+
const caps = getCapabilities('gemini');
|
|
182
|
+
|
|
183
|
+
expect(caps).toContain('design');
|
|
184
|
+
expect(caps).toContain('vision');
|
|
185
|
+
expect(caps).toContain('review');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('returns empty array for unknown CLI', () => {
|
|
189
|
+
const caps = getCapabilities('unknown');
|
|
190
|
+
|
|
191
|
+
expect(caps).toEqual([]);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('CLI_TOOLS', () => {
|
|
196
|
+
it('exports claude tool config', () => {
|
|
197
|
+
expect(CLI_TOOLS.claude).toBeDefined();
|
|
198
|
+
expect(CLI_TOOLS.claude.command).toBe('claude');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('exports codex tool config', () => {
|
|
202
|
+
expect(CLI_TOOLS.codex).toBeDefined();
|
|
203
|
+
expect(CLI_TOOLS.codex.command).toBe('codex');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('exports gemini tool config', () => {
|
|
207
|
+
expect(CLI_TOOLS.gemini).toBeDefined();
|
|
208
|
+
expect(CLI_TOOLS.gemini.command).toBe('gemini');
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('includes headless args for each tool', () => {
|
|
212
|
+
expect(CLI_TOOLS.claude.headlessArgs).toBeDefined();
|
|
213
|
+
expect(CLI_TOOLS.codex.headlessArgs).toBeDefined();
|
|
214
|
+
expect(CLI_TOOLS.gemini.headlessArgs).toBeDefined();
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe('Windows compatibility', () => {
|
|
219
|
+
it('handles Windows command extensions', async () => {
|
|
220
|
+
// Simulate Windows where 'where' is used instead of 'which'
|
|
221
|
+
const originalPlatform = process.platform;
|
|
222
|
+
Object.defineProperty(process, 'platform', { value: 'win32' });
|
|
223
|
+
|
|
224
|
+
execSync.mockImplementation((cmd) => {
|
|
225
|
+
if (cmd.includes('where')) {
|
|
226
|
+
return Buffer.from('C:\\Program Files\\claude\\claude.exe\n');
|
|
227
|
+
}
|
|
228
|
+
return Buffer.from('v1.0.0\n');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const result = await detectCLI('claude');
|
|
232
|
+
|
|
233
|
+
// Restore
|
|
234
|
+
Object.defineProperty(process, 'platform', { value: originalPlatform });
|
|
235
|
+
|
|
236
|
+
expect(result).not.toBeNull();
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('PATH variations', () => {
|
|
241
|
+
it('handles CLI in non-standard paths', async () => {
|
|
242
|
+
execSync.mockImplementation((cmd) => {
|
|
243
|
+
if (cmd.includes('which') || cmd.includes('where')) {
|
|
244
|
+
return Buffer.from('/opt/custom/bin/claude\n');
|
|
245
|
+
}
|
|
246
|
+
return Buffer.from('v4.0.0\n');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const result = await detectCLI('claude');
|
|
250
|
+
|
|
251
|
+
expect(result.path).toBe('/opt/custom/bin/claude');
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe('timeout handling', () => {
|
|
256
|
+
it('handles slow detection with timeout', async () => {
|
|
257
|
+
execSync.mockImplementation(() => {
|
|
258
|
+
// Simulate timeout by throwing
|
|
259
|
+
const error = new Error('Command timed out');
|
|
260
|
+
error.code = 'ETIMEDOUT';
|
|
261
|
+
throw error;
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const result = await detectCLI('claude');
|
|
265
|
+
|
|
266
|
+
expect(result).toBeNull();
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
});
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Provider - Provider implementation for CLI tools
|
|
3
|
+
*
|
|
4
|
+
* Supports running AI CLI tools locally or via devserver:
|
|
5
|
+
* - claude (Claude Code)
|
|
6
|
+
* - codex (Codex CLI)
|
|
7
|
+
* - gemini (Gemini CLI)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { spawn } from 'child_process';
|
|
11
|
+
import { createProvider, PROVIDER_TYPES } from './provider-interface.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Parse output, trying to extract JSON
|
|
15
|
+
* @param {string} output - Raw output string
|
|
16
|
+
* @returns {Object|null} Parsed JSON or null
|
|
17
|
+
*/
|
|
18
|
+
export function parseOutput(output) {
|
|
19
|
+
if (!output || output.trim() === '') {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Try parsing the whole thing as JSON
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(output);
|
|
26
|
+
} catch (e) {
|
|
27
|
+
// Try to find JSON in the output
|
|
28
|
+
const jsonMatch = output.match(/\{[\s\S]*\}/);
|
|
29
|
+
if (jsonMatch) {
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(jsonMatch[0]);
|
|
32
|
+
} catch (e2) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build command-line arguments for a CLI tool
|
|
42
|
+
* @param {string} command - CLI command name
|
|
43
|
+
* @param {string} prompt - The prompt
|
|
44
|
+
* @param {Object} opts - Options
|
|
45
|
+
* @returns {string[]} Array of arguments
|
|
46
|
+
*/
|
|
47
|
+
export function buildArgs(command, prompt, opts = {}) {
|
|
48
|
+
const args = [...(opts.headlessArgs || [])];
|
|
49
|
+
|
|
50
|
+
// Add the prompt
|
|
51
|
+
args.push(prompt);
|
|
52
|
+
|
|
53
|
+
return args;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Run a CLI tool locally
|
|
58
|
+
* @param {string} command - CLI command
|
|
59
|
+
* @param {string} prompt - The prompt
|
|
60
|
+
* @param {Object} opts - Options
|
|
61
|
+
* @returns {Promise<Object>} ProviderResult
|
|
62
|
+
*/
|
|
63
|
+
export function runLocal(command, prompt, opts = {}) {
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
const args = buildArgs(command, prompt, opts);
|
|
66
|
+
const timeout = opts.timeout || 120000;
|
|
67
|
+
|
|
68
|
+
let stdout = '';
|
|
69
|
+
let stderr = '';
|
|
70
|
+
let timedOut = false;
|
|
71
|
+
|
|
72
|
+
const proc = spawn(command, args, {
|
|
73
|
+
cwd: opts.cwd,
|
|
74
|
+
env: process.env,
|
|
75
|
+
shell: false,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const timeoutId = setTimeout(() => {
|
|
79
|
+
timedOut = true;
|
|
80
|
+
proc.kill('SIGTERM');
|
|
81
|
+
reject(new Error(`CLI timeout after ${timeout}ms`));
|
|
82
|
+
}, timeout);
|
|
83
|
+
|
|
84
|
+
proc.stdout.on('data', (data) => {
|
|
85
|
+
stdout += data.toString();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
proc.stderr.on('data', (data) => {
|
|
89
|
+
stderr += data.toString();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
proc.on('close', (code) => {
|
|
93
|
+
clearTimeout(timeoutId);
|
|
94
|
+
|
|
95
|
+
if (timedOut) return;
|
|
96
|
+
|
|
97
|
+
const parsed = parseOutput(stdout);
|
|
98
|
+
|
|
99
|
+
resolve({
|
|
100
|
+
raw: stdout,
|
|
101
|
+
parsed,
|
|
102
|
+
exitCode: code || 0,
|
|
103
|
+
stderr,
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
proc.on('error', (err) => {
|
|
108
|
+
clearTimeout(timeoutId);
|
|
109
|
+
if (!timedOut) {
|
|
110
|
+
reject(err);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Run a CLI tool via devserver
|
|
118
|
+
* @param {Object} params - Parameters
|
|
119
|
+
* @param {string} params.devserverUrl - Devserver URL
|
|
120
|
+
* @param {string} params.provider - Provider name
|
|
121
|
+
* @param {string} params.prompt - The prompt
|
|
122
|
+
* @param {Object} params.opts - Run options
|
|
123
|
+
* @param {number} [params.pollInterval=1000] - Poll interval in ms
|
|
124
|
+
* @param {number} [params.maxPollTime=300000] - Max poll time in ms
|
|
125
|
+
* @returns {Promise<Object>} ProviderResult
|
|
126
|
+
*/
|
|
127
|
+
export async function runViaDevserver({
|
|
128
|
+
devserverUrl,
|
|
129
|
+
provider,
|
|
130
|
+
prompt,
|
|
131
|
+
opts = {},
|
|
132
|
+
pollInterval = 1000,
|
|
133
|
+
maxPollTime = 300000,
|
|
134
|
+
}) {
|
|
135
|
+
// Submit task
|
|
136
|
+
const submitResponse = await fetch(`${devserverUrl}/api/run`, {
|
|
137
|
+
method: 'POST',
|
|
138
|
+
headers: {
|
|
139
|
+
'Content-Type': 'application/json',
|
|
140
|
+
},
|
|
141
|
+
body: JSON.stringify({
|
|
142
|
+
provider,
|
|
143
|
+
prompt,
|
|
144
|
+
opts,
|
|
145
|
+
}),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (!submitResponse.ok) {
|
|
149
|
+
throw new Error(`Failed to submit task: ${submitResponse.statusText}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const { taskId } = await submitResponse.json();
|
|
153
|
+
|
|
154
|
+
// Poll for result
|
|
155
|
+
const startTime = Date.now();
|
|
156
|
+
|
|
157
|
+
while (Date.now() - startTime < maxPollTime) {
|
|
158
|
+
const statusResponse = await fetch(`${devserverUrl}/api/task/${taskId}`);
|
|
159
|
+
|
|
160
|
+
if (!statusResponse.ok) {
|
|
161
|
+
throw new Error(`Failed to get task status: ${statusResponse.statusText}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const status = await statusResponse.json();
|
|
165
|
+
|
|
166
|
+
if (status.status === 'completed') {
|
|
167
|
+
return status.result;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (status.status === 'failed') {
|
|
171
|
+
throw new Error(status.error || 'Task failed');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Wait before polling again
|
|
175
|
+
await new Promise(r => setTimeout(r, pollInterval));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
throw new Error('Task timed out waiting for devserver response');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Create a CLI provider instance
|
|
183
|
+
* @param {Object} config - Provider configuration
|
|
184
|
+
* @returns {Object} Provider instance
|
|
185
|
+
*/
|
|
186
|
+
export function createCLIProvider(config) {
|
|
187
|
+
const runner = async (prompt, opts) => {
|
|
188
|
+
if (config.detected) {
|
|
189
|
+
return runLocal(config.command, prompt, {
|
|
190
|
+
...opts,
|
|
191
|
+
headlessArgs: config.headlessArgs,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (config.devserverUrl) {
|
|
196
|
+
return runViaDevserver({
|
|
197
|
+
devserverUrl: config.devserverUrl,
|
|
198
|
+
provider: config.name,
|
|
199
|
+
prompt,
|
|
200
|
+
opts,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
throw new Error(`CLI ${config.name} not detected and no devserver configured`);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
return createProvider({
|
|
208
|
+
...config,
|
|
209
|
+
type: PROVIDER_TYPES.CLI,
|
|
210
|
+
runner,
|
|
211
|
+
});
|
|
212
|
+
}
|