@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.
@@ -16,13 +16,13 @@ import {
16
16
  import "../chunk-HRYXLRXJ.js";
17
17
  import {
18
18
  createApoOpenAI
19
- } from "../chunk-A3UYQ7M4.js";
19
+ } from "../chunk-NSMKSPBU.js";
20
20
  import {
21
21
  createApoAnthropic
22
- } from "../chunk-X73JGMK3.js";
22
+ } from "../chunk-4BQSCD6T.js";
23
23
  import {
24
24
  servingHostFromBaseURL
25
- } from "../chunk-JTWQD27M.js";
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 = new Array(MAX_UNIT_CHARS + 1).fill(0);
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
- return /(^|\.)api\.openai\.com$/.test(new URL(baseURL).hostname) ? openAIProvider : openRouterProvider;
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: usage.prompt_tokens ?? 0,
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 defaultJudgeBaseURL(baseURL) {
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 baseURL = defaultJudgeBaseURL(args.baseURL);
1273
- const apiKey = defaultJudgeAPIKey(args.apiKey);
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: 0,
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 = await fetchWithPromptCacheKey(`${baseURL}/chat/completions`, {
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 { kind: "completion", completion: await readCompletion(response, onData, guards) };
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 provider = createOpenAICompatible({
2294
- name: "openrouter",
2295
- baseURL: spec.baseURL ?? "https://openrouter.ai/api/v1",
2296
- fetch: fetchWithPromptCacheKey,
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: provider(spec.model),
2374
- system,
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 tokens = r?.judge?.tokens;
2739
- return tokens ? { prompt_tokens: tokens.input, completion_tokens: tokens.output } : void 0;
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 model = openRouterModel ?? process.env.OPENAI_MODEL;
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
- apiKey: openRouterModel ? process.env.OPENROUTER_API_KEY : process.env.OPENAI_API_KEY,
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