@apo-ai/sdk 0.11.0 → 0.12.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
  };
@@ -1166,9 +1336,16 @@ async function callJudge(args) {
1166
1336
  ...schema && response.status === 400 && /json_schema|response_format/.test(body) ? { schemaRejected: true } : {}
1167
1337
  };
1168
1338
  }
1339
+ const guards = isJudgeLoopGuardEnabled() ? {
1340
+ reasoning: new RepetitionGuard({ diversity: true }),
1341
+ content: new RepetitionGuard({ diversity: false })
1342
+ } : void 0;
1169
1343
  try {
1170
- return { kind: "completion", completion: await readCompletion(response, onData) };
1344
+ return { kind: "completion", completion: await readCompletion(response, onData, guards) };
1171
1345
  } catch (error) {
1346
+ if (error instanceof JudgeReasoningLoopError) {
1347
+ return { kind: "unavailable", error, retryable: true, loop: true };
1348
+ }
1172
1349
  return transportFailure("Judge response failed", controller.signal.reason ?? error);
1173
1350
  }
1174
1351
  } finally {
@@ -1202,6 +1379,7 @@ async function callJudge(args) {
1202
1379
  if (result.schemaRejected) schema = false;
1203
1380
  result = await attempt(stream, schema, deadline);
1204
1381
  }
1382
+ let loops = result.kind === "unavailable" && result.loop ? 1 : 0;
1205
1383
  const noVerdict = (r) => r.kind === "completion" && (isTruncated(r.completion) || judgeVerdict(r.completion.text) === void 0);
1206
1384
  const retryable = result.kind === "unavailable" && result.retryable;
1207
1385
  if (noVerdict(result) || retryable) {
@@ -1210,6 +1388,7 @@ async function callJudge(args) {
1210
1388
  await new Promise((resolve5) => setTimeout(resolve5, delay));
1211
1389
  const first = result;
1212
1390
  result = await attempt(stream, schema, deadline);
1391
+ if (result.kind === "unavailable" && result.loop) loops++;
1213
1392
  if (result.kind === "unavailable" && first.kind === "unavailable") {
1214
1393
  result = {
1215
1394
  ...result,
@@ -1220,17 +1399,25 @@ async function callJudge(args) {
1220
1399
  }
1221
1400
  }
1222
1401
  }
1223
- if (result.kind === "unavailable") throw result.error;
1224
- const { text, usage } = result.completion;
1225
- const judge = {
1402
+ const judgeMetadata = async (completion) => ({
1226
1403
  model: args.model,
1227
1404
  contract: judgeContractInUse(),
1228
1405
  prompt: { system: systemPromptText, user: instructionText },
1229
- response: text,
1230
- tokens: parseJudgeUsage(usage),
1406
+ ...completion ? { response: completion.text, tokens: parseJudgeUsage(completion.usage) } : {},
1231
1407
  latency_ms: Date.now() - startedAt,
1232
- secondJudge: await secondJudgePromise
1233
- };
1408
+ secondJudge: await secondJudgePromise,
1409
+ ...loops > 0 ? { reasoning_loops: loops } : {}
1410
+ });
1411
+ if (result.kind === "unavailable" && result.loop) {
1412
+ return {
1413
+ pass: false,
1414
+ 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.`,
1415
+ judge: await judgeMetadata(),
1416
+ unavailable: true
1417
+ };
1418
+ }
1419
+ if (result.kind === "unavailable") throw result.error;
1420
+ const judge = await judgeMetadata(result.completion);
1234
1421
  if (isTruncated(result.completion)) {
1235
1422
  return {
1236
1423
  pass: false,
@@ -1239,11 +1426,11 @@ async function callJudge(args) {
1239
1426
  unavailable: true
1240
1427
  };
1241
1428
  }
1242
- const verdict = judgeVerdict(text);
1429
+ const verdict = judgeVerdict(result.completion.text);
1243
1430
  if (verdict === void 0) {
1244
1431
  return {
1245
1432
  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.",
1433
+ 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
1434
  judge,
1248
1435
  unavailable: true
1249
1436
  };
@@ -1886,10 +2073,15 @@ function buildEvidenceTools(args) {
1886
2073
  }
1887
2074
  const unsupported = rejectUnsupportedPcreConstructs(pattern);
1888
2075
  if (unsupported) return { error: unsupported };
2076
+ let translated;
2077
+ try {
2078
+ translated = stripLeadingPcreFlags(pattern);
2079
+ } catch (error) {
2080
+ return { error: `invalid regex: ${error instanceof Error ? error.message : String(error)}` };
2081
+ }
1889
2082
  let re;
1890
2083
  try {
1891
- const stripped = stripLeadingPcreFlags(pattern);
1892
- re = new RegExp(stripped.pattern, stripped.flags);
2084
+ re = new RegExp(translated.pattern, translated.flags);
1893
2085
  } catch (error) {
1894
2086
  return {
1895
2087
  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 +4602,13 @@ function sleep(ms) {
4410
4602
 
4411
4603
  // src/agent-task/checks/agent-history.ts
4412
4604
  function createBackendReaderFromEnv(env = process.env) {
4605
+ const apiKey = env.APO_API_KEY;
4413
4606
  const token = env.APO_AUTH_TOKEN;
4414
4607
  const publicKey = env.APO_PUBLIC_KEY;
4415
4608
  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;
4609
+ const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : token ? { Authorization: `Bearer ${token}` } : publicKey && secretKey ? { Authorization: `Basic ${Buffer.from(`${publicKey}:${secretKey}`).toString("base64")}` } : void 0;
4417
4610
  if (!headers) return void 0;
4418
- const endpoint = env.APO_BACKEND_URL ?? env.AGENT_TASK_TRACE_ENDPOINT ?? "http://127.0.0.1:8000";
4611
+ const endpoint = env.APO_BACKEND_URL ?? "http://127.0.0.1:8000";
4419
4612
  return {
4420
4613
  async get(path) {
4421
4614
  const res = await fetch(`${endpoint.replace(/\/$/, "")}/v1${path}`, { headers });
@@ -4429,10 +4622,23 @@ function createBackendReaderFromEnv(env = process.env) {
4429
4622
  function freezeHistoryPlane(args) {
4430
4623
  const { reader, taskId, selfRunId } = args;
4431
4624
  const detailCache = /* @__PURE__ */ new Map();
4625
+ const listByTaskId = async (id) => await reader.get(
4626
+ `/agent-task-runs?task_id=${encodeURIComponent(id)}&limit=50`
4627
+ );
4432
4628
  const loadRuns = (async () => {
4433
- const raw = await reader.get(
4434
- `/agent-task-runs?task_id=${encodeURIComponent(taskId)}&limit=50`
4435
- );
4629
+ let raw = await listByTaskId(taskId);
4630
+ if ((raw ?? []).length === 0 && selfRunId) {
4631
+ try {
4632
+ const self = await reader.get(
4633
+ `/agent-task-runs/${encodeURIComponent(selfRunId)}`
4634
+ );
4635
+ const canonical = self?.task_id;
4636
+ if (typeof canonical === "string" && canonical !== taskId) {
4637
+ raw = await listByTaskId(canonical);
4638
+ }
4639
+ } catch {
4640
+ }
4641
+ }
4436
4642
  return (raw ?? []).map(
4437
4643
  (r) => ({
4438
4644
  id: String(r.id),
@@ -4496,7 +4702,9 @@ async function freezeHistoryPlaneFromEnv(taskId, env = process.env) {
4496
4702
  taskId,
4497
4703
  ...env.AGENT_TASK_RUN_ID ? { selfRunId: env.AGENT_TASK_RUN_ID } : {}
4498
4704
  });
4499
- } catch {
4705
+ } catch (error) {
4706
+ const reason = error instanceof Error ? error.message : String(error);
4707
+ console.warn(`apo: judge history plane unavailable \u2014 ${reason}`);
4500
4708
  return void 0;
4501
4709
  }
4502
4710
  }
@@ -6365,6 +6573,9 @@ function createOtelAgentTaskTraceClient(config) {
6365
6573
  params.reasoning_tokens
6366
6574
  );
6367
6575
  }
6576
+ if (params.served_model) {
6577
+ active.span.setAttribute("gen_ai.response.model", params.served_model);
6578
+ }
6368
6579
  if (params.status_message) {
6369
6580
  active.span.setAttribute("apo.status_message", params.status_message);
6370
6581
  }