@orchagent/cli 0.2.2 → 0.2.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/dist/commands/run.js +189 -1
- package/dist/lib/api.js +12 -0
- package/package.json +1 -1
package/dist/commands/run.js
CHANGED
|
@@ -266,6 +266,183 @@ async function executeCodeAgent(agentData, args) {
|
|
|
266
266
|
process.exit(code);
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
|
+
async function unzipBundle(zipPath, destDir) {
|
|
270
|
+
// Use spawn with array arguments to avoid shell injection
|
|
271
|
+
return new Promise((resolve, reject) => {
|
|
272
|
+
const proc = (0, child_process_1.spawn)('unzip', ['-q', zipPath, '-d', destDir], {
|
|
273
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
274
|
+
});
|
|
275
|
+
let stderr = '';
|
|
276
|
+
proc.stderr?.on('data', (data) => {
|
|
277
|
+
stderr += data.toString();
|
|
278
|
+
});
|
|
279
|
+
proc.on('close', (code) => {
|
|
280
|
+
if (code !== 0) {
|
|
281
|
+
reject(new errors_1.CliError(`Failed to extract bundle: ${stderr || `exit code ${code}`}`));
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
resolve();
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
proc.on('error', (err) => {
|
|
288
|
+
reject(new errors_1.CliError(`Failed to run unzip: ${err.message}. Make sure unzip is installed.`));
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
async function executeBundleAgent(config, org, agentName, version, agentData, args) {
|
|
293
|
+
// Create temp directory for the bundle
|
|
294
|
+
const tempDir = path_1.default.join(os_1.default.tmpdir(), `orchagent-${agentName}-${Date.now()}`);
|
|
295
|
+
await promises_1.default.mkdir(tempDir, { recursive: true });
|
|
296
|
+
const bundleZip = path_1.default.join(tempDir, 'bundle.zip');
|
|
297
|
+
const extractDir = path_1.default.join(tempDir, 'agent');
|
|
298
|
+
try {
|
|
299
|
+
// Download the bundle
|
|
300
|
+
process.stderr.write(`Downloading bundle...\n`);
|
|
301
|
+
const bundleBuffer = await (0, api_1.downloadCodeBundle)(config, org, agentName, version);
|
|
302
|
+
await promises_1.default.writeFile(bundleZip, bundleBuffer);
|
|
303
|
+
process.stderr.write(`Bundle downloaded (${bundleBuffer.length} bytes)\n`);
|
|
304
|
+
// Extract the bundle
|
|
305
|
+
await promises_1.default.mkdir(extractDir, { recursive: true });
|
|
306
|
+
process.stderr.write(`Extracting bundle...\n`);
|
|
307
|
+
await unzipBundle(bundleZip, extractDir);
|
|
308
|
+
// Check if requirements.txt exists and install dependencies
|
|
309
|
+
const requirementsPath = path_1.default.join(extractDir, 'requirements.txt');
|
|
310
|
+
try {
|
|
311
|
+
await promises_1.default.access(requirementsPath);
|
|
312
|
+
process.stderr.write(`Installing dependencies from requirements.txt...\n`);
|
|
313
|
+
const { code } = await runCommand('python3', ['-m', 'pip', 'install', '-q', '-r', requirementsPath]);
|
|
314
|
+
if (code !== 0) {
|
|
315
|
+
throw new errors_1.CliError('Failed to install dependencies from requirements.txt');
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
catch (err) {
|
|
319
|
+
if (err.code !== 'ENOENT') {
|
|
320
|
+
throw err;
|
|
321
|
+
}
|
|
322
|
+
// requirements.txt doesn't exist, skip installation
|
|
323
|
+
}
|
|
324
|
+
// Determine entrypoint
|
|
325
|
+
const entrypoint = agentData.entrypoint || 'sandbox_main.py';
|
|
326
|
+
const entrypointPath = path_1.default.join(extractDir, entrypoint);
|
|
327
|
+
// Verify entrypoint exists
|
|
328
|
+
try {
|
|
329
|
+
await promises_1.default.access(entrypointPath);
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
throw new errors_1.CliError(`Entrypoint not found: ${entrypoint}`);
|
|
333
|
+
}
|
|
334
|
+
// Build input JSON from args
|
|
335
|
+
// The first arg should be the input (file path or JSON string)
|
|
336
|
+
let inputJson = '{}';
|
|
337
|
+
if (args.length > 0) {
|
|
338
|
+
const firstArg = args[0];
|
|
339
|
+
// Check if it's a file path
|
|
340
|
+
try {
|
|
341
|
+
const stat = await promises_1.default.stat(firstArg);
|
|
342
|
+
if (stat.isFile()) {
|
|
343
|
+
// Read file content as input
|
|
344
|
+
const fileContent = await promises_1.default.readFile(firstArg, 'utf-8');
|
|
345
|
+
// Check if it's already JSON
|
|
346
|
+
try {
|
|
347
|
+
JSON.parse(fileContent);
|
|
348
|
+
inputJson = fileContent;
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
// Wrap as file_path in JSON
|
|
352
|
+
inputJson = JSON.stringify({ file_path: firstArg });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
else if (stat.isDirectory()) {
|
|
356
|
+
// Pass directory path
|
|
357
|
+
inputJson = JSON.stringify({ directory: firstArg });
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
// Not a file, check if it's JSON
|
|
362
|
+
try {
|
|
363
|
+
JSON.parse(firstArg);
|
|
364
|
+
inputJson = firstArg;
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
// Treat as a simple string input
|
|
368
|
+
inputJson = JSON.stringify({ input: firstArg });
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
// Run the entrypoint with input via stdin
|
|
373
|
+
process.stderr.write(`\nRunning: python3 ${entrypoint}\n\n`);
|
|
374
|
+
const proc = (0, child_process_1.spawn)('python3', [entrypointPath], {
|
|
375
|
+
cwd: extractDir,
|
|
376
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
377
|
+
});
|
|
378
|
+
// Send input JSON via stdin
|
|
379
|
+
proc.stdin.write(inputJson);
|
|
380
|
+
proc.stdin.end();
|
|
381
|
+
// Collect output
|
|
382
|
+
let stdout = '';
|
|
383
|
+
let stderr = '';
|
|
384
|
+
proc.stdout?.on('data', (data) => {
|
|
385
|
+
const text = data.toString();
|
|
386
|
+
stdout += text;
|
|
387
|
+
});
|
|
388
|
+
proc.stderr?.on('data', (data) => {
|
|
389
|
+
const text = data.toString();
|
|
390
|
+
stderr += text;
|
|
391
|
+
process.stderr.write(text);
|
|
392
|
+
});
|
|
393
|
+
const exitCode = await new Promise((resolve) => {
|
|
394
|
+
proc.on('close', (code) => {
|
|
395
|
+
resolve(code ?? 1);
|
|
396
|
+
});
|
|
397
|
+
proc.on('error', (err) => {
|
|
398
|
+
process.stderr.write(`Error running agent: ${err.message}\n`);
|
|
399
|
+
resolve(1);
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
// Handle output - check for errors in stdout even on failure
|
|
403
|
+
if (stdout.trim()) {
|
|
404
|
+
try {
|
|
405
|
+
const result = JSON.parse(stdout.trim());
|
|
406
|
+
// Check if it's an error response
|
|
407
|
+
if (exitCode !== 0 && typeof result === 'object' && result !== null && 'error' in result) {
|
|
408
|
+
throw new errors_1.CliError(`Agent error: ${result.error}`);
|
|
409
|
+
}
|
|
410
|
+
if (exitCode !== 0) {
|
|
411
|
+
// Non-zero exit but output isn't an error object - show it and fail
|
|
412
|
+
(0, output_1.printJson)(result);
|
|
413
|
+
throw new errors_1.CliError(`Agent exited with code ${exitCode}`);
|
|
414
|
+
}
|
|
415
|
+
// Success - print result
|
|
416
|
+
(0, output_1.printJson)(result);
|
|
417
|
+
}
|
|
418
|
+
catch (err) {
|
|
419
|
+
if (err instanceof errors_1.CliError)
|
|
420
|
+
throw err;
|
|
421
|
+
// Not JSON, print as-is
|
|
422
|
+
process.stdout.write(stdout);
|
|
423
|
+
if (exitCode !== 0) {
|
|
424
|
+
throw new errors_1.CliError(`Agent exited with code ${exitCode}`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
else if (exitCode !== 0) {
|
|
429
|
+
// No stdout, check stderr
|
|
430
|
+
if (stderr.trim()) {
|
|
431
|
+
throw new errors_1.CliError(`Agent error: ${stderr.trim()}`);
|
|
432
|
+
}
|
|
433
|
+
throw new errors_1.CliError(`Agent exited with code ${exitCode} (no output)`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
finally {
|
|
437
|
+
// Clean up temp directory
|
|
438
|
+
try {
|
|
439
|
+
await promises_1.default.rm(tempDir, { recursive: true, force: true });
|
|
440
|
+
}
|
|
441
|
+
catch {
|
|
442
|
+
// Ignore cleanup errors
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
269
446
|
async function saveAgentLocally(org, agent, agentData) {
|
|
270
447
|
const agentDir = path_1.default.join(AGENTS_DIR, org, agent);
|
|
271
448
|
await promises_1.default.mkdir(agentDir, { recursive: true });
|
|
@@ -351,7 +528,18 @@ function registerRunCommand(program) {
|
|
|
351
528
|
const agentDir = await saveAgentLocally(org, parsed.agent, agentData);
|
|
352
529
|
process.stderr.write(`\nAgent saved to: ${agentDir}\n`);
|
|
353
530
|
if (agentData.type === 'code') {
|
|
354
|
-
// Check if this agent
|
|
531
|
+
// Check if this agent has a bundle available for local execution
|
|
532
|
+
if (agentData.has_bundle) {
|
|
533
|
+
if (options.downloadOnly) {
|
|
534
|
+
process.stdout.write(`\nCode agent has bundle available for local execution.\n`);
|
|
535
|
+
process.stdout.write(`Run with: orch run ${org}/${parsed.agent} [args...]\n`);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
// Execute the bundle-based code agent locally
|
|
539
|
+
await executeBundleAgent(resolved, org, parsed.agent, parsed.version, agentData, args);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
// Check for pip/source-based local execution (legacy)
|
|
355
543
|
if (agentData.run_command && (agentData.source_url || agentData.pip_package)) {
|
|
356
544
|
if (options.downloadOnly) {
|
|
357
545
|
process.stdout.write(`\nCode agent ready for local execution.\n`);
|
package/dist/lib/api.js
CHANGED
|
@@ -47,6 +47,7 @@ exports.unstarAgent = unstarAgent;
|
|
|
47
47
|
exports.forkAgent = forkAgent;
|
|
48
48
|
exports.searchAgents = searchAgents;
|
|
49
49
|
exports.fetchLlmKeys = fetchLlmKeys;
|
|
50
|
+
exports.downloadCodeBundle = downloadCodeBundle;
|
|
50
51
|
exports.uploadCodeBundle = uploadCodeBundle;
|
|
51
52
|
class ApiError extends Error {
|
|
52
53
|
status;
|
|
@@ -151,6 +152,17 @@ async function fetchLlmKeys(config) {
|
|
|
151
152
|
const result = await request(config, 'GET', '/llm-keys/export');
|
|
152
153
|
return result.keys;
|
|
153
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Download a code bundle for local execution.
|
|
157
|
+
*/
|
|
158
|
+
async function downloadCodeBundle(config, org, agent, version) {
|
|
159
|
+
const response = await fetch(`${config.apiUrl.replace(/\/$/, '')}/public/agents/${org}/${agent}/${version}/bundle`);
|
|
160
|
+
if (!response.ok) {
|
|
161
|
+
throw await parseError(response);
|
|
162
|
+
}
|
|
163
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
164
|
+
return Buffer.from(arrayBuffer);
|
|
165
|
+
}
|
|
154
166
|
/**
|
|
155
167
|
* Upload a code bundle for a hosted code agent.
|
|
156
168
|
*/
|