@agentfield/sdk 0.1.130-rc.4 → 0.1.130
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/index.d.ts +24 -1
- package/dist/index.js +457 -172
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs, { promises, readFileSync } from 'fs';
|
|
2
|
-
import * as
|
|
3
|
-
import
|
|
2
|
+
import * as path3 from 'path';
|
|
3
|
+
import path3__default, { resolve, dirname } from 'path';
|
|
4
4
|
import { createRequire } from 'module';
|
|
5
5
|
import { spawn } from 'child_process';
|
|
6
6
|
import express from 'express';
|
|
@@ -197,7 +197,7 @@ function estimateTokens(text2) {
|
|
|
197
197
|
}
|
|
198
198
|
function writeSchemaFile(schemaJson, cwd) {
|
|
199
199
|
const filePath = getSchemaPath(cwd);
|
|
200
|
-
fs.mkdirSync(
|
|
200
|
+
fs.mkdirSync(path3__default.dirname(filePath), { recursive: true });
|
|
201
201
|
const fd = fs.openSync(filePath, "w", 384);
|
|
202
202
|
try {
|
|
203
203
|
fs.writeFileSync(fd, schemaJson, "utf8");
|
|
@@ -213,10 +213,10 @@ function validateAgainstSchema(data, schema) {
|
|
|
213
213
|
return data;
|
|
214
214
|
}
|
|
215
215
|
function getOutputPath(cwd) {
|
|
216
|
-
return
|
|
216
|
+
return path3__default.join(cwd, OUTPUT_FILENAME);
|
|
217
217
|
}
|
|
218
218
|
function getSchemaPath(cwd) {
|
|
219
|
-
return
|
|
219
|
+
return path3__default.join(cwd, SCHEMA_FILENAME);
|
|
220
220
|
}
|
|
221
221
|
function schemaToJsonSchema(schema) {
|
|
222
222
|
if (isRecord2(schema)) {
|
|
@@ -322,7 +322,7 @@ function parseAndValidate(filePath, schema) {
|
|
|
322
322
|
function cleanupTempFiles(cwd) {
|
|
323
323
|
for (const filename of [OUTPUT_FILENAME, SCHEMA_FILENAME]) {
|
|
324
324
|
try {
|
|
325
|
-
fs.unlinkSync(
|
|
325
|
+
fs.unlinkSync(path3__default.join(cwd, filename));
|
|
326
326
|
} catch {
|
|
327
327
|
}
|
|
328
328
|
}
|
|
@@ -361,6 +361,393 @@ var init_types = __esm({
|
|
|
361
361
|
"src/harness/types.ts"() {
|
|
362
362
|
}
|
|
363
363
|
});
|
|
364
|
+
function resolveIdleMs(idleSeconds) {
|
|
365
|
+
let seconds = idleSeconds;
|
|
366
|
+
if (seconds === void 0) {
|
|
367
|
+
const raw = process.env.AGENTFIELD_HARNESS_IDLE_SECONDS;
|
|
368
|
+
const parsed = raw !== void 0 ? Number(raw) : NaN;
|
|
369
|
+
seconds = Number.isFinite(parsed) ? parsed : DEFAULT_IDLE_SECONDS;
|
|
370
|
+
}
|
|
371
|
+
return seconds > 0 ? seconds * 1e3 : void 0;
|
|
372
|
+
}
|
|
373
|
+
function runCli(cmd, options) {
|
|
374
|
+
return new Promise((resolve3, reject) => {
|
|
375
|
+
const [bin, ...args] = cmd;
|
|
376
|
+
const env = { ...process.env, ...options?.env };
|
|
377
|
+
applyOpenRouterAttributionEnv(env);
|
|
378
|
+
const hasInput = options?.inputText !== void 0;
|
|
379
|
+
const proc = spawn(bin, args, {
|
|
380
|
+
env,
|
|
381
|
+
cwd: options?.cwd,
|
|
382
|
+
stdio: [hasInput ? "pipe" : "ignore", "pipe", "pipe"]
|
|
383
|
+
});
|
|
384
|
+
if (hasInput) {
|
|
385
|
+
proc.stdin?.end(options.inputText);
|
|
386
|
+
}
|
|
387
|
+
let stdout = "";
|
|
388
|
+
let stderr = "";
|
|
389
|
+
let settled = false;
|
|
390
|
+
let lastActivity = Date.now();
|
|
391
|
+
proc.stdout.on("data", (data) => {
|
|
392
|
+
stdout += data.toString();
|
|
393
|
+
lastActivity = Date.now();
|
|
394
|
+
});
|
|
395
|
+
proc.stderr.on("data", (data) => {
|
|
396
|
+
stderr += data.toString();
|
|
397
|
+
lastActivity = Date.now();
|
|
398
|
+
});
|
|
399
|
+
const timer = options?.timeout ? setTimeout(() => {
|
|
400
|
+
if (settled) {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
settled = true;
|
|
404
|
+
cleanup();
|
|
405
|
+
proc.kill("SIGKILL");
|
|
406
|
+
reject(new Error(`CLI timed out after ${options.timeout}ms`));
|
|
407
|
+
}, options.timeout) : void 0;
|
|
408
|
+
const idleMs = resolveIdleMs(options?.idleSeconds);
|
|
409
|
+
const idleTimer = idleMs ? setInterval(() => {
|
|
410
|
+
if (settled) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (Date.now() - lastActivity >= idleMs) {
|
|
414
|
+
settled = true;
|
|
415
|
+
cleanup();
|
|
416
|
+
proc.kill("SIGKILL");
|
|
417
|
+
reject(
|
|
418
|
+
new Error(
|
|
419
|
+
`CLI made no progress for ${Math.round(idleMs / 1e3)}s`
|
|
420
|
+
)
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
}, Math.min(idleMs, 1e3)) : void 0;
|
|
424
|
+
function cleanup() {
|
|
425
|
+
if (timer) {
|
|
426
|
+
clearTimeout(timer);
|
|
427
|
+
}
|
|
428
|
+
if (idleTimer) {
|
|
429
|
+
clearInterval(idleTimer);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
proc.on("close", (code) => {
|
|
433
|
+
if (settled) {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
settled = true;
|
|
437
|
+
cleanup();
|
|
438
|
+
resolve3({ stdout, stderr, exitCode: code ?? 0 });
|
|
439
|
+
});
|
|
440
|
+
proc.on("error", (err) => {
|
|
441
|
+
if (settled) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
settled = true;
|
|
445
|
+
cleanup();
|
|
446
|
+
reject(err);
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
function parseJsonl(text2) {
|
|
451
|
+
const events = [];
|
|
452
|
+
for (const line of text2.split("\n")) {
|
|
453
|
+
const trimmed = line.trim();
|
|
454
|
+
if (!trimmed) {
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
try {
|
|
458
|
+
events.push(JSON.parse(trimmed));
|
|
459
|
+
} catch {
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return events;
|
|
464
|
+
}
|
|
465
|
+
function extractFinalText(events) {
|
|
466
|
+
let result;
|
|
467
|
+
for (const event of events) {
|
|
468
|
+
const type = event.type;
|
|
469
|
+
if (type === "item.completed") {
|
|
470
|
+
const item = event.item;
|
|
471
|
+
if (typeof item === "object" && item !== null) {
|
|
472
|
+
const itemType = item.type;
|
|
473
|
+
const itemText = item.text;
|
|
474
|
+
if (itemType === "agent_message" && typeof itemText === "string") {
|
|
475
|
+
result = itemText;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
} else if (type === "result") {
|
|
479
|
+
const candidate = event.result ?? event.text;
|
|
480
|
+
if (typeof candidate === "string") {
|
|
481
|
+
result = candidate;
|
|
482
|
+
}
|
|
483
|
+
} else if (type === "turn.completed" && typeof event.text === "string") {
|
|
484
|
+
result = event.text;
|
|
485
|
+
} else if ((type === "message" || type === "assistant") && typeof event.content === "string") {
|
|
486
|
+
result = event.content;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return result;
|
|
490
|
+
}
|
|
491
|
+
var DEFAULT_IDLE_SECONDS;
|
|
492
|
+
var init_cli = __esm({
|
|
493
|
+
"src/harness/cli.ts"() {
|
|
494
|
+
init_openrouterAttribution();
|
|
495
|
+
DEFAULT_IDLE_SECONDS = 120;
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
// src/harness/providers/aforge.ts
|
|
500
|
+
var aforge_exports = {};
|
|
501
|
+
__export(aforge_exports, {
|
|
502
|
+
AFORGE_DEFAULT_MODEL: () => AFORGE_DEFAULT_MODEL,
|
|
503
|
+
AforgeProvider: () => AforgeProvider
|
|
504
|
+
});
|
|
505
|
+
function resolveMaxConcurrent() {
|
|
506
|
+
const parsed = Number.parseInt(process.env.AFORGE_MAX_CONCURRENT ?? "", 10);
|
|
507
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_CONCURRENT;
|
|
508
|
+
}
|
|
509
|
+
function stripOpenRouterPrefix(model) {
|
|
510
|
+
return model.startsWith("openrouter/") ? model.slice("openrouter/".length) : model;
|
|
511
|
+
}
|
|
512
|
+
function parseEnvelope(stdout) {
|
|
513
|
+
try {
|
|
514
|
+
const value = JSON.parse(stdout.trim());
|
|
515
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value) && ("deliverable" in value || "text" in value)) {
|
|
516
|
+
return value;
|
|
517
|
+
}
|
|
518
|
+
} catch {
|
|
519
|
+
}
|
|
520
|
+
const lines = stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
521
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
522
|
+
try {
|
|
523
|
+
const value = JSON.parse(lines[index]);
|
|
524
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value) && ("deliverable" in value || "text" in value)) {
|
|
525
|
+
return value;
|
|
526
|
+
}
|
|
527
|
+
} catch {
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return void 0;
|
|
531
|
+
}
|
|
532
|
+
function numeric(value) {
|
|
533
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
534
|
+
}
|
|
535
|
+
function timeoutSeconds() {
|
|
536
|
+
const parsed = Number.parseInt(process.env.AGENTFIELD_HARNESS_TIMEOUT_SECONDS ?? "", 10);
|
|
537
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_TIMEOUT_SECONDS;
|
|
538
|
+
}
|
|
539
|
+
function innerTimeout(outer) {
|
|
540
|
+
return outer > LANDING_WINDOW_SECONDS ? outer - LANDING_WINDOW_SECONDS : 1;
|
|
541
|
+
}
|
|
542
|
+
function taskInput(prompt, systemPrompt) {
|
|
543
|
+
return typeof systemPrompt === "string" && systemPrompt.trim() ? `${systemPrompt.trim()}
|
|
544
|
+
|
|
545
|
+
Task:
|
|
546
|
+
${prompt}` : prompt;
|
|
547
|
+
}
|
|
548
|
+
function crashMessage(exitCode, blockedOn, deliverable, stderr) {
|
|
549
|
+
const cleanStderr = stderr.trim().replace(ANSI_PATTERN, "");
|
|
550
|
+
const exitContext = `aforge exit code ${exitCode}`;
|
|
551
|
+
let message = exitCode < 0 ? `Process killed by signal ${-exitCode}. ${exitContext}` : exitContext;
|
|
552
|
+
if (cleanStderr) {
|
|
553
|
+
message += `. stderr: ${cleanStderr.slice(0, 1e3)}`;
|
|
554
|
+
} else if (blockedOn) {
|
|
555
|
+
message += `. blocked_on: ${blockedOn.slice(0, 1e3)}`;
|
|
556
|
+
} else if (deliverable) {
|
|
557
|
+
message += `. partial: ${deliverable.slice(0, 1e3)}`;
|
|
558
|
+
}
|
|
559
|
+
return message;
|
|
560
|
+
}
|
|
561
|
+
function stringOptions(value) {
|
|
562
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
563
|
+
return {};
|
|
564
|
+
}
|
|
565
|
+
const result = {};
|
|
566
|
+
for (const [key, item] of Object.entries(value)) {
|
|
567
|
+
if (typeof item === "string") {
|
|
568
|
+
result[key] = item;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return result;
|
|
572
|
+
}
|
|
573
|
+
var REASONING_VARIANTS, DEFAULT_TIMEOUT_SECONDS, LANDING_WINDOW_SECONDS, DEFAULT_MAX_CONCURRENT, ANSI_PATTERN, AFORGE_DEFAULT_MODEL, Semaphore, aforgeSemaphore, AforgeProvider;
|
|
574
|
+
var init_aforge = __esm({
|
|
575
|
+
"src/harness/providers/aforge.ts"() {
|
|
576
|
+
init_types();
|
|
577
|
+
init_cli();
|
|
578
|
+
init_modelVariant();
|
|
579
|
+
REASONING_VARIANTS = /* @__PURE__ */ new Set(["off", "low", "medium", "high"]);
|
|
580
|
+
DEFAULT_TIMEOUT_SECONDS = 1800;
|
|
581
|
+
LANDING_WINDOW_SECONDS = 5;
|
|
582
|
+
DEFAULT_MAX_CONCURRENT = 8;
|
|
583
|
+
ANSI_PATTERN = /\x1B\[[0-?]*[ -/]*[@-~]/g;
|
|
584
|
+
AFORGE_DEFAULT_MODEL = "~deepseek/deepseek-v4-flash-latest";
|
|
585
|
+
Semaphore = class {
|
|
586
|
+
constructor(limit) {
|
|
587
|
+
this.limit = limit;
|
|
588
|
+
}
|
|
589
|
+
limit;
|
|
590
|
+
active = 0;
|
|
591
|
+
waiters = [];
|
|
592
|
+
async use(operation) {
|
|
593
|
+
await this.acquire();
|
|
594
|
+
try {
|
|
595
|
+
return await operation();
|
|
596
|
+
} finally {
|
|
597
|
+
this.release();
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
acquire() {
|
|
601
|
+
if (this.active < this.limit) {
|
|
602
|
+
this.active += 1;
|
|
603
|
+
return Promise.resolve();
|
|
604
|
+
}
|
|
605
|
+
return new Promise((resolve3) => {
|
|
606
|
+
this.waiters.push(() => {
|
|
607
|
+
this.active += 1;
|
|
608
|
+
resolve3();
|
|
609
|
+
});
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
release() {
|
|
613
|
+
this.active -= 1;
|
|
614
|
+
this.waiters.shift()?.();
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
aforgeSemaphore = new Semaphore(resolveMaxConcurrent());
|
|
618
|
+
AforgeProvider = class {
|
|
619
|
+
bin;
|
|
620
|
+
constructor(bin = "aforge") {
|
|
621
|
+
this.bin = bin === "aforge" ? process.env.AFORGE_BIN?.trim() || bin : bin;
|
|
622
|
+
}
|
|
623
|
+
async execute(prompt, options) {
|
|
624
|
+
return aforgeSemaphore.use(() => this.executeImpl(prompt, options));
|
|
625
|
+
}
|
|
626
|
+
async executeImpl(prompt, options) {
|
|
627
|
+
const projectDir = typeof options.projectDir === "string" ? options.projectDir : typeof options.project_dir === "string" ? options.project_dir : void 0;
|
|
628
|
+
const cwd = typeof options.cwd === "string" ? options.cwd : void 0;
|
|
629
|
+
const root = projectDir ?? cwd ?? ".";
|
|
630
|
+
const outerTimeout = timeoutSeconds();
|
|
631
|
+
const command = (process.env.AGENTFIELD_AFORGE_COMMAND ?? "exec").trim().toLowerCase();
|
|
632
|
+
if (command !== "do" && command !== "exec") {
|
|
633
|
+
return createRawResult({
|
|
634
|
+
isError: true,
|
|
635
|
+
errorMessage: `AGENTFIELD_AFORGE_COMMAND must be 'do' or 'exec', got ${JSON.stringify(command)}`,
|
|
636
|
+
failureType: "crash",
|
|
637
|
+
metrics: createMetrics()
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
const systemPrompt = options.systemPrompt ?? options.system_prompt;
|
|
641
|
+
const cmd = command === "exec" ? [
|
|
642
|
+
this.bin,
|
|
643
|
+
"exec",
|
|
644
|
+
"--json",
|
|
645
|
+
"-w",
|
|
646
|
+
root,
|
|
647
|
+
"--timeout",
|
|
648
|
+
String(innerTimeout(outerTimeout))
|
|
649
|
+
] : [
|
|
650
|
+
this.bin,
|
|
651
|
+
"do",
|
|
652
|
+
"--json",
|
|
653
|
+
"--yes-spend",
|
|
654
|
+
"-w",
|
|
655
|
+
root,
|
|
656
|
+
"--timeout",
|
|
657
|
+
String(innerTimeout(outerTimeout))
|
|
658
|
+
];
|
|
659
|
+
if (command === "exec" && typeof options.maxTurns === "number" && Number.isFinite(options.maxTurns) && options.maxTurns > 0) {
|
|
660
|
+
cmd.push("--turns", String(Math.trunc(options.maxTurns)));
|
|
661
|
+
}
|
|
662
|
+
if (command === "exec") {
|
|
663
|
+
cmd.push("--context-fill", "60", "--completion-reserve", "65536");
|
|
664
|
+
}
|
|
665
|
+
if (command === "exec" && typeof systemPrompt === "string" && systemPrompt.trim()) {
|
|
666
|
+
cmd.push("--system", systemPrompt.trim());
|
|
667
|
+
}
|
|
668
|
+
const { model, variant } = resolveModelAndVariant(options);
|
|
669
|
+
const env = command === "exec" ? { AFORGE_MODELS: "" } : {};
|
|
670
|
+
if (model) {
|
|
671
|
+
const slug = stripOpenRouterPrefix(model);
|
|
672
|
+
env.AFORGE_MODEL = slug;
|
|
673
|
+
if (command === "exec") {
|
|
674
|
+
cmd.push("--model", slug, "--plan-model", slug);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (variant) {
|
|
678
|
+
const normalized = variant.trim().toLowerCase();
|
|
679
|
+
if (REASONING_VARIANTS.has(normalized)) {
|
|
680
|
+
env.AFORGE_EXEC_REASONING = normalized;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
Object.assign(env, stringOptions(options.env));
|
|
684
|
+
const effectiveModel = model || env.AFORGE_MODEL || AFORGE_DEFAULT_MODEL;
|
|
685
|
+
const startApi = Date.now();
|
|
686
|
+
try {
|
|
687
|
+
const { stdout, stderr, exitCode } = await runCli(cmd, {
|
|
688
|
+
env,
|
|
689
|
+
cwd: void 0,
|
|
690
|
+
timeout: outerTimeout * 1e3,
|
|
691
|
+
idleSeconds: 0,
|
|
692
|
+
inputText: command === "exec" ? prompt : taskInput(prompt, systemPrompt)
|
|
693
|
+
});
|
|
694
|
+
const envelope = parseEnvelope(stdout);
|
|
695
|
+
const outputValue = command === "exec" ? envelope?.text : envelope?.deliverable;
|
|
696
|
+
const resultText = typeof outputValue === "string" && outputValue.trim() ? outputValue.trim() : void 0;
|
|
697
|
+
const blockedOn = typeof envelope?.blocked_on === "string" ? envelope.blocked_on.trim() : "";
|
|
698
|
+
const stop = typeof envelope?.stop === "string" ? envelope.stop.trim() : "";
|
|
699
|
+
const usage = typeof envelope?.usage === "object" && envelope.usage !== null && !Array.isArray(envelope.usage) ? envelope.usage : {};
|
|
700
|
+
const isError = command === "exec" ? exitCode < 0 || resultText === void 0 || ![0, 2, 3].includes(exitCode) : exitCode !== 0 || resultText === void 0 || blockedOn !== "";
|
|
701
|
+
const inputTokens = Math.trunc(numeric(usage.prompt_tokens) ?? 0);
|
|
702
|
+
const outputTokens = Math.trunc(numeric(usage.completion_tokens) ?? 0);
|
|
703
|
+
const cacheReadTokens = Math.trunc(numeric(usage.cached_tokens) ?? 0);
|
|
704
|
+
const calls = Math.trunc(numeric(command === "exec" ? envelope?.turns : usage.calls) ?? 0);
|
|
705
|
+
const nativeSpend = numeric(envelope?.spend);
|
|
706
|
+
const legacyCost = numeric(usage.cost);
|
|
707
|
+
const providerCost = nativeSpend !== void 0 && nativeSpend > 0 ? nativeSpend : legacyCost;
|
|
708
|
+
return createRawResult({
|
|
709
|
+
result: resultText,
|
|
710
|
+
messages: envelope ? [envelope] : [],
|
|
711
|
+
metrics: createMetrics({
|
|
712
|
+
durationApiMs: Date.now() - startApi,
|
|
713
|
+
numTurns: calls,
|
|
714
|
+
totalCostUsd: providerCost !== void 0 && providerCost > 0 ? providerCost : void 0,
|
|
715
|
+
usage,
|
|
716
|
+
sessionId: "",
|
|
717
|
+
inputTokens,
|
|
718
|
+
outputTokens,
|
|
719
|
+
cacheReadTokens,
|
|
720
|
+
cacheCreationTokens: 0,
|
|
721
|
+
totalTokens: inputTokens + outputTokens,
|
|
722
|
+
model: effectiveModel
|
|
723
|
+
}),
|
|
724
|
+
isError,
|
|
725
|
+
errorMessage: isError ? crashMessage(exitCode, blockedOn || stop, resultText, stderr) : void 0,
|
|
726
|
+
failureType: isError ? command === "do" && exitCode === 2 || command === "exec" && exitCode === 4 ? "timeout" : "crash" : "none",
|
|
727
|
+
returnCode: exitCode
|
|
728
|
+
});
|
|
729
|
+
} catch (error) {
|
|
730
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
731
|
+
if (message.includes("ENOENT")) {
|
|
732
|
+
return createRawResult({
|
|
733
|
+
isError: true,
|
|
734
|
+
errorMessage: `AForge binary not found at '${this.bin}'. Install it with \`af aforge ensure\`, or set AFORGE_BIN to its path.`,
|
|
735
|
+
failureType: "crash",
|
|
736
|
+
metrics: createMetrics({ durationApiMs: Date.now() - startApi })
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
const timedOut = /timed out|deadline exceeded|no progress/i.test(message);
|
|
740
|
+
return createRawResult({
|
|
741
|
+
isError: true,
|
|
742
|
+
errorMessage: message,
|
|
743
|
+
failureType: timedOut ? "timeout" : "crash",
|
|
744
|
+
metrics: createMetrics({ durationApiMs: Date.now() - startApi })
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
});
|
|
364
751
|
|
|
365
752
|
// src/harness/providers/claude.ts
|
|
366
753
|
var claude_exports = {};
|
|
@@ -499,136 +886,6 @@ var init_claude = __esm({
|
|
|
499
886
|
};
|
|
500
887
|
}
|
|
501
888
|
});
|
|
502
|
-
function resolveIdleMs(idleSeconds) {
|
|
503
|
-
let seconds = idleSeconds;
|
|
504
|
-
if (seconds === void 0) {
|
|
505
|
-
const raw = process.env.AGENTFIELD_HARNESS_IDLE_SECONDS;
|
|
506
|
-
const parsed = raw !== void 0 ? Number(raw) : NaN;
|
|
507
|
-
seconds = Number.isFinite(parsed) ? parsed : DEFAULT_IDLE_SECONDS;
|
|
508
|
-
}
|
|
509
|
-
return seconds > 0 ? seconds * 1e3 : void 0;
|
|
510
|
-
}
|
|
511
|
-
function runCli(cmd, options) {
|
|
512
|
-
return new Promise((resolve3, reject) => {
|
|
513
|
-
const [bin, ...args] = cmd;
|
|
514
|
-
const env = { ...process.env, ...options?.env };
|
|
515
|
-
applyOpenRouterAttributionEnv(env);
|
|
516
|
-
const proc = spawn(bin, args, {
|
|
517
|
-
env,
|
|
518
|
-
cwd: options?.cwd,
|
|
519
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
520
|
-
});
|
|
521
|
-
let stdout = "";
|
|
522
|
-
let stderr = "";
|
|
523
|
-
let settled = false;
|
|
524
|
-
let lastActivity = Date.now();
|
|
525
|
-
proc.stdout.on("data", (data) => {
|
|
526
|
-
stdout += data.toString();
|
|
527
|
-
lastActivity = Date.now();
|
|
528
|
-
});
|
|
529
|
-
proc.stderr.on("data", (data) => {
|
|
530
|
-
stderr += data.toString();
|
|
531
|
-
lastActivity = Date.now();
|
|
532
|
-
});
|
|
533
|
-
const timer = options?.timeout ? setTimeout(() => {
|
|
534
|
-
if (settled) {
|
|
535
|
-
return;
|
|
536
|
-
}
|
|
537
|
-
settled = true;
|
|
538
|
-
cleanup();
|
|
539
|
-
proc.kill("SIGKILL");
|
|
540
|
-
reject(new Error(`CLI timed out after ${options.timeout}ms`));
|
|
541
|
-
}, options.timeout) : void 0;
|
|
542
|
-
const idleMs = resolveIdleMs(options?.idleSeconds);
|
|
543
|
-
const idleTimer = idleMs ? setInterval(() => {
|
|
544
|
-
if (settled) {
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
547
|
-
if (Date.now() - lastActivity >= idleMs) {
|
|
548
|
-
settled = true;
|
|
549
|
-
cleanup();
|
|
550
|
-
proc.kill("SIGKILL");
|
|
551
|
-
reject(
|
|
552
|
-
new Error(
|
|
553
|
-
`CLI made no progress for ${Math.round(idleMs / 1e3)}s`
|
|
554
|
-
)
|
|
555
|
-
);
|
|
556
|
-
}
|
|
557
|
-
}, Math.min(idleMs, 1e3)) : void 0;
|
|
558
|
-
function cleanup() {
|
|
559
|
-
if (timer) {
|
|
560
|
-
clearTimeout(timer);
|
|
561
|
-
}
|
|
562
|
-
if (idleTimer) {
|
|
563
|
-
clearInterval(idleTimer);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
proc.on("close", (code) => {
|
|
567
|
-
if (settled) {
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
|
-
settled = true;
|
|
571
|
-
cleanup();
|
|
572
|
-
resolve3({ stdout, stderr, exitCode: code ?? 0 });
|
|
573
|
-
});
|
|
574
|
-
proc.on("error", (err) => {
|
|
575
|
-
if (settled) {
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
settled = true;
|
|
579
|
-
cleanup();
|
|
580
|
-
reject(err);
|
|
581
|
-
});
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
function parseJsonl(text2) {
|
|
585
|
-
const events = [];
|
|
586
|
-
for (const line of text2.split("\n")) {
|
|
587
|
-
const trimmed = line.trim();
|
|
588
|
-
if (!trimmed) {
|
|
589
|
-
continue;
|
|
590
|
-
}
|
|
591
|
-
try {
|
|
592
|
-
events.push(JSON.parse(trimmed));
|
|
593
|
-
} catch {
|
|
594
|
-
continue;
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
return events;
|
|
598
|
-
}
|
|
599
|
-
function extractFinalText(events) {
|
|
600
|
-
let result;
|
|
601
|
-
for (const event of events) {
|
|
602
|
-
const type = event.type;
|
|
603
|
-
if (type === "item.completed") {
|
|
604
|
-
const item = event.item;
|
|
605
|
-
if (typeof item === "object" && item !== null) {
|
|
606
|
-
const itemType = item.type;
|
|
607
|
-
const itemText = item.text;
|
|
608
|
-
if (itemType === "agent_message" && typeof itemText === "string") {
|
|
609
|
-
result = itemText;
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
} else if (type === "result") {
|
|
613
|
-
const candidate = event.result ?? event.text;
|
|
614
|
-
if (typeof candidate === "string") {
|
|
615
|
-
result = candidate;
|
|
616
|
-
}
|
|
617
|
-
} else if (type === "turn.completed" && typeof event.text === "string") {
|
|
618
|
-
result = event.text;
|
|
619
|
-
} else if ((type === "message" || type === "assistant") && typeof event.content === "string") {
|
|
620
|
-
result = event.content;
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
return result;
|
|
624
|
-
}
|
|
625
|
-
var DEFAULT_IDLE_SECONDS;
|
|
626
|
-
var init_cli = __esm({
|
|
627
|
-
"src/harness/cli.ts"() {
|
|
628
|
-
init_openrouterAttribution();
|
|
629
|
-
DEFAULT_IDLE_SECONDS = 120;
|
|
630
|
-
}
|
|
631
|
-
});
|
|
632
889
|
|
|
633
890
|
// src/harness/providers/codex.ts
|
|
634
891
|
var codex_exports = {};
|
|
@@ -875,34 +1132,49 @@ ${prompt}`;
|
|
|
875
1132
|
});
|
|
876
1133
|
|
|
877
1134
|
// src/harness/providers/factory.ts
|
|
1135
|
+
function resolveProviderName(explicit) {
|
|
1136
|
+
const explicitName = explicit?.trim();
|
|
1137
|
+
if (explicitName) {
|
|
1138
|
+
return explicitName;
|
|
1139
|
+
}
|
|
1140
|
+
const envName = process.env[HARNESS_PROVIDER_ENV_VAR]?.trim();
|
|
1141
|
+
return envName || DEFAULT_HARNESS_PROVIDER;
|
|
1142
|
+
}
|
|
878
1143
|
async function buildProvider(config) {
|
|
879
|
-
|
|
1144
|
+
const provider = resolveProviderName(config.provider);
|
|
1145
|
+
if (!SUPPORTED_PROVIDERS.has(provider)) {
|
|
880
1146
|
throw new Error(
|
|
881
|
-
`Unknown harness provider: "${
|
|
1147
|
+
`Unknown harness provider: "${provider}". Supported: ${[...SUPPORTED_PROVIDERS].sort().join(", ")}`
|
|
882
1148
|
);
|
|
883
1149
|
}
|
|
884
|
-
if (
|
|
1150
|
+
if (provider === "aforge") {
|
|
1151
|
+
const { AforgeProvider: AforgeProvider2 } = await Promise.resolve().then(() => (init_aforge(), aforge_exports));
|
|
1152
|
+
return new AforgeProvider2(config.aforgeBin ?? "aforge");
|
|
1153
|
+
}
|
|
1154
|
+
if (provider === "claude-code") {
|
|
885
1155
|
const { ClaudeCodeProvider: ClaudeCodeProvider2 } = await Promise.resolve().then(() => (init_claude(), claude_exports));
|
|
886
1156
|
return new ClaudeCodeProvider2();
|
|
887
1157
|
}
|
|
888
|
-
if (
|
|
1158
|
+
if (provider === "codex") {
|
|
889
1159
|
const { CodexProvider: CodexProvider2 } = await Promise.resolve().then(() => (init_codex(), codex_exports));
|
|
890
1160
|
return new CodexProvider2(config.codexBin ?? "codex");
|
|
891
1161
|
}
|
|
892
|
-
if (
|
|
1162
|
+
if (provider === "gemini") {
|
|
893
1163
|
const { GeminiProvider: GeminiProvider2 } = await Promise.resolve().then(() => (init_gemini(), gemini_exports));
|
|
894
1164
|
return new GeminiProvider2(config.geminiBin ?? "gemini");
|
|
895
1165
|
}
|
|
896
|
-
if (
|
|
1166
|
+
if (provider === "opencode") {
|
|
897
1167
|
const { OpenCodeProvider: OpenCodeProvider2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
|
|
898
1168
|
return new OpenCodeProvider2(config.opencodeBin ?? "opencode");
|
|
899
1169
|
}
|
|
900
|
-
throw new Error(`Provider "${
|
|
1170
|
+
throw new Error(`Provider "${provider}" is not yet implemented.`);
|
|
901
1171
|
}
|
|
902
|
-
var SUPPORTED_PROVIDERS;
|
|
1172
|
+
var SUPPORTED_PROVIDERS, DEFAULT_HARNESS_PROVIDER, HARNESS_PROVIDER_ENV_VAR;
|
|
903
1173
|
var init_factory = __esm({
|
|
904
1174
|
"src/harness/providers/factory.ts"() {
|
|
905
|
-
SUPPORTED_PROVIDERS = /* @__PURE__ */ new Set(["claude-code", "codex", "gemini", "opencode"]);
|
|
1175
|
+
SUPPORTED_PROVIDERS = /* @__PURE__ */ new Set(["aforge", "claude-code", "codex", "gemini", "opencode"]);
|
|
1176
|
+
DEFAULT_HARNESS_PROVIDER = "aforge";
|
|
1177
|
+
HARNESS_PROVIDER_ENV_VAR = "AGENTFIELD_HARNESS_PROVIDER";
|
|
906
1178
|
}
|
|
907
1179
|
});
|
|
908
1180
|
|
|
@@ -945,22 +1217,28 @@ var init_runner = __esm({
|
|
|
945
1217
|
async run(prompt, options = {}) {
|
|
946
1218
|
const { schema, ...rest } = options;
|
|
947
1219
|
const resolved = this.resolveOptions(this.config, rest);
|
|
948
|
-
|
|
949
|
-
throw new Error("No harness provider specified. Set 'provider' in HarnessConfig or pass it to .harness() call.");
|
|
950
|
-
}
|
|
951
|
-
const cwd = resolved.cwd ?? ".";
|
|
1220
|
+
resolved.provider = resolveProviderName(resolved.provider);
|
|
952
1221
|
const provider = await this.buildProvider(resolved.provider, resolved);
|
|
953
|
-
const
|
|
1222
|
+
const cwd = resolved.cwd ?? ".";
|
|
1223
|
+
const outputRoot = resolved.projectDir ?? cwd;
|
|
1224
|
+
let outputDir;
|
|
1225
|
+
if (schema !== void 0) {
|
|
1226
|
+
fs.mkdirSync(outputRoot, { recursive: true });
|
|
1227
|
+
outputDir = fs.mkdtempSync(path3__default.join(outputRoot, ".agentfield-out-"));
|
|
1228
|
+
}
|
|
1229
|
+
const effectivePrompt = schema === void 0 ? prompt : `${prompt}${buildPromptSuffix(schema, outputDir)}`;
|
|
954
1230
|
const startTime = Date.now();
|
|
955
1231
|
try {
|
|
956
1232
|
const raw = await this.executeWithRetry(provider, effectivePrompt, resolved);
|
|
957
1233
|
if (schema !== void 0) {
|
|
958
|
-
return this.handleSchemaOutput(raw, schema,
|
|
1234
|
+
return this.handleSchemaOutput(raw, schema, outputDir, startTime);
|
|
959
1235
|
}
|
|
960
1236
|
return createHarnessResult({
|
|
961
1237
|
result: raw.result,
|
|
962
1238
|
isError: raw.isError,
|
|
963
1239
|
errorMessage: raw.errorMessage,
|
|
1240
|
+
failureType: raw.failureType,
|
|
1241
|
+
returnCode: raw.returnCode,
|
|
964
1242
|
costUsd: raw.metrics.totalCostUsd,
|
|
965
1243
|
numTurns: raw.metrics.numTurns,
|
|
966
1244
|
durationMs: Date.now() - startTime,
|
|
@@ -970,7 +1248,8 @@ var init_runner = __esm({
|
|
|
970
1248
|
});
|
|
971
1249
|
} finally {
|
|
972
1250
|
if (schema !== void 0) {
|
|
973
|
-
cleanupTempFiles(
|
|
1251
|
+
cleanupTempFiles(outputDir);
|
|
1252
|
+
fs.rmSync(outputDir, { recursive: true, force: true });
|
|
974
1253
|
}
|
|
975
1254
|
}
|
|
976
1255
|
}
|
|
@@ -992,6 +1271,8 @@ var init_runner = __esm({
|
|
|
992
1271
|
"systemPrompt",
|
|
993
1272
|
"env",
|
|
994
1273
|
"cwd",
|
|
1274
|
+
"projectDir",
|
|
1275
|
+
"aforgeBin",
|
|
995
1276
|
"codexBin",
|
|
996
1277
|
"geminiBin",
|
|
997
1278
|
"opencodeBin"
|
|
@@ -1056,6 +1337,8 @@ var init_runner = __esm({
|
|
|
1056
1337
|
result: raw.result,
|
|
1057
1338
|
parsed,
|
|
1058
1339
|
isError: false,
|
|
1340
|
+
failureType: raw.failureType,
|
|
1341
|
+
returnCode: raw.returnCode,
|
|
1059
1342
|
costUsd: raw.metrics.totalCostUsd,
|
|
1060
1343
|
numTurns: raw.metrics.numTurns,
|
|
1061
1344
|
durationMs: Date.now() - startTime,
|
|
@@ -1068,6 +1351,8 @@ var init_runner = __esm({
|
|
|
1068
1351
|
result: raw.result,
|
|
1069
1352
|
isError: true,
|
|
1070
1353
|
errorMessage: "Schema validation failed after parse and cosmetic repair attempts.",
|
|
1354
|
+
failureType: "schema",
|
|
1355
|
+
returnCode: raw.returnCode,
|
|
1071
1356
|
costUsd: raw.metrics.totalCostUsd,
|
|
1072
1357
|
numTurns: raw.metrics.numTurns,
|
|
1073
1358
|
durationMs: Date.now() - startTime,
|
|
@@ -5849,22 +6134,22 @@ var Agent = class {
|
|
|
5849
6134
|
},
|
|
5850
6135
|
message: { error: "rate_limit_exceeded", message: "Too many authentication attempts. Try again later." },
|
|
5851
6136
|
skip: (req) => {
|
|
5852
|
-
const
|
|
5853
|
-
if (!
|
|
6137
|
+
const path4 = req.path;
|
|
6138
|
+
if (!path4.startsWith("/reasoners/") && !path4.startsWith("/skills/") && !path4.startsWith("/execute") && !path4.startsWith("/api/v1/reasoners/") && !path4.startsWith("/api/v1/skills/")) {
|
|
5854
6139
|
return true;
|
|
5855
6140
|
}
|
|
5856
|
-
const parts =
|
|
6141
|
+
const parts = path4.replace(/^\/+/, "").split("/");
|
|
5857
6142
|
const funcName = parts[parts.length - 1] ?? "";
|
|
5858
6143
|
return realtimeFunctions.has(funcName);
|
|
5859
6144
|
}
|
|
5860
6145
|
});
|
|
5861
6146
|
this.app.use(authRateLimiter);
|
|
5862
6147
|
this.app.use(async (req, res, next) => {
|
|
5863
|
-
const
|
|
5864
|
-
if (!
|
|
6148
|
+
const path4 = req.path;
|
|
6149
|
+
if (!path4.startsWith("/reasoners/") && !path4.startsWith("/skills/") && !path4.startsWith("/execute") && !path4.startsWith("/api/v1/reasoners/") && !path4.startsWith("/api/v1/skills/")) {
|
|
5865
6150
|
return next();
|
|
5866
6151
|
}
|
|
5867
|
-
const parts =
|
|
6152
|
+
const parts = path4.replace(/^\/+/, "").split("/");
|
|
5868
6153
|
const funcName = parts[parts.length - 1] ?? "";
|
|
5869
6154
|
if (realtimeFunctions.has(funcName)) {
|
|
5870
6155
|
return next();
|
|
@@ -6064,9 +6349,9 @@ var Agent = class {
|
|
|
6064
6349
|
return handler(req, res);
|
|
6065
6350
|
}
|
|
6066
6351
|
async handleServerlessEvent(event) {
|
|
6067
|
-
const
|
|
6352
|
+
const path4 = event?.path ?? event?.rawPath ?? "";
|
|
6068
6353
|
const action = event?.action ?? "";
|
|
6069
|
-
if (
|
|
6354
|
+
if (path4 === "/discover" || action === "discover") {
|
|
6070
6355
|
return {
|
|
6071
6356
|
statusCode: 200,
|
|
6072
6357
|
headers: { "content-type": "application/json" },
|
|
@@ -6075,7 +6360,7 @@ var Agent = class {
|
|
|
6075
6360
|
}
|
|
6076
6361
|
const body = this.normalizeEventBody(event);
|
|
6077
6362
|
const invocation = this.extractInvocationDetails({
|
|
6078
|
-
path:
|
|
6363
|
+
path: path4,
|
|
6079
6364
|
query: event?.queryStringParameters,
|
|
6080
6365
|
body,
|
|
6081
6366
|
reasoner: event?.reasoner,
|
|
@@ -6154,9 +6439,9 @@ var Agent = class {
|
|
|
6154
6439
|
const input = this.normalizeInputPayload(params.body);
|
|
6155
6440
|
return { name: name ?? void 0, targetType: typeValue, input };
|
|
6156
6441
|
}
|
|
6157
|
-
parsePathTarget(
|
|
6158
|
-
if (!
|
|
6159
|
-
const normalized =
|
|
6442
|
+
parsePathTarget(path4) {
|
|
6443
|
+
if (!path4) return {};
|
|
6444
|
+
const normalized = path4.split("?")[0];
|
|
6160
6445
|
const reasonerMatch = normalized.match(/\/reasoners\/([^/]+)/);
|
|
6161
6446
|
if (reasonerMatch?.[1]) {
|
|
6162
6447
|
return { name: reasonerMatch[1], targetType: "reasoner" };
|
|
@@ -7326,7 +7611,7 @@ var MultimodalResponse = class {
|
|
|
7326
7611
|
*/
|
|
7327
7612
|
async saveImage(image, imagePath) {
|
|
7328
7613
|
const bytes = await this.getImageBytes(image);
|
|
7329
|
-
await promises.mkdir(
|
|
7614
|
+
await promises.mkdir(path3.dirname(imagePath), { recursive: true });
|
|
7330
7615
|
await promises.writeFile(imagePath, bytes);
|
|
7331
7616
|
}
|
|
7332
7617
|
/**
|
|
@@ -7334,7 +7619,7 @@ var MultimodalResponse = class {
|
|
|
7334
7619
|
*/
|
|
7335
7620
|
async saveAudio(audio, audioPath) {
|
|
7336
7621
|
const bytes = await this.getAudioBytes(audio);
|
|
7337
|
-
await promises.mkdir(
|
|
7622
|
+
await promises.mkdir(path3.dirname(audioPath), { recursive: true });
|
|
7338
7623
|
await promises.writeFile(audioPath, bytes);
|
|
7339
7624
|
}
|
|
7340
7625
|
/**
|
|
@@ -7342,7 +7627,7 @@ var MultimodalResponse = class {
|
|
|
7342
7627
|
*/
|
|
7343
7628
|
async saveFile(file, filePath) {
|
|
7344
7629
|
const bytes = await this.getFileBytes(file);
|
|
7345
|
-
await promises.mkdir(
|
|
7630
|
+
await promises.mkdir(path3.dirname(filePath), { recursive: true });
|
|
7346
7631
|
await promises.writeFile(filePath, bytes);
|
|
7347
7632
|
}
|
|
7348
7633
|
/**
|
|
@@ -7353,7 +7638,7 @@ var MultimodalResponse = class {
|
|
|
7353
7638
|
const savedFiles = {};
|
|
7354
7639
|
await promises.mkdir(outputDir, { recursive: true });
|
|
7355
7640
|
if (this._audio) {
|
|
7356
|
-
const audioPath =
|
|
7641
|
+
const audioPath = path3.join(outputDir, `${prefix}_audio.${this._audio.format}`);
|
|
7357
7642
|
await this.saveAudio(this._audio, audioPath);
|
|
7358
7643
|
savedFiles["audio"] = audioPath;
|
|
7359
7644
|
}
|
|
@@ -7361,22 +7646,22 @@ var MultimodalResponse = class {
|
|
|
7361
7646
|
const image = this._images[i];
|
|
7362
7647
|
let ext = "png";
|
|
7363
7648
|
if (image.url) {
|
|
7364
|
-
const urlExt =
|
|
7649
|
+
const urlExt = path3.extname(image.url).slice(1);
|
|
7365
7650
|
if (urlExt) ext = urlExt;
|
|
7366
7651
|
}
|
|
7367
|
-
const imagePath =
|
|
7652
|
+
const imagePath = path3.join(outputDir, `${prefix}_image_${i}.${ext}`);
|
|
7368
7653
|
await this.saveImage(image, imagePath);
|
|
7369
7654
|
savedFiles[`image_${i}`] = imagePath;
|
|
7370
7655
|
}
|
|
7371
7656
|
for (let i = 0; i < this._files.length; i++) {
|
|
7372
7657
|
const file = this._files[i];
|
|
7373
7658
|
const filename = file.filename || `${prefix}_file_${i}`;
|
|
7374
|
-
const filePath =
|
|
7659
|
+
const filePath = path3.join(outputDir, filename);
|
|
7375
7660
|
await this.saveFile(file, filePath);
|
|
7376
7661
|
savedFiles[`file_${i}`] = filePath;
|
|
7377
7662
|
}
|
|
7378
7663
|
if (this._text) {
|
|
7379
|
-
const textPath =
|
|
7664
|
+
const textPath = path3.join(outputDir, `${prefix}_text.txt`);
|
|
7380
7665
|
await promises.writeFile(textPath, this._text, "utf-8");
|
|
7381
7666
|
savedFiles["text"] = textPath;
|
|
7382
7667
|
}
|