@apo-ai/sdk 0.11.0 → 0.13.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.
@@ -1,4 +1,4 @@
1
- import { A as AgentTaskTraceContext } from '../../tracing-DoIGvJW1.js';
1
+ import { A as AgentTaskTraceContext } from '../../tracing-FqFObrmt.js';
2
2
 
3
3
  /**
4
4
  * Vercel AI SDK tracing integration for apo.
@@ -1,4 +1,4 @@
1
- import { A as AgentTaskTraceContext } from '../../tracing-DoIGvJW1.js';
1
+ import { A as AgentTaskTraceContext } from '../../tracing-FqFObrmt.js';
2
2
 
3
3
  /**
4
4
  * Anthropic JS SDK tracing integration for apo.
@@ -2,8 +2,8 @@
2
2
  // Do not edit by hand — edit the src/ originals and rebuild.
3
3
  import {
4
4
  createApoAnthropic
5
- } from "../../chunk-6H2GACBG.js";
6
- import "../../chunk-Z64A566W.js";
5
+ } from "../../chunk-X73JGMK3.js";
6
+ import "../../chunk-JTWQD27M.js";
7
7
  export {
8
8
  createApoAnthropic
9
9
  };
@@ -1,4 +1,4 @@
1
- import { A as AgentTaskTraceContext } from '../../tracing-DoIGvJW1.js';
1
+ import { A as AgentTaskTraceContext } from '../../tracing-FqFObrmt.js';
2
2
 
3
3
  /**
4
4
  * OpenAI JS SDK tracing integration for apo.
@@ -2,8 +2,8 @@
2
2
  // Do not edit by hand — edit the src/ originals and rebuild.
3
3
  import {
4
4
  createApoOpenAI
5
- } from "../../chunk-P43KL5AR.js";
6
- import "../../chunk-Z64A566W.js";
5
+ } from "../../chunk-A3UYQ7M4.js";
6
+ import "../../chunk-JTWQD27M.js";
7
7
  export {
8
8
  createApoOpenAI
9
9
  };
@@ -1,4 +1,4 @@
1
- import { A as AgentTaskTraceContext, a as AgentTaskTraceOptions } from '../tracing-DoIGvJW1.js';
1
+ import { A as AgentTaskTraceContext, a as AgentTaskTraceOptions } from '../tracing-FqFObrmt.js';
2
2
  export { CreateApoTracerOptions, createApoTracer } from './integrations/ai-sdk.js';
3
3
  export { CreateApoOpenAIOptions, createApoOpenAI } from './integrations/openai.js';
4
4
  export { CreateApoAnthropicOptions, createApoAnthropic } from './integrations/anthropic.js';
@@ -673,6 +673,13 @@ type JudgeMetadata = {
673
673
  * the decision model, and `response` carries its typed verdict.
674
674
  */
675
675
  verdict_by?: "second-judge";
676
+ /**
677
+ * How many attempts of this call were cut because the judge's stream fell
678
+ * into a repetition loop (`t.judge` only). Absent = none. 1 with a verdict:
679
+ * the first draw looped and the retry answered. A call whose last draw
680
+ * looped has no verdict and is recorded as a judge error.
681
+ */
682
+ reasoning_loops?: number;
676
683
  };
