@hackerrank/astra-cli 0.1.5 → 0.1.6
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/README.md +11 -0
- package/package.json +1 -1
- package/src/agent.js +29 -0
- package/src/bench.js +11 -2
- package/src/cli.js +84 -5
- package/src/report.js +1 -0
package/README.md
CHANGED
|
@@ -204,6 +204,17 @@ astra --resume <id> # resume (continue an interactive chat,
|
|
|
204
204
|
# or inspect/continue an autonomous run)
|
|
205
205
|
```
|
|
206
206
|
|
|
207
|
+
Resume an interrupted benchmark without repeating its task, model, or output
|
|
208
|
+
arguments:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
astra bench --resume <session-id>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
It reopens the original candidate workspace and updates that same run's
|
|
215
|
+
trajectory, metrics row, and report telemetry. Explicit flags override the
|
|
216
|
+
saved setting when needed.
|
|
217
|
+
|
|
207
218
|
Verified working models on the gateway include `claude-sonnet-5`, `claude-opus-5`,
|
|
208
219
|
`gpt-5.6-sol`, `gpt-5.6-terra`, `gemini-3.7-flash`, `grok-4.6`, `kimi-k3`, and others.
|
|
209
220
|
Some routes (e.g. `glm-5.2`) may return HTTP 402 when the underlying provider is out of
|
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -64,6 +64,7 @@ export class Agent {
|
|
|
64
64
|
this.sessionId = opts.sessionId ?? null;
|
|
65
65
|
this.sessionCreated = opts.sessionCreated ?? new Date().toISOString();
|
|
66
66
|
this.title = opts.title ?? "";
|
|
67
|
+
this.bench = opts.bench ?? null;
|
|
67
68
|
this._saveSession = opts.saveSession ?? null; // (id, doc) => void
|
|
68
69
|
}
|
|
69
70
|
|
|
@@ -240,6 +241,7 @@ export class Agent {
|
|
|
240
241
|
id: this.sessionId ?? undefined,
|
|
241
242
|
created: this.sessionCreated,
|
|
242
243
|
title: this.title || undefined,
|
|
244
|
+
bench: this.bench ?? undefined,
|
|
243
245
|
info: {
|
|
244
246
|
mode: this.mode,
|
|
245
247
|
task: this.task || undefined,
|
|
@@ -248,10 +250,20 @@ export class Agent {
|
|
|
248
250
|
n_steps: this.nSteps,
|
|
249
251
|
model: this.model.model,
|
|
250
252
|
n_calls: this.model.nCalls,
|
|
253
|
+
n_retries: this.model.nRetries,
|
|
254
|
+
counters: {
|
|
255
|
+
commands: this.nCommands,
|
|
256
|
+
failed_commands: this.nFailedCommands,
|
|
257
|
+
format_errors: this.nFormatErrors,
|
|
258
|
+
declined: this.nDeclined,
|
|
259
|
+
},
|
|
251
260
|
elapsed_seconds: Math.round((Date.now() - this.startTime) / 1000),
|
|
252
261
|
tokens: {
|
|
253
262
|
prompt: this.model.totalPromptTokens,
|
|
254
263
|
completion: this.model.totalCompletionTokens,
|
|
264
|
+
cached: this.model.totalCachedTokens,
|
|
265
|
+
cache_write: this.model.totalCacheWriteTokens,
|
|
266
|
+
reasoning: this.model.totalReasoningTokens,
|
|
255
267
|
total: this.model.totalPromptTokens + this.model.totalCompletionTokens,
|
|
256
268
|
last_context: this.lastContextTokens,
|
|
257
269
|
},
|
|
@@ -282,8 +294,25 @@ export class Agent {
|
|
|
282
294
|
this.task = data?.info?.task ?? this.task;
|
|
283
295
|
this.mode = data?.info?.mode === "interactive" ? "interactive" : this.mode;
|
|
284
296
|
this.nSteps = data?.info?.n_steps ?? this.messages.filter((m) => m.role === "assistant").length;
|
|
297
|
+
this.nCommands = data?.info?.counters?.commands ?? 0;
|
|
298
|
+
this.nFailedCommands = data?.info?.counters?.failed_commands ?? 0;
|
|
299
|
+
this.nFormatErrors = data?.info?.counters?.format_errors ?? 0;
|
|
300
|
+
this.nDeclined = data?.info?.counters?.declined ?? 0;
|
|
285
301
|
this.lastContextTokens = data?.info?.tokens?.last_context ?? 0;
|
|
302
|
+
this.model.nCalls = data?.info?.n_calls ?? this.model.nCalls;
|
|
303
|
+
this.model.nRetries = data?.info?.n_retries ?? this.model.nRetries;
|
|
304
|
+
this.model.totalPromptTokens = data?.info?.tokens?.prompt ?? this.model.totalPromptTokens;
|
|
305
|
+
this.model.totalCompletionTokens = data?.info?.tokens?.completion ?? this.model.totalCompletionTokens;
|
|
306
|
+
this.model.totalCachedTokens = data?.info?.tokens?.cached ?? this.model.totalCachedTokens;
|
|
307
|
+
this.model.totalCacheWriteTokens = data?.info?.tokens?.cache_write ?? this.model.totalCacheWriteTokens;
|
|
308
|
+
this.model.totalReasoningTokens = data?.info?.tokens?.reasoning ?? this.model.totalReasoningTokens;
|
|
309
|
+
this.model.totalCostUsd = data?.info?.cost?.usd ?? this.model.totalCostUsd;
|
|
310
|
+
this.model.reportedCostUsd = data?.info?.cost?.reported_usd ?? this.model.reportedCostUsd;
|
|
311
|
+
this.model.estimatedCostUsd = data?.info?.cost?.estimated_usd ?? this.model.estimatedCostUsd;
|
|
312
|
+
this.model.costSource = data?.info?.cost?.source ?? this.model.costSource;
|
|
313
|
+
this.startTime = Date.now() - Number(data?.info?.elapsed_seconds || 0) * 1000;
|
|
286
314
|
this.exitStatus = data?.info?.exit_status || null;
|
|
315
|
+
this.bench = data?.bench ?? this.bench;
|
|
287
316
|
if (data?.id) this.sessionId = data.id;
|
|
288
317
|
if (data?.created) this.sessionCreated = data.created;
|
|
289
318
|
if (data?.title) this.title = data.title;
|
package/src/bench.js
CHANGED
|
@@ -26,6 +26,7 @@ import { Agent } from "./agent.js";
|
|
|
26
26
|
|
|
27
27
|
/** CSV columns, in order, for the metrics table. */
|
|
28
28
|
export const METRIC_COLUMNS = [
|
|
29
|
+
"run_path",
|
|
29
30
|
"timestamp",
|
|
30
31
|
"model",
|
|
31
32
|
"reasoning",
|
|
@@ -211,8 +212,9 @@ export function metricsHeader() {
|
|
|
211
212
|
* the top-level rolled-up index at <root>/metrics.csv.
|
|
212
213
|
*/
|
|
213
214
|
export function writeMetrics({ runDir, root, metrics }) {
|
|
215
|
+
const rowMetrics = { ...metrics, run_path: path.relative(benchRoot(root), runDir) };
|
|
214
216
|
const header = metricsHeader();
|
|
215
|
-
const row = metricsRow(
|
|
217
|
+
const row = metricsRow(rowMetrics);
|
|
216
218
|
|
|
217
219
|
// Per-run file.
|
|
218
220
|
fs.writeFileSync(path.join(runDir, "metrics.csv"), header + "\n" + row + "\n");
|
|
@@ -222,7 +224,14 @@ export function writeMetrics({ runDir, root, metrics }) {
|
|
|
222
224
|
if (!fs.existsSync(indexPath)) {
|
|
223
225
|
fs.writeFileSync(indexPath, header + "\n" + row + "\n");
|
|
224
226
|
} else {
|
|
225
|
-
fs.
|
|
227
|
+
const lines = fs.readFileSync(indexPath, "utf8").trimEnd().split("\n");
|
|
228
|
+
const runPathIndex = lines[0].split(",").indexOf("run_path");
|
|
229
|
+
const existing = runPathIndex < 0
|
|
230
|
+
? -1
|
|
231
|
+
: lines.findIndex((line, index) => index > 0 && line.split(",")[runPathIndex] === rowMetrics.run_path);
|
|
232
|
+
if (existing >= 0) lines[existing] = row;
|
|
233
|
+
else lines.push(row);
|
|
234
|
+
fs.writeFileSync(indexPath, lines.join("\n") + "\n");
|
|
226
235
|
}
|
|
227
236
|
return { runMetrics: path.join(runDir, "metrics.csv"), index: indexPath };
|
|
228
237
|
}
|
package/src/cli.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* astra -m <model> -f path/to/instruction.md
|
|
18
18
|
* astra -m <model> -p tasks/dummy-slugify bench run from a task directory
|
|
19
19
|
* astra bench --project ./task project-configured bench run
|
|
20
|
+
* astra bench --resume <session-id> continue the same bench attempt
|
|
20
21
|
* astra --resume <session-id> resume a saved session
|
|
21
22
|
* astra --sessions list saved sessions
|
|
22
23
|
*
|
|
@@ -198,6 +199,19 @@ async function main() {
|
|
|
198
199
|
// Bench mode uses the autonomous engine; agent mode uses the interactive one.
|
|
199
200
|
let task = args.task;
|
|
200
201
|
if (args["task-file"]) task = fs.readFileSync(args["task-file"], "utf8");
|
|
202
|
+
let resumeBench = resumeDoc?.bench ?? null;
|
|
203
|
+
if (args.bench && args.resume && !resumeBench) {
|
|
204
|
+
console.error("\x1b[31m[astra] request_error: this session is not a bench run.\x1b[0m");
|
|
205
|
+
process.exit(2);
|
|
206
|
+
}
|
|
207
|
+
if (resumeBench) {
|
|
208
|
+
if (!args._provided.has("task") && !args._provided.has("task-file")) task = resumeBench.task;
|
|
209
|
+
if (!args._provided.has("path")) args.path = resumeBench.taskPath;
|
|
210
|
+
if (!args._provided.has("bench-root")) args["bench-root"] = resumeBench.root;
|
|
211
|
+
if (!args._provided.has("steps")) args.steps = resumeBench.steps;
|
|
212
|
+
if (!args._provided.has("wall")) args.wall = resumeBench.wall;
|
|
213
|
+
if (!args._provided.has("timeout")) args.timeout = resumeBench.timeout;
|
|
214
|
+
}
|
|
201
215
|
let project = null;
|
|
202
216
|
if (args.project) {
|
|
203
217
|
try {
|
|
@@ -261,7 +275,7 @@ async function main() {
|
|
|
261
275
|
// 5. hard fallback: glm-5.2 with medium reasoning
|
|
262
276
|
// The chosen values are cached so the next `astra` needs no flags.
|
|
263
277
|
let modelId = args.model || resumeDoc?.info?.model;
|
|
264
|
-
let reasoning = args.reasoning || "";
|
|
278
|
+
let reasoning = args.reasoning || resumeBench?.reasoning || "";
|
|
265
279
|
|
|
266
280
|
if (mode === "autonomous") {
|
|
267
281
|
if (project && !modelId) modelId = project.bench.models.join(",");
|
|
@@ -330,7 +344,21 @@ async function main() {
|
|
|
330
344
|
// workspace under bench/<model-name-reasoning>/run-NN/. The agent's commands
|
|
331
345
|
// run inside that workspace and metrics are recorded when it finishes.
|
|
332
346
|
let benchRun = null;
|
|
333
|
-
if (mode === "autonomous" &&
|
|
347
|
+
if (mode === "autonomous" && resumeBench) {
|
|
348
|
+
if (!fs.existsSync(resumeBench.dir) || !fs.existsSync(resumeBench.workspace)) {
|
|
349
|
+
console.error("\x1b[31m[astra] request_error: saved bench workspace is missing.\x1b[0m");
|
|
350
|
+
process.exit(2);
|
|
351
|
+
}
|
|
352
|
+
benchRun = {
|
|
353
|
+
dir: resumeBench.dir,
|
|
354
|
+
workspace: resumeBench.workspace,
|
|
355
|
+
root: resumeBench.root,
|
|
356
|
+
slug: path.basename(path.dirname(resumeBench.dir)),
|
|
357
|
+
runId: path.basename(resumeBench.dir),
|
|
358
|
+
};
|
|
359
|
+
resumeBench.segments = Array.isArray(resumeBench.segments) ? resumeBench.segments : [];
|
|
360
|
+
resumeBench.segments.push({ started_at: new Date().toISOString(), resumed: true });
|
|
361
|
+
} else if (mode === "autonomous" && !resumeDoc) {
|
|
334
362
|
benchRun = allocateRun({ model: modelId, reasoning, root: args["bench-root"] });
|
|
335
363
|
const seeded = seedWorkspace(benchRun.workspace, {
|
|
336
364
|
taskPath: args.path,
|
|
@@ -341,9 +369,22 @@ async function main() {
|
|
|
341
369
|
// Persist the task text alongside the run for reproducibility.
|
|
342
370
|
fs.writeFileSync(path.join(benchRun.dir, "task.md"), task || "");
|
|
343
371
|
writeProjectMetadata(benchRun.dir, project?.metadata);
|
|
372
|
+
resumeBench = {
|
|
373
|
+
dir: benchRun.dir,
|
|
374
|
+
workspace: benchRun.workspace,
|
|
375
|
+
root: benchRun.root,
|
|
376
|
+
task,
|
|
377
|
+
taskPath: args.path,
|
|
378
|
+
model: modelId,
|
|
379
|
+
reasoning,
|
|
380
|
+
steps: Number(args.steps),
|
|
381
|
+
wall: Number(args.wall),
|
|
382
|
+
timeout: Number(args.timeout),
|
|
383
|
+
segments: [{ started_at: new Date().toISOString(), resumed: false }],
|
|
384
|
+
};
|
|
344
385
|
}
|
|
345
386
|
|
|
346
|
-
const cmdCwd = benchRun
|
|
387
|
+
const cmdCwd = benchRun?.workspace || cwd;
|
|
347
388
|
const env = new LocalEnvironment({
|
|
348
389
|
cwd: cmdCwd,
|
|
349
390
|
timeout: Number(args.timeout),
|
|
@@ -362,12 +403,23 @@ async function main() {
|
|
|
362
403
|
: null,
|
|
363
404
|
sessionId,
|
|
364
405
|
saveSession,
|
|
406
|
+
bench: resumeBench,
|
|
365
407
|
onEvent: quiet || mode === "interactive" ? () => {} : (msg) => printEvent(msg),
|
|
366
408
|
onStep: quiet || mode === "interactive" ? () => {} : (s) => printStep(s),
|
|
367
409
|
});
|
|
368
410
|
|
|
369
411
|
if (resumeDoc) {
|
|
370
412
|
agent.restore(resumeDoc);
|
|
413
|
+
if (resumeBench) {
|
|
414
|
+
agent.exitStatus = null;
|
|
415
|
+
if (!args._provided.has("steps") && Number(resumeBench.steps) > 0) {
|
|
416
|
+
agent.stepLimit = agent.nSteps + Number(resumeBench.steps);
|
|
417
|
+
}
|
|
418
|
+
if (!args._provided.has("wall") && Number(resumeBench.wall) > 0) {
|
|
419
|
+
agent.wallTimeLimitSeconds =
|
|
420
|
+
Math.ceil((Date.now() - agent.startTime) / 1000) + Number(resumeBench.wall);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
371
423
|
console.error(`\x1b[2m[astra] resumed ${sessionId} (${agent.messages.length} messages)\x1b[0m`);
|
|
372
424
|
}
|
|
373
425
|
|
|
@@ -423,6 +475,7 @@ async function main() {
|
|
|
423
475
|
`${Number(args.steps) > 0 ? ` turns<=${args.steps}` : ""}\x1b[0m`
|
|
424
476
|
);
|
|
425
477
|
console.error(`\x1b[2m[astra] workspace -> ${benchRun.workspace}\x1b[0m`);
|
|
478
|
+
console.error(`\x1b[2m[astra] session -> ${sessionId}\x1b[0m`);
|
|
426
479
|
} else {
|
|
427
480
|
console.error(
|
|
428
481
|
`\x1b[2m[astra] bench · model=${modelId} cwd=${cmdCwd}` +
|
|
@@ -431,12 +484,38 @@ async function main() {
|
|
|
431
484
|
}
|
|
432
485
|
}
|
|
433
486
|
|
|
434
|
-
|
|
435
|
-
|
|
487
|
+
let result;
|
|
488
|
+
try {
|
|
489
|
+
result = resumeDoc ? await continueAutonomous(agent) : await agent.run(task);
|
|
490
|
+
} catch (error) {
|
|
491
|
+
agent.exitStatus = "Error";
|
|
492
|
+
const segment = agent.bench?.segments?.at(-1);
|
|
493
|
+
if (segment) {
|
|
494
|
+
segment.finished_at = new Date().toISOString();
|
|
495
|
+
segment.exit_status = "Error";
|
|
496
|
+
segment.error = String(error?.message || error);
|
|
497
|
+
}
|
|
498
|
+
agent.save();
|
|
499
|
+
if (benchRun) {
|
|
500
|
+
writeMetrics({
|
|
501
|
+
runDir: benchRun.dir,
|
|
502
|
+
root: benchRun.root,
|
|
503
|
+
metrics: collectMetrics({ agent, model, reasoning }),
|
|
504
|
+
});
|
|
505
|
+
try { refreshReport(benchRun.root); } catch {}
|
|
506
|
+
}
|
|
507
|
+
throw error;
|
|
508
|
+
}
|
|
436
509
|
agent.save();
|
|
437
510
|
|
|
438
511
|
// Record benchmark metrics (per-run CSV + rolled-up index).
|
|
439
512
|
if (benchRun) {
|
|
513
|
+
const segment = agent.bench?.segments?.at(-1);
|
|
514
|
+
if (segment) {
|
|
515
|
+
segment.finished_at = new Date().toISOString();
|
|
516
|
+
segment.exit_status = result.exit_status;
|
|
517
|
+
}
|
|
518
|
+
agent.save();
|
|
440
519
|
const metrics = collectMetrics({ agent, model, reasoning });
|
|
441
520
|
const paths = writeMetrics({ runDir: benchRun.dir, root: benchRun.root, metrics });
|
|
442
521
|
if (!quiet) {
|
package/src/report.js
CHANGED
|
@@ -296,6 +296,7 @@ function readRunDir({ rootDir, slug, runId, dir }) {
|
|
|
296
296
|
tokens,
|
|
297
297
|
cost_usd: costSource ? num(metricsRow?.cost_usd ?? traj?.info?.cost?.usd) : null,
|
|
298
298
|
cost_source: costSource || "",
|
|
299
|
+
resume_segments: Array.isArray(traj?.bench?.segments) ? traj.bench.segments : [],
|
|
299
300
|
timeline,
|
|
300
301
|
paths: {
|
|
301
302
|
dir: path.relative(rootDir, dir),
|