@orchagent/cli 0.2.2 → 0.2.3
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 +171 -1
- package/dist/lib/api.js +12 -0
- package/package.json +1 -1
package/dist/commands/run.js
CHANGED
|
@@ -266,6 +266,165 @@ 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
|
+
await new Promise((resolve, reject) => {
|
|
394
|
+
proc.on('close', (code) => {
|
|
395
|
+
if (code !== 0) {
|
|
396
|
+
reject(new errors_1.CliError(`Agent exited with code ${code}`));
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
resolve();
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
proc.on('error', (err) => {
|
|
403
|
+
reject(err);
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
// Parse and print the output
|
|
407
|
+
if (stdout.trim()) {
|
|
408
|
+
try {
|
|
409
|
+
const result = JSON.parse(stdout.trim());
|
|
410
|
+
(0, output_1.printJson)(result);
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
// Not JSON, print as-is
|
|
414
|
+
process.stdout.write(stdout);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
finally {
|
|
419
|
+
// Clean up temp directory
|
|
420
|
+
try {
|
|
421
|
+
await promises_1.default.rm(tempDir, { recursive: true, force: true });
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
// Ignore cleanup errors
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
269
428
|
async function saveAgentLocally(org, agent, agentData) {
|
|
270
429
|
const agentDir = path_1.default.join(AGENTS_DIR, org, agent);
|
|
271
430
|
await promises_1.default.mkdir(agentDir, { recursive: true });
|
|
@@ -351,7 +510,18 @@ function registerRunCommand(program) {
|
|
|
351
510
|
const agentDir = await saveAgentLocally(org, parsed.agent, agentData);
|
|
352
511
|
process.stderr.write(`\nAgent saved to: ${agentDir}\n`);
|
|
353
512
|
if (agentData.type === 'code') {
|
|
354
|
-
// Check if this agent
|
|
513
|
+
// Check if this agent has a bundle available for local execution
|
|
514
|
+
if (agentData.has_bundle) {
|
|
515
|
+
if (options.downloadOnly) {
|
|
516
|
+
process.stdout.write(`\nCode agent has bundle available for local execution.\n`);
|
|
517
|
+
process.stdout.write(`Run with: orch run ${org}/${parsed.agent} [args...]\n`);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
// Execute the bundle-based code agent locally
|
|
521
|
+
await executeBundleAgent(resolved, org, parsed.agent, parsed.version, agentData, args);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
// Check for pip/source-based local execution (legacy)
|
|
355
525
|
if (agentData.run_command && (agentData.source_url || agentData.pip_package)) {
|
|
356
526
|
if (options.downloadOnly) {
|
|
357
527
|
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
|
*/
|