677
684
  /**
678
685
  * Evidence from the second grader. `error` is set when the decisions
@@ -2318,8 +2325,8 @@ declare function getActiveApoRun(): ApoRunContext | undefined;
2318
2325
  *
2319
2326
  * Pure, dependency-free canonicalizer. Both this module and its Python twin
2320
2327
  * (`backend/apo/execution/task_revision_manifest.py`) MUST produce
2321
- * byte-identical canonical JSON and digests for every fixture under
2322
- * `specs/contracts/task-revision/v1/cases/`.
2328
+ * byte-identical canonical JSON and digests for any given file tree — the
2329
+ * task-revision identity is content-addressed on both sides of the wire.
2323
2330
  *
2324
2331
  * Canonicalization rules:
2325
2332
  * - `/` path separators (caller `\` normalized to `/`);
@@ -9,20 +9,20 @@ import {
9
9
  resolveApoAuthHeaders,
10
10
  withApoRun,
11
11
  withApoRunSync
12
- } from "../chunk-LO343UZO.js";
12
+ } from "../chunk-P24ECOI3.js";
13
13
  import {
14
14
  createApoTracer
15
15
  } from "../chunk-OZWOLCJ5.js";
16
16
  import "../chunk-HRYXLRXJ.js";
17
17
  import {
18
18
  createApoOpenAI
19
- } from "../chunk-P43KL5AR.js";
19
+ } from "../chunk-A3UYQ7M4.js";
20
20
  import {
21
21
  createApoAnthropic
22
- } from "../chunk-6H2GACBG.js";
22
+ } from "../chunk-X73JGMK3.js";
23
23
  import {
24
24
  servingHostFromBaseURL
25
- } from "../chunk-Z64A566W.js";
25
+ } from "../chunk-JTWQD27M.js";
26
26
 
27
27
  // src/agent-task/run/steer-scheduler.ts
28
28
  var TASK_STEER_SPAN_NAME = "task.steer";
@@ -644,6 +644,158 @@ function levenshtein(a, b) {
644
644
  // src/agent-task/checks/judge.ts
645
645
  import { createHash } from "crypto";
646
646
 
647
+ // src/agent-task/checks/reasoning-loop.ts
648
+ var MAX_UNIT_CHARS = 64;
649
+ var MIN_UNIT_COPIES = 100;
650
+ var MIN_REPEAT_CHARS = 1e3;
651
+ var MAX_CHAR_RUN = 3;
652
+ var GRAM = 24;
653
+ var DIVERSITY_WINDOW = 3e3;
654
+ var MIN_DIVERSITY = 0.1;
655
+ var DIVERSITY_STRIDE = 256;
656
+ var MAX_DIVERSITY_LINE_CHARS = 2e3;
657
+ var RECYCLE_GRAM = 32;
658
+ var RECYCLE_WINDOW = 5e3;
659
+ var MAX_RECYCLED = 0.95;
660
+ var isFoldable = (ch) => /[\p{L}\p{N}]/u.test(ch);
661
+ function foldForRepetition(text) {
662
+ let out = "";
663
+ let last = "";
664
+ let run = 0;
665
+ for (const raw of text) {
666
+ if (!isFoldable(raw)) continue;
667
+ const ch = raw.toLowerCase();
668
+ run = ch === last ? run + 1 : 1;
669
+ last = ch;
670
+ if (run <= MAX_CHAR_RUN) out += ch;
671
+ }
672
+ return out;
673
+ }
674
+ function isTableLine(line) {
675
+ const trimmed = line.trim();
676
+ if (trimmed.startsWith("|")) return true;
677
+ return (trimmed.match(/\|/g)?.length ?? 0) >= 3;
678
+ }
679
+ function diversity(folded) {
680
+ const total = folded.length - GRAM + 1;
681
+ if (total <= 0) return 1;
682
+ const grams = /* @__PURE__ */ new Set();
683
+ for (let i = 0; i < total; i++) grams.add(folded.slice(i, i + GRAM));
684
+ return grams.size / total;
685
+ }
686
+ var RepetitionGuard = class {
687
+ constructor(opts) {
688
+ this.rawChars = 0;
689
+ // Repeated-unit state: the folded tail, and for each period p the number
690
+ // of consecutive folded chars equal to the char p before them.
691
+ this.tail = "";
692
+ this.lastChar = "";
693
+ this.charRun = 0;
694
+ this.periodRuns = new Array(MAX_UNIT_CHARS + 1).fill(0);
695
+ // Low-diversity state: the current raw line, and the folded window built
696
+ // from finished lines that are not tables.
697
+ this.line = "";
698
+ this.window = "";
699
+ this.sinceMeasured = 0;
700
+ // Recycled state: every gram of the filtered folded stream so far, and for
701
+ // the window's grams whether each had been seen when it arrived.
702
+ this.seenGrams = /* @__PURE__ */ new Set();
703
+ this.gramTail = "";
704
+ this.windowFlags = [];
705
+ this.flagHead = 0;
706
+ this.recycledInWindow = 0;
707
+ this.checkDiversity = opts.diversity;
708
+ }
709
+ get tripped() {
710
+ return this.trip;
711
+ }
712
+ push(delta) {
713
+ if (this.trip || !delta) return this.trip;
714
+ for (const raw of delta) {
715
+ this.rawChars++;
716
+ if (this.checkDiversity) this.pushLineChar(raw);
717
+ if (isFoldable(raw)) this.pushFolded(raw.toLowerCase());
718
+ if (this.trip) break;
719
+ }
720
+ return this.trip;
721
+ }
722
+ pushFolded(ch) {
723
+ this.charRun = ch === this.lastChar ? this.charRun + 1 : 1;
724
+ this.lastChar = ch;
725
+ if (this.charRun > MAX_CHAR_RUN) return;
726
+ this.tail += ch;
727
+ const n = this.tail.length - 1;
728
+ for (let p = 1; p <= MAX_UNIT_CHARS; p++) {
729
+ this.periodRuns[p] = n >= p && this.tail[n - p] === ch ? this.periodRuns[p] + 1 : 0;
730
+ const span = this.periodRuns[p] + p;
731
+ if (span >= MIN_REPEAT_CHARS && span / p >= MIN_UNIT_COPIES) {
732
+ this.trip = {
733
+ rule: "repeated-unit",
734
+ atChar: this.rawChars,
735
+ unit: this.tail.slice(-p),
736
+ copies: Math.floor(span / p)
737
+ };
738
+ return;
739
+ }
740
+ }
741
+ if (this.tail.length > MAX_UNIT_CHARS * 2) this.tail = this.tail.slice(-MAX_UNIT_CHARS);
742
+ }
743
+ pushLineChar(raw) {
744
+ if (raw !== "\n") {
745
+ if (this.line.length <= MAX_DIVERSITY_LINE_CHARS) this.line += raw;
746
+ return;
747
+ }
748
+ const line = this.line;
749
+ this.line = "";
750
+ if (line.length > MAX_DIVERSITY_LINE_CHARS || isTableLine(line)) return;
751
+ const folded = foldForRepetition(line);
752
+ if (!folded) return;
753
+ this.pushRecycled(folded);
754
+ this.window = (this.window + folded).slice(-DIVERSITY_WINDOW);
755
+ this.sinceMeasured += folded.length;
756
+ if (this.window.length < DIVERSITY_WINDOW || this.sinceMeasured < DIVERSITY_STRIDE) return;
757
+ this.sinceMeasured = 0;
758
+ const ratio = diversity(this.window);
759
+ if (ratio < MIN_DIVERSITY) {
760
+ this.trip = { rule: "low-diversity", atChar: this.rawChars, diversity: ratio };
761
+ return;
762
+ }
763
+ const windowGrams = this.windowFlags.length - this.flagHead;
764
+ if (windowGrams < RECYCLE_WINDOW) return;
765
+ const recycled = this.recycledInWindow / windowGrams;
766
+ if (recycled >= MAX_RECYCLED) {
767
+ this.trip = { rule: "recycled", atChar: this.rawChars, recycled };
768
+ }
769
+ }
770
+ pushRecycled(folded) {
771
+ for (const ch of folded) {
772
+ this.gramTail = (this.gramTail + ch).slice(-RECYCLE_GRAM);
773
+ if (this.gramTail.length < RECYCLE_GRAM) continue;
774
+ const seen = this.seenGrams.has(this.gramTail);
775
+ if (!seen) this.seenGrams.add(this.gramTail);
776
+ this.windowFlags.push(seen);
777
+ if (seen) this.recycledInWindow++;
778
+ if (this.windowFlags.length - this.flagHead > RECYCLE_WINDOW) {
779
+ if (this.windowFlags[this.flagHead++]) this.recycledInWindow--;
780
+ }
781
+ }
782
+ if (this.flagHead > RECYCLE_WINDOW * 4) {
783
+ this.windowFlags.splice(0, this.flagHead);
784
+ this.flagHead = 0;
785
+ }
786
+ }
787
+ };
788
+ function describeTrip(trip) {
789
+ switch (trip.rule) {
790
+ case "repeated-unit":
791
+ return `"${trip.unit}" repeated ${trip.copies}\xD7 at char ${trip.atChar}`;
792
+ case "low-diversity":
793
+ return `distinct-${GRAM}-gram ratio ${trip.diversity.toFixed(3)} at char ${trip.atChar}`;
794
+ case "recycled":
795
+ return `${Math.round(trip.recycled * 100)}% of the last ${RECYCLE_WINDOW} chars replay earlier reasoning, at char ${trip.atChar}`;
796
+ }
797
+ }
798
+
647
799
  // src/agent-task/checks/second-judge.ts
