@apo-ai/sdk 0.9.2 → 0.9.4
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() {
|
|
@@ -557,8 +560,19 @@ function parseJudgeJson(raw) {
|
|
|
557
560
|
reasoning: "Judge response could not be parsed as JSON \u2014 the verdict is unknown, so this check is treated as a failure. The model's raw response is available in the judge metadata."
|
|
558
561
|
};
|
|
559
562
|
}
|
|
560
|
-
var JUDGE_TIMEOUT_MS = 3e5;
|
|
561
563
|
var JUDGE_IDLE_TIMEOUT_MS = 9e4;
|
|
564
|
+
function envMs(name, fallback) {
|
|
565
|
+
const raw = process.env[name]?.trim();
|
|
566
|
+
if (!raw) return fallback;
|
|
567
|
+
const ms = Number(raw);
|
|
568
|
+
return Number.isFinite(ms) && ms > 0 ? ms : fallback;
|
|
569
|
+
}
|
|
570
|
+
function judgeFirstDataTimeoutMs() {
|
|
571
|
+
return envMs("APO_JUDGE_TIMEOUT_MS", 3e5);
|
|
572
|
+
}
|
|
573
|
+
function judgeMaxDurationMs() {
|
|
574
|
+
return envMs("APO_JUDGE_MAX_DURATION_MS", 12e5);
|
|
575
|
+
}
|
|
562
576
|
var JUDGE_RETRY_DELAY_MS = 1e3;
|
|
563
577
|
var JUDGE_RETRY_AFTER_CAP_MS = 2e4;
|
|
564
578
|
var JUDGE_MIN_RETRY_BUDGET_MS = 6e4;
|
|
@@ -644,6 +658,36 @@ async function readCompletion(response, onData) {
|
|
|
644
658
|
return { text, usage, finishReason };
|
|
645
659
|
}
|
|
646
660
|
var prefixQueues = /* @__PURE__ */ new Map();
|
|
661
|
+
function promptCacheKey(prefix) {
|
|
662
|
+
return `apo-${createHash("sha256").update(prefix).digest("hex").slice(0, 32)}`;
|
|
663
|
+
}
|
|
664
|
+
var cacheKeyRejectedBy = /* @__PURE__ */ new Set();
|
|
665
|
+
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;
|
|
666
|
+
function rejectsPromptCacheKey(status, body) {
|
|
667
|
+
return status === 400 && CACHE_KEY_REJECTED.test(body);
|
|
668
|
+
}
|
|
669
|
+
async function fetchWithPromptCacheKey(input, init) {
|
|
670
|
+
let body;
|
|
671
|
+
try {
|
|
672
|
+
body = typeof init?.body === "string" ? JSON.parse(init.body) : void 0;
|
|
673
|
+
} catch {
|
|
674
|
+
body = void 0;
|
|
675
|
+
}
|
|
676
|
+
if (!body || !("prompt_cache_key" in body)) return fetch(input, init);
|
|
677
|
+
const scope = `${input instanceof Request ? input.url : String(input)}\0${String(body.model)}`;
|
|
678
|
+
const { prompt_cache_key: _omitted, ...rest } = body;
|
|
679
|
+
const withoutKey = () => fetch(input, { ...init, body: JSON.stringify(rest) });
|
|
680
|
+
if (cacheKeyRejectedBy.has(scope)) return withoutKey();
|
|
681
|
+
const response = await fetch(input, init);
|
|
682
|
+
if (response.status !== 400) return response;
|
|
683
|
+
const text = await response.text();
|
|
684
|
+
if (!rejectsPromptCacheKey(response.status, text)) {
|
|
685
|
+
return new Response(text, { status: response.status, statusText: response.statusText, headers: response.headers });
|
|
686
|
+
}
|
|
687
|
+
const retry = await withoutKey();
|
|
688
|
+
if (retry.ok) cacheKeyRejectedBy.add(scope);
|
|
689
|
+
return retry;
|
|
690
|
+
}
|
|
647
691
|
function runWithSharedPrefix(key, task2) {
|
|
648
692
|
const prev = prefixQueues.get(key) ?? Promise.resolve();
|
|
649
693
|
const next = prev.then(task2, task2);
|
|
@@ -684,6 +728,7 @@ ${instructionText}`,
|
|
|
684
728
|
const cacheKey = `${args.model}\0${briefingText}\0${deliverableText}`;
|
|
685
729
|
const requestBody = (stream) => JSON.stringify({
|
|
686
730
|
model: args.model,
|
|
731
|
+
prompt_cache_key: promptCacheKey(cacheKey),
|
|
687
732
|
messages: [
|
|
688
733
|
{
|
|
689
734
|
role: "system",
|
|
@@ -702,23 +747,36 @@ ${instructionText}`,
|
|
|
702
747
|
response_format: { type: "json_object" },
|
|
703
748
|
...stream ? { stream: true, stream_options: { include_usage: true } } : {}
|
|
704
749
|
});
|
|
705
|
-
const attempt = async (stream,
|
|
750
|
+
const attempt = async (stream, maxDeadline) => {
|
|
706
751
|
const controller = new AbortController();
|
|
707
752
|
const expire = (reason, name) => () => controller.abort(new DOMException(reason, name));
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
|
|
753
|
+
const firstDataMs = judgeFirstDataTimeoutMs();
|
|
754
|
+
const runaway = setTimeout(
|
|
755
|
+
expire(
|
|
756
|
+
`still streaming after ${Math.round(judgeMaxDurationMs() / 1e3)}s (APO_JUDGE_MAX_DURATION_MS)`,
|
|
757
|
+
"TimeoutError"
|
|
758
|
+
),
|
|
759
|
+
Math.max(maxDeadline - Date.now(), 0)
|
|
760
|
+
);
|
|
761
|
+
let waiting = setTimeout(
|
|
762
|
+
expire(
|
|
763
|
+
`no data within ${Math.round(firstDataMs / 1e3)}s (APO_JUDGE_TIMEOUT_MS)`,
|
|
764
|
+
"FirstDataTimeoutError"
|
|
765
|
+
),
|
|
766
|
+
firstDataMs
|
|
711
767
|
);
|
|
712
768
|
const stalled = expire(`no data for ${JUDGE_IDLE_TIMEOUT_MS / 1e3}s`, "IdleTimeoutError");
|
|
713
769
|
let idle;
|
|
714
770
|
const onData = () => {
|
|
771
|
+
clearTimeout(waiting);
|
|
772
|
+
waiting = void 0;
|
|
715
773
|
clearTimeout(idle);
|
|
716
774
|
idle = setTimeout(stalled, JUDGE_IDLE_TIMEOUT_MS);
|
|
717
775
|
};
|
|
718
776
|
try {
|
|
719
777
|
let response;
|
|
720
778
|
try {
|
|
721
|
-
response = await
|
|
779
|
+
response = await fetchWithPromptCacheKey(`${baseURL}/chat/completions`, {
|
|
722
780
|
method: "POST",
|
|
723
781
|
signal: controller.signal,
|
|
724
782
|
headers: {
|
|
@@ -748,7 +806,8 @@ ${instructionText}`,
|
|
|
748
806
|
return transportFailure("Judge response failed", controller.signal.reason ?? error);
|
|
749
807
|
}
|
|
750
808
|
} finally {
|
|
751
|
-
clearTimeout(
|
|
809
|
+
clearTimeout(runaway);
|
|
810
|
+
clearTimeout(waiting);
|
|
752
811
|
clearTimeout(idle);
|
|
753
812
|
controller.abort();
|
|
754
813
|
}
|
|
@@ -768,7 +827,7 @@ ${instructionText}`,
|
|
|
768
827
|
};
|
|
769
828
|
return runWithSharedPrefix(cacheKey, async () => {
|
|
770
829
|
const startedAt = Date.now();
|
|
771
|
-
const deadline = startedAt +
|
|
830
|
+
const deadline = startedAt + judgeMaxDurationMs();
|
|
772
831
|
let stream = true;
|
|
773
832
|
let result = await attempt(stream, deadline);
|
|
774
833
|
const truncated = result.kind === "completion" && isTruncated(result.completion);
|
|
@@ -860,7 +919,7 @@ ${args.instruction}`
|
|
|
860
919
|
}
|
|
861
920
|
|
|
862
921
|
// src/agent-task/checks/agent-session.ts
|
|
863
|
-
import { createHash as
|
|
922
|
+
import { createHash as createHash3 } from "crypto";
|
|
864
923
|
|
|
865
924
|
// src/agent-task/tracing.ts
|
|
866
925
|
function createNoopAgentTaskTraceContext() {
|
|
@@ -908,7 +967,7 @@ function isTraceableSpanId(spanId) {
|
|
|
908
967
|
}
|
|
909
968
|
|
|
910
969
|
// src/agent-task/checks/mcp-tools.ts
|
|
911
|
-
import { createHash } from "crypto";
|
|
970
|
+
import { createHash as createHash2 } from "crypto";
|
|
912
971
|
import { isAbsolute, resolve } from "path";
|
|
913
972
|
import { readFile } from "fs/promises";
|
|
914
973
|
var DEFAULT_TOOL_TIMEOUT_MS = 3e4;
|
|
@@ -1065,7 +1124,7 @@ function validateMcpServerConfig(server, where) {
|
|
|
1065
1124
|
}
|
|
1066
1125
|
}
|
|
1067
1126
|
function sha256(text) {
|
|
1068
|
-
return
|
|
1127
|
+
return createHash2("sha256").update(text).digest("hex");
|
|
1069
1128
|
}
|
|
1070
1129
|
function renderValue(value) {
|
|
1071
1130
|
if (typeof value === "string") return value;
|
|
@@ -1259,8 +1318,17 @@ var RECORD_TEXT_LIMIT = 2 * 1024;
|
|
|
1259
1318
|
var RECORD_BRIEFING_LIMIT = 16 * 1024;
|
|
1260
1319
|
var RECORD_MAX_STEPS = 64;
|
|
1261
1320
|
var RECORD_KEEP_HEAD = 56;
|
|
1321
|
+
function sessionPrefix(body) {
|
|
1322
|
+
const messages = body.messages ?? [];
|
|
1323
|
+
const firstUser = messages.find((m) => m.role === "user");
|
|
1324
|
+
return [
|
|
1325
|
+
String(body.model),
|
|
1326
|
+
...messages.filter((m) => m.role === "system").map((m) => m.content),
|
|
1327
|
+
firstUser?.content
|
|
1328
|
+
].map((part) => typeof part === "string" ? part : JSON.stringify(part ?? null)).join("\0");
|
|
1329
|
+
}
|
|
1262
1330
|
function sha2562(text) {
|
|
1263
|
-
return
|
|
1331
|
+
return createHash3("sha256").update(text).digest("hex");
|
|
1264
1332
|
}
|
|
1265
1333
|
function renderValue2(value) {
|
|
1266
1334
|
if (typeof value === "string") return value;
|
|
@@ -1543,11 +1611,16 @@ async function runAgentSession(spec) {
|
|
|
1543
1611
|
const provider = createOpenAICompatible({
|
|
1544
1612
|
name: "openrouter",
|
|
1545
1613
|
baseURL: spec.baseURL ?? "https://openrouter.ai/api/v1",
|
|
1614
|
+
fetch: fetchWithPromptCacheKey,
|
|
1546
1615
|
apiKey: spec.apiKey ?? process.env.OPENROUTER_API_KEY ?? process.env.OPENAI_API_KEY,
|
|
1547
1616
|
// Mark the briefing prefix cacheable — repeated samples (rejudge) re-bill
|
|
1548
|
-
// nothing for the shared turn-0 context
|
|
1617
|
+
// nothing for the shared turn-0 context — and keep every step of one
|
|
1618
|
+
// session on the replica holding its growing prefix. The key is per
|
|
1619
|
+
// session (briefing + criterion): sessions run concurrently, unlike
|
|
1620
|
+
// t.judge calls, and one key per task would pile them onto one replica.
|
|
1549
1621
|
transformRequestBody: (body) => ({
|
|
1550
1622
|
...body,
|
|
1623
|
+
prompt_cache_key: promptCacheKey(sessionPrefix(body)),
|
|
1551
1624
|
messages: body.messages?.map(
|
|
1552
1625
|
(m) => m.role === "system" && typeof m.content === "string" ? { ...m, content: [{ type: "text", text: m.content, cache_control: { type: "ephemeral" } }] } : m
|
|
1553
1626
|
)
|
|
@@ -2290,7 +2363,7 @@ var Recorder = class {
|
|
|
2290
2363
|
};
|
|
2291
2364
|
|
|
2292
2365
|
// src/agent-task/checks/compact.ts
|
|
2293
|
-
import { createHash as
|
|
2366
|
+
import { createHash as createHash4 } from "crypto";
|
|
2294
2367
|
var RECEIVED_VALUE_LIMIT = 4 * 1024;
|
|
2295
2368
|
var JUDGE_SEGMENT_LIMIT = 16 * 1024;
|
|
2296
2369
|
var PREVIEW_CHARS = 256;
|
|
@@ -2398,7 +2471,7 @@ function marker(bytes) {
|
|
|
2398
2471
|
kind: "truncated",
|
|
2399
2472
|
preview,
|
|
2400
2473
|
size_bytes: bytes.length,
|
|
2401
|
-
sha256:
|
|
2474
|
+
sha256: createHash4("sha256").update(bytes).digest("hex")
|
|
2402
2475
|
};
|
|
2403
2476
|
}
|
|
2404
2477
|
function truncateSessionText(session) {
|
|
@@ -3043,7 +3116,7 @@ function isFileArtifact(value) {
|
|
|
3043
3116
|
}
|
|
3044
3117
|
|
|
3045
3118
|
// src/agent-task/deliverables/upload.ts
|
|
3046
|
-
import { createHash as
|
|
3119
|
+
import { createHash as createHash5 } from "crypto";
|
|
3047
3120
|
import { createReadStream } from "fs";
|
|
3048
3121
|
import { Readable } from "stream";
|
|
3049
3122
|
var DEFAULT_FETCH = typeof fetch === "function" ? fetch : void 0;
|
|
@@ -3098,7 +3171,7 @@ async function uploadOne(name, artifact, config) {
|
|
|
3098
3171
|
}
|
|
3099
3172
|
async function hashFile(path) {
|
|
3100
3173
|
return new Promise((resolve5, reject) => {
|
|
3101
|
-
const hash =
|
|
3174
|
+
const hash = createHash5("sha256");
|
|
3102
3175
|
let size = 0;
|
|
3103
3176
|
const stream = createReadStream(path);
|
|
3104
3177
|
stream.on("data", (chunk) => {
|
|
@@ -4338,7 +4411,7 @@ function usageOf2(raw) {
|
|
|
4338
4411
|
}
|
|
4339
4412
|
|
|
4340
4413
|
// src/agent-task/transcript-replay/otlp.ts
|
|
4341
|
-
import { createHash as
|
|
4414
|
+
import { createHash as createHash6 } from "crypto";
|
|
4342
4415
|
var SPAN_KIND_INTERNAL = 1;
|
|
4343
4416
|
var STATUS_CODE_UNSET = 0;
|
|
4344
4417
|
function transcriptSessionToOtlp(session, options = {}) {
|
|
@@ -4485,7 +4558,7 @@ function messageAttrValue(role, content) {
|
|
|
4485
4558
|
return JSON.stringify([{ role, content }]);
|
|
4486
4559
|
}
|
|
4487
4560
|
function sha256Hex(input) {
|
|
4488
|
-
return
|
|
4561
|
+
return createHash6("sha256").update(input).digest("hex");
|
|
4489
4562
|
}
|
|
4490
4563
|
function deriveTraceId(source, sessionId) {
|
|
4491
4564
|
return sha256Hex(`apo-transcript-replay|${source}|${sessionId}`).slice(0, 32);
|
|
@@ -6148,12 +6221,12 @@ function printOverallSummary(summaries, hasErrors) {
|
|
|
6148
6221
|
}
|
|
6149
6222
|
|
|
6150
6223
|
// src/agent-task/task-revision-manifest.ts
|
|
6151
|
-
import { createHash as
|
|
6224
|
+
import { createHash as createHash7 } from "crypto";
|
|
6152
6225
|
function normalizeManifestPath(path) {
|
|
6153
6226
|
return path.split("\\").join("/").normalize("NFC");
|
|
6154
6227
|
}
|
|
6155
6228
|
function sha256Hex2(bytes) {
|
|
6156
|
-
return
|
|
6229
|
+
return createHash7("sha256").update(bytes).digest("hex");
|
|
6157
6230
|
}
|
|
6158
6231
|
function compareBytewiseUtf8(a, b) {
|
|
6159
6232
|
const ba = Buffer.from(a, "utf8");
|