@tangle-network/agent-runtime 0.83.0 → 0.85.0

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.
@@ -15,7 +15,7 @@ import {
15
15
  settledToIteration,
16
16
  supervise,
17
17
  withDriverExecutor
18
- } from "./chunk-SSA6OERV.js";
18
+ } from "./chunk-XN5TIJGP.js";
19
19
  import {
20
20
  addTokenUsage,
21
21
  isAbortError,
@@ -24,8 +24,12 @@ import {
24
24
  stringifySafe,
25
25
  zeroTokenUsage
26
26
  } from "./chunk-BZF3KQ6G.js";
27
+ import {
28
+ mapSandboxEvent
29
+ } from "./chunk-4J6RBI3K.js";
27
30
  import {
28
31
  AnalystError,
32
+ BackendTransportError,
29
33
  PlannerError,
30
34
  ValidationError
31
35
  } from "./chunk-YEJR7IXO.js";
@@ -604,6 +608,487 @@ function deterministicCompletion(check) {
604
608
  };
605
609
  }
606
610
 
611
+ // src/runtime/define-leaderboard.ts
612
+ import { execFileSync } from "child_process";
613
+ import { mkdirSync, writeFileSync } from "fs";
614
+ import { tmpdir } from "os";
615
+ import { join } from "path";
616
+ import {
617
+ CODING_HARNESSES,
618
+ expandProfileAxes,
619
+ harnessAxisOf
620
+ } from "@tangle-network/agent-eval";
621
+ import {
622
+ runProfileMatrix
623
+ } from "@tangle-network/agent-eval/campaign";
624
+ import { collectAgentResponseText } from "@tangle-network/sandbox";
625
+
626
+ // src/runtime/report-usage.ts
627
+ function reportLoopUsage(cost, result, source = "loop") {
628
+ cost.observe(result.costUsd, source);
629
+ cost.observeTokens(result.tokenUsage);
630
+ }
631
+
632
+ // src/runtime/loop-dispatch.ts
633
+ function campaignTraceToLoopEmitter(trace) {
634
+ return {
635
+ emit(event) {
636
+ trace.span(event.kind, { runId: event.runId, timestamp: event.timestamp, ...event.payload }).end();
637
+ }
638
+ };
639
+ }
640
+ async function runLoopForCell(opts, scenario, profile, ctx) {
641
+ const loopOptions = opts.toLoopOptions(scenario, profile);
642
+ return runLoopWithCampaignContext(opts, loopOptions, ctx);
643
+ }
644
+ async function runLoopWithCampaignContext(opts, loopOptions, ctx) {
645
+ const result = await runLoop({
646
+ ...loopOptions,
647
+ ctx: {
648
+ sandboxClient: opts.sandboxClient,
649
+ signal: ctx.signal,
650
+ traceEmitter: opts.forwardTrace === false ? void 0 : campaignTraceToLoopEmitter(ctx.trace)
651
+ }
652
+ });
653
+ reportLoopUsage(ctx.cost, result, opts.costSource ?? "loop");
654
+ const toArtifact = opts.toArtifact ?? ((r) => r.winner?.output);
655
+ return toArtifact(result);
656
+ }
657
+ function loopCampaignDispatch(opts) {
658
+ return (scenario, ctx) => runLoopWithCampaignContext(opts, opts.toLoopOptions(scenario), ctx);
659
+ }
660
+ function loopDispatch(opts) {
661
+ return (profile, scenario, ctx) => runLoopForCell(opts, scenario, profile, ctx);
662
+ }
663
+
664
+ // src/runtime/inline-sandbox-client.ts
665
+ function isAsyncIterable(v) {
666
+ return typeof v === "object" && v !== null && Symbol.asyncIterator in v;
667
+ }
668
+ async function settle(exec, task, signal) {
669
+ const r = exec.execute(task, signal);
670
+ if (isAsyncIterable(r)) {
671
+ for await (const _ of r) {
672
+ }
673
+ return exec.resultArtifact();
674
+ }
675
+ return r;
676
+ }
677
+ function inlineSandboxClient(factory) {
678
+ let seq = 0;
679
+ return {
680
+ async create(options) {
681
+ const id = `inline-${seq++}`;
682
+ const createOptions = options;
683
+ return {
684
+ id,
685
+ async *streamPrompt(message, opts) {
686
+ const controller = new AbortController();
687
+ const callerSignal = opts?.signal;
688
+ const onAbort = () => controller.abort(callerSignal?.reason ?? new Error("prompt aborted"));
689
+ if (callerSignal) {
690
+ if (callerSignal.aborted) onAbort();
691
+ else callerSignal.addEventListener("abort", onAbort, { once: true });
692
+ }
693
+ const spec = { profile: { name: id }, harness: null };
694
+ const exec = factory(spec, { signal: controller.signal, seams: { createOptions } });
695
+ try {
696
+ const artifact = await settle(exec, message, controller.signal);
697
+ const out = artifact.out;
698
+ const tokensIn = artifact.spent.tokens.input;
699
+ const tokensOut = artifact.spent.tokens.output;
700
+ const costUsd = artifact.spent.usd;
701
+ if (tokensIn || tokensOut || costUsd) {
702
+ yield {
703
+ type: "llm_call",
704
+ data: { tokensIn, tokensOut, costUsd }
705
+ };
706
+ }
707
+ yield {
708
+ type: "result",
709
+ data: {
710
+ finalText: out?.content ?? "",
711
+ tokenUsage: {
712
+ inputTokens: tokensIn,
713
+ outputTokens: tokensOut
714
+ },
715
+ costUsd
716
+ }
717
+ };
718
+ } finally {
719
+ callerSignal?.removeEventListener("abort", onAbort);
720
+ await exec.teardown("brutalKill").catch(() => {
721
+ });
722
+ }
723
+ },
724
+ async delete() {
725
+ }
726
+ };
727
+ }
728
+ };
729
+ }
730
+
731
+ // src/runtime/resolve-sandbox-client.ts
732
+ function resolveSandboxClient(opts) {
733
+ switch (opts.backend) {
734
+ case "sandbox": {
735
+ if (!opts.sandboxClient) {
736
+ throw new Error("resolveSandboxClient: backend 'sandbox' requires opts.sandboxClient");
737
+ }
738
+ return opts.sandboxClient;
739
+ }
740
+ case "bridge": {
741
+ const bridge = opts.bridge;
742
+ if (!bridge?.bearer || !bridge.model) {
743
+ throw new Error(
744
+ "resolveSandboxClient: backend 'bridge' requires bridge.bearer and bridge.model"
745
+ );
746
+ }
747
+ return inlineSandboxClient(
748
+ createExecutor({
749
+ backend: "bridge",
750
+ bridgeUrl: bridge.url ?? "http://127.0.0.1:3355",
751
+ bridgeBearer: bridge.bearer,
752
+ model: bridge.model,
753
+ timeoutMs: bridge.timeoutMs
754
+ })
755
+ );
756
+ }
757
+ case "router": {
758
+ const router = opts.router;
759
+ if (!router?.baseUrl || !router.key || !router.model) {
760
+ throw new Error(
761
+ "resolveSandboxClient: backend 'router' requires router.baseUrl, router.key and router.model"
762
+ );
763
+ }
764
+ return inlineSandboxClient(
765
+ createExecutor({
766
+ backend: "router",
767
+ routerBaseUrl: router.baseUrl,
768
+ routerKey: router.key,
769
+ model: router.model
770
+ })
771
+ );
772
+ }
773
+ }
774
+ }
775
+
776
+ // src/runtime/steering-drivers.ts
777
+ function decideUntilValidOrCapped(history, maxIterations) {
778
+ if (history.some((it) => it.verdict?.valid)) return "pick-winner";
779
+ return history.length < maxIterations ? "refine" : "fail";
780
+ }
781
+ function naiveDriver(options) {
782
+ const { continuation, applyContinuation, maxIterations, name = "naive" } = options;
783
+ return {
784
+ name,
785
+ plan(task, history) {
786
+ if (history.length === 0) return Promise.resolve([task]);
787
+ const last = history[history.length - 1];
788
+ if (last?.verdict?.valid) return Promise.resolve([]);
789
+ if (history.length >= maxIterations) return Promise.resolve([]);
790
+ return Promise.resolve([applyContinuation(task, continuation)]);
791
+ },
792
+ decide(history) {
793
+ return decideUntilValidOrCapped(history, maxIterations);
794
+ },
795
+ // Pure refine topology (one task per round) — render the steer move.
796
+ describePlan() {
797
+ return { kind: "refine", rationale: "naive fixed continuation (no grade signal)" };
798
+ }
799
+ };
800
+ }
801
+ function dumbDriver(options) {
802
+ const { onPass, onFail, applyContinuation, maxIterations, name = "dumb" } = options;
803
+ return {
804
+ name,
805
+ plan(task, history) {
806
+ if (history.length === 0) return Promise.resolve([task]);
807
+ const last = history[history.length - 1];
808
+ const passed = last?.verdict?.valid === true;
809
+ if (passed) return Promise.resolve([]);
810
+ if (history.length >= maxIterations) return Promise.resolve([]);
811
+ const continuation = passed ? onPass : onFail;
812
+ return Promise.resolve([applyContinuation(task, continuation)]);
813
+ },
814
+ decide(history) {
815
+ return decideUntilValidOrCapped(history, maxIterations);
816
+ },
817
+ describePlan() {
818
+ return { kind: "refine", rationale: "dumb pass/fail-only continuation (no grader findings)" };
819
+ }
820
+ };
821
+ }
822
+
823
+ // src/runtime/define-leaderboard.ts
824
+ function argOf(argv, name) {
825
+ const i = argv.indexOf(`--${name}`);
826
+ if (i >= 0 && i + 1 < argv.length) return argv[i + 1];
827
+ return void 0;
828
+ }
829
+ function splitList(v) {
830
+ if (v === void 0) return void 0;
831
+ const parts = v.split(",").map((s) => s.trim()).filter(Boolean);
832
+ return parts.length > 0 ? parts : void 0;
833
+ }
834
+ function withSnapshot(model, snapshot) {
835
+ return model.includes("@") ? model : `${model}@${snapshot}`;
836
+ }
837
+ function bareModel(model) {
838
+ return model.split("@")[0] ?? model;
839
+ }
840
+ function gitSha() {
841
+ try {
842
+ return execFileSync("git", ["rev-parse", "HEAD"], { encoding: "utf8" }).trim();
843
+ } catch {
844
+ return "unknown";
845
+ }
846
+ }
847
+ function normalizeScore(s) {
848
+ return typeof s === "number" ? { composite: s } : s;
849
+ }
850
+ function defineLeaderboard(spec) {
851
+ const caseId = (c) => {
852
+ const id = spec.caseId ? spec.caseId(c) : c.id;
853
+ if (typeof id !== "string" || id.length === 0) {
854
+ throw new Error(
855
+ `defineLeaderboard(${spec.name}): every case needs a stable string id \u2014 give cases an \`id\` property or supply spec.caseId`
856
+ );
857
+ }
858
+ return id;
859
+ };
860
+ const selectCases = (ids) => {
861
+ if (!ids) return spec.cases;
862
+ const byId = new Map(spec.cases.map((c) => [caseId(c), c]));
863
+ return ids.map((id) => {
864
+ const c = byId.get(id);
865
+ if (c === void 0) {
866
+ throw new Error(
867
+ `defineLeaderboard(${spec.name}): unknown case "${id}" (have: ${[...byId.keys()].join(", ")})`
868
+ );
869
+ }
870
+ return c;
871
+ });
872
+ };
873
+ const scoreJudge = {
874
+ name: `${spec.name}-score`,
875
+ dimensions: [{ key: "composite", description: `${spec.name} case score` }],
876
+ score({ artifact, scenario }) {
877
+ const s = normalizeScore(spec.score(artifact, scenario.case));
878
+ return {
879
+ composite: s.composite,
880
+ dimensions: { composite: s.composite, ...s.dimensions },
881
+ notes: s.notes ?? ""
882
+ };
883
+ }
884
+ };
885
+ async function run(argv = process.argv.slice(2)) {
886
+ const args = {};
887
+ for (const name of [
888
+ "backend",
889
+ "harnesses",
890
+ "models",
891
+ "cases",
892
+ "shots",
893
+ "reps",
894
+ "model-snapshot",
895
+ "run-dir",
896
+ "export-dir"
897
+ ]) {
898
+ args[name] = argOf(argv, name);
899
+ }
900
+ for (const [name, flag] of Object.entries(spec.flags ?? {})) {
901
+ args[name] = argOf(argv, name) ?? flag.default;
902
+ }
903
+ const backendName = args.backend ?? "sandbox";
904
+ const shots = Number(args.shots ?? spec.shots ?? 1);
905
+ const reps = Number(args.reps ?? spec.reps ?? 1);
906
+ const snapshot = args["model-snapshot"] ?? "leaderboard";
907
+ const runDir = args["run-dir"] ?? join(tmpdir(), `leaderboard-${spec.name}-${Date.now()}-${process.pid}`);
908
+ const exportDir = args["export-dir"] ?? join(runDir, "export");
909
+ mkdirSync(runDir, { recursive: true });
910
+ const cases = selectCases(splitList(args.cases));
911
+ if (cases.length === 0) throw new Error(`defineLeaderboard(${spec.name}): no cases to run`);
912
+ const scenarios = cases.map((c) => ({
913
+ id: caseId(c),
914
+ kind: spec.name,
915
+ case: c
916
+ }));
917
+ const harnesses = splitList(args.harnesses) ?? spec.axis?.harnesses ?? CODING_HARNESSES;
918
+ const rawModels = splitList(args.models) ?? spec.axis?.models ?? (spec.baseProfile?.model?.default !== void 0 ? [spec.baseProfile.model.default] : void 0);
919
+ if (!rawModels || rawModels.length === 0) {
920
+ throw new Error(
921
+ `defineLeaderboard(${spec.name}): no models \u2014 pass --models, set spec.axis.models, or give spec.baseProfile a model.default`
922
+ );
923
+ }
924
+ const models = rawModels.map((m) => withSnapshot(m, snapshot));
925
+ const base = spec.baseProfile ?? { name: spec.name, model: { default: bareModel(models[0] ?? "") } };
926
+ const profiles = expandProfileAxes({ base, harnesses, models });
927
+ const ctx = {
928
+ name: spec.name,
929
+ backend: backendName,
930
+ runDir,
931
+ exportDir,
932
+ args,
933
+ harnesses,
934
+ models,
935
+ caseIds: scenarios.map((s) => s.id),
936
+ shots,
937
+ reps
938
+ };
939
+ const backends = {
940
+ sandbox: () => {
941
+ throw new Error(
942
+ `defineLeaderboard(${spec.name}): the 'sandbox' backend needs your product's real SandboxClient \u2014 supply spec.backends.sandbox (e.g. () => new SandboxClient({ apiKey, baseUrl }))`
943
+ );
944
+ },
945
+ "cli-bridge": () => {
946
+ const bearer = process.env.BRIDGE_BEARER ?? process.env.CLI_BRIDGE_BEARER;
947
+ if (!bearer) {
948
+ throw new Error(
949
+ `defineLeaderboard(${spec.name}): backend 'cli-bridge' needs BRIDGE_BEARER or CLI_BRIDGE_BEARER set`
950
+ );
951
+ }
952
+ return resolveSandboxClient({
953
+ backend: "bridge",
954
+ bridge: {
955
+ url: process.env.CLI_BRIDGE_URL,
956
+ bearer,
957
+ model: bareModel(models[0] ?? ""),
958
+ timeoutMs: 9e5
959
+ }
960
+ });
961
+ },
962
+ ...spec.backends
963
+ };
964
+ const makeClient = backends[backendName];
965
+ if (!makeClient) {
966
+ throw new Error(
967
+ `defineLeaderboard(${spec.name}): unknown backend "${backendName}" (have: ${Object.keys(backends).join(", ")})`
968
+ );
969
+ }
970
+ const sandboxClient = makeClient();
971
+ const promptById = /* @__PURE__ */ new Map();
972
+ for (const s of scenarios) promptById.set(s.id, await spec.prompt(s.case));
973
+ const promptOf = (s) => {
974
+ const p = promptById.get(s.id);
975
+ if (p === void 0)
976
+ throw new Error(`defineLeaderboard(${spec.name}): no prompt for case "${s.id}"`);
977
+ return p;
978
+ };
979
+ let shotNonce = 0;
980
+ const dispatch = spec.dispatch ?? loopDispatch({
981
+ sandboxClient,
982
+ toLoopOptions: (scenario, profile) => {
983
+ const axis = harnessAxisOf(profile);
984
+ const modelId = bareModel(axis?.model ?? models[0] ?? "");
985
+ return {
986
+ // naiveDriver = the no-signal retry floor: re-run the same case as
987
+ // an independent attempt until one scores (>0) or the shot cap.
988
+ driver: naiveDriver({
989
+ continuation: "",
990
+ applyContinuation: (task) => task,
991
+ maxIterations: shots
992
+ }),
993
+ agentRun: {
994
+ profile,
995
+ taskToPrompt: (s) => `${promptOf(s)}
996
+
997
+ <!-- independent-attempt:${shotNonce++} -->`,
998
+ ...axis ? {
999
+ sandboxOverrides: {
1000
+ backend: {
1001
+ type: axis.harness,
1002
+ model: { ...spec.modelBackend, model: modelId }
1003
+ }
1004
+ }
1005
+ } : {}
1006
+ },
1007
+ output: {
1008
+ parse: (events) => {
1009
+ spec.onCellEvents?.(events, scenario.case);
1010
+ return spec.parseOutput ? spec.parseOutput(events, scenario.case) : collectAgentResponseText(events) ?? "";
1011
+ }
1012
+ },
1013
+ validator: {
1014
+ validate: async (output) => {
1015
+ const s = normalizeScore(spec.score(output, scenario.case));
1016
+ return { valid: s.composite > 0, score: s.composite };
1017
+ }
1018
+ },
1019
+ task: scenario,
1020
+ maxIterations: shots
1021
+ };
1022
+ }
1023
+ });
1024
+ await spec.setup?.(ctx);
1025
+ try {
1026
+ const result = await runProfileMatrix({
1027
+ profiles,
1028
+ scenarios,
1029
+ dispatch,
1030
+ judges: spec.judges ?? [scoreJudge],
1031
+ runDir,
1032
+ commitSha: gitSha(),
1033
+ reps,
1034
+ ...spec.matrix
1035
+ });
1036
+ if (spec.export) {
1037
+ await spec.export(result, ctx);
1038
+ } else {
1039
+ mkdirSync(exportDir, { recursive: true });
1040
+ writeFileSync(join(runDir, "matrix-result.json"), `${JSON.stringify(result, null, 2)}
1041
+ `);
1042
+ const table = renderLeaderboardMarkdown(
1043
+ leaderboard(result.records, { title: spec.name, meta: { backend: backendName } })
1044
+ );
1045
+ writeFileSync(join(exportDir, "leaderboard.md"), table);
1046
+ console.log(table);
1047
+ }
1048
+ return result;
1049
+ } finally {
1050
+ await spec.teardown?.(ctx);
1051
+ }
1052
+ }
1053
+ function toBenchmarkAdapter() {
1054
+ return {
1055
+ name: spec.name,
1056
+ async preflight() {
1057
+ const seen = /* @__PURE__ */ new Set();
1058
+ for (const c of spec.cases) {
1059
+ const id = caseId(c);
1060
+ if (seen.has(id)) {
1061
+ throw new Error(`defineLeaderboard(${spec.name}): duplicate case id "${id}"`);
1062
+ }
1063
+ seen.add(id);
1064
+ }
1065
+ },
1066
+ async loadTasks(opts) {
1067
+ const selected = selectCases(opts?.ids);
1068
+ const limited = opts?.limit !== void 0 ? selected.slice(0, opts.limit) : selected;
1069
+ return Promise.all(
1070
+ limited.map(async (c) => ({
1071
+ id: caseId(c),
1072
+ prompt: await spec.prompt(c),
1073
+ metadata: { case: c }
1074
+ }))
1075
+ );
1076
+ },
1077
+ async judge(task, artifact) {
1078
+ const [c] = selectCases([task.id]);
1079
+ if (c === void 0)
1080
+ throw new Error(`defineLeaderboard(${spec.name}): no case "${task.id}"`);
1081
+ const s = normalizeScore(spec.score(artifact, c));
1082
+ return { resolved: s.composite > 0, score: s.composite, detail: s.notes };
1083
+ },
1084
+ async goldArtifact() {
1085
+ return void 0;
1086
+ }
1087
+ };
1088
+ }
1089
+ return { run, toBenchmarkAdapter };
1090
+ }
1091
+
607
1092
  // src/runtime/observe.ts