648
800
  function secondJudgeFacts(check) {
649
801
  const sj = check.judge?.secondJudge;
@@ -828,6 +980,16 @@ var JudgeUnavailableError = class extends Error {
828
980
  this.name = "JudgeUnavailableError";
829
981
  }
830
982
  };
983
+ var JudgeReasoningLoopError = class extends JudgeUnavailableError {
984
+ constructor(trip, channel) {
985
+ super(`judge reasoning loop: ${channel} ${describeTrip(trip)}`);
986
+ this.name = "JudgeReasoningLoopError";
987
+ }
988
+ };
989
+ function isJudgeLoopGuardEnabled() {
990
+ const value = process.env.APO_JUDGE_LOOP_GUARD?.trim().toLowerCase();
991
+ return value !== "0" && value !== "false" && value !== "off";
992
+ }
831
993
  var QUOTE_RULE = " Inside the reasoning, quote the output with single quotes, never with unescaped double quotes.";
832
994
  var VERDICT_FIRST_CONTRACT = 'Respond with ONLY a JSON object: {"pass": true/false, "reasoning": "your reasoning"}.' + QUOTE_RULE;
833
995
  var REASONING_FIRST_CONTRACT = 'Respond with ONLY a JSON object: {"reasoning": "your reasoning", "pass": true/false}.' + QUOTE_RULE;
