@apo-ai/sdk 0.9.2 → 0.9.3

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.
@@ -377,6 +377,9 @@ function levenshtein(a, b) {
377
377
  return prev[n];
378
378
  }
379
379
 
380
+ // src/agent-task/checks/judge.ts
381
+ import { createHash } from "crypto";
382
+
380
383
  // src/agent-task/checks/second-judge.ts
381
384
  var SECOND_JUDGE_TIMEOUT_MS = 3e4;
382
385
  function resolveSecondJudgeModel() {
@@ -644,6 +647,36 @@ async function readCompletion(response, onData) {
644
647
  return { text, usage, finishReason };
645
648
  }
646
649
  var prefixQueues = /* @__PURE__ */ new Map();
650
+ function promptCacheKey(prefix) {
651
+ return `apo-${createHash("sha256").update(prefix).digest("hex").slice(0, 32)}`;
652
+ }
653
+ var cacheKeyRejectedBy = /* @__PURE__ */ new Set();
654
+ var CACHE_KEY_REJECTED = /(unknown|unrecogni[sz]ed|unsupported|unexpected|extra|not permitted|not allowed|cannot find|invalid)[^\n]{0,80}prompt_cache_key|prompt_cache_key[^\n]{0,80}(unknown|unrecogni[sz]ed|unsupported|unexpected|not permitted|not allowed|not supported|extra)/i;
655
+ function rejectsPromptCacheKey(status, body) {
656
+ return status === 400 && CACHE_KEY_REJECTED.test(body);
657
+ }
658
+ async function fetchWithPromptCacheKey(input, init) {
659
+ let body;
660
+ try {
661
+ body = typeof init?.body === "string" ? JSON.parse(init.body) : void 0;
662
+ } catch {
663
+ body = void 0;
664
+ }
665
+ if (!body || !("prompt_cache_key" in body)) return fetch(input, init);
666
+ const scope = `${input instanceof Request ? input.url : String(input)}\0${String(body.model)}`;
667
+ const { prompt_cache_key: _omitted, ...rest } = body;
668
+ const withoutKey = () => fetch(input, { ...init, body: JSON.stringify(rest) });
669
+ if (cacheKeyRejectedBy.has(scope)) return withoutKey();
670
+ const response = await fetch(input, init);
671
+ if (response.status !== 400) return response;
672
+ const text = await response.text();
673
+ if (!rejectsPromptCacheKey(response.status, text)) {
674
+ return new Response(text, { status: response.status, statusText: response.statusText, headers: response.headers });
675
+ }
676
+ const retry = await withoutKey();
677
+ if (retry.ok) cacheKeyRejectedBy.add(scope);
678
+ return retry;
679
+ }
647
680
  function runWithSharedPrefix(key, task2) {
648
681
  const prev = prefixQueues.get(key) ?? Promise.resolve();
649
682
  const next = prev.then(task2, task2);
@@ -684,6 +717,7 @@ ${instructionText}`,
684
717
  const cacheKey = `${args.model}\0${briefingText}\0${deliverableText}`;
685
718
  const requestBody = (stream) => JSON.stringify({
686
719
  model: args.model,
720
+ prompt_cache_key: promptCacheKey(cacheKey),
687
721
  messages: [
688
722
  {
689
723
  role: "system",
@@ -718,7 +752,7 @@ ${instructionText}`,
718
752
  try {
719
753
  let response;
720
754
  try {
721
- response = await fetch(`${baseURL}/chat/completions`, {
755
+ response = await fetchWithPromptCacheKey(`${baseURL}/chat/completions`, {
722
756
  method: "POST",
723
757
  signal: controller.signal,
724
758
  headers: {
@@ -860,7 +894,7 @@ ${args.instruction}`
860
894
  }
861
895
 
862
896
  // src/agent-task/checks/agent-session.ts
863
- import { createHash as createHash2 } from "crypto";
897
+ import { createHash as createHash3 } from "crypto";
864
898
 
865
899
  // src/agent-task/tracing.ts
866
900
  function createNoopAgentTaskTraceContext() {
@@ -908,7 +942,7 @@ function isTraceableSpanId(spanId) {
908
942
  }
909
943
 
910
944
  // src/agent-task/checks/mcp-tools.ts
911
- import { createHash } from "crypto";
945
+ import { createHash as createHash2 } from "crypto";
912
946
  import { isAbsolute, resolve } from "path";
913
947
  import { readFile } from "fs/promises";
914
948
  var DEFAULT_TOOL_TIMEOUT_MS = 3e4;
@@ -1065,7 +1099,7 @@ function validateMcpServerConfig(server, where) {
1065
1099
  }
1066
1100
  }
1067
1101
  function sha256(text) {
1068
- return createHash("sha256").update(text).digest("hex");
1102
+ return createHash2("sha256").update(text).digest("hex");
1069
1103
  }
1070
1104
  function renderValue(value) {
1071
1105
  if (typeof value === "string") return value;
@@ -1259,8 +1293,17 @@ var RECORD_TEXT_LIMIT = 2 * 1024;
1259
1293
  var RECORD_BRIEFING_LIMIT = 16 * 1024;
1260
1294
  var RECORD_MAX_STEPS = 64;
1261
1295
  var RECORD_KEEP_HEAD = 56;
1296
+ function sessionPrefix(body) {
1297
+ const messages = body.messages ?? [];
1298
+ const firstUser = messages.find((m) => m.role === "user");
1299
+ return [
1300
+ String(body.model),
1301
+ ...messages.filter((m) => m.role === "system").map((m) => m.content),
1302
+ firstUser?.content
1303
+ ].map((part) => typeof part === "string" ? part : JSON.stringify(part ?? null)).join("\0");
1304
+ }
1262
1305
  function sha2562(text) {
1263
- return createHash2("sha256").update(text).digest("hex");
1306
+ return createHash3("sha256").update(text).digest("hex");
1264
1307
  }
1265
1308
  function renderValue2(value) {
1266
1309
  if (typeof value === "string") return value;
@@ -1543,11 +1586,16 @@ async function runAgentSession(spec) {
1543
1586
  const provider = createOpenAICompatible({
1544
1587
  name: "openrouter",
1545
1588
  baseURL: spec.baseURL ?? "https://openrouter.ai/api/v1",
1589
+ fetch: fetchWithPromptCacheKey,
1546
1590
  apiKey: spec.apiKey ?? process.env.OPENROUTER_API_KEY ?? process.env.OPENAI_API_KEY,
1547
1591
  // Mark the briefing prefix cacheable — repeated samples (rejudge) re-bill
1548
- // nothing for the shared turn-0 context.
1592
+ // nothing for the shared turn-0 context — and keep every step of one
1593
+ // session on the replica holding its growing prefix. The key is per
1594
+ // session (briefing + criterion): sessions run concurrently, unlike
1595
+ // t.judge calls, and one key per task would pile them onto one replica.
1549
1596
  transformRequestBody: (body) => ({
1550
1597
  ...body,
1598
+ prompt_cache_key: promptCacheKey(sessionPrefix(body)),
1551
1599
  messages: body.messages?.map(
1552
1600
  (m) => m.role === "system" && typeof m.content === "string" ? { ...m, content: [{ type: "text", text: m.content, cache_control: { type: "ephemeral" } }] } : m
1553
1601
  )
@@ -2290,7 +2338,7 @@ var Recorder = class {
2290
2338
  };
2291
2339
 
2292
2340
  // src/agent-task/checks/compact.ts
2293
- import { createHash as createHash3 } from "crypto";
2341
+ import { createHash as createHash4 } from "crypto";
2294
2342
  var RECEIVED_VALUE_LIMIT = 4 * 1024;
2295
2343
  var JUDGE_SEGMENT_LIMIT = 16 * 1024;
2296
2344
  var PREVIEW_CHARS = 256;
@@ -2398,7 +2446,7 @@ function marker(bytes) {
2398
2446
  kind: "truncated",
2399
2447
  preview,
2400
2448
  size_bytes: bytes.length,
2401
- sha256: createHash3("sha256").update(bytes).digest("hex")
2449
+ sha256: createHash4("sha256").update(bytes).digest("hex")
2402
2450
  };
2403
2451
  }
2404
2452
  function truncateSessionText(session) {
@@ -3043,7 +3091,7 @@ function isFileArtifact(value) {
3043
3091
  }
3044
3092
 
3045
3093
  // src/agent-task/deliverables/upload.ts
3046
- import { createHash as createHash4 } from "crypto";
3094
+ import { createHash as createHash5 } from "crypto";
3047
3095
  import { createReadStream } from "fs";
3048
3096
  import { Readable } from "stream";
3049
3097
  var DEFAULT_FETCH = typeof fetch === "function" ? fetch : void 0;
@@ -3098,7 +3146,7 @@ async function uploadOne(name, artifact, config) {
3098
3146
  }
3099
3147
  async function hashFile(path) {
3100
3148
  return new Promise((resolve5, reject) => {
3101
- const hash = createHash4("sha256");
3149
+ const hash = createHash5("sha256");
3102
3150
  let size = 0;
3103
3151
  const stream = createReadStream(path);
3104
3152
  stream.on("data", (chunk) => {
@@ -4338,7 +4386,7 @@ function usageOf2(raw) {
4338
4386
  }
4339
4387
 
4340
4388
  // src/agent-task/transcript-replay/otlp.ts
4341
- import { createHash as createHash5 } from "crypto";
4389
+ import { createHash as createHash6 } from "crypto";
4342
4390
  var SPAN_KIND_INTERNAL = 1;
4343
4391
  var STATUS_CODE_UNSET = 0;
4344
4392
  function transcriptSessionToOtlp(session, options = {}) {
@@ -4485,7 +4533,7 @@ function messageAttrValue(role, content) {
4485
4533
  return JSON.stringify([{ role, content }]);
4486
4534
  }
4487
4535
  function sha256Hex(input) {
4488
- return createHash5("sha256").update(input).digest("hex");
4536
+ return createHash6("sha256").update(input).digest("hex");
4489
4537
  }
4490
4538
  function deriveTraceId(source, sessionId) {
4491
4539
  return sha256Hex(`apo-transcript-replay|${source}|${sessionId}`).slice(0, 32);
@@ -6148,12 +6196,12 @@ function printOverallSummary(summaries, hasErrors) {
6148
6196
  }
6149
6197
 
6150
6198
  // src/agent-task/task-revision-manifest.ts
6151
- import { createHash as createHash6 } from "crypto";
6199
+ import { createHash as createHash7 } from "crypto";
6152
6200
  function normalizeManifestPath(path) {
6153
6201
  return path.split("\\").join("/").normalize("NFC");
6154
6202
  }
6155
6203
  function sha256Hex2(bytes) {
6156
- return createHash6("sha256").update(bytes).digest("hex");
6204
+ return createHash7("sha256").update(bytes).digest("hex");
6157
6205
  }
6158
6206
  function compareBytewiseUtf8(a, b) {
6159
6207
  const ba = Buffer.from(a, "utf8");