608
1093
  import { makeFinding } from "@tangle-network/agent-eval";
609
1094
  var observerId = "observe/trace";
@@ -815,9 +1300,9 @@ async function harvestCorpus(opts) {
815
1300
  // src/runtime/in-process-sandbox-client.ts
816
1301
  import { mkdtempSync, rmSync } from "fs";
817
1302
  import { mkdir, readFile, writeFile } from "fs/promises";
818
- import { tmpdir } from "os";
819
- import { dirname, join } from "path";
820
- function isAsyncIterable(v) {
1303
+ import { tmpdir as tmpdir2 } from "os";
1304
+ import { dirname, join as join2 } from "path";
1305
+ function isAsyncIterable2(v) {
821
1306
  return typeof v === "object" && v !== null && Symbol.asyncIterator in v;
822
1307
  }
823
1308
  function inProcessSandboxClient(options) {
@@ -827,15 +1312,15 @@ function inProcessSandboxClient(options) {
827
1312
  async create(_options) {
828
1313
  const current = seq++;
829
1314
  const id = typeof idOption === "function" ? idOption(current) : idOption ?? `in-process-${current}`;
830
- const boxWorkdir = workdirPrefix !== void 0 ? mkdtempSync(join(tmpdir(), `${workdirPrefix}-`)) : void 0;
1315
+ const boxWorkdir = workdirPrefix !== void 0 ? mkdtempSync(join2(tmpdir2(), `${workdirPrefix}-`)) : void 0;
831
1316
  let round = 0;
832
1317
  const fsMembers = boxWorkdir !== void 0 ? {
833
1318
  fs: {
834
1319
  async read(path) {
835
- return readFile(join(boxWorkdir, path), "utf8");
1320
+ return readFile(join2(boxWorkdir, path), "utf8");
836
1321
  },
837
1322
  async write(path, content) {
838
- const abs = join(boxWorkdir, path);
1323
+ const abs = join2(boxWorkdir, path);
839
1324
  await mkdir(dirname(abs), { recursive: true });
840
1325
  await writeFile(abs, content, "utf8");
841
1326
  }
@@ -868,7 +1353,7 @@ function inProcessSandboxClient(options) {
868
1353
  const ctx = { round, workdir: boxWorkdir, signal };
869
1354
  round += 1;
870
1355
  const produced = await onPrompt(prompt, ctx);
871
- if (isAsyncIterable(produced)) {
1356
+ if (isAsyncIterable2(produced)) {
872
1357
  for await (const ev of produced) yield ev;
873
1358
  } else {
874
1359
  for (const ev of produced) yield ev;
@@ -884,104 +1369,6 @@ function inProcessSandboxClient(options) {
884
1369
  };
885
1370
  }
886
1371
 
887
- // src/runtime/inline-sandbox-client.ts
888
- function isAsyncIterable2(v) {
889
- return typeof v === "object" && v !== null && Symbol.asyncIterator in v;
890
- }
891
- async function settle(exec, task, signal) {
892
- const r = exec.execute(task, signal);
893
- if (isAsyncIterable2(r)) {
894
- for await (const _ of r) {
895
- }
896
- return exec.resultArtifact();
897
- }
898
- return r;
899
- }
900
- function inlineSandboxClient(factory) {
901
- let seq = 0;
902
- return {
903
- async create(options) {
904
- const id = `inline-${seq++}`;
905
- const createOptions = options;
906
- return {
907
- id,
908
- async *streamPrompt(message) {
909
- const controller = new AbortController();
910
- const spec = { profile: { name: id }, harness: null };
911
- const exec = factory(spec, { signal: controller.signal, seams: { createOptions } });
912
- try {
913
- const artifact = await settle(exec, message, controller.signal);
914
- const out = artifact.out;
915
- const tokensIn = artifact.spent.tokens.input;
916
- const tokensOut = artifact.spent.tokens.output;
917
- const costUsd = artifact.spent.usd;
918
- if (tokensIn || tokensOut || costUsd) {
919
- yield {
920
- type: "llm_call",
921
- data: { tokensIn, tokensOut, costUsd }
922
- };
923
- }
924
- yield {
925
- type: "result",
926
- data: {
927
- finalText: out?.content ?? "",
928
- tokenUsage: {
929
- inputTokens: tokensIn,
930
- outputTokens: tokensOut
931
- },
932
- costUsd
933
- }
934
- };
935
- } finally {
936
- await exec.teardown("brutalKill").catch(() => {
937
- });
938
- }
939
- },
940
- async delete() {
941
- }
942
- };
943
- }
944
- };
945
- }
946
-
947
- // src/runtime/report-usage.ts
948
- function reportLoopUsage(cost, result, source = "loop") {
949
- cost.observe(result.costUsd, source);
950
- cost.observeTokens(result.tokenUsage);
951
- }
952
-
953
- // src/runtime/loop-dispatch.ts
954
- function campaignTraceToLoopEmitter(trace) {
955
- return {
956
- emit(event) {
957
- trace.span(event.kind, { runId: event.runId, timestamp: event.timestamp, ...event.payload }).end();
958
- }
959
- };
960
- }
961
- async function runLoopForCell(opts, scenario, profile, ctx) {
962
- const loopOptions = opts.toLoopOptions(scenario, profile);
963
- return runLoopWithCampaignContext(opts, loopOptions, ctx);
964
- }
965
- async function runLoopWithCampaignContext(opts, loopOptions, ctx) {
966
- const result = await runLoop({
967
- ...loopOptions,
968
- ctx: {
969
- sandboxClient: opts.sandboxClient,
970
- signal: ctx.signal,
971
- traceEmitter: opts.forwardTrace === false ? void 0 : campaignTraceToLoopEmitter(ctx.trace)
972
- }
973
- });
974
- reportLoopUsage(ctx.cost, result, opts.costSource ?? "loop");
975
- const toArtifact = opts.toArtifact ?? ((r) => r.winner?.output);
976
- return toArtifact(result);
977
- }
978
- function loopCampaignDispatch(opts) {
979
- return (scenario, ctx) => runLoopWithCampaignContext(opts, opts.toLoopOptions(scenario), ctx);
980
- }
981
- function loopDispatch(opts) {
982
- return (profile, scenario, ctx) => runLoopForCell(opts, scenario, profile, ctx);
983
- }
984
-
985
1372
  // src/runtime/mcp-environment.ts
986
1373
  async function rpc(endpoint, body) {
987
1374
  let lastErr;
@@ -2204,65 +2591,20 @@ function promotionGate(opts) {
2204
2591
  promoted: false,
2205
2592
  reason: "not-cheaper",
2206
2593
  mode,
2207
- n: scoreSig.n,
2208
- lift,
2209
- costSavings,
2210
- latency
2211
- };
2212
- return {
2213
- promoted: true,
2214
- reason: "non-inferior-and-cheaper",
2215
- mode,
2216
- n: scoreSig.n,
2217
- lift,
2218
- costSavings,
2219
- latency
2220
- };
2221
- }
2222
-
2223
- // src/runtime/resolve-sandbox-client.ts
2224
- function resolveSandboxClient(opts) {
2225
- switch (opts.backend) {
2226
- case "sandbox": {
2227
- if (!opts.sandboxClient) {
2228
- throw new Error("resolveSandboxClient: backend 'sandbox' requires opts.sandboxClient");
2229
- }
2230
- return opts.sandboxClient;
2231
- }
2232
- case "bridge": {
2233
- const bridge = opts.bridge;
2234
- if (!bridge?.bearer || !bridge.model) {
2235
- throw new Error(
2236
- "resolveSandboxClient: backend 'bridge' requires bridge.bearer and bridge.model"
2237
- );
2238
- }
2239
- return inlineSandboxClient(
2240
- createExecutor({
2241
- backend: "bridge",
2242
- bridgeUrl: bridge.url ?? "http://127.0.0.1:3355",
2243
- bridgeBearer: bridge.bearer,
2244
- model: bridge.model,
2245
- timeoutMs: bridge.timeoutMs
2246
- })
2247
- );
2248
- }
2249
- case "router": {
2250
- const router = opts.router;
2251
- if (!router?.baseUrl || !router.key || !router.model) {
2252
- throw new Error(
2253
- "resolveSandboxClient: backend 'router' requires router.baseUrl, router.key and router.model"
2254
- );
2255
- }
2256
- return inlineSandboxClient(
2257
- createExecutor({
2258
- backend: "router",
2259
- routerBaseUrl: router.baseUrl,
2260
- routerKey: router.key,
2261
- model: router.model
2262
- })
2263
- );
2264
- }
2265
- }
2594
+ n: scoreSig.n,
2595
+ lift,
2596
+ costSavings,
2597
+ latency
2598
+ };
2599
+ return {
2600
+ promoted: true,
2601
+ reason: "non-inferior-and-cheaper",
2602
+ mode,
2603
+ n: scoreSig.n,
2604
+ lift,
2605
+ costSavings,
2606
+ latency
2607
+ };
2266
2608
  }
2267
2609
 
2268
2610
  // src/runtime/run-benchmark.ts
@@ -3267,56 +3609,9 @@ function errorMessage(error) {
3267
3609
  return error instanceof Error ? error.message : String(error);
3268
3610
  }
3269
3611
 
3270
- // src/runtime/steering-drivers.ts
3271
- function decideUntilValidOrCapped(history, maxIterations) {
3272
- if (history.some((it) => it.verdict?.valid)) return "pick-winner";
3273
- return history.length < maxIterations ? "refine" : "fail";
3274
- }
3275
- function naiveDriver(options) {
3276
- const { continuation, applyContinuation, maxIterations, name = "naive" } = options;
3277
- return {
3278
- name,
3279
- plan(task, history) {
3280
- if (history.length === 0) return Promise.resolve([task]);
3281
- const last = history[history.length - 1];
3282
- if (last?.verdict?.valid) return Promise.resolve([]);
3283
- if (history.length >= maxIterations) return Promise.resolve([]);
3284
- return Promise.resolve([applyContinuation(task, continuation)]);
3285
- },
3286
- decide(history) {
3287
- return decideUntilValidOrCapped(history, maxIterations);
3288
- },
3289
- // Pure refine topology (one task per round) — render the steer move.
3290
- describePlan() {
3291
- return { kind: "refine", rationale: "naive fixed continuation (no grade signal)" };
3292
- }
3293
- };
3294
- }
3295
- function dumbDriver(options) {
3296
- const { onPass, onFail, applyContinuation, maxIterations, name = "dumb" } = options;
3297
- return {
3298
- name,
3299
- plan(task, history) {
3300
- if (history.length === 0) return Promise.resolve([task]);
3301
- const last = history[history.length - 1];
3302
- const passed = last?.verdict?.valid === true;
3303
- if (passed) return Promise.resolve([]);
3304
- if (history.length >= maxIterations) return Promise.resolve([]);
3305
- const continuation = passed ? onPass : onFail;
3306
- return Promise.resolve([applyContinuation(task, continuation)]);
3307
- },
3308
- decide(history) {
3309
- return decideUntilValidOrCapped(history, maxIterations);
3310
- },
3311
- describePlan() {
3312
- return { kind: "refine", rationale: "dumb pass/fail-only continuation (no grader findings)" };
3313
- }
3314
- };
3315
- }
3316
-
3317
3612
  // src/runtime/strategy-author.ts
3318
- import { mkdirSync, writeFileSync } from "fs";
3319
- import { join as join2 } from "path";
3613
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
3614
+ import { join as join3 } from "path";
3320
3615
  var strategyAuthorContract = `
3321
3616
  You author an OPTIMIZATION STRATEGY for an agentic loop system. A strategy decides how to
3322
3617
  spend a compute budget to beat a task's deployable check. You compose exactly two steps:
@@ -3434,9 +3729,9 @@ async function authorStrategy(opts) {
3434
3729
  code = await requestAuthoredCode(opts, opts.fallbackModel);
3435
3730
  }
3436
3731
  assertStrategyContract(code);
3437
- mkdirSync(opts.outDir, { recursive: true });
3438
- const file = join2(opts.outDir, `authored-${Date.now()}.mts`);
3439
- writeFileSync(file, code);
3732
+ mkdirSync2(opts.outDir, { recursive: true });
3733
+ const file = join3(opts.outDir, `authored-${Date.now()}.mts`);
3734
+ writeFileSync2(file, code);
3440
3735
  const mod = await import(`file://${file}`);
3441
3736
  if (!mod.default || typeof mod.default.driver !== "function" || !mod.default.name) {
3442
3737
  throw new Error(`authorStrategy: ${file} does not export a default Strategy`);
@@ -3445,7 +3740,7 @@ async function authorStrategy(opts) {
3445
3740
  }
3446
3741
 
3447
3742
  // src/runtime/strategy-evolution.ts
3448
- import { existsSync, readFileSync, writeFileSync as writeFileSync2 } from "fs";
3743
+ import { existsSync, readFileSync, writeFileSync as writeFileSync3 } from "fs";
3449
3744
  import { gzipSync } from "zlib";
3450
3745
  function discriminatingMeans(report, fieldOrder) {
3451
3746
  const rows = report.perTask.filter((r) => {
@@ -3557,7 +3852,7 @@ async function runStrategyEvolution(cfg) {
3557
3852
  }
3558
3853
  const save = (state) => {
3559
3854
  if (cfg.checkpoint)
3560
- writeFileSync2(cfg.checkpoint.path, JSON.stringify({ ...state, fingerprint }, null, 1));
3855
+ writeFileSync3(cfg.checkpoint.path, JSON.stringify({ ...state, fingerprint }, null, 1));
3561
3856
  };
3562
3857
  const bench = async (phase, tasks, strategies) => {
3563
3858
  await cfg.onPhase?.(phase);
@@ -3854,6 +4149,800 @@ ${summary}`,
3854
4149
  };
3855
4150
  }
3856
4151
 
4152
+ // src/runtime/stream-agent-turn.ts
4153
+ import { scoreKnowledgeReadiness } from "@tangle-network/agent-eval";
4154
+
4155
+ // src/sessions.ts
4156
+ function newRuntimeSession(backend, requestedId, metadata) {
4157
+ const now = nowIso();
4158
+ return {
4159
+ id: requestedId || crypto.randomUUID(),
4160
+ backend,
4161
+ status: "active",
4162
+ createdAt: now,
4163
+ updatedAt: now,
4164
+ metadata
4165
+ };
4166
+ }
4167
+ function touchSession(session) {
4168
+ return { ...session, updatedAt: nowIso() };
4169
+ }
4170
+ function nowIso() {
4171
+ return (/* @__PURE__ */ new Date()).toISOString();
4172
+ }
4173
+ var InMemoryRuntimeSessionStore = class {
4174
+ sessions = /* @__PURE__ */ new Map();
4175
+ events = /* @__PURE__ */ new Map();
4176
+ get(sessionId) {
4177
+ return this.sessions.get(sessionId);
4178
+ }
4179
+ put(session) {
4180
+ this.sessions.set(session.id, session);
4181
+ }
4182
+ appendEvent(sessionId, event) {
4183
+ const existing = this.events.get(sessionId) ?? [];
4184
+ existing.push(event);
4185
+ this.events.set(sessionId, existing);
4186
+ }
4187
+ listEvents(sessionId) {
4188
+ return [...this.events.get(sessionId) ?? []];
4189
+ }
4190
+ };
4191
+
4192
+ // src/backends.ts
4193
+ function createIterableBackend(options) {
4194
+ return options;
4195
+ }
4196
+ function createSandboxPromptBackend(options) {
4197
+ const kind = options.kind ?? "sandbox";
4198
+ return {
4199
+ kind,
4200
+ async start(input, context) {
4201
+ const box = await options.getBox(input, context);
4202
+ return newRuntimeSession(
4203
+ kind,
4204
+ options.getSessionId?.(box, input) ?? context.requestedSessionId,
4205
+ { resumable: true }
4206
+ );
4207
+ },
4208
+ resume(session) {
4209
+ return touchSession({ ...session, status: "active" });
4210
+ },
4211
+ async *stream(input, context) {
4212
+ const box = await options.getBox(input, context);
4213
+ const message = input.message ?? input.messages?.at(-1)?.content ?? context.task.intent;
4214
+ for await (const event of options.streamPrompt(box, message, context)) {
4215
+ const mapped = options.mapEvent?.(event, context) ?? mapCommonBackendEvent(event, context);
4216
+ if (mapped) yield mapped;
4217
+ }
4218
+ }
4219
+ };
4220
+ }
4221
+ var DEFAULT_RETRY_STATUSES = [408, 425, 429, 500, 502, 503, 504];
4222
+ function pickRetryDelayMs(attempt, policy) {
4223
+ const exp = policy.initialBackoffMs * 2 ** (attempt - 1);
4224
+ const capped = Math.min(exp, policy.maxBackoffMs);
4225
+ const jitter = capped * policy.jitter * (Math.random() * 2 - 1);
4226
+ return Math.max(0, Math.round(capped + jitter));
4227
+ }
4228
+ function withTimeout(callerSignal, timeoutMs) {
4229
+ if (timeoutMs <= 0) {
4230
+ return { signal: callerSignal ?? new AbortController().signal, dispose: () => void 0 };
4231
+ }
4232
+ const controller = new AbortController();
4233
+ const timer = setTimeout(
4234
+ () => controller.abort(new Error(`request timed out after ${timeoutMs}ms`)),
4235
+ timeoutMs
4236
+ );
4237
+ if (typeof timer.unref === "function") {
4238
+ ;
4239
+ timer.unref();
4240
+ }
4241
+ const onCallerAbort = () => controller.abort(callerSignal?.reason ?? new Error("aborted"));
4242
+ if (callerSignal) {
4243
+ if (callerSignal.aborted) onCallerAbort();
4244
+ else callerSignal.addEventListener("abort", onCallerAbort, { once: true });
4245
+ }
4246
+ return {
4247
+ signal: controller.signal,
4248
+ dispose: () => {
4249
+ clearTimeout(timer);
4250
+ callerSignal?.removeEventListener("abort", onCallerAbort);
4251
+ }
4252
+ };
4253
+ }
4254
+ function sleep2(ms, signal) {
4255
+ return new Promise((resolve, reject) => {
4256
+ if (signal?.aborted) {
4257
+ reject(signal.reason ?? new Error("aborted"));
4258
+ return;
4259
+ }
4260
+ const t = setTimeout(() => {
4261
+ signal?.removeEventListener("abort", onAbort);
4262
+ resolve();
4263
+ }, ms);
4264
+ const onAbort = () => {
4265
+ clearTimeout(t);
4266
+ reject(signal?.reason ?? new Error("aborted"));
4267
+ };
4268
+ signal?.addEventListener("abort", onAbort, { once: true });
4269
+ });
4270
+ }
4271
+ function createOpenAICompatibleBackend(options) {
4272
+ const fetcher = options.fetchImpl ?? fetch;
4273
+ const kind = options.kind ?? "tcloud";
4274
+ const retryPolicy = {
4275
+ maxAttempts: options.retry?.maxAttempts ?? 5,
4276
+ initialBackoffMs: options.retry?.initialBackoffMs ?? 1e3,
4277
+ maxBackoffMs: options.retry?.maxBackoffMs ?? 3e4,
4278
+ jitter: options.retry?.jitter ?? 0.25,
4279
+ retryStatuses: options.retry?.retryStatuses ?? DEFAULT_RETRY_STATUSES,
4280
+ requestTimeoutMs: options.retry?.requestTimeoutMs ?? 12e4
4281
+ };
4282
+ return {
4283
+ kind,
4284
+ start(_input, context) {
4285
+ return newRuntimeSession(kind, context.requestedSessionId);
4286
+ },
4287
+ async *stream(input, context) {
4288
+ const url = `${options.baseUrl.replace(/\/$/, "")}/chat/completions`;
4289
+ const bodyPayload = {
4290
+ model: options.model,
4291
+ stream: true,
4292
+ stream_options: { include_usage: true },
4293
+ messages: input.messages ?? [
4294
+ { role: "user", content: input.message ?? context.task.intent }
4295
+ ]
4296
+ };
4297
+ if (options.tools && options.tools.length > 0) {
4298
+ bodyPayload.tools = options.tools;
4299
+ if (options.toolChoice !== void 0) bodyPayload.tool_choice = options.toolChoice;
4300
+ }
4301
+ if (options.responseFormat !== void 0) {
4302
+ bodyPayload.response_format = options.responseFormat;
4303
+ }
4304
+ const requestBody = JSON.stringify(bodyPayload);
4305
+ let response;
4306
+ let lastStatus = 0;
4307
+ let lastThrown;
4308
+ for (let attempt = 1; attempt <= retryPolicy.maxAttempts; attempt++) {
4309
+ lastThrown = void 0;
4310
+ const attemptSignal = withTimeout(context.signal, retryPolicy.requestTimeoutMs);
4311
+ try {
4312
+ response = await fetcher(url, {
4313
+ method: "POST",
4314
+ headers: {
4315
+ Authorization: `Bearer ${options.apiKey}`,
4316
+ "Content-Type": "application/json",
4317
+ // Cross-gateway forwarding: when this call is part of a
4318
+ // multi-agent conversation, the runner stamps run/turn/
4319
+ // depth/forwarded-auth headers onto the context. They flow
4320
+ // through to the downstream gateway verbatim so the original
4321
+ // user gets billed, the recursion depth stays bounded, and
4322
+ // the trace correlates across hops.
4323
+ ...context.propagatedHeaders ?? {}
4324
+ },
4325
+ body: requestBody,
4326
+ signal: attemptSignal.signal
4327
+ });
4328
+ } catch (err) {
4329
+ attemptSignal.dispose();
4330
+ if (context.signal?.aborted) throw err;
4331
+ lastThrown = err;
4332
+ response = void 0;
4333
+ if (attempt === retryPolicy.maxAttempts) break;
4334
+ await sleep2(pickRetryDelayMs(attempt, retryPolicy), context.signal);
4335
+ continue;
4336
+ }
4337
+ attemptSignal.dispose();
4338
+ if (response.ok) break;
4339
+ lastStatus = response.status;
4340
+ if (!retryPolicy.retryStatuses.includes(response.status)) break;
4341
+ if (attempt === retryPolicy.maxAttempts) break;
4342
+ try {
4343
+ await response.body?.cancel();
4344
+ } catch {
4345
+ }
4346
+ const delayMs = pickRetryDelayMs(attempt, retryPolicy);
4347
+ await sleep2(delayMs, context.signal);
4348
+ }
4349
+ if (!response) {
4350
+ const reason = lastThrown instanceof Error ? lastThrown.message : String(lastThrown);
4351
+ throw new BackendTransportError(
4352
+ kind,
4353
+ `chat backend unreachable after ${retryPolicy.maxAttempts} attempts: ${reason}`,
4354
+ { status: 0 }
4355
+ );
4356
+ }
4357
+ if (!response.ok) {
4358
+ let body;
4359
+ try {
4360
+ const raw = await response.text();
4361
+ body = raw.length > MAX_ERROR_BODY_BYTES ? `${raw.slice(0, MAX_ERROR_BODY_BYTES)}\u2026` : raw;
4362
+ } catch {
4363
+ body = void 0;
4364
+ }
4365
+ throw new BackendTransportError(kind, `chat backend returned ${lastStatus || "unknown"}`, {
4366
+ status: lastStatus || 0,
4367
+ body
4368
+ });
4369
+ }
4370
+ yield* streamResponseEvents(response, context, options.model);
4371
+ }
4372
+ };
4373
+ }
4374
+ var MAX_ERROR_BODY_BYTES = 2048;
4375
+ function normalizeBackendStreamEvent(event, task, session) {
4376
+ if ("task" in event && event.task && "session" in event && event.session && "timestamp" in event && event.timestamp) {
4377
+ return event;
4378
+ }
4379
+ return {
4380
+ ...event,
4381
+ task: "task" in event && event.task ? event.task : task,
4382
+ session: "session" in event && event.session ? event.session : session,
4383
+ timestamp: "timestamp" in event && event.timestamp ? event.timestamp : nowIso()
4384
+ };
4385
+ }
4386
+ function mapCommonBackendEvent(event, context) {
4387
+ if (!event || typeof event !== "object") return void 0;
4388
+ const record = event;
4389
+ const type = String(record.type ?? "");
4390
+ const data = record.data && typeof record.data === "object" ? record.data : record;
4391
+ if (type === "message.part.updated" || type === "text_delta" || type === "delta") {
4392
+ const part = data.part;
4393
+ const partText = part !== void 0 && typeof part === "object" && (part.type === "text" || part.type === void 0) ? stringValue(part.text) : void 0;
4394
+ const text = stringValue(data.text) ?? stringValue(data.delta) ?? stringValue(record.text) ?? partText;
4395
+ return text ? {
4396
+ type: "text_delta",
4397
+ task: context.task,
4398
+ session: context.session,
4399
+ text,
4400
+ timestamp: nowIso()
4401
+ } : void 0;
4402
+ }
4403
+ if (type === "reasoning_delta") {
4404
+ const text = stringValue(data.text) ?? stringValue(record.text);
4405
+ return text ? {
4406
+ type: "reasoning_delta",
4407
+ task: context.task,
4408
+ session: context.session,
4409
+ text,
4410
+ timestamp: nowIso()
4411
+ } : void 0;
4412
+ }
4413
+ if (type === "tool_call") {
4414
+ return {
4415
+ type: "tool_call",
4416
+ task: context.task,
4417
+ session: context.session,
4418
+ toolName: stringValue(data.name) ?? stringValue(record.toolName) ?? "tool",
4419
+ toolCallId: stringValue(data.id) ?? stringValue(record.toolCallId),
4420
+ args: data.args ?? data.input ?? record.args,
4421
+ timestamp: nowIso()
4422
+ };
4423
+ }
4424
+ if (type === "tool_result") {
4425
+ return {
4426
+ type: "tool_result",
4427
+ task: context.task,
4428
+ session: context.session,
4429
+ toolName: stringValue(data.name) ?? stringValue(record.toolName) ?? "tool",
4430
+ toolCallId: stringValue(data.id) ?? stringValue(record.toolCallId),
4431
+ result: data.result ?? data.output ?? record.result,
4432
+ timestamp: nowIso()
4433
+ };
4434
+ }
4435
+ if (type === "artifact") {
4436
+ const artifactId = stringValue(data.artifactId) ?? stringValue(data.id) ?? stringValue(record.artifactId);
4437
+ if (!artifactId) return void 0;
4438
+ return {
4439
+ type: "artifact",
4440
+ task: context.task,
4441
+ session: context.session,
4442
+ artifactId,
4443
+ name: stringValue(data.name) ?? stringValue(record.name),
4444
+ mimeType: stringValue(data.mimeType) ?? stringValue(record.mimeType),
4445
+ uri: stringValue(data.uri) ?? stringValue(record.uri),
4446
+ content: stringValue(data.content) ?? stringValue(data.body) ?? stringValue(record.content),
4447
+ metadata: data.metadata && typeof data.metadata === "object" ? data.metadata : void 0,
4448
+ timestamp: nowIso()
4449
+ };
4450
+ }
4451
+ if (type === "proposal_created" || type === "proposal" || type === "filing") {
4452
+ const proposalId = stringValue(data.proposalId) ?? stringValue(data.id) ?? stringValue(record.proposalId);
4453
+ if (!proposalId) return void 0;
4454
+ const status = stringValue(data.status) ?? stringValue(record.status);
4455
+ return {
4456
+ type: "proposal_created",
4457
+ task: context.task,
4458
+ session: context.session,
4459
+ proposalId,
4460
+ title: stringValue(data.title) ?? stringValue(record.title) ?? proposalId,
4461
+ status: status === "pending" || status === "approved" || status === "rejected" ? status : void 0,
4462
+ content: stringValue(data.content) ?? stringValue(data.body) ?? stringValue(record.content),
4463
+ timestamp: nowIso()
4464
+ };
4465
+ }
4466
+ if (type === "result" || type === "final") {
4467
+ const text = stringValue(data.finalText) ?? stringValue(data.text) ?? stringValue(record.text);
4468
+ return text ? {
4469
+ type: "text_delta",
4470
+ task: context.task,
4471
+ session: context.session,
4472
+ text,
4473
+ timestamp: nowIso()
4474
+ } : void 0;
4475
+ }
4476
+ return void 0;
4477
+ }
4478
+ async function* streamResponseEvents(response, context, requestedModel) {
4479
+ const body = response.body;
4480
+ if (!body) return;
4481
+ const reader = body.getReader();
4482
+ const decoder = new TextDecoder();
4483
+ let buffer = "";
4484
+ const usage = { saw: false };
4485
+ const toolCalls = /* @__PURE__ */ new Map();
4486
+ const startedAt = Date.now();
4487
+ for (; ; ) {
4488
+ const { done, value } = await reader.read();
4489
+ if (done) break;
4490
+ buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
4491
+ for (const event of drainStreamBuffer(false)) yield event;
4492
+ }
4493
+ buffer += decoder.decode().replace(/\r\n/g, "\n");
4494
+ for (const event of drainStreamBuffer(true)) yield event;
4495
+ if (buffer.trim()) {
4496
+ for (const event of parseStreamChunk(buffer, context, usage, toolCalls)) yield event;
4497
+ }
4498
+ for (const event of flushPendingToolCalls(toolCalls, context)) yield event;
4499
+ if (usage.saw) {
4500
+ yield {
4501
+ type: "llm_call",
4502
+ task: context.task,
4503
+ session: context.session,
4504
+ model: usage.model ?? requestedModel,
4505
+ tokensIn: usage.tokensIn,
4506
+ tokensOut: usage.tokensOut,
4507
+ // `costUsd` is intentionally absent — pricing tables live in consumers
4508
+ // (agent-eval's `estimateCost`, MetricsCollector). Emitting a wrong
4509
+ // number here is worse than emitting none.
4510
+ latencyMs: Date.now() - startedAt,
4511
+ finishReason: usage.finishReason,
4512
+ timestamp: nowIso()
4513
+ };
4514
+ }
4515
+ function* drainStreamBuffer(flush) {
4516
+ for (; ; ) {
4517
+ const sseBoundary = buffer.indexOf("\n\n");
4518
+ if (sseBoundary >= 0) {
4519
+ const chunk = buffer.slice(0, sseBoundary);
4520
+ buffer = buffer.slice(sseBoundary + 2);
4521
+ for (const event of parseStreamChunk(chunk, context, usage, toolCalls)) yield event;
4522
+ continue;
4523
+ }
4524
+ const newline = buffer.indexOf("\n");
4525
+ if (newline >= 0 && !buffer.slice(0, newline).startsWith("data:")) {
4526
+ const line = buffer.slice(0, newline);
4527
+ buffer = buffer.slice(newline + 1);
4528
+ for (const event of parseStreamChunk(line, context, usage, toolCalls)) yield event;
4529
+ continue;
4530
+ }
4531
+ if (flush && buffer.trim() && !buffer.trimStart().startsWith("data:")) {
4532
+ const line = buffer;
4533
+ buffer = "";
4534
+ for (const event of parseStreamChunk(line, context, usage, toolCalls)) yield event;
4535
+ continue;
4536
+ }
4537
+ break;
4538
+ }
4539
+ }
4540
+ }
4541
+ function* parseStreamChunk(chunk, context, usage, toolCalls) {
4542
+ const lines = chunk.split(/\r?\n/);
4543
+ const dataLines = lines.filter((line) => line.startsWith("data:"));
4544
+ if (dataLines.length === 0 && lines.every((line) => {
4545
+ const trimmed = line.trim();
4546
+ return trimmed.length === 0 || trimmed.startsWith(":");
4547
+ })) {
4548
+ return;
4549
+ }
4550
+ const data = dataLines.length > 0 ? dataLines.map((line) => line.slice(5).trimStart()).join("\n") : chunk.trim();
4551
+ if (!data || data === "[DONE]") return;
4552
+ let parsed;
4553
+ try {
4554
+ parsed = JSON.parse(data);
4555
+ } catch {
4556
+ yield {
4557
+ type: "text_delta",
4558
+ task: context.task,
4559
+ session: context.session,
4560
+ text: data,
4561
+ timestamp: nowIso()
4562
+ };
4563
+ return;
4564
+ }
4565
+ captureStreamUsage(parsed, usage);
4566
+ const choices = parsed.choices;
4567
+ const choice = Array.isArray(choices) ? choices[0] : void 0;
4568
+ const delta = choice?.delta;
4569
+ const message = choice?.message;
4570
+ const deltaToolCalls = delta?.tool_calls;
4571
+ if (Array.isArray(deltaToolCalls)) {
4572
+ for (const tc of deltaToolCalls) {
4573
+ if (!tc || typeof tc !== "object") continue;
4574
+ const rec = tc;
4575
+ const idx = numberValue(rec.index) ?? 0;
4576
+ const key = `openai:${idx}`;
4577
+ const acc = toolCalls.get(key) ?? { argsRaw: "", source: "openai", finalized: false };
4578
+ const id = stringValue(rec.id);
4579
+ if (id) acc.id = id;
4580
+ const fn = rec.function;
4581
+ const name = stringValue(fn?.name);
4582
+ if (name) acc.name = name;
4583
+ const args = stringValue(fn?.arguments);
4584
+ if (args) acc.argsRaw += args;
4585
+ toolCalls.set(key, acc);
4586
+ }
4587
+ }
4588
+ const messageToolCalls = message?.tool_calls;
4589
+ if (Array.isArray(messageToolCalls)) {
4590
+ for (const tc of messageToolCalls) {
4591
+ if (!tc || typeof tc !== "object") continue;
4592
+ const rec = tc;
4593
+ const fn = rec.function;
4594
+ const idx = numberValue(rec.index) ?? messageToolCalls.indexOf(tc);
4595
+ const key = `openai:${idx}`;
4596
+ const acc = toolCalls.get(key) ?? { argsRaw: "", source: "openai", finalized: false };
4597
+ const id = stringValue(rec.id);
4598
+ if (id) acc.id = id;
4599
+ const name = stringValue(fn?.name);
4600
+ if (name) acc.name = name;
4601
+ const args = stringValue(fn?.arguments);
4602
+ if (args) acc.argsRaw += args;
4603
+ acc.finalized = true;
4604
+ toolCalls.set(key, acc);
4605
+ }
4606
+ }
4607
+ const finishReason = stringValue(choice?.finish_reason);
4608
+ if (finishReason === "tool_calls") {
4609
+ for (const [key, acc] of toolCalls) {
4610
+ if (acc.source === "openai" && !acc.finalized) acc.finalized = true;
4611
+ toolCalls.set(key, acc);
4612
+ }
4613
+ }
4614
+ const eventType = stringValue(parsed.type);
4615
+ if (eventType === "content_block_start") {
4616
+ const block = parsed.content_block;
4617
+ if (block && stringValue(block.type) === "tool_use") {
4618
+ const idx = numberValue(parsed.index) ?? 0;
4619
+ const key = `anthropic:${idx}`;
4620
+ toolCalls.set(key, {
4621
+ id: stringValue(block.id),
4622
+ name: stringValue(block.name),
4623
+ argsRaw: "",
4624
+ source: "anthropic",
4625
+ finalized: false
4626
+ });
4627
+ }
4628
+ }
4629
+ if (eventType === "content_block_delta") {
4630
+ const d = parsed.delta;
4631
+ const dType = stringValue(d?.type);
4632
+ if (dType === "input_json_delta") {
4633
+ const idx = numberValue(parsed.index) ?? 0;
4634
+ const key = `anthropic:${idx}`;
4635
+ const acc = toolCalls.get(key);
4636
+ if (acc) {
4637
+ const partial = stringValue(d?.partial_json) ?? "";
4638
+ acc.argsRaw += partial;
4639
+ toolCalls.set(key, acc);
4640
+ }
4641
+ } else {
4642
+ const text2 = stringValue(d?.text);
4643
+ if (text2) {
4644
+ yield {
4645
+ type: "text_delta",
4646
+ task: context.task,
4647
+ session: context.session,
4648
+ text: text2,
4649
+ timestamp: nowIso()
4650
+ };
4651
+ }
4652
+ }
4653
+ }
4654
+ if (eventType === "content_block_stop") {
4655
+ const idx = numberValue(parsed.index) ?? 0;
4656
+ const key = `anthropic:${idx}`;
4657
+ const acc = toolCalls.get(key);
4658
+ if (acc) {
4659
+ acc.finalized = true;
4660
+ toolCalls.set(key, acc);
4661
+ }
4662
+ }
4663
+ for (const event of drainFinalizedToolCalls(toolCalls, context)) yield event;
4664
+ const text = stringValue(delta?.content) ?? stringValue(message?.content) ?? stringValue(parsed.text);
4665
+ if (text) {
4666
+ yield {
4667
+ type: "text_delta",
4668
+ task: context.task,
4669
+ session: context.session,
4670
+ text,
4671
+ timestamp: nowIso()
4672
+ };
4673
+ return;
4674
+ }
4675
+ const mapped = mapCommonBackendEvent(parsed, context);
4676
+ if (mapped) yield mapped;
4677
+ }
4678
+ function* drainFinalizedToolCalls(toolCalls, context) {
4679
+ for (const [key, acc] of toolCalls) {
4680
+ if (!acc.finalized) continue;
4681
+ toolCalls.delete(key);
4682
+ yield buildToolCallEvent(acc, context);
4683
+ }
4684
+ }
4685
+ function* flushPendingToolCalls(toolCalls, context) {
4686
+ for (const [key, acc] of toolCalls) {
4687
+ toolCalls.delete(key);
4688
+ yield buildToolCallEvent(acc, context);
4689
+ }
4690
+ }
4691
+ function buildToolCallEvent(acc, context) {
4692
+ let args = acc.argsRaw;
4693
+ if (acc.argsRaw.length > 0) {
4694
+ try {
4695
+ args = JSON.parse(acc.argsRaw);
4696
+ } catch {
4697
+ args = acc.argsRaw;
4698
+ }
4699
+ } else {
4700
+ args = {};
4701
+ }
4702
+ return {
4703
+ type: "tool_call",
4704
+ task: context.task,
4705
+ session: context.session,
4706
+ toolName: acc.name ?? "tool",
4707
+ toolCallId: acc.id,
4708
+ args,
4709
+ timestamp: nowIso()
4710
+ };
4711
+ }
4712
+ function captureStreamUsage(parsed, usage) {
4713
+ const model = stringValue(parsed.model);
4714
+ if (model && !usage.model) usage.model = model;
4715
+ const openAiUsage = parsed.usage;
4716
+ if (openAiUsage && typeof openAiUsage === "object") {
4717
+ const promptTokens = numberValue(openAiUsage.prompt_tokens);
4718
+ const completionTokens = numberValue(openAiUsage.completion_tokens);
4719
+ const inputTokens = numberValue(openAiUsage.input_tokens);
4720
+ const outputTokens = numberValue(openAiUsage.output_tokens);
4721
+ if (promptTokens !== void 0) {
4722
+ usage.tokensIn = promptTokens;
4723
+ usage.saw = true;
4724
+ } else if (inputTokens !== void 0) {
4725
+ usage.tokensIn = (usage.tokensIn ?? 0) + inputTokens;
4726
+ usage.saw = true;
4727
+ }
4728
+ if (completionTokens !== void 0) {
4729
+ usage.tokensOut = completionTokens;
4730
+ usage.saw = true;
4731
+ } else if (outputTokens !== void 0) {
4732
+ usage.tokensOut = (usage.tokensOut ?? 0) + outputTokens;
4733
+ usage.saw = true;
4734
+ }
4735
+ }
4736
+ const type = stringValue(parsed.type);
4737
+ if (type === "message_start") {
4738
+ const message = parsed.message;
4739
+ const messageModel = stringValue(message?.model);
4740
+ if (messageModel && !usage.model) usage.model = messageModel;
4741
+ const messageUsage = message?.usage;
4742
+ const inputTokens = numberValue(messageUsage?.input_tokens);
4743
+ if (inputTokens !== void 0) {
4744
+ usage.tokensIn = inputTokens;
4745
+ usage.saw = true;
4746
+ }
4747
+ const outputTokens = numberValue(messageUsage?.output_tokens);
4748
+ if (outputTokens !== void 0) {
4749
+ usage.tokensOut = (usage.tokensOut ?? 0) + outputTokens;
4750
+ usage.saw = true;
4751
+ }
4752
+ }
4753
+ if (type === "message_delta") {
4754
+ const delta = parsed.delta;
4755
+ const stopReason = stringValue(delta?.stop_reason);
4756
+ if (stopReason) usage.finishReason = stopReason;
4757
+ }
4758
+ const choices = parsed.choices;
4759
+ if (Array.isArray(choices)) {
4760
+ const finishReason = stringValue(
4761
+ choices[0]?.finish_reason
4762
+ );
4763
+ if (finishReason) usage.finishReason = finishReason;
4764
+ }
4765
+ }
4766
+ function numberValue(value) {
4767
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
4768
+ }
4769
+ function stringValue(value) {
4770
+ return typeof value === "string" && value.length > 0 ? value : void 0;
4771
+ }
4772
+
4773
+ // src/runtime/stream-agent-turn.ts
4774
+ async function* streamAgentTurn(backend, prompt, opts = {}) {
4775
+ const label = backend.kind === "chat" ? backend.backend.kind : backend.kind;
4776
+ const task = { id: `turn-${crypto.randomUUID()}`, intent: prompt };
4777
+ const acc = { deltaText: "", input: 0, output: 0, costUsd: 0 };
4778
+ const deadline = deriveTurnSignal(opts.signal, opts.timeoutMs ?? 0);
4779
+ let session;
4780
+ try {
4781
+ session = await startTurnSession(backend, task, prompt, deadline.signal, label);
4782
+ yield { type: "backend_start", task, session, backend: label, timestamp: nowIso() };
4783
+ const inner = backend.kind === "chat" ? driveChatTurn(backend.backend, task, session, prompt, deadline.signal, acc) : driveBoxTurn(
4784
+ backend.kind === "box" ? backend.box : await inlineSandboxClient(backend.factory).create(),
4785
+ prompt,
4786
+ deadline.signal,
4787
+ backend.agentRunName ?? "agent",
4788
+ acc
4789
+ );
4790
+ for await (const event of inner) {
4791
+ yield event;
4792
+ throwIfAborted(deadline.signal);
4793
+ }
4794
+ yield buildFinalEvent(task, session, acc, { status: "completed", reason: "turn completed" });
4795
+ } catch (err) {
4796
+ const callerAborted = opts.signal?.aborted === true;
4797
+ const status = callerAborted ? "aborted" : "failed";
4798
+ const message = err instanceof Error ? err.message : String(err);
4799
+ const error = err instanceof BackendTransportError ? { kind: "transport", message, status: err.status, body: err.body } : { kind: "backend", message };
4800
+ yield {
4801
+ type: "backend_error",
4802
+ task,
4803
+ ...session ? { session } : {},
4804
+ backend: label,
4805
+ message,
4806
+ recoverable: !callerAborted,
4807
+ error,
4808
+ timestamp: nowIso()
4809
+ };
4810
+ yield buildFinalEvent(task, session, acc, { status, reason: message, error });
4811
+ } finally {
4812
+ deadline.dispose();
4813
+ }
4814
+ }
4815
+ async function collectAgentTurn(stream) {
4816
+ const events = [];
4817
+ for await (const event of stream) events.push(event);
4818
+ const final = events.at(-1);
4819
+ if (!final || final.type !== "final") {
4820
+ throw new Error(
4821
+ `collectAgentTurn: stream ended without a terminal 'final' event (last: ${final ? final.type : "none"})`
4822
+ );
4823
+ }
4824
+ const metadata = final.metadata ?? {};
4825
+ const tokenUsage = metadata.tokenUsage && typeof metadata.tokenUsage === "object" ? metadata.tokenUsage : {};
4826
+ const usage = {
4827
+ input: finiteNumber(tokenUsage.input) ?? 0,
4828
+ output: finiteNumber(tokenUsage.output) ?? 0
4829
+ };
4830
+ const costUsd = finiteNumber(metadata.costUsd);
4831
+ if (costUsd !== void 0) usage.costUsd = costUsd;
4832
+ if (typeof metadata.model === "string" && metadata.model.length > 0) {
4833
+ usage.model = metadata.model;
4834
+ }
4835
+ return {
4836
+ finalText: final.text ?? "",
4837
+ usage,
4838
+ events,
4839
+ status: final.status,
4840
+ ...final.error ? { error: final.error } : {}
4841
+ };
4842
+ }
4843
+ async function startTurnSession(backend, task, prompt, signal, label) {
4844
+ if (backend.kind === "chat" && backend.backend.start) {
4845
+ return backend.backend.start(
4846
+ { task, message: prompt },
4847
+ { task, knowledge: emptyReadiness(task), signal }
4848
+ );
4849
+ }
4850
+ return newRuntimeSession(label);
4851
+ }
4852
+ async function* driveBoxTurn(box, prompt, signal, agentRunName, acc) {
4853
+ for await (const event of box.streamPrompt(prompt, { signal })) {
4854
+ const terminalText = terminalTextFromSandboxEvent(event);
4855
+ if (terminalText !== void 0) acc.terminalText = terminalText;
4856
+ const mapped = mapSandboxEvent(event, { agentRunName });
4857
+ if (!mapped) continue;
4858
+ foldEvent(mapped, acc, agentRunName);
4859
+ yield mapped;
4860
+ }
4861
+ }
4862
+ async function* driveChatTurn(backend, task, session, prompt, signal, acc) {
4863
+ const input = { task, message: prompt };
4864
+ const context = { task, knowledge: emptyReadiness(task), session, signal };
4865
+ for await (const raw of backend.stream(input, context)) {
4866
+ const event = normalizeBackendStreamEvent(raw, task, session);
4867
+ foldEvent(event, acc);
4868
+ yield event;
4869
+ }
4870
+ }
4871
+ function foldEvent(event, acc, fallbackModelLabel) {
4872
+ if (event.type === "text_delta") {
4873
+ acc.deltaText += event.text;
4874
+ return;
4875
+ }
4876
+ if (event.type === "llm_call") {
4877
+ acc.input += event.tokensIn ?? 0;
4878
+ acc.output += event.tokensOut ?? 0;
4879
+ acc.costUsd += event.costUsd ?? 0;
4880
+ if (event.model && event.model !== fallbackModelLabel) acc.model = event.model;
4881
+ }
4882
+ }
4883
+ function terminalTextFromSandboxEvent(event) {
4884
+ if (!event || typeof event !== "object") return void 0;
4885
+ const type = String(event.type ?? "");
4886
+ if (type !== "result" && type !== "done" && type !== "final") return void 0;
4887
+ const data = event.data && typeof event.data === "object" ? event.data : {};
4888
+ for (const key of ["finalText", "text", "response", "content"]) {
4889
+ const value = data[key];
4890
+ if (typeof value === "string") return value;
4891
+ }
4892
+ return void 0;
4893
+ }
4894
+ function buildFinalEvent(task, session, acc, outcome) {
4895
+ const finalText = acc.terminalText ?? acc.deltaText;
4896
+ return {
4897
+ type: "final",
4898
+ task,
4899
+ ...session ? { session } : {},
4900
+ status: outcome.status,
4901
+ reason: outcome.reason,
4902
+ ...finalText ? { text: finalText } : {},
4903
+ metadata: {
4904
+ tokenUsage: { input: acc.input, output: acc.output },
4905
+ ...acc.costUsd > 0 ? { costUsd: acc.costUsd } : {},
4906
+ ...acc.model ? { model: acc.model } : {}
4907
+ },
4908
+ ...outcome.error ? { error: outcome.error } : {},
4909
+ timestamp: nowIso()
4910
+ };
4911
+ }
4912
+ function emptyReadiness(task) {
4913
+ return scoreKnowledgeReadiness({ taskId: task.id, requirements: [] });
4914
+ }
4915
+ function finiteNumber(value) {
4916
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
4917
+ }
4918
+ function throwIfAborted(signal) {
4919
+ if (!signal.aborted) return;
4920
+ throw signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason));
4921
+ }
4922
+ function deriveTurnSignal(callerSignal, timeoutMs) {
4923
+ const controller = new AbortController();
4924
+ const timer = timeoutMs > 0 ? setTimeout(
4925
+ () => controller.abort(new Error(`agent turn timed out after ${timeoutMs}ms`)),
4926
+ timeoutMs
4927
+ ) : void 0;
4928
+ if (timer && typeof timer.unref === "function") {
4929
+ ;
4930
+ timer.unref();
4931
+ }
4932
+ const onCallerAbort = () => controller.abort(callerSignal?.reason ?? new Error("agent turn aborted"));
4933
+ if (callerSignal) {
4934
+ if (callerSignal.aborted) onCallerAbort();
4935
+ else callerSignal.addEventListener("abort", onCallerAbort, { once: true });
4936
+ }
4937
+ return {
4938
+ signal: controller.signal,
4939
+ dispose: () => {
4940
+ if (timer) clearTimeout(timer);
4941
+ callerSignal?.removeEventListener("abort", onCallerAbort);
4942
+ }
4943
+ };
4944
+ }
4945
+
3857
4946
  // src/runtime/supervise/detector-monitor.ts
3858
4947
  import {
3859
4948
  argHash,
@@ -4641,9 +5730,9 @@ function jjWorkspace(opts) {
4641
5730
  }
4642
5731
  async function runInWorkspace(ws, body, opts = {}) {
4643
5732
  const { mkdtempSync: mkdtempSync2, rmSync: rmSync2 } = await import("fs");
4644
- const { tmpdir: tmpdir2 } = await import("os");
4645
- const { join: join3 } = await import("path");
4646
- const dir = mkdtempSync2(join3(tmpdir2(), opts.tmpPrefix ?? "ws-run-"));
5733
+ const { tmpdir: tmpdir3 } = await import("os");
5734
+ const { join: join4 } = await import("path");
5735
+ const dir = mkdtempSync2(join4(tmpdir3(), opts.tmpPrefix ?? "ws-run-"));
4647
5736
  try {
4648
5737
  await ws.materialize(dir);
4649
5738
  const r = await body(dir);
@@ -4662,6 +5751,14 @@ function tail(s) {
4662
5751
  }
4663
5752
 
4664
5753
  export {
5754
+ newRuntimeSession,
5755
+ touchSession,
5756
+ nowIso,
5757
+ InMemoryRuntimeSessionStore,
5758
+ createIterableBackend,
5759
+ createSandboxPromptBackend,
5760
+ createOpenAICompatibleBackend,
5761
+ normalizeBackendStreamEvent,
4665
5762
  anytimeReport,
4666
5763
  renderAnytimeTable,
4667
5764
  defaultAuditorInstruction,
@@ -4676,15 +5773,19 @@ export {
4676
5773
  stopSentinel,
4677
5774
  sentinelCompletion,
4678
5775
  deterministicCompletion,
5776
+ reportLoopUsage,
5777
+ loopCampaignDispatch,
5778
+ loopDispatch,
5779
+ inlineSandboxClient,
5780
+ resolveSandboxClient,
5781
+ naiveDriver,
5782
+ dumbDriver,
5783
+ defineLeaderboard,
4679
5784
  defaultAnalystInstruction,
4680
5785
  observe,
4681
5786
  renderReport,
4682
5787
  harvestCorpus,
4683
5788
  inProcessSandboxClient,
4684
- inlineSandboxClient,
4685
- reportLoopUsage,
4686
- loopCampaignDispatch,
4687
- loopDispatch,
4688
5789
  createMcpEnvironment,
4689
5790
  assertTraceDerivedFindings,
4690
5791
  createScopeAnalyst,
@@ -4709,7 +5810,6 @@ export {
4709
5810
  trajectoryReport,
4710
5811
  equalKOnCost,
4711
5812
  promotionGate,
4712
- resolveSandboxClient,
4713
5813
  depthStrategy,
4714
5814
  breadthStrategy,
4715
5815
  sample,
@@ -4722,8 +5822,6 @@ export {
4722
5822
  printBenchmarkReport,
4723
5823
  SandboxRunAbortError,
4724
5824
  openSandboxRun,
4725
- naiveDriver,
4726
- dumbDriver,
4727
5825
  strategyAuthorContract,
4728
5826
  assertStrategyContract,
4729
5827
  authorStrategy,
@@ -4731,6 +5829,8 @@ export {
4731
5829
  pickChampion,
4732
5830
  selectChampion,
4733
5831
  runStrategyEvolution,
5832
+ streamAgentTurn,
5833
+ collectAgentTurn,
4734
5834
  defaultToolDetectors,
4735
5835
  watchTrace,
4736
5836
  runCoderChecks,
@@ -4751,4 +5851,4 @@ export {
4751
5851
  computeFindingId,
4752
5852
  makeFinding2 as makeFinding
4753
5853
  };
4754
- //# sourceMappingURL=chunk-QDVLTRCP.js.map
5854
+ //# sourceMappingURL=chunk-DLAEEF26.js.map