@apo-ai/sdk 0.8.0 → 0.9.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 +2 -1
- package/dist/agent-task/integrations/ai-sdk.js +2 -2
- package/dist/agent-task/public.d.ts +134 -5
- package/dist/agent-task/public.js +395 -47
- package/dist/agent-task/public.js.map +1 -1
- package/dist/{chunk-HLRXQAZ5.js → chunk-HRYXLRXJ.js} +2 -2
- package/dist/{chunk-HLRXQAZ5.js.map → chunk-HRYXLRXJ.js.map} +1 -1
- package/dist/{chunk-CKB3SUDO.js → chunk-IK4GJX7H.js} +2 -2
- package/dist/{chunk-IPQ6IIEG.js → chunk-KLZLE3VQ.js} +2 -2
- package/dist/otel/index.js +2 -2
- package/package.json +1 -1
- /package/dist/{chunk-CKB3SUDO.js.map → chunk-IK4GJX7H.js.map} +0 -0
- /package/dist/{chunk-IPQ6IIEG.js.map → chunk-KLZLE3VQ.js.map} +0 -0
|
@@ -8,11 +8,11 @@ import {
|
|
|
8
8
|
resetApoTracing,
|
|
9
9
|
withApoRun,
|
|
10
10
|
withApoRunSync
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-KLZLE3VQ.js";
|
|
12
12
|
import {
|
|
13
13
|
createApoTracer
|
|
14
|
-
} from "../chunk-
|
|
15
|
-
import "../chunk-
|
|
14
|
+
} from "../chunk-IK4GJX7H.js";
|
|
15
|
+
import "../chunk-HRYXLRXJ.js";
|
|
16
16
|
import {
|
|
17
17
|
createApoOpenAI
|
|
18
18
|
} from "../chunk-3B3L2H4Y.js";
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
import "../chunk-3OQ3JBFJ.js";
|
|
23
23
|
|
|
24
24
|
// src/agent-task/trace-projection/view.ts
|
|
25
|
+
var TASK_TURN_SPAN_NAME = "task.turn";
|
|
25
26
|
function invocationOrderKey(obs) {
|
|
26
27
|
const hasTs = obs.startedAt != null ? 0 : 1;
|
|
27
28
|
return [hasTs, obs.startedAt ?? "", obs.spanId];
|
|
@@ -32,11 +33,11 @@ var TraceView = class {
|
|
|
32
33
|
}
|
|
33
34
|
/** Evidence availability for a capability. */
|
|
34
35
|
requireCapability(capability) {
|
|
35
|
-
return this.snapshot.capabilities[capability];
|
|
36
|
+
return this.snapshot.capabilities[capability] ?? "unavailable";
|
|
36
37
|
}
|
|
37
38
|
/** Whether a capability is reported as available (not partial/unavailable). */
|
|
38
39
|
isAvailable(capability) {
|
|
39
|
-
return this.
|
|
40
|
+
return this.requireCapability(capability) === "available";
|
|
40
41
|
}
|
|
41
42
|
/** All chat messages flattened across generation observations, in order. */
|
|
42
43
|
get messages() {
|
|
@@ -115,6 +116,135 @@ var TraceView = class {
|
|
|
115
116
|
}
|
|
116
117
|
return void 0;
|
|
117
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Task Turns in invocation order, or `undefined` when timing evidence is
|
|
121
|
+
* not available. Each is one `task.turn` span: the adapter's whole
|
|
122
|
+
* `sendUserTurn` call for that turn.
|
|
123
|
+
*/
|
|
124
|
+
get turns() {
|
|
125
|
+
if (!this.isAvailable("timing")) return void 0;
|
|
126
|
+
return this.turnSpans.map((o, i) => ({
|
|
127
|
+
turnNumber: i + 1,
|
|
128
|
+
spanId: o.spanId,
|
|
129
|
+
...o.durationMs !== void 0 ? { durationMs: o.durationMs } : {},
|
|
130
|
+
status: o.status
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Tokens the agent under test spent: usage on observations inside
|
|
135
|
+
* `task.turn` spans (every turn, or only turn `turn`). Work outside the
|
|
136
|
+
* turns — the evaluation phase's judges, adapter setup — is not the
|
|
137
|
+
* agent's and is excluded. `undefined` when usage evidence is not
|
|
138
|
+
* available or the requested turn does not exist.
|
|
139
|
+
*/
|
|
140
|
+
/** How many Task Turns ran — independent of timing evidence. */
|
|
141
|
+
get turnCountFromSpans() {
|
|
142
|
+
return this.turnSpans.length;
|
|
143
|
+
}
|
|
144
|
+
tokens(kind, opts) {
|
|
145
|
+
if (!this.isAvailable("usage")) return void 0;
|
|
146
|
+
const turnSpans = this.turnSpans;
|
|
147
|
+
let roots;
|
|
148
|
+
if (opts?.turn !== void 0) {
|
|
149
|
+
const one = turnSpans[opts.turn - 1];
|
|
150
|
+
if (!one) return void 0;
|
|
151
|
+
roots = [one];
|
|
152
|
+
} else {
|
|
153
|
+
roots = turnSpans;
|
|
154
|
+
}
|
|
155
|
+
const tally = { tokens: 0, reported: 0, unreported: 0 };
|
|
156
|
+
const children = this.childrenByParent;
|
|
157
|
+
const isLlmCall = (o) => o.type === "GENERATION" || o.usage != null;
|
|
158
|
+
const isStep = (o) => isLlmCall(o) && o.type !== "TOOL" && o.type !== "AGENT";
|
|
159
|
+
const order = [];
|
|
160
|
+
const visited = new Set(roots.map((r) => r.spanId));
|
|
161
|
+
const topLevel = roots.flatMap((r) => children.get(r.spanId) ?? []);
|
|
162
|
+
const stack = [...topLevel];
|
|
163
|
+
const scopeChildren = /* @__PURE__ */ new Map();
|
|
164
|
+
while (stack.length > 0) {
|
|
165
|
+
const obs = stack.pop();
|
|
166
|
+
if (visited.has(obs.spanId)) continue;
|
|
167
|
+
visited.add(obs.spanId);
|
|
168
|
+
order.push(obs);
|
|
169
|
+
const kids = (children.get(obs.spanId) ?? []).filter((c) => !visited.has(c.spanId));
|
|
170
|
+
scopeChildren.set(obs.spanId, kids);
|
|
171
|
+
for (const kid of kids) stack.push(kid);
|
|
172
|
+
}
|
|
173
|
+
const effective = /* @__PURE__ */ new Map();
|
|
174
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
175
|
+
const obs = order[i];
|
|
176
|
+
const { count, complete } = tokenCount(obs, kind);
|
|
177
|
+
if (count !== void 0) tally.reported += 1;
|
|
178
|
+
const ownKnown = complete && obs.status !== "error";
|
|
179
|
+
let steps;
|
|
180
|
+
let stepsUnreported = 0;
|
|
181
|
+
let separate;
|
|
182
|
+
let separateUnreported = 0;
|
|
183
|
+
for (const child of scopeChildren.get(obs.spanId) ?? []) {
|
|
184
|
+
const c = effective.get(child.spanId);
|
|
185
|
+
if (!c) continue;
|
|
186
|
+
if (isStep(child)) {
|
|
187
|
+
if (c.tokens !== void 0) steps = (steps ?? 0) + c.tokens;
|
|
188
|
+
stepsUnreported += c.unreported;
|
|
189
|
+
} else {
|
|
190
|
+
if (c.tokens !== void 0) separate = (separate ?? 0) + c.tokens;
|
|
191
|
+
separateUnreported += c.unreported;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const own = count === void 0 ? steps : steps === void 0 ? count : Math.max(count, steps);
|
|
195
|
+
const unreported = (isLlmCall(obs) && !ownKnown ? 1 : 0) + (isLlmCall(obs) && ownKnown ? 0 : stepsUnreported) + separateUnreported;
|
|
196
|
+
effective.set(obs.spanId, {
|
|
197
|
+
tokens: own === void 0 && separate === void 0 ? void 0 : (own ?? 0) + (separate ?? 0),
|
|
198
|
+
unreported
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
for (const obs of topLevel) {
|
|
202
|
+
const e = effective.get(obs.spanId);
|
|
203
|
+
if (!e) continue;
|
|
204
|
+
tally.tokens += e.tokens ?? 0;
|
|
205
|
+
tally.unreported += e.unreported;
|
|
206
|
+
effective.delete(obs.spanId);
|
|
207
|
+
}
|
|
208
|
+
return tally;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The run's own Task Turns: `task.turn` observations with no `task.turn`
|
|
212
|
+
* ancestor (a nested run or a user span of the same name inside a turn is
|
|
213
|
+
* part of that turn, not a turn of its own). Turns run sequentially, so
|
|
214
|
+
* invocation order is turn order.
|
|
215
|
+
*/
|
|
216
|
+
get turnSpans() {
|
|
217
|
+
if (this._turnSpans !== void 0) return this._turnSpans;
|
|
218
|
+
const byId = new Map(this.snapshot.observations.map((o) => [o.spanId, o]));
|
|
219
|
+
const insideAnotherTurn = (obs) => {
|
|
220
|
+
const visited = /* @__PURE__ */ new Set([obs.spanId]);
|
|
221
|
+
let parentId = obs.parentSpanId;
|
|
222
|
+
while (parentId !== void 0 && !visited.has(parentId)) {
|
|
223
|
+
visited.add(parentId);
|
|
224
|
+
const parent = byId.get(parentId);
|
|
225
|
+
if (!parent) return false;
|
|
226
|
+
if (parent.name === TASK_TURN_SPAN_NAME) return true;
|
|
227
|
+
parentId = parent.parentSpanId;
|
|
228
|
+
}
|
|
229
|
+
return false;
|
|
230
|
+
};
|
|
231
|
+
this._turnSpans = this.sortedObservations.filter(
|
|
232
|
+
(o) => o.name === TASK_TURN_SPAN_NAME && !insideAnotherTurn(o)
|
|
233
|
+
);
|
|
234
|
+
return this._turnSpans;
|
|
235
|
+
}
|
|
236
|
+
get childrenByParent() {
|
|
237
|
+
if (this._children !== void 0) return this._children;
|
|
238
|
+
const children = /* @__PURE__ */ new Map();
|
|
239
|
+
for (const obs of this.snapshot.observations) {
|
|
240
|
+
if (obs.parentSpanId == null) continue;
|
|
241
|
+
const list = children.get(obs.parentSpanId);
|
|
242
|
+
if (list) list.push(obs);
|
|
243
|
+
else children.set(obs.parentSpanId, [obs]);
|
|
244
|
+
}
|
|
245
|
+
this._children = children;
|
|
246
|
+
return children;
|
|
247
|
+
}
|
|
118
248
|
/**
|
|
119
249
|
* Observations sorted deterministically by invocation time then span ID.
|
|
120
250
|
* Memoized per TraceView instance since the snapshot is immutable.
|
|
@@ -130,6 +260,18 @@ var TraceView = class {
|
|
|
130
260
|
return sorted;
|
|
131
261
|
}
|
|
132
262
|
};
|
|
263
|
+
function tokenCount(obs, kind) {
|
|
264
|
+
const known = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
265
|
+
const input = known(obs.usage?.inputTokens);
|
|
266
|
+
const output = known(obs.usage?.outputTokens);
|
|
267
|
+
if (kind === "input") return { count: input, complete: input !== void 0 };
|
|
268
|
+
if (kind === "output") return { count: output, complete: output !== void 0 };
|
|
269
|
+
if (input === void 0 && output === void 0) return { count: void 0, complete: false };
|
|
270
|
+
return {
|
|
271
|
+
count: (input ?? 0) + (output ?? 0),
|
|
272
|
+
complete: input !== void 0 && output !== void 0
|
|
273
|
+
};
|
|
274
|
+
}
|
|
133
275
|
|
|
134
276
|
// src/agent-task/checks/matchers.ts
|
|
135
277
|
function asString(value) {
|
|
@@ -253,9 +395,24 @@ function decisionsEndpoint(chatBaseURL) {
|
|
|
253
395
|
const base = chatBaseURL.replace(/\/v1\/?$/, "");
|
|
254
396
|
return `${base}/alpha/decisions`;
|
|
255
397
|
}
|
|
398
|
+
var INPUT_TOO_LARGE_MARKERS = [
|
|
399
|
+
"max_tokens_exceeded",
|
|
400
|
+
"context_length_exceeded",
|
|
401
|
+
"maximum context length"
|
|
402
|
+
];
|
|
403
|
+
function isInputTooLargeRejection(status, body) {
|
|
404
|
+
if (status !== 400 && status !== 413) return false;
|
|
405
|
+
return INPUT_TOO_LARGE_MARKERS.some((marker2) => body.includes(marker2));
|
|
406
|
+
}
|
|
407
|
+
function estimateTokens(text) {
|
|
408
|
+
return Math.round(text.length / 4 / 1e3) * 1e3;
|
|
409
|
+
}
|
|
256
410
|
async function callSecondJudge(args) {
|
|
257
411
|
const startedAt = Date.now();
|
|
258
|
-
const evidence = {
|
|
412
|
+
const evidence = {
|
|
413
|
+
model: args.model,
|
|
414
|
+
...args.projected ? { projected: true } : {}
|
|
415
|
+
};
|
|
259
416
|
try {
|
|
260
417
|
const response = await fetch(decisionsEndpoint(args.baseURL), {
|
|
261
418
|
method: "POST",
|
|
@@ -281,6 +438,10 @@ async function callSecondJudge(args) {
|
|
|
281
438
|
});
|
|
282
439
|
if (!response.ok) {
|
|
283
440
|
const body = await response.text().catch(() => "");
|
|
441
|
+
if (isInputTooLargeRejection(response.status, body)) {
|
|
442
|
+
evidence.skipped = `input ~${estimateTokens(args.state).toLocaleString("en-US")} tokens exceeds ${args.model}'s context limit \u2014 project a smaller view with t.judge(value, instruction, { secondJudgeValue })`;
|
|
443
|
+
return evidence;
|
|
444
|
+
}
|
|
284
445
|
evidence.error = `Second judge API ${response.status}: ${body.slice(0, 200)}`;
|
|
285
446
|
return evidence;
|
|
286
447
|
}
|
|
@@ -504,13 +665,20 @@ ${formatJudgeValues(args.values)}`;
|
|
|
504
665
|
|
|
505
666
|
${deliverableText}`;
|
|
506
667
|
const secondJudgeModel = resolveSecondJudgeModel();
|
|
668
|
+
const secondJudgeProjected = args.secondJudgeValue !== void 0;
|
|
669
|
+
const secondJudgeValues = args.secondJudgeValue ?? args.values;
|
|
670
|
+
const secondJudgeDeliverableText = `Values to evaluate:
|
|
671
|
+
${formatJudgeValues(secondJudgeValues)}`;
|
|
507
672
|
const secondJudgePromise = secondJudgeModel ? callSecondJudge({
|
|
508
|
-
state: `${
|
|
673
|
+
state: `${briefingText}
|
|
674
|
+
|
|
675
|
+
${secondJudgeDeliverableText}
|
|
509
676
|
|
|
510
677
|
${instructionText}`,
|
|
511
678
|
model: secondJudgeModel,
|
|
512
679
|
baseURL: resolveSecondJudgeBaseURL(baseURL),
|
|
513
|
-
apiKey: resolveSecondJudgeAPIKey(apiKey)
|
|
680
|
+
apiKey: resolveSecondJudgeAPIKey(apiKey),
|
|
681
|
+
...secondJudgeProjected ? { projected: true } : {}
|
|
514
682
|
}) : void 0;
|
|
515
683
|
const cacheKey = `${args.model}\0${briefingText}\0${deliverableText}`;
|
|
516
684
|
const requestBody = (stream) => JSON.stringify({
|
|
@@ -1163,7 +1331,7 @@ function createAgentMethod(rec, judgeConfig, judgeScope, evidence, judgeTracer)
|
|
|
1163
1331
|
label,
|
|
1164
1332
|
false,
|
|
1165
1333
|
"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.",
|
|
1166
|
-
{ evaluator_type: "agent", location }
|
|
1334
|
+
{ evaluator_type: "agent", location, outcome: "error" }
|
|
1167
1335
|
);
|
|
1168
1336
|
return;
|
|
1169
1337
|
}
|
|
@@ -1271,6 +1439,8 @@ var TEST_METHOD_NAMES = [
|
|
|
1271
1439
|
"messageIncludes",
|
|
1272
1440
|
"maxTurns",
|
|
1273
1441
|
"maxDurationMs",
|
|
1442
|
+
"maxTokens",
|
|
1443
|
+
"minTokens",
|
|
1274
1444
|
"assert",
|
|
1275
1445
|
"check",
|
|
1276
1446
|
"judge",
|
|
@@ -1304,11 +1474,13 @@ function createJudgeMethod(rec, judgeConfig, judgeScope, judgeTracer) {
|
|
|
1304
1474
|
label,
|
|
1305
1475
|
false,
|
|
1306
1476
|
"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.",
|
|
1307
|
-
|
|
1477
|
+
// No judge means no verdict — the same bucket as an unreachable judge.
|
|
1478
|
+
{ evaluator_type: "llm", location, outcome: "error" }
|
|
1308
1479
|
);
|
|
1309
1480
|
return;
|
|
1310
1481
|
}
|
|
1311
1482
|
const context2 = judgeScopeToContext(judgeScope);
|
|
1483
|
+
const secondJudgeValue = opts?.secondJudgeValue;
|
|
1312
1484
|
const call = () => callJudge({
|
|
1313
1485
|
values: valueArray,
|
|
1314
1486
|
instruction,
|
|
@@ -1316,7 +1488,10 @@ function createJudgeMethod(rec, judgeConfig, judgeScope, judgeTracer) {
|
|
|
1316
1488
|
baseURL: effective.baseURL,
|
|
1317
1489
|
apiKey: effective.apiKey,
|
|
1318
1490
|
prompt: effective.prompt,
|
|
1319
|
-
...context2 ? { context: context2 } : {}
|
|
1491
|
+
...context2 ? { context: context2 } : {},
|
|
1492
|
+
...secondJudgeValue !== void 0 ? {
|
|
1493
|
+
secondJudgeValue: Array.isArray(secondJudgeValue) ? secondJudgeValue : [secondJudgeValue]
|
|
1494
|
+
} : {}
|
|
1320
1495
|
});
|
|
1321
1496
|
let judgeSpanId;
|
|
1322
1497
|
const traced = judgeTracer ? judgeTracer.step(
|
|
@@ -1395,7 +1570,8 @@ var CAPABILITY_LABELS = {
|
|
|
1395
1570
|
errors: "error",
|
|
1396
1571
|
timing: "timing",
|
|
1397
1572
|
skills: "skill",
|
|
1398
|
-
subagents: "subagent"
|
|
1573
|
+
subagents: "subagent",
|
|
1574
|
+
usage: "token usage"
|
|
1399
1575
|
};
|
|
1400
1576
|
function createTraceTestContext(view, rec, judgeConfig, judgeScope, agentEvidence, judgeTracer) {
|
|
1401
1577
|
const unsupported = (id, capability) => {
|
|
@@ -1550,7 +1726,11 @@ function createTraceTestContext(view, rec, judgeConfig, judgeScope, agentEvidenc
|
|
|
1550
1726
|
received: `${turns}`
|
|
1551
1727
|
});
|
|
1552
1728
|
},
|
|
1553
|
-
maxDurationMs(n) {
|
|
1729
|
+
maxDurationMs(n, opts) {
|
|
1730
|
+
if (opts?.turn !== void 0) {
|
|
1731
|
+
recordTurnDuration(view, rec, n, opts.turn, unsupported);
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1554
1734
|
if (!isAvailable("timing")) {
|
|
1555
1735
|
unsupported(`maxDurationMs(${n})`, "timing");
|
|
1556
1736
|
return;
|
|
@@ -1565,6 +1745,12 @@ function createTraceTestContext(view, rec, judgeConfig, judgeScope, agentEvidenc
|
|
|
1565
1745
|
received: `${ms}ms`
|
|
1566
1746
|
});
|
|
1567
1747
|
},
|
|
1748
|
+
maxTokens(n, opts) {
|
|
1749
|
+
recordTokenBudget(view, rec, "max", n, opts, unsupported);
|
|
1750
|
+
},
|
|
1751
|
+
minTokens(n, opts) {
|
|
1752
|
+
recordTokenBudget(view, rec, "min", n, opts, unsupported);
|
|
1753
|
+
},
|
|
1568
1754
|
assert(label, predicate) {
|
|
1569
1755
|
const pass = predicate(view);
|
|
1570
1756
|
rec.record(label, pass, pass ? "" : `assertion "${label}" failed`, {
|
|
@@ -1587,6 +1773,95 @@ function createTraceTestContext(view, rec, judgeConfig, judgeScope, agentEvidenc
|
|
|
1587
1773
|
agent: trackPending(rec, createAgentMethod(rec, judgeConfig, judgeScope, agentEvidence ?? { deliverables: {}, view }, judgeTracer))
|
|
1588
1774
|
};
|
|
1589
1775
|
}
|
|
1776
|
+
function requireBudget(method, n) {
|
|
1777
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
1778
|
+
throw new TypeError(`${method}: the budget must be a finite, non-negative number, got ${n}`);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
var TOKEN_KINDS = ["input", "output", "total"];
|
|
1782
|
+
function requireTurnNumber(method, turn2) {
|
|
1783
|
+
if (!Number.isInteger(turn2) || turn2 < 1) {
|
|
1784
|
+
throw new TypeError(`${method}: turn must be a positive integer (1-based), got ${turn2}`);
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
function recordTurnDuration(view, rec, n, turn2, unsupported) {
|
|
1788
|
+
requireBudget("maxDurationMs", n);
|
|
1789
|
+
requireTurnNumber("maxDurationMs", turn2);
|
|
1790
|
+
const id = `maxDurationMs(${n}, turn ${turn2})`;
|
|
1791
|
+
const turns = view.turns;
|
|
1792
|
+
if (turns === void 0) {
|
|
1793
|
+
unsupported(id, "timing");
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
const record = turns[turn2 - 1];
|
|
1797
|
+
if (!record) {
|
|
1798
|
+
rec.record(id, false, `turn ${turn2} did not run (the run had ${turns.length} turn(s))`, {
|
|
1799
|
+
expected: `turn ${turn2} \u2264 ${n}ms`,
|
|
1800
|
+
received: `${turns.length} turn(s)`
|
|
1801
|
+
});
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
const ms = record.durationMs;
|
|
1805
|
+
if (ms === void 0) {
|
|
1806
|
+
unsupported(id, "timing");
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
rec.record(id, ms <= n, ms <= n ? "" : `expected turn ${turn2} \u2264 ${n}ms, took ${ms}ms`, {
|
|
1810
|
+
expected: `turn ${turn2} \u2264 ${n}ms`,
|
|
1811
|
+
received: `${ms}ms`
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
function recordTokenBudget(view, rec, bound, n, opts, unsupported) {
|
|
1815
|
+
const method = bound === "max" ? "maxTokens" : "minTokens";
|
|
1816
|
+
const kind = opts?.kind ?? "total";
|
|
1817
|
+
const turn2 = opts?.turn;
|
|
1818
|
+
requireBudget(method, n);
|
|
1819
|
+
if (!TOKEN_KINDS.includes(kind)) {
|
|
1820
|
+
throw new TypeError(`${method}: kind must be one of ${TOKEN_KINDS.join(", ")}, got ${String(kind)}`);
|
|
1821
|
+
}
|
|
1822
|
+
if (turn2 !== void 0) requireTurnNumber(method, turn2);
|
|
1823
|
+
const scope = turn2 === void 0 ? "" : `, turn ${turn2}`;
|
|
1824
|
+
const id = `${method}(${n}${kind === "total" ? "" : `, ${kind}`}${scope})`;
|
|
1825
|
+
const op = bound === "max" ? "\u2264" : "\u2265";
|
|
1826
|
+
const expected = `${op} ${n} ${kind} tokens${turn2 === void 0 ? "" : ` in turn ${turn2}`}`;
|
|
1827
|
+
if (view.requireCapability("usage") !== "available") {
|
|
1828
|
+
unsupported(id, "usage");
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1831
|
+
if (turn2 !== void 0) {
|
|
1832
|
+
const turnCount = view.turnCountFromSpans;
|
|
1833
|
+
if (turn2 > turnCount) {
|
|
1834
|
+
rec.record(id, false, `turn ${turn2} did not run (the run had ${turnCount} turn(s))`, {
|
|
1835
|
+
expected,
|
|
1836
|
+
received: `${turnCount} turn(s)`
|
|
1837
|
+
});
|
|
1838
|
+
return;
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
const tally = view.tokens(kind, turn2 === void 0 ? void 0 : { turn: turn2 });
|
|
1842
|
+
if (tally === void 0 || tally.reported === 0 && tally.unreported === 0) {
|
|
1843
|
+
rec.record(id, false, `no LLM call${turn2 === void 0 ? " inside a turn" : ` in turn ${turn2}`} reported ${kind} token usage`, {
|
|
1844
|
+
outcome: "unsupported",
|
|
1845
|
+
expected,
|
|
1846
|
+
received: "no usage-bearing LLM calls in scope"
|
|
1847
|
+
});
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
const received = tally.unreported > 0 ? `\u2265 ${tally.tokens} tokens (${tally.unreported} LLM call(s) with unknown or errored usage)` : `${tally.tokens} tokens`;
|
|
1851
|
+
const within = bound === "max" ? tally.tokens <= n : tally.tokens >= n;
|
|
1852
|
+
if (tally.unreported > 0 && !(bound === "min" && within)) {
|
|
1853
|
+
rec.record(id, false, `usage is incomplete: ${tally.unreported} LLM call(s) in scope have unknown or errored ${kind} usage`, {
|
|
1854
|
+
outcome: "unsupported",
|
|
1855
|
+
expected,
|
|
1856
|
+
received
|
|
1857
|
+
});
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
rec.record(id, within, within ? "" : `expected ${expected}, got ${tally.tokens}`, {
|
|
1861
|
+
expected,
|
|
1862
|
+
received
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1590
1865
|
function matchesTraceToolCall(call, name, opts) {
|
|
1591
1866
|
if (!matchName(call.name, name)) return false;
|
|
1592
1867
|
if (opts?.input !== void 0 && !matchValue(call.input, opts.input)) {
|
|
@@ -2688,6 +2963,7 @@ function resolveDeliverableSchema(definition) {
|
|
|
2688
2963
|
}
|
|
2689
2964
|
|
|
2690
2965
|
// src/agent-task/trace-projection/projection-tee.ts
|
|
2966
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
2691
2967
|
function monotonicNowMs() {
|
|
2692
2968
|
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
2693
2969
|
return performance.now();
|
|
@@ -2727,7 +3003,7 @@ function extractText(output) {
|
|
|
2727
3003
|
}
|
|
2728
3004
|
return "";
|
|
2729
3005
|
}
|
|
2730
|
-
function deriveCapabilities(observations) {
|
|
3006
|
+
function deriveCapabilities(observations, hierarchyAmbiguous) {
|
|
2731
3007
|
const hasType = (t) => observations.some((o) => o.type === t);
|
|
2732
3008
|
const hasMessages = observations.some(
|
|
2733
3009
|
(o) => o.type === "GENERATION" && o.messages && o.messages.length > 0
|
|
@@ -2740,13 +3016,29 @@ function deriveCapabilities(observations) {
|
|
|
2740
3016
|
errors: "available",
|
|
2741
3017
|
timing: "available",
|
|
2742
3018
|
skills: hasType("SKILL") ? "available" : "unavailable",
|
|
2743
|
-
subagents: hasType("AGENT") ? "available" : "unavailable"
|
|
3019
|
+
subagents: hasType("AGENT") ? "available" : "unavailable",
|
|
3020
|
+
// Usage is attributed to turns through parentSpanId; with an ambiguous
|
|
3021
|
+
// step id the chain is broken, so a per-turn or per-run sum could
|
|
3022
|
+
// silently drop turns.
|
|
3023
|
+
usage: !observations.some((o) => o.usage !== void 0) ? "unavailable" : hierarchyAmbiguous ? "partial" : "available"
|
|
3024
|
+
};
|
|
3025
|
+
}
|
|
3026
|
+
function usageFrom(params) {
|
|
3027
|
+
if (params?.prompt_tokens === void 0 && params?.completion_tokens === void 0) {
|
|
3028
|
+
return void 0;
|
|
3029
|
+
}
|
|
3030
|
+
return {
|
|
3031
|
+
...params.prompt_tokens !== void 0 ? { inputTokens: params.prompt_tokens } : {},
|
|
3032
|
+
...params.completion_tokens !== void 0 ? { outputTokens: params.completion_tokens } : {}
|
|
2744
3033
|
};
|
|
2745
3034
|
}
|
|
2746
3035
|
function createProjectionTee(real) {
|
|
2747
3036
|
const observations = [];
|
|
2748
3037
|
const pending = /* @__PURE__ */ new Map();
|
|
2749
3038
|
const realIdByTeeId = /* @__PURE__ */ new Map();
|
|
3039
|
+
const teeIdByRealStepId = /* @__PURE__ */ new Map();
|
|
3040
|
+
const ambiguousRealStepIds = /* @__PURE__ */ new Set();
|
|
3041
|
+
const activeStep = new AsyncLocalStorage();
|
|
2750
3042
|
let spanCounter = 0;
|
|
2751
3043
|
const rootStartMs = monotonicNowMs();
|
|
2752
3044
|
const rootStartIso = new Date(rootStartMs).toISOString();
|
|
@@ -2768,6 +3060,8 @@ function createProjectionTee(real) {
|
|
|
2768
3060
|
};
|
|
2769
3061
|
if (p.parentSpanId) obs.parentSpanId = p.parentSpanId;
|
|
2770
3062
|
if (p.model) obs.model = p.model;
|
|
3063
|
+
const usage = usageFrom(params);
|
|
3064
|
+
if (usage) obs.usage = usage;
|
|
2771
3065
|
if (isError && params?.status_message) obs.errorMessage = params.status_message;
|
|
2772
3066
|
const meta = params?.metadata;
|
|
2773
3067
|
if (p.type === "TOOL") {
|
|
@@ -2790,9 +3084,10 @@ function createProjectionTee(real) {
|
|
|
2790
3084
|
function recordStart(opts) {
|
|
2791
3085
|
spanCounter += 1;
|
|
2792
3086
|
const spanId = `${real.rootSpanId}-${String(spanCounter).padStart(6, "0")}`;
|
|
3087
|
+
const parent = opts.parent_call_id ?? activeStep.getStore() ?? real.rootSpanId;
|
|
2793
3088
|
pending.set(spanId, {
|
|
2794
3089
|
spanId,
|
|
2795
|
-
parentSpanId:
|
|
3090
|
+
parentSpanId: teeIdByRealStepId.get(parent) ?? parent,
|
|
2796
3091
|
type: observationTypeFor(opts.observation_type),
|
|
2797
3092
|
name: opts.step_name ?? "step",
|
|
2798
3093
|
toolName: opts.tool_name ?? void 0,
|
|
@@ -2810,11 +3105,22 @@ function createProjectionTee(real) {
|
|
|
2810
3105
|
const obsSpanId = recordStart(opts);
|
|
2811
3106
|
const start = monotonicNowMs();
|
|
2812
3107
|
try {
|
|
2813
|
-
const result = await real.step(opts,
|
|
3108
|
+
const result = await real.step(opts, (realSpanId) => {
|
|
3109
|
+
if (realSpanId && !ambiguousRealStepIds.has(realSpanId)) {
|
|
3110
|
+
if (teeIdByRealStepId.has(realSpanId)) {
|
|
3111
|
+
teeIdByRealStepId.delete(realSpanId);
|
|
3112
|
+
ambiguousRealStepIds.add(realSpanId);
|
|
3113
|
+
} else {
|
|
3114
|
+
teeIdByRealStepId.set(realSpanId, obsSpanId);
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
return activeStep.run(obsSpanId, () => fn(realSpanId));
|
|
3118
|
+
});
|
|
2814
3119
|
complete(obsSpanId, {
|
|
2815
3120
|
latency_ms: round3(monotonicNowMs() - start),
|
|
2816
3121
|
output: opts.summarize?.(result),
|
|
2817
|
-
metadata: opts.metadata
|
|
3122
|
+
metadata: opts.metadata,
|
|
3123
|
+
...opts.usage?.(result)
|
|
2818
3124
|
});
|
|
2819
3125
|
return result;
|
|
2820
3126
|
} catch (error) {
|
|
@@ -2927,7 +3233,7 @@ function createProjectionTee(real) {
|
|
|
2927
3233
|
durationMs: round3(endMs - rootStartMs),
|
|
2928
3234
|
complete: true
|
|
2929
3235
|
},
|
|
2930
|
-
capabilities: deriveCapabilities(observations),
|
|
3236
|
+
capabilities: deriveCapabilities(observations, ambiguousRealStepIds.size > 0),
|
|
2931
3237
|
observations: [...observations]
|
|
2932
3238
|
};
|
|
2933
3239
|
}
|
|
@@ -3079,7 +3385,22 @@ function aggregateResult(checksResults) {
|
|
|
3079
3385
|
return { checks: checksResults, pass: false };
|
|
3080
3386
|
}
|
|
3081
3387
|
const pass = checksResults.every((r) => r.pass);
|
|
3082
|
-
return { checks: checksResults, pass };
|
|
3388
|
+
return isNoVerdict(checksResults) ? { checks: checksResults, pass, noVerdict: true } : { checks: checksResults, pass };
|
|
3389
|
+
}
|
|
3390
|
+
function isNoVerdict(checksResults) {
|
|
3391
|
+
const failing = checksResults.filter((r) => !r.pass);
|
|
3392
|
+
return failing.length > 0 && failing.every((r) => checkOutcome(r) === "error");
|
|
3393
|
+
}
|
|
3394
|
+
function checkOutcome(check) {
|
|
3395
|
+
if (check.pass) return void 0;
|
|
3396
|
+
const assertions = check.assertions;
|
|
3397
|
+
if (assertions && assertions.length > 0) {
|
|
3398
|
+
const failed = assertions.filter((a) => !a.pass).map((a) => a.outcome);
|
|
3399
|
+
if (failed.length === 0) return void 0;
|
|
3400
|
+
if (!failed.every((o) => o === "error" || o === "unsupported")) return void 0;
|
|
3401
|
+
return failed.includes("error") ? "error" : "unsupported";
|
|
3402
|
+
}
|
|
3403
|
+
return check.outcome;
|
|
3083
3404
|
}
|
|
3084
3405
|
|
|
3085
3406
|
// src/agent-task/run/runTask.ts
|
|
@@ -3447,11 +3768,18 @@ async function runTurnLoop(loaded, options, session, trace2, rawTrace) {
|
|
|
3447
3768
|
metadata: { turnNumber: turnNum },
|
|
3448
3769
|
summarize: (r) => ({ response: r.response })
|
|
3449
3770
|
},
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3771
|
+
// Re-scope the run context to this turn so GenAI spans the OTel
|
|
3772
|
+
// processor captures parent under the turn span, not the run root —
|
|
3773
|
+
// turn-scoped assertions (t.maxTokens({ turn })) walk that chain.
|
|
3774
|
+
async (spanId) => {
|
|
3775
|
+
const run = getActiveApoRun();
|
|
3776
|
+
const send = () => session.sendUserTurn(userTurn, {
|
|
3777
|
+
trace: trace2,
|
|
3778
|
+
turnNumber: turnNum,
|
|
3779
|
+
parentSpanId: spanId
|
|
3780
|
+
});
|
|
3781
|
+
return run ? withApoRun({ ...run, parentSpanId: spanId, turnNumber: turnNum }, send) : send();
|
|
3782
|
+
}
|
|
3455
3783
|
);
|
|
3456
3784
|
turnTranscript.push({
|
|
3457
3785
|
turnNumber: turnNum,
|
|
@@ -3579,12 +3907,20 @@ var RejudgeError = class extends Error {
|
|
|
3579
3907
|
};
|
|
3580
3908
|
var TERMINAL_VERDICT_STATUSES = /* @__PURE__ */ new Set(["passed", "failed"]);
|
|
3581
3909
|
var MAX_SAMPLES = 50;
|
|
3910
|
+
var NO_VERDICT_MESSAGE_PREFIX = "No verdict: ";
|
|
3911
|
+
function isJudgeNoVerdictRun(detail) {
|
|
3912
|
+
if (detail.status !== "error" || detail.pass_result != null) return false;
|
|
3913
|
+
if (detail.no_verdict_reason !== void 0) return detail.no_verdict_reason === "judge";
|
|
3914
|
+
const generations = detail.generation_execution;
|
|
3915
|
+
const generationsDominate = generations != null && generations.total > 0 && generations.errored * 2 > generations.total;
|
|
3916
|
+
return (detail.error_message ?? "").startsWith(NO_VERDICT_MESSAGE_PREFIX) && detail.failed_checks === 0 && (detail.errored_checks ?? 0) > 0 && !generationsDominate;
|
|
3917
|
+
}
|
|
3582
3918
|
async function rejudgeTaskRun(runId, endpoints, options = {}) {
|
|
3583
3919
|
const api = createApiClient(endpoints);
|
|
3584
3920
|
const progress = options.onProgress ?? (() => {
|
|
3585
3921
|
});
|
|
3586
3922
|
const detail = await api.get(`/v1/agent-task-runs/${runId}`);
|
|
3587
|
-
if (!TERMINAL_VERDICT_STATUSES.has(detail.status)) {
|
|
3923
|
+
if (!TERMINAL_VERDICT_STATUSES.has(detail.status) && !isJudgeNoVerdictRun(detail)) {
|
|
3588
3924
|
throw new RejudgeError(
|
|
3589
3925
|
`Run ${runId} is '${detail.status}', not a completed run with a verdict \u2014 there is nothing to replay against yet`
|
|
3590
3926
|
);
|
|
@@ -3656,27 +3992,14 @@ async function rejudgeTaskRun(runId, endpoints, options = {}) {
|
|
|
3656
3992
|
loaded.adapter.deliverables
|
|
3657
3993
|
);
|
|
3658
3994
|
const proxied = proxyBrokenDeliverables(deliverables, validation.brokenDeliverables);
|
|
3659
|
-
const
|
|
3660
|
-
const errorCounts = /* @__PURE__ */ new Map();
|
|
3661
|
-
let primary = [];
|
|
3995
|
+
const sampled = [];
|
|
3662
3996
|
const judgeConfig = resolveJudgeConfig(options.judge, loaded.task.judge);
|
|
3663
3997
|
for (let sample = 0; sample < samples; sample++) {
|
|
3664
3998
|
if (samples > 1) progress(`judging sample ${sample + 1}/${samples}`);
|
|
3665
|
-
|
|
3666
|
-
if (sample === 0) primary = results;
|
|
3667
|
-
for (const result of results) {
|
|
3668
|
-
passCounts.set(result.id, (passCounts.get(result.id) ?? 0) + (result.pass ? 1 : 0));
|
|
3669
|
-
if (!result.pass && result.outcome === "error") {
|
|
3670
|
-
errorCounts.set(result.id, (errorCounts.get(result.id) ?? 0) + 1);
|
|
3671
|
-
}
|
|
3672
|
-
}
|
|
3999
|
+
sampled.push(await runChecksOnce(loaded, proxied, snapshot, judgeConfig));
|
|
3673
4000
|
}
|
|
3674
|
-
const
|
|
3675
|
-
|
|
3676
|
-
passes: passCounts.get(result.id) ?? 0,
|
|
3677
|
-
samples,
|
|
3678
|
-
...errorCounts.get(result.id) ? { errored: errorCounts.get(result.id) } : {}
|
|
3679
|
-
}));
|
|
4001
|
+
const primary = sampled[0] ?? [];
|
|
4002
|
+
const stability = checkStability(sampled);
|
|
3680
4003
|
const aggregate = aggregateResult(primary);
|
|
3681
4004
|
return {
|
|
3682
4005
|
runId,
|
|
@@ -3690,6 +4013,7 @@ async function rejudgeTaskRun(runId, endpoints, options = {}) {
|
|
|
3690
4013
|
samples,
|
|
3691
4014
|
checks: aggregate.checks,
|
|
3692
4015
|
pass: aggregate.pass,
|
|
4016
|
+
...aggregate.noVerdict ? { noVerdict: true } : {},
|
|
3693
4017
|
stability,
|
|
3694
4018
|
traceSnapshotAvailable: snapshotResult.available,
|
|
3695
4019
|
taskDirUsed: taskDir
|
|
@@ -3699,6 +4023,24 @@ async function rejudgeTaskRun(runId, endpoints, options = {}) {
|
|
|
3699
4023
|
if (scaffoldDir) rmSync(scaffoldDir, { recursive: true, force: true });
|
|
3700
4024
|
}
|
|
3701
4025
|
}
|
|
4026
|
+
function checkStability(sampled) {
|
|
4027
|
+
const passCounts = /* @__PURE__ */ new Map();
|
|
4028
|
+
const errorCounts = /* @__PURE__ */ new Map();
|
|
4029
|
+
for (const results of sampled) {
|
|
4030
|
+
for (const result of results) {
|
|
4031
|
+
passCounts.set(result.id, (passCounts.get(result.id) ?? 0) + (result.pass ? 1 : 0));
|
|
4032
|
+
if (checkOutcome(result) === "error") {
|
|
4033
|
+
errorCounts.set(result.id, (errorCounts.get(result.id) ?? 0) + 1);
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
}
|
|
4037
|
+
return (sampled[0] ?? []).map((result) => ({
|
|
4038
|
+
check_id: result.id,
|
|
4039
|
+
passes: passCounts.get(result.id) ?? 0,
|
|
4040
|
+
samples: sampled.length,
|
|
4041
|
+
...errorCounts.get(result.id) ? { errored: errorCounts.get(result.id) } : {}
|
|
4042
|
+
}));
|
|
4043
|
+
}
|
|
3702
4044
|
async function runChecksOnce(loaded, deliverables, snapshot, judgeConfig) {
|
|
3703
4045
|
if (loaded.inlineChecks) {
|
|
3704
4046
|
return runTraceChecks({
|
|
@@ -3771,7 +4113,8 @@ function degenerateSnapshot(runId) {
|
|
|
3771
4113
|
errors: "unavailable",
|
|
3772
4114
|
timing: "unavailable",
|
|
3773
4115
|
skills: "unavailable",
|
|
3774
|
-
subagents: "unavailable"
|
|
4116
|
+
subagents: "unavailable",
|
|
4117
|
+
usage: "unavailable"
|
|
3775
4118
|
},
|
|
3776
4119
|
observations: []
|
|
3777
4120
|
};
|
|
@@ -4303,6 +4646,7 @@ async function runTaskDir(taskDir) {
|
|
|
4303
4646
|
taskDir: loaded.taskDir,
|
|
4304
4647
|
taskId: loaded.task.id,
|
|
4305
4648
|
pass: result.result.pass,
|
|
4649
|
+
...result.result.noVerdict ? { noVerdict: true } : {},
|
|
4306
4650
|
checks: result.result.checks,
|
|
4307
4651
|
adapterName: loaded.adapter.name,
|
|
4308
4652
|
traceRunId: result.traceRunId,
|
|
@@ -4379,7 +4723,8 @@ async function runAgentTaskCli(argv, cwd = process.cwd()) {
|
|
|
4379
4723
|
if (result.status === "fulfilled") {
|
|
4380
4724
|
summaries.push(result.value);
|
|
4381
4725
|
printTaskSummary(result.value, cwd);
|
|
4382
|
-
|
|
4726
|
+
if (result.value.noVerdict) hasErrors = true;
|
|
4727
|
+
else hasFailures ||= !result.value.pass;
|
|
4383
4728
|
} else {
|
|
4384
4729
|
hasErrors = true;
|
|
4385
4730
|
printTaskError(taskDir, cwd, result.reason);
|
|
@@ -4427,7 +4772,8 @@ function printHelp() {
|
|
|
4427
4772
|
function printTaskSummary(summary, cwd) {
|
|
4428
4773
|
const relativeTaskDir = relative(cwd, summary.taskDir);
|
|
4429
4774
|
console.log("");
|
|
4430
|
-
|
|
4775
|
+
const verdict = summary.noVerdict ? "NO VERDICT" : summary.pass ? "PASS" : "FAIL";
|
|
4776
|
+
console.log(`${verdict} ${summary.taskId}`);
|
|
4431
4777
|
console.log(`Path: ${relativeTaskDir}`);
|
|
4432
4778
|
printResultGroup("Checks", summary.checks);
|
|
4433
4779
|
if (summary.checks.length === 0 && !summary.pass) {
|
|
@@ -4454,12 +4800,14 @@ function printTaskError(taskDir, cwd, error) {
|
|
|
4454
4800
|
}
|
|
4455
4801
|
function printOverallSummary(summaries, hasErrors) {
|
|
4456
4802
|
const passed = summaries.filter((summary) => summary.pass).length;
|
|
4457
|
-
const
|
|
4803
|
+
const noVerdict = summaries.filter((summary) => summary.noVerdict).length;
|
|
4804
|
+
const failed = summaries.length - passed - noVerdict;
|
|
4458
4805
|
console.log("");
|
|
4459
4806
|
console.log("Summary");
|
|
4460
4807
|
console.log(`- total: ${summaries.length}`);
|
|
4461
4808
|
console.log(`- passed: ${passed}`);
|
|
4462
4809
|
console.log(`- failed: ${failed}`);
|
|
4810
|
+
if (noVerdict > 0) console.log(`- no verdict: ${noVerdict}`);
|
|
4463
4811
|
console.log(`- errors: ${hasErrors ? "yes" : "no"}`);
|
|
4464
4812
|
}
|
|
4465
4813
|
|