@@ -952,7 +1114,7 @@ function retryDelayMs(retryAfter) {
952
1114
  if (!Number.isFinite(ms)) return JUDGE_RETRY_DELAY_MS;
953
1115
  return Math.min(Math.max(ms, JUDGE_RETRY_DELAY_MS), JUDGE_RETRY_AFTER_CAP_MS);
954
1116
  }
955
- async function readCompletion(response, onData) {
1117
+ async function readCompletion(response, onData, guards) {
956
1118
  const contentType = response.headers.get("content-type") ?? "";
957
1119
  if (!contentType.includes("text/event-stream")) {
958
1120
  const data = await response.json();
@@ -989,7 +1151,15 @@ async function readCompletion(response, onData) {
989
1151
  }
990
1152
  if (!chunk) return;
991
1153
  const choice = chunk.choices?.[0];
992
- text += choice?.delta?.content ?? "";
1154
+ const content = choice?.delta?.content ?? "";
1155
+ text += content;
1156
+ if (guards) {
1157
+ const thinking = choice?.delta?.reasoning_content ?? choice?.delta?.reasoning ?? "";
1158
+ const reasoningTrip = guards.reasoning.push(thinking);
1159
+ if (reasoningTrip) throw new JudgeReasoningLoopError(reasoningTrip, "reasoning");
1160
+ const contentTrip = guards.content.push(content);
1161
+ if (contentTrip) throw new JudgeReasoningLoopError(contentTrip, "content");
1162
+ }
993
1163
  if (choice?.finish_reason) finishReason = choice.finish_reason;
994
1164
  if (chunk.usage) usage = chunk.usage;
995
1165
  };
@@ -1023,7 +1193,13 @@ async function readCompletion(response, onData) {
1023
1193
  dispatch();
1024
1194
  return { text, usage, finishReason };
1025
1195
  }
1026
- var prefixQueues = /* @__PURE__ */ new Map();
1196
+ var prefixLanes = /* @__PURE__ */ new Map();
1197
+ function judgeConcurrency() {
1198
+ const raw = process.env.APO_JUDGE_CONCURRENCY?.trim();
1199
+ if (!raw) return 1;
1200
+ const n = Number(raw);
1201
+ return Number.isInteger(n) && n >= 1 ? n : 1;
1202
+ }
1027
1203
  function promptCacheKey(prefix) {
1028
1204
  return `apo-${createHash("sha256").update(prefix).digest("hex").slice(0, 32)}`;
1029
1205
  }
@@ -1055,16 +1231,36 @@ async function fetchWithPromptCacheKey(input, init) {
1055
1231
  return retry;
1056
1232
  }
1057
1233
  function runWithSharedPrefix(key, task2) {
1058
- const prev = prefixQueues.get(key) ?? Promise.resolve();
1059
- const next = prev.then(task2, task2);
1060
- prefixQueues.set(
1061
- key,
1062
- next.then(
1063
- () => void 0,
1064
- () => void 0
1065
- )
1066
- );
1067
- return next;
1234
+ const existing = prefixLanes.get(key);
1235
+ if (!existing) {
1236
+ const warmer = task2();
1237
+ prefixLanes.set(key, {
1238
+ warmed: warmer.then(
1239
+ () => void 0,
1240
+ () => void 0
1241
+ ),
1242
+ active: 0,
1243
+ waiting: []
1244
+ });
1245
+ return warmer;
1246
+ }
1247
+ const lane = existing;
1248
+ const acquire = () => new Promise((resolve5) => {
1249
+ if (lane.active < judgeConcurrency()) {
1250
+ lane.active += 1;
1251
+ resolve5();
1252
+ } else {
1253
+ lane.waiting.push(() => {
1254
+ lane.active += 1;
1255
+ resolve5();
1256
+ });
1257
+ }
1258
+ });
1259
+ const release = () => {
1260
+ lane.active -= 1;
1261
+ lane.waiting.shift()?.();
1262
+ };
1263
+ return lane.warmed.then(acquire).then(() => task2().finally(release));
1068
1264
  }
1069
1265
  function defaultJudgeBaseURL(baseURL) {
1070
1266
  return baseURL ?? process.env.OPENROUTER_BASE_URL ?? "https://openrouter.ai/api/v1";
@@ -1166,9 +1362,16 @@ async function callJudge(args) {
1166
1362
  ...schema && response.status === 400 && /json_schema|response_format/.test(body) ? { schemaRejected: true } : {}
1167
1363
  };
1168
1364
  }
1365
+ const guards = isJudgeLoopGuardEnabled() ? {
1366
+ reasoning: new RepetitionGuard({ diversity: true }),
1367
+ content: new RepetitionGuard({ diversity: false })
1368
+ } : void 0;
1169
1369
  try {
1170
- return { kind: "completion", completion: await readCompletion(response, onData) };
1370
+ return { kind: "completion", completion: await readCompletion(response, onData, guards) };
1171
1371
  } catch (error) {
1372
+ if (error instanceof JudgeReasoningLoopError) {
1373
+ return { kind: "unavailable", error, retryable: true, loop: true };
1374
+ }
1172
1375
  return transportFailure("Judge response failed", controller.signal.reason ?? error);
1173
1376
  }
1174
1377
  } finally {
@@ -1202,6 +1405,7 @@ async function callJudge(args) {
1202
1405
  if (result.schemaRejected) schema = false;
1203
1406
  result = await attempt(stream, schema, deadline);
1204
1407
  }
1408
+ let loops = result.kind === "unavailable" && result.loop ? 1 : 0;
1205
1409
  const noVerdict = (r) => r.kind === "completion" && (isTruncated(r.completion) || judgeVerdict(r.completion.text) === void 0);
1206
1410
  const retryable = result.kind === "unavailable" && result.retryable;
1207
1411
  if (noVerdict(result) || retryable) {
@@ -1210,6 +1414,7 @@ async function callJudge(args) {
1210
1414
  await new Promise((resolve5) => setTimeout(resolve5, delay));
1211
1415
  const first = result;
1212
1416
  result = await attempt(stream, schema, deadline);
1417
+ if (result.kind === "unavailable" && result.loop) loops++;
1213
1418
  if (result.kind === "unavailable" && first.kind === "unavailable") {
1214
1419
  result = {
1215
1420
  ...result,
@@ -1220,17 +1425,25 @@ async function callJudge(args) {
1220
1425
  }
1221
1426
  }
1222
1427
  }
1223
- if (result.kind === "unavailable") throw result.error;
1224
- const { text, usage } = result.completion;
1225
- const judge = {
1428
+ const judgeMetadata = async (completion) => ({
1226
1429
  model: args.model,
1227
1430
  contract: judgeContractInUse(),
1228
1431
  prompt: { system: systemPromptText, user: instructionText },
1229
- response: text,
1230
- tokens: parseJudgeUsage(usage),
1432
+ ...completion ? { response: completion.text, tokens: parseJudgeUsage(completion.usage) } : {},
1231
1433
  latency_ms: Date.now() - startedAt,
1232
- secondJudge: await secondJudgePromise
1233
- };
1434
+ secondJudge: await secondJudgePromise,
1435
+ ...loops > 0 ? { reasoning_loops: loops } : {}
1436
+ });
1437
+ if (result.kind === "unavailable" && result.loop) {
1438
+ return {
1439
+ pass: false,
1440
+ reasoning: `Judge reasoning loop \u2014 the judge's stream degenerated into repetition and was cut${loops > 1 ? ", on the retry too" : ""} (${result.error.message}). The verdict is unknown, so this check is recorded as a judge error, not a verdict.`,
1441
+ judge: await judgeMetadata(),
1442
+ unavailable: true
1443
+ };
1444
+ }
1445
+ if (result.kind === "unavailable") throw result.error;
1446
+ const judge = await judgeMetadata(result.completion);
1234
1447
  if (isTruncated(result.completion)) {
1235
1448
  return {
1236
1449
  pass: false,
@@ -1239,11 +1452,11 @@ async function callJudge(args) {
1239
1452
  unavailable: true
1240
1453
  };
1241
1454
  }
1242
- const verdict = judgeVerdict(text);
1455
+ const verdict = judgeVerdict(result.completion.text);
1243
1456
  if (verdict === void 0) {
1244
1457
  return {
1245
1458
  pass: false,
1246
- reasoning: "Judge reply carried no verdict (no boolean `pass`) on two draws. The verdict is unknown, so this check is recorded as a judge error, not a verdict. The raw reply is on the judge metadata.",
1459
+ reasoning: "Judge reply carried no verdict (no boolean `pass`). The verdict is unknown, so this check is recorded as a judge error, not a verdict. The raw reply is on the judge metadata.",
1247
1460
  judge,
1248
1461
  unavailable: true
1249
1462
  };
@@ -1886,10 +2099,15 @@ function buildEvidenceTools(args) {
1886
2099
  }
1887
2100
  const unsupported = rejectUnsupportedPcreConstructs(pattern);
1888
2101
  if (unsupported) return { error: unsupported };
2102
+ let translated;
2103
+ try {
2104
+ translated = stripLeadingPcreFlags(pattern);
2105
+ } catch (error) {
2106
+ return { error: `invalid regex: ${error instanceof Error ? error.message : String(error)}` };
2107
+ }
1889
2108
  let re;
1890
2109
  try {
1891
- const stripped = stripLeadingPcreFlags(pattern);
1892
- re = new RegExp(stripped.pattern, stripped.flags);
2110
+ re = new RegExp(translated.pattern, translated.flags);
1893
2111
  } catch (error) {
1894
2112
  return {
1895
2113
  error: `invalid regex: ${error instanceof Error ? error.message : String(error)} \u2014 patterns compile as case-insensitive multiline; leading PCRE flags like (?i) are stripped automatically`
@@ -4410,12 +4628,13 @@ function sleep(ms) {
4410
4628
 
4411
4629
  // src/agent-task/checks/agent-history.ts
4412
4630
  function createBackendReaderFromEnv(env = process.env) {
4631
+ const apiKey = env.APO_API_KEY;
4413
4632
  const token = env.APO_AUTH_TOKEN;
4414
4633
  const publicKey = env.APO_PUBLIC_KEY;
4415
4634
  const secretKey = env.APO_SECRET_KEY;
4416
- const headers = token ? { Authorization: `Bearer ${token}` } : publicKey && secretKey ? { Authorization: `Basic ${Buffer.from(`${publicKey}:${secretKey}`).toString("base64")}` } : void 0;
4635
+ const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : token ? { Authorization: `Bearer ${token}` } : publicKey && secretKey ? { Authorization: `Basic ${Buffer.from(`${publicKey}:${secretKey}`).toString("base64")}` } : void 0;
4417
4636
  if (!headers) return void 0;
4418
- const endpoint = env.APO_BACKEND_URL ?? env.AGENT_TASK_TRACE_ENDPOINT ?? "http://127.0.0.1:8000";
4637
+ const endpoint = env.APO_BACKEND_URL ?? "http://127.0.0.1:8000";
4419
4638
  return {
4420
4639
  async get(path) {
4421
4640
  const res = await fetch(`${endpoint.replace(/\/$/, "")}/v1${path}`, { headers });
@@ -4429,10 +4648,23 @@ function createBackendReaderFromEnv(env = process.env) {
4429
4648
  function freezeHistoryPlane(args) {
4430
4649
  const { reader, taskId, selfRunId } = args;
4431
4650
  const detailCache = /* @__PURE__ */ new Map();
4651
+ const listByTaskId = async (id) => await reader.get(
4652
+ `/agent-task-runs?task_id=${encodeURIComponent(id)}&limit=50`
4653
+ );
4432
4654
  const loadRuns = (async () => {
4433
- const raw = await reader.get(
4434
- `/agent-task-runs?task_id=${encodeURIComponent(taskId)}&limit=50`
4435
- );
4655
+ let raw = await listByTaskId(taskId);
4656
+ if ((raw ?? []).length === 0 && selfRunId) {
4657
+ try {
4658
+ const self = await reader.get(
4659
+ `/agent-task-runs/${encodeURIComponent(selfRunId)}`
4660
+ );
4661
+ const canonical = self?.task_id;
4662
+ if (typeof canonical === "string" && canonical !== taskId) {
4663
+ raw = await listByTaskId(canonical);
4664
+ }
4665
+ } catch {
4666
+ }
4667
+ }
4436
4668
  return (raw ?? []).map(
4437
4669
  (r) => ({
4438
4670
  id: String(r.id),
@@ -4496,7 +4728,9 @@ async function freezeHistoryPlaneFromEnv(taskId, env = process.env) {
4496
4728
  taskId,
4497
4729
  ...env.AGENT_TASK_RUN_ID ? { selfRunId: env.AGENT_TASK_RUN_ID } : {}
4498
4730
  });
4499
- } catch {
4731
+ } catch (error) {
4732
+ const reason = error instanceof Error ? error.message : String(error);
4733
+ console.warn(`apo: judge history plane unavailable \u2014 ${reason}`);
4500
4734
  return void 0;
4501
4735
  }
4502
4736
  }
@@ -6365,6 +6599,9 @@ function createOtelAgentTaskTraceClient(config) {
6365
6599
  params.reasoning_tokens
6366
6600
  );
6367
6601
  }
6602
+ if (params.served_model) {
6603
+ active.span.setAttribute("gen_ai.response.model", params.served_model);
6604
+ }
6368
6605
  if (params.status_message) {
6369
6606
  active.span.setAttribute("apo.status_message", params.status_message);
6370
6607
  }