@apo-ai/sdk 0.13.0 → 0.15.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.
- package/README.md +5 -2
- package/dist/agent-task/integrations/ai-sdk.d.ts +1 -1
- package/dist/agent-task/integrations/anthropic.d.ts +1 -1
- package/dist/agent-task/integrations/anthropic.js +2 -2
- package/dist/agent-task/integrations/openai.d.ts +4 -1
- package/dist/agent-task/integrations/openai.js +2 -2
- package/dist/agent-task/public.d.ts +24 -1
- package/dist/agent-task/public.js +261 -49
- package/dist/agent-task/public.js.map +1 -1
- package/dist/{chunk-X73JGMK3.js → chunk-4BQSCD6T.js} +2 -2
- package/dist/{chunk-JTWQD27M.js → chunk-JN7RHBTS.js} +36 -3
- package/dist/chunk-JN7RHBTS.js.map +1 -0
- package/dist/{chunk-A3UYQ7M4.js → chunk-NSMKSPBU.js} +20 -3
- package/dist/chunk-NSMKSPBU.js.map +1 -0
- package/dist/{tracing-FqFObrmt.d.ts → tracing-CEucJqQI.d.ts} +22 -0
- package/package.json +2 -1
- package/dist/chunk-A3UYQ7M4.js.map +0 -1
- package/dist/chunk-JTWQD27M.js.map +0 -1
- /package/dist/{chunk-X73JGMK3.js.map → chunk-4BQSCD6T.js.map} +0 -0
|
@@ -16,13 +16,13 @@ import {
|
|
|
16
16
|
import "../chunk-HRYXLRXJ.js";
|
|
17
17
|
import {
|
|
18
18
|
createApoOpenAI
|
|
19
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-NSMKSPBU.js";
|
|
20
20
|
import {
|
|
21
21
|
createApoAnthropic
|
|
22
|
-
} from "../chunk-
|
|
22
|
+
} from "../chunk-4BQSCD6T.js";
|
|
23
23
|
import {
|
|
24
24
|
servingHostFromBaseURL
|
|
25
|
-
} from "../chunk-
|
|
25
|
+
} from "../chunk-JN7RHBTS.js";
|
|
26
26
|
|
|
27
27
|
// src/agent-task/run/steer-scheduler.ts
|
|
28
28
|
var TASK_STEER_SPAN_NAME = "task.steer";
|
|
@@ -691,7 +691,7 @@ var RepetitionGuard = class {
|
|
|
691
691
|
this.tail = "";
|
|
692
692
|
this.lastChar = "";
|
|
693
693
|
this.charRun = 0;
|
|
694
|
-
this.periodRuns =
|
|
694
|
+
this.periodRuns = Array.from({ length: MAX_UNIT_CHARS + 1 }, () => 0);
|
|
695
695
|
// Low-diversity state: the current raw line, and the folded window built
|
|
696
696
|
// from finished lines that are not tables.
|
|
697
697
|
this.line = "";
|
|
@@ -872,9 +872,19 @@ var openAIProvider = {
|
|
|
872
872
|
apiRootExample: "https://api.openai.com/v1",
|
|
873
873
|
reserved: "The OpenAI Decisions API provider is not implemented yet: the endpoint is limited-preview and its schema is unpublished. " + OPENROUTER_RESERVED_HINT + "."
|
|
874
874
|
};
|
|
875
|
+
var anthropicProvider = {
|
|
876
|
+
decisionsURL: (chatBaseURL) => `${chatBaseURL.replace(/\/v1\/?$/, "")}/decisions`,
|
|
877
|
+
requestBody: () => {
|
|
878
|
+
throw new Error("unreachable \u2014 reserved guard returns before the call");
|
|
879
|
+
},
|
|
880
|
+
parseResponse: () => ({ error: "unreachable \u2014 reserved guard returns before the call" }),
|
|
881
|
+
apiRootExample: "https://api.anthropic.com",
|
|
882
|
+
reserved: "Anthropic-direct serves no decisions API \u2014 the second judge cannot run there. " + OPENROUTER_RESERVED_HINT + "."
|
|
883
|
+
};
|
|
875
884
|
var PROVIDERS = {
|
|
876
885
|
openrouter: openRouterProvider,
|
|
877
|
-
openai: openAIProvider
|
|
886
|
+
openai: openAIProvider,
|
|
887
|
+
anthropic: anthropicProvider
|
|
878
888
|
};
|
|
879
889
|
function resolveSecondJudgeModel() {
|
|
880
890
|
const value = process.env.APO_SECOND_JUDGE_MODEL?.trim();
|
|
@@ -900,7 +910,10 @@ function resolveSecondJudgeProvider(baseURL) {
|
|
|
900
910
|
}
|
|
901
911
|
return provider;
|
|
902
912
|
}
|
|
903
|
-
|
|
913
|
+
const hostname = new URL(baseURL).hostname;
|
|
914
|
+
if (/(^|\.)api\.openai\.com$/.test(hostname)) return openAIProvider;
|
|
915
|
+
if (/(^|\.)api\.anthropic\.com$/.test(hostname)) return anthropicProvider;
|
|
916
|
+
return openRouterProvider;
|
|
904
917
|
}
|
|
905
918
|
var INPUT_TOO_LARGE_MARKERS = [
|
|
906
919
|
"max_tokens_exceeded",
|
|
@@ -1049,10 +1062,12 @@ ${formatValue(v)}`).join("\n\n");
|
|
|
1049
1062
|
}
|
|
1050
1063
|
function parseJudgeUsage(usage) {
|
|
1051
1064
|
if (!usage) return void 0;
|
|
1065
|
+
const anthropicShape = usage.cache_creation_input_tokens !== void 0 || usage.cache_read_input_tokens !== void 0;
|
|
1052
1066
|
const cacheCreation = usage.cache_creation_input_tokens ?? usage.prompt_tokens_details?.cache_write_tokens;
|
|
1053
1067
|
const cacheRead = usage.cache_read_input_tokens ?? usage.prompt_tokens_details?.cached_tokens;
|
|
1068
|
+
const prompt = usage.prompt_tokens ?? 0;
|
|
1054
1069
|
const tokens = {
|
|
1055
|
-
input:
|
|
1070
|
+
input: anthropicShape ? prompt + (cacheCreation ?? 0) + (cacheRead ?? 0) : prompt,
|
|
1056
1071
|
output: usage.completion_tokens ?? 0
|
|
1057
1072
|
};
|
|
1058
1073
|
if (typeof cacheCreation === "number") tokens.cache_creation = cacheCreation;
|
|
@@ -1089,6 +1104,7 @@ function judgeVerdict(raw) {
|
|
|
1089
1104
|
};
|
|
1090
1105
|
}
|
|
1091
1106
|
var JUDGE_IDLE_TIMEOUT_MS = 9e4;
|
|
1107
|
+
var ANTHROPIC_JUDGE_MAX_TOKENS = 8192;
|
|
1092
1108
|
function envMs(name, fallback) {
|
|
1093
1109
|
const raw = process.env[name]?.trim();
|
|
1094
1110
|
if (!raw) return fallback;
|
|
@@ -1121,12 +1137,14 @@ async function readCompletion(response, onData, guards) {
|
|
|
1121
1137
|
return {
|
|
1122
1138
|
text: data.choices?.[0]?.message?.content ?? "",
|
|
1123
1139
|
usage: data.usage,
|
|
1124
|
-
finishReason: data.choices?.[0]?.finish_reason ?? void 0
|
|
1140
|
+
finishReason: data.choices?.[0]?.finish_reason ?? void 0,
|
|
1141
|
+
servedModel: typeof data.model === "string" && data.model ? data.model : void 0
|
|
1125
1142
|
};
|
|
1126
1143
|
}
|
|
1127
1144
|
let text = "";
|
|
1128
1145
|
let usage;
|
|
1129
1146
|
let finishReason;
|
|
1147
|
+
let servedModel;
|
|
1130
1148
|
let eventType = "message";
|
|
1131
1149
|
let dataLines = [];
|
|
1132
1150
|
let finished = false;
|
|
@@ -1162,6 +1180,7 @@ async function readCompletion(response, onData, guards) {
|
|
|
1162
1180
|
}
|
|
1163
1181
|
if (choice?.finish_reason) finishReason = choice.finish_reason;
|
|
1164
1182
|
if (chunk.usage) usage = chunk.usage;
|
|
1183
|
+
if (typeof chunk.model === "string" && chunk.model) servedModel = chunk.model;
|
|
1165
1184
|
};
|
|
1166
1185
|
const handleLine = (line) => {
|
|
1167
1186
|
if (line === "") return dispatch();
|
|
@@ -1174,7 +1193,7 @@ async function readCompletion(response, onData, guards) {
|
|
|
1174
1193
|
else if (field === "event") eventType = value;
|
|
1175
1194
|
};
|
|
1176
1195
|
const reader = response.body?.getReader();
|
|
1177
|
-
if (!reader) return { text, usage, finishReason };
|
|
1196
|
+
if (!reader) return { text, usage, finishReason, servedModel };
|
|
1178
1197
|
const decoder = new TextDecoder();
|
|
1179
1198
|
let buffer = "";
|
|
1180
1199
|
for (; ; ) {
|
|
@@ -1185,13 +1204,29 @@ async function readCompletion(response, onData, guards) {
|
|
|
1185
1204
|
buffer = lines.pop() ?? "";
|
|
1186
1205
|
for (const line of lines) {
|
|
1187
1206
|
handleLine(line);
|
|
1188
|
-
if (finished) return { text, usage, finishReason };
|
|
1207
|
+
if (finished) return { text, usage, finishReason, servedModel };
|
|
1189
1208
|
}
|
|
1190
1209
|
}
|
|
1191
1210
|
buffer += decoder.decode();
|
|
1192
1211
|
for (const line of buffer.split(/\r\n|\r|\n/)) if (line) handleLine(line);
|
|
1193
1212
|
dispatch();
|
|
1194
|
-
return { text, usage, finishReason };
|
|
1213
|
+
return { text, usage, finishReason, servedModel };
|
|
1214
|
+
}
|
|
1215
|
+
async function readAnthropicCompletion(response) {
|
|
1216
|
+
const data = await response.json();
|
|
1217
|
+
const text = (data.content ?? []).filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text).join("");
|
|
1218
|
+
const finishReason = data.stop_reason === "max_tokens" ? "length" : data.stop_reason === "refusal" ? "content_filter" : data.stop_reason;
|
|
1219
|
+
return {
|
|
1220
|
+
text,
|
|
1221
|
+
finishReason,
|
|
1222
|
+
servedModel: typeof data.model === "string" && data.model ? data.model : void 0,
|
|
1223
|
+
usage: data.usage ? {
|
|
1224
|
+
prompt_tokens: data.usage.input_tokens,
|
|
1225
|
+
completion_tokens: data.usage.output_tokens,
|
|
1226
|
+
...data.usage.cache_creation_input_tokens !== void 0 ? { cache_creation_input_tokens: data.usage.cache_creation_input_tokens } : {},
|
|
1227
|
+
...data.usage.cache_read_input_tokens !== void 0 ? { cache_read_input_tokens: data.usage.cache_read_input_tokens } : {}
|
|
1228
|
+
} : void 0
|
|
1229
|
+
};
|
|
1195
1230
|
}
|
|
1196
1231
|
var prefixLanes = /* @__PURE__ */ new Map();
|
|
1197
1232
|
function judgeConcurrency() {
|
|
@@ -1262,15 +1297,58 @@ function runWithSharedPrefix(key, task2) {
|
|
|
1262
1297
|
};
|
|
1263
1298
|
return lane.warmed.then(acquire).then(() => task2().finally(release));
|
|
1264
1299
|
}
|
|
1265
|
-
function
|
|
1300
|
+
function judgeTemperature(configured, wire = "openai-compatible") {
|
|
1301
|
+
const max = wire === "anthropic" ? 1 : 2;
|
|
1302
|
+
const raw = process.env.APO_JUDGE_TEMPERATURE?.trim();
|
|
1303
|
+
const fromEnv = raw ? Number(raw) : void 0;
|
|
1304
|
+
const value = configured ?? fromEnv ?? 0;
|
|
1305
|
+
if (!Number.isFinite(value) || value < 0 || value > max) {
|
|
1306
|
+
const source = configured !== void 0 ? "judge.temperature" : "APO_JUDGE_TEMPERATURE";
|
|
1307
|
+
throw new Error(`${source} must be a number between 0 and ${max}, got ${configured ?? raw}`);
|
|
1308
|
+
}
|
|
1309
|
+
return value;
|
|
1310
|
+
}
|
|
1311
|
+
function isAnthropicHost(baseURL) {
|
|
1312
|
+
try {
|
|
1313
|
+
return /(^|\.)api\.anthropic\.com$/.test(new URL(baseURL).hostname);
|
|
1314
|
+
} catch {
|
|
1315
|
+
return false;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
function resolveJudgeWire(opts) {
|
|
1319
|
+
if (opts.provider === "anthropic") return "anthropic";
|
|
1320
|
+
if (opts.baseURL !== void 0 && isAnthropicHost(opts.baseURL)) return "anthropic";
|
|
1321
|
+
if (opts.baseURL === void 0 && /^claude-[a-z0-9]/.test(opts.model)) return "anthropic";
|
|
1322
|
+
return "openai-compatible";
|
|
1323
|
+
}
|
|
1324
|
+
function anthropicAuth(apiKey) {
|
|
1325
|
+
if (apiKey) return { apiKey, asBearer: false };
|
|
1326
|
+
if (process.env.ANTHROPIC_API_KEY) return { apiKey: process.env.ANTHROPIC_API_KEY, asBearer: false };
|
|
1327
|
+
if (process.env.ANTHROPIC_AUTH_TOKEN) return { apiKey: process.env.ANTHROPIC_AUTH_TOKEN, asBearer: true };
|
|
1328
|
+
return { apiKey: void 0, asBearer: false };
|
|
1329
|
+
}
|
|
1330
|
+
function defaultJudgeBaseURL(baseURL, wire = "openai-compatible") {
|
|
1331
|
+
if (wire === "anthropic") {
|
|
1332
|
+
return baseURL ?? process.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com";
|
|
1333
|
+
}
|
|
1266
1334
|
return baseURL ?? process.env.OPENROUTER_BASE_URL ?? "https://openrouter.ai/api/v1";
|
|
1267
1335
|
}
|
|
1268
|
-
function defaultJudgeAPIKey(apiKey) {
|
|
1336
|
+
function defaultJudgeAPIKey(apiKey, wire = "openai-compatible") {
|
|
1337
|
+
if (wire === "anthropic") {
|
|
1338
|
+
return anthropicAuth(apiKey).apiKey;
|
|
1339
|
+
}
|
|
1269
1340
|
return apiKey ?? process.env.OPENROUTER_API_KEY ?? process.env.OPENAI_API_KEY;
|
|
1270
1341
|
}
|
|
1271
1342
|
async function callJudge(args) {
|
|
1272
|
-
const
|
|
1273
|
-
|
|
1343
|
+
const wire = resolveJudgeWire({
|
|
1344
|
+
baseURL: args.baseURL,
|
|
1345
|
+
model: args.model,
|
|
1346
|
+
provider: args.provider
|
|
1347
|
+
});
|
|
1348
|
+
const baseURL = defaultJudgeBaseURL(args.baseURL, wire);
|
|
1349
|
+
const auth = wire === "anthropic" ? anthropicAuth(args.apiKey) : { apiKey: void 0, asBearer: false };
|
|
1350
|
+
const apiKey = wire === "anthropic" ? auth.apiKey : defaultJudgeAPIKey(args.apiKey);
|
|
1351
|
+
const temperature = judgeTemperature(args.temperature, wire);
|
|
1274
1352
|
const { briefingText, instructionText, systemPromptText, deliverableText, secondJudgeState, secondJudgeProjected } = buildJudgePromptParts({
|
|
1275
1353
|
values: args.values,
|
|
1276
1354
|
instruction: args.instruction,
|
|
@@ -1287,6 +1365,21 @@ async function callJudge(args) {
|
|
|
1287
1365
|
...secondJudgeProjected ? { projected: true } : {}
|
|
1288
1366
|
}) : void 0;
|
|
1289
1367
|
const cacheKey = `${args.model}\0${briefingText}\0${deliverableText}`;
|
|
1368
|
+
const anthropicRequestBody = () => JSON.stringify({
|
|
1369
|
+
model: args.model,
|
|
1370
|
+
max_tokens: ANTHROPIC_JUDGE_MAX_TOKENS,
|
|
1371
|
+
temperature,
|
|
1372
|
+
system: [
|
|
1373
|
+
{ type: "text", text: briefingText },
|
|
1374
|
+
{ type: "text", text: deliverableText, cache_control: { type: "ephemeral" } }
|
|
1375
|
+
],
|
|
1376
|
+
messages: [{ role: "user", content: instructionText }]
|
|
1377
|
+
});
|
|
1378
|
+
const anthropicHeaders = {
|
|
1379
|
+
"Content-Type": "application/json",
|
|
1380
|
+
"anthropic-version": "2023-06-01",
|
|
1381
|
+
...apiKey !== void 0 ? auth.asBearer ? { Authorization: `Bearer ${apiKey}` } : { "x-api-key": apiKey } : {}
|
|
1382
|
+
};
|
|
1290
1383
|
const requestBody = (stream, schema) => JSON.stringify({
|
|
1291
1384
|
model: args.model,
|
|
1292
1385
|
prompt_cache_key: promptCacheKey(cacheKey),
|
|
@@ -1304,7 +1397,7 @@ async function callJudge(args) {
|
|
|
1304
1397
|
},
|
|
1305
1398
|
{ role: "user", content: instructionText }
|
|
1306
1399
|
],
|
|
1307
|
-
temperature
|
|
1400
|
+
temperature,
|
|
1308
1401
|
response_format: schema ? judgeResponseFormat() : JSON_OBJECT_FORMAT,
|
|
1309
1402
|
...stream ? { stream: true, stream_options: { include_usage: true } } : {}
|
|
1310
1403
|
});
|
|
@@ -1337,7 +1430,16 @@ async function callJudge(args) {
|
|
|
1337
1430
|
try {
|
|
1338
1431
|
let response;
|
|
1339
1432
|
try {
|
|
1340
|
-
response =
|
|
1433
|
+
response = wire === "anthropic" ? (
|
|
1434
|
+
// Plain fetch: the Anthropic body never carries prompt_cache_key,
|
|
1435
|
+
// so the resend wrapper would be a pass-through.
|
|
1436
|
+
await fetch(`${baseURL}/v1/messages`, {
|
|
1437
|
+
method: "POST",
|
|
1438
|
+
signal: controller.signal,
|
|
1439
|
+
headers: anthropicHeaders,
|
|
1440
|
+
body: anthropicRequestBody()
|
|
1441
|
+
})
|
|
1442
|
+
) : await fetchWithPromptCacheKey(`${baseURL}/chat/completions`, {
|
|
1341
1443
|
method: "POST",
|
|
1342
1444
|
signal: controller.signal,
|
|
1343
1445
|
headers: {
|
|
@@ -1367,7 +1469,10 @@ async function callJudge(args) {
|
|
|
1367
1469
|
content: new RepetitionGuard({ diversity: false })
|
|
1368
1470
|
} : void 0;
|
|
1369
1471
|
try {
|
|
1370
|
-
return {
|
|
1472
|
+
return {
|
|
1473
|
+
kind: "completion",
|
|
1474
|
+
completion: wire === "anthropic" ? await readAnthropicCompletion(response) : await readCompletion(response, onData, guards)
|
|
1475
|
+
};
|
|
1371
1476
|
} catch (error) {
|
|
1372
1477
|
if (error instanceof JudgeReasoningLoopError) {
|
|
1373
1478
|
return { kind: "unavailable", error, retryable: true, loop: true };
|
|
@@ -1427,6 +1532,10 @@ async function callJudge(args) {
|
|
|
1427
1532
|
}
|
|
1428
1533
|
const judgeMetadata = async (completion) => ({
|
|
1429
1534
|
model: args.model,
|
|
1535
|
+
// The model that actually judged, when the gateway fell back to a
|
|
1536
|
+
// different one than requested.
|
|
1537
|
+
...completion?.servedModel && completion.servedModel !== args.model ? { served_model: completion.servedModel } : {},
|
|
1538
|
+
temperature,
|
|
1430
1539
|
contract: judgeContractInUse(),
|
|
1431
1540
|
prompt: { system: systemPromptText, user: instructionText },
|
|
1432
1541
|
...completion ? { response: completion.text, tokens: parseJudgeUsage(completion.usage) } : {},
|
|
@@ -1543,11 +1652,16 @@ async function cascadePreflight(args) {
|
|
|
1543
1652
|
...args.context ? { context: args.context } : {},
|
|
1544
1653
|
...args.secondJudgeValue !== void 0 ? { secondJudgeValue: args.secondJudgeValue } : {}
|
|
1545
1654
|
});
|
|
1655
|
+
const wire = resolveJudgeWire({
|
|
1656
|
+
baseURL: args.effective.baseURL,
|
|
1657
|
+
model: args.effective.model,
|
|
1658
|
+
provider: args.effective.provider
|
|
1659
|
+
});
|
|
1546
1660
|
const evidence = await callSecondJudge({
|
|
1547
1661
|
state: parts.secondJudgeState,
|
|
1548
1662
|
model,
|
|
1549
|
-
baseURL: resolveSecondJudgeBaseURL(defaultJudgeBaseURL(args.effective.baseURL)),
|
|
1550
|
-
apiKey: resolveSecondJudgeAPIKey(defaultJudgeAPIKey(args.effective.apiKey)),
|
|
1663
|
+
baseURL: resolveSecondJudgeBaseURL(defaultJudgeBaseURL(args.effective.baseURL, wire)),
|
|
1664
|
+
apiKey: resolveSecondJudgeAPIKey(defaultJudgeAPIKey(args.effective.apiKey, wire)),
|
|
1551
1665
|
...parts.secondJudgeProjected ? { projected: true } : {}
|
|
1552
1666
|
});
|
|
1553
1667
|
return { kind: "ran", evidence, parts };
|
|
@@ -1950,6 +2064,7 @@ var DEFAULT_BUDGET = {
|
|
|
1950
2064
|
timeoutMs: 3e5,
|
|
1951
2065
|
maxReadBytes: 2 * 1024 * 1024
|
|
1952
2066
|
};
|
|
2067
|
+
var ANTHROPIC_AGENT_MAX_TOKENS = 8192;
|
|
1953
2068
|
var MAX_READ_WINDOW = 12e3;
|
|
1954
2069
|
var SEARCH_HITS = 8;
|
|
1955
2070
|
var SEARCH_CONTEXT = 400;
|
|
@@ -2285,29 +2400,50 @@ async function runAgentSession(spec) {
|
|
|
2285
2400
|
const budget = { ...DEFAULT_BUDGET, ...spec.budget };
|
|
2286
2401
|
const started = Date.now();
|
|
2287
2402
|
const ai = await import("ai");
|
|
2288
|
-
const { createOpenAICompatible } = await import("@ai-sdk/openai-compatible");
|
|
2289
2403
|
const { z } = await import("zod");
|
|
2290
2404
|
const { generateText } = ai;
|
|
2291
2405
|
const stepCountIs = (n) => ai.stepCountIs(n);
|
|
2292
2406
|
const tool = (def) => ai.tool(def);
|
|
2293
|
-
const
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
apiKey: spec.apiKey ?? process.env.OPENROUTER_API_KEY ?? process.env.OPENAI_API_KEY,
|
|
2298
|
-
// Mark the briefing prefix cacheable — repeated samples (rejudge) re-bill
|
|
2299
|
-
// nothing for the shared turn-0 context — and keep every step of one
|
|
2300
|
-
// session on the replica holding its growing prefix. The key is per
|
|
2301
|
-
// session (briefing + criterion): sessions run concurrently, unlike
|
|
2302
|
-
// t.judge calls, and one key per task would pile them onto one replica.
|
|
2303
|
-
transformRequestBody: (body) => ({
|
|
2304
|
-
...body,
|
|
2305
|
-
prompt_cache_key: promptCacheKey(sessionPrefix(body)),
|
|
2306
|
-
messages: body.messages?.map(
|
|
2307
|
-
(m) => m.role === "system" && typeof m.content === "string" ? { ...m, content: [{ type: "text", text: m.content, cache_control: { type: "ephemeral" } }] } : m
|
|
2308
|
-
)
|
|
2309
|
-
})
|
|
2407
|
+
const wire = resolveJudgeWire({
|
|
2408
|
+
baseURL: spec.baseURL,
|
|
2409
|
+
model: spec.model,
|
|
2410
|
+
provider: spec.provider
|
|
2310
2411
|
});
|
|
2412
|
+
let model;
|
|
2413
|
+
let wireOptions;
|
|
2414
|
+
if (wire === "anthropic") {
|
|
2415
|
+
const { createAnthropic } = await import("@ai-sdk/anthropic");
|
|
2416
|
+
const auth = anthropicAuth(spec.apiKey);
|
|
2417
|
+
const base = (spec.baseURL ?? process.env.ANTHROPIC_BASE_URL)?.replace(/\/+$/, "");
|
|
2418
|
+
const anthropicEngine = createAnthropic({
|
|
2419
|
+
...base ? { baseURL: /\/v\d+$/.test(base) ? base : `${base}/v1` } : {},
|
|
2420
|
+
...auth.apiKey ? auth.asBearer ? { authToken: auth.apiKey } : { apiKey: auth.apiKey } : {}
|
|
2421
|
+
});
|
|
2422
|
+
model = anthropicEngine(spec.model);
|
|
2423
|
+
wireOptions = { maxOutputTokens: ANTHROPIC_AGENT_MAX_TOKENS };
|
|
2424
|
+
} else {
|
|
2425
|
+
const { createOpenAICompatible } = await import("@ai-sdk/openai-compatible");
|
|
2426
|
+
const provider = createOpenAICompatible({
|
|
2427
|
+
name: "openrouter",
|
|
2428
|
+
baseURL: spec.baseURL ?? "https://openrouter.ai/api/v1",
|
|
2429
|
+
fetch: fetchWithPromptCacheKey,
|
|
2430
|
+
apiKey: spec.apiKey ?? process.env.OPENROUTER_API_KEY ?? process.env.OPENAI_API_KEY,
|
|
2431
|
+
// Mark the briefing prefix cacheable — repeated samples (rejudge) re-bill
|
|
2432
|
+
// nothing for the shared turn-0 context — and keep every step of one
|
|
2433
|
+
// session on the replica holding its growing prefix. The key is per
|
|
2434
|
+
// session (briefing + criterion): sessions run concurrently, unlike
|
|
2435
|
+
// t.judge calls, and one key per task would pile them onto one replica.
|
|
2436
|
+
transformRequestBody: (body) => ({
|
|
2437
|
+
...body,
|
|
2438
|
+
prompt_cache_key: promptCacheKey(sessionPrefix(body)),
|
|
2439
|
+
messages: body.messages?.map(
|
|
2440
|
+
(m) => m.role === "system" && typeof m.content === "string" ? { ...m, content: [{ type: "text", text: m.content, cache_control: { type: "ephemeral" } }] } : m
|
|
2441
|
+
)
|
|
2442
|
+
})
|
|
2443
|
+
});
|
|
2444
|
+
model = provider(spec.model);
|
|
2445
|
+
wireOptions = {};
|
|
2446
|
+
}
|
|
2311
2447
|
const ledger = { toolCallsUsed: 0, readBytesUsed: 0, manifest: [] };
|
|
2312
2448
|
const budgetGuard = () => {
|
|
2313
2449
|
ledger.toolCallsUsed += 1;
|
|
@@ -2370,8 +2506,17 @@ async function runAgentSession(spec) {
|
|
|
2370
2506
|
try {
|
|
2371
2507
|
try {
|
|
2372
2508
|
const result = await generateText({
|
|
2373
|
-
model
|
|
2374
|
-
|
|
2509
|
+
model,
|
|
2510
|
+
// On the Anthropic wire the briefing's cache breakpoint is a per-part
|
|
2511
|
+
// provider option on the system message (the request-body rewrite the
|
|
2512
|
+
// OpenAI-compatible path uses has no equivalent there).
|
|
2513
|
+
system: wire === "anthropic" ? [{
|
|
2514
|
+
role: "system",
|
|
2515
|
+
content: system,
|
|
2516
|
+
// Message-level providerOptions: the Anthropic engine reads
|
|
2517
|
+
// the cache breakpoint from here, not from content parts.
|
|
2518
|
+
providerOptions: { anthropic: { cacheControl: { type: "ephemeral" } } }
|
|
2519
|
+
}] : system,
|
|
2375
2520
|
prompt: spec.instruction + exhibitsBlock,
|
|
2376
2521
|
temperature: 0,
|
|
2377
2522
|
toolChoice: "required",
|
|
@@ -2379,7 +2524,8 @@ async function runAgentSession(spec) {
|
|
|
2379
2524
|
tools,
|
|
2380
2525
|
abortSignal: AbortSignal.timeout(budget.timeoutMs),
|
|
2381
2526
|
maxRetries: 2,
|
|
2382
|
-
onStepFinish: onStep
|
|
2527
|
+
onStepFinish: onStep,
|
|
2528
|
+
...wireOptions
|
|
2383
2529
|
});
|
|
2384
2530
|
engineUsage = result.usage;
|
|
2385
2531
|
} catch (error) {
|
|
@@ -2437,7 +2583,7 @@ function createAgentMethod(rec, judgeConfig, judgeScope, evidence, judgeTracer,
|
|
|
2437
2583
|
rec.record(
|
|
2438
2584
|
label,
|
|
2439
2585
|
false,
|
|
2440
|
-
"No judge model configured for t.agent. Set one of:\n\u2022 OPENROUTER_MODEL + OPENROUTER_API_KEY (OpenRouter \u2014 needs a tool-calling-capable model)\n\u2022 OPENAI_MODEL + OPENAI_API_KEY (OpenAI direct)\nOr pass { judge } to runTask() programmatically.",
|
|
2586
|
+
"No judge model configured for t.agent. Set one of:\n\u2022 OPENROUTER_MODEL + OPENROUTER_API_KEY (OpenRouter \u2014 needs a tool-calling-capable model)\n\u2022 OPENAI_MODEL + OPENAI_API_KEY (OpenAI direct)\n\u2022 ANTHROPIC_MODEL + ANTHROPIC_API_KEY (Anthropic direct \u2014 needs a tool-calling-capable model; or ANTHROPIC_AUTH_TOKEN for plan credits)\nOr pass { judge } to runTask() programmatically.",
|
|
2441
2587
|
{ evaluator_type: "agent", location, outcome: "error" }
|
|
2442
2588
|
);
|
|
2443
2589
|
return;
|
|
@@ -2447,6 +2593,7 @@ function createAgentMethod(rec, judgeConfig, judgeScope, evidence, judgeTracer,
|
|
|
2447
2593
|
model: effective.model,
|
|
2448
2594
|
baseURL: effective.baseURL,
|
|
2449
2595
|
apiKey: effective.apiKey,
|
|
2596
|
+
provider: effective.provider,
|
|
2450
2597
|
evidence: evidence ?? { deliverables: {} },
|
|
2451
2598
|
scope: judgeScope,
|
|
2452
2599
|
exhibits,
|
|
@@ -2540,8 +2687,10 @@ function resolveJudgeConfig(judgeConfig, override) {
|
|
|
2540
2687
|
model,
|
|
2541
2688
|
baseURL: override?.baseURL ?? judgeConfig?.baseURL,
|
|
2542
2689
|
apiKey: override?.apiKey ?? judgeConfig?.apiKey,
|
|
2690
|
+
provider: override?.provider ?? judgeConfig?.provider,
|
|
2543
2691
|
prompt: override?.prompt ?? judgeConfig?.prompt,
|
|
2544
|
-
mode: override?.mode ?? judgeConfig?.mode
|
|
2692
|
+
mode: override?.mode ?? judgeConfig?.mode,
|
|
2693
|
+
temperature: override?.temperature ?? judgeConfig?.temperature
|
|
2545
2694
|
};
|
|
2546
2695
|
}
|
|
2547
2696
|
var TEST_METHOD_NAMES = [
|
|
@@ -2644,12 +2793,31 @@ function createJudgeMethod(rec, judgeConfig, judgeScope, judgeTracer) {
|
|
|
2644
2793
|
rec.record(
|
|
2645
2794
|
label,
|
|
2646
2795
|
false,
|
|
2647
|
-
"No judge model configured. Set one of:\n\u2022 OPENROUTER_MODEL + OPENROUTER_API_KEY (OpenRouter \u2014 works with 200+ models, one account)\n\u2022 OPENAI_MODEL + OPENAI_API_KEY (OpenAI direct)\nOr pass { judge } to runTask() programmatically.",
|
|
2796
|
+
"No judge model configured. Set one of:\n\u2022 OPENROUTER_MODEL + OPENROUTER_API_KEY (OpenRouter \u2014 works with 200+ models, one account)\n\u2022 OPENAI_MODEL + OPENAI_API_KEY (OpenAI direct)\n\u2022 ANTHROPIC_MODEL + ANTHROPIC_API_KEY (Anthropic direct \u2014 or ANTHROPIC_AUTH_TOKEN for plan credits)\nOr pass { judge } to runTask() programmatically.",
|
|
2648
2797
|
// No judge means no verdict — the same bucket as an unreachable judge.
|
|
2649
2798
|
{ evaluator_type: "llm", location, outcome: "error" }
|
|
2650
2799
|
);
|
|
2651
2800
|
return;
|
|
2652
2801
|
}
|
|
2802
|
+
try {
|
|
2803
|
+
judgeTemperature(
|
|
2804
|
+
effective.temperature,
|
|
2805
|
+
resolveJudgeWire({
|
|
2806
|
+
baseURL: effective.baseURL,
|
|
2807
|
+
model: effective.model,
|
|
2808
|
+
provider: effective.provider
|
|
2809
|
+
})
|
|
2810
|
+
);
|
|
2811
|
+
} catch (error) {
|
|
2812
|
+
rec.record(label, false, `judge failed: ${error instanceof Error ? error.message : String(error)}`, {
|
|
2813
|
+
evaluator_type: "llm",
|
|
2814
|
+
expected: instruction,
|
|
2815
|
+
received: valueArray.length === 1 ? valueArray[0] : valueArray,
|
|
2816
|
+
location,
|
|
2817
|
+
outcome: "error"
|
|
2818
|
+
});
|
|
2819
|
+
return;
|
|
2820
|
+
}
|
|
2653
2821
|
const context2 = judgeScopeToContext(judgeScope);
|
|
2654
2822
|
const judgeServing = servingHostFromBaseURL(effective.baseURL);
|
|
2655
2823
|
const secondJudgeValue = opts?.secondJudgeValue;
|
|
@@ -2705,7 +2873,9 @@ function createJudgeMethod(rec, judgeConfig, judgeScope, judgeTracer) {
|
|
|
2705
2873
|
model: effective.model,
|
|
2706
2874
|
baseURL: effective.baseURL,
|
|
2707
2875
|
apiKey: effective.apiKey,
|
|
2876
|
+
provider: effective.provider,
|
|
2708
2877
|
prompt: effective.prompt,
|
|
2878
|
+
...effective.temperature !== void 0 ? { temperature: effective.temperature } : {},
|
|
2709
2879
|
...context2 ? { context: context2 } : {},
|
|
2710
2880
|
...prefetchedSecondJudge ? { prefetchedSecondJudge } : {},
|
|
2711
2881
|
...secondJudgeValue !== void 0 ? {
|
|
@@ -2735,8 +2905,21 @@ function createJudgeMethod(rec, judgeConfig, judgeScope, judgeTracer) {
|
|
|
2735
2905
|
};
|
|
2736
2906
|
},
|
|
2737
2907
|
usage: (r) => {
|
|
2738
|
-
const
|
|
2739
|
-
|
|
2908
|
+
const judgeMeta = r?.judge;
|
|
2909
|
+
const tokens = judgeMeta?.tokens;
|
|
2910
|
+
return {
|
|
2911
|
+
...tokens ? {
|
|
2912
|
+
prompt_tokens: tokens.input,
|
|
2913
|
+
completion_tokens: tokens.output,
|
|
2914
|
+
// Without these the backend prices every cached
|
|
2915
|
+
// prefix read at the full input rate.
|
|
2916
|
+
...tokens.cache_read !== void 0 ? { cache_read_tokens: tokens.cache_read } : {},
|
|
2917
|
+
...tokens.cache_creation !== void 0 ? { cache_creation_tokens: tokens.cache_creation } : {}
|
|
2918
|
+
} : {},
|
|
2919
|
+
// The model that actually judged — a gateway fallback
|
|
2920
|
+
// diverges from the requested judge model.
|
|
2921
|
+
...judgeMeta?.served_model ? { served_model: judgeMeta.served_model } : {}
|
|
2922
|
+
};
|
|
2740
2923
|
}
|
|
2741
2924
|
},
|
|
2742
2925
|
async (spanId) => {
|
|
@@ -6593,6 +6776,18 @@ function createOtelAgentTaskTraceClient(config) {
|
|
|
6593
6776
|
if (params.completion_tokens !== void 0) {
|
|
6594
6777
|
active.span.setAttribute("gen_ai.usage.output_tokens", params.completion_tokens);
|
|
6595
6778
|
}
|
|
6779
|
+
if (params.cache_read_tokens !== void 0 || params.cache_creation_tokens !== void 0) {
|
|
6780
|
+
active.span.setAttribute("apo.usage.input_includes_cache", true);
|
|
6781
|
+
if (params.cache_read_tokens !== void 0) {
|
|
6782
|
+
active.span.setAttribute("gen_ai.usage.cache_read.input_tokens", params.cache_read_tokens);
|
|
6783
|
+
}
|
|
6784
|
+
if (params.cache_creation_tokens !== void 0) {
|
|
6785
|
+
active.span.setAttribute(
|
|
6786
|
+
"gen_ai.usage.cache_creation.input_tokens",
|
|
6787
|
+
params.cache_creation_tokens
|
|
6788
|
+
);
|
|
6789
|
+
}
|
|
6790
|
+
}
|
|
6596
6791
|
if (params.reasoning_tokens !== void 0) {
|
|
6597
6792
|
active.span.setAttribute(
|
|
6598
6793
|
"gen_ai.usage.reasoning.output_tokens",
|
|
@@ -6602,6 +6797,12 @@ function createOtelAgentTaskTraceClient(config) {
|
|
|
6602
6797
|
if (params.served_model) {
|
|
6603
6798
|
active.span.setAttribute("gen_ai.response.model", params.served_model);
|
|
6604
6799
|
}
|
|
6800
|
+
if (params.provider) {
|
|
6801
|
+
active.span.setAttribute("gen_ai.provider.name", params.provider);
|
|
6802
|
+
}
|
|
6803
|
+
if (params.route) {
|
|
6804
|
+
active.span.setAttribute("apo.llm.route", params.route);
|
|
6805
|
+
}
|
|
6605
6806
|
if (params.status_message) {
|
|
6606
6807
|
active.span.setAttribute("apo.status_message", params.status_message);
|
|
6607
6808
|
}
|
|
@@ -6935,12 +7136,23 @@ async function loadTaskRuntime(_taskDir) {
|
|
|
6935
7136
|
}
|
|
6936
7137
|
function resolveJudgeFromEnv() {
|
|
6937
7138
|
const openRouterModel = process.env.OPENROUTER_MODEL;
|
|
6938
|
-
const
|
|
7139
|
+
const openAIModel = process.env.OPENAI_MODEL;
|
|
7140
|
+
const anthropicModel = process.env.ANTHROPIC_MODEL;
|
|
7141
|
+
const model = openRouterModel ?? openAIModel ?? anthropicModel;
|
|
6939
7142
|
if (!model) return void 0;
|
|
6940
7143
|
return {
|
|
6941
7144
|
model,
|
|
6942
|
-
baseURL: openRouterModel ? process.env.OPENROUTER_BASE_URL : process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1",
|
|
6943
|
-
|
|
7145
|
+
baseURL: openRouterModel ? process.env.OPENROUTER_BASE_URL : openAIModel ? process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1" : process.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com",
|
|
7146
|
+
// ANTHROPIC_AUTH_TOKEN deliberately stays out of apiKey: an explicit
|
|
7147
|
+
// config key is always sent as x-api-key, which 401s for the OAuth-style
|
|
7148
|
+
// token. Leaving apiKey unset lets anthropicAuth pick the token up from
|
|
7149
|
+
// the env and send it as Authorization: Bearer.
|
|
7150
|
+
apiKey: openRouterModel ? process.env.OPENROUTER_API_KEY : openAIModel ? process.env.OPENAI_API_KEY : process.env.ANTHROPIC_API_KEY,
|
|
7151
|
+
// Anthropic is last in the precedence chain (OPENROUTER > OPENAI >
|
|
7152
|
+
// ANTHROPIC): a workspace that already configures one of the others
|
|
7153
|
+
// keeps its behavior, so the arm can be reached only deliberately.
|
|
7154
|
+
// Reaching this return with neither set means ANTHROPIC_MODEL won.
|
|
7155
|
+
...openRouterModel || openAIModel ? {} : { provider: "anthropic" },
|
|
6944
7156
|
// "cascade" turns on second-judge-first judging (a confident decision
|
|
6945
7157
|
// model verdict stands without calling the primary judge); any other
|
|
6946
7158
|
// value is ignored so a typo degrades to the default dual mode, never
|