@hachej/boring-agent 0.1.111 → 0.2.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/LICENSE +1 -1
- package/README.md +262 -51
- package/dist/agents.d.ts +75 -0
- package/dist/agents.js +91 -0
- package/dist/approval.d.ts +71 -0
- package/dist/approval.js +126 -0
- package/dist/artifacts.d.ts +53 -0
- package/dist/artifacts.js +129 -0
- package/dist/ask-user.d.ts +40 -0
- package/dist/ask-user.js +134 -0
- package/dist/attachment.d.ts +5 -0
- package/dist/attachment.js +4 -0
- package/dist/browser-task.d.ts +32 -0
- package/dist/browser-task.js +119 -0
- package/dist/chat-transport.d.ts +80 -0
- package/dist/chat-transport.js +306 -0
- package/dist/contracts.d.ts +40 -0
- package/dist/contracts.js +1 -0
- package/dist/conversations.d.ts +136 -0
- package/dist/conversations.js +366 -0
- package/dist/definitions.d.ts +37 -0
- package/dist/definitions.js +99 -0
- package/dist/delivery-input.d.ts +35 -0
- package/dist/delivery-input.js +56 -0
- package/dist/delivery.d.ts +33 -0
- package/dist/delivery.js +154 -0
- package/dist/durable-wait.d.ts +23 -0
- package/dist/durable-wait.js +79 -0
- package/dist/file-convert.d.ts +46 -0
- package/dist/file-convert.js +159 -0
- package/dist/file-guard.d.ts +50 -0
- package/dist/file-guard.js +171 -0
- package/dist/file-types.d.ts +17 -0
- package/dist/file-types.js +95 -0
- package/dist/gateway-provider.d.ts +24 -0
- package/dist/gateway-provider.js +42 -0
- package/dist/git.d.ts +34 -0
- package/dist/git.js +79 -0
- package/dist/harness-pool.d.ts +38 -0
- package/dist/harness-pool.js +134 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1 -0
- package/dist/json-schema-tool.d.ts +25 -0
- package/dist/json-schema-tool.js +19 -0
- package/dist/memory/optchat-core.d.ts +118 -0
- package/dist/memory/optchat-core.js +317 -0
- package/dist/memory/optchat.d.ts +156 -0
- package/dist/memory/optchat.js +432 -0
- package/dist/mentions.d.ts +56 -0
- package/dist/mentions.js +140 -0
- package/dist/metering.d.ts +183 -0
- package/dist/metering.js +443 -0
- package/dist/model-gateway.d.ts +86 -0
- package/dist/model-gateway.js +241 -0
- package/dist/native-version.d.ts +9 -0
- package/dist/native-version.js +7 -0
- package/dist/native.d.ts +4 -0
- package/dist/native.js +1 -0
- package/dist/presentation.d.ts +17 -0
- package/dist/presentation.js +39 -0
- package/dist/projection-format.d.ts +68 -0
- package/dist/projection-format.js +118 -0
- package/dist/projection-window.d.ts +9 -0
- package/dist/projection-window.js +93 -0
- package/dist/projection.d.ts +22 -0
- package/dist/projection.js +153 -0
- package/dist/question-response.d.ts +20 -0
- package/dist/question-response.js +75 -0
- package/dist/questions.d.ts +74 -0
- package/dist/questions.js +199 -0
- package/dist/self-evolving.d.ts +55 -0
- package/dist/self-evolving.js +239 -0
- package/dist/skills.d.ts +15 -0
- package/dist/skills.js +51 -0
- package/dist/tool-evidence.d.ts +14 -0
- package/dist/tool-evidence.js +54 -0
- package/dist/validation.d.ts +38 -0
- package/dist/validation.js +68 -0
- package/dist/workspaces.d.ts +107 -0
- package/dist/workspaces.js +212 -0
- package/package.json +145 -150
- package/dist/DebugDrawer-JNA43SVR.js +0 -8
- package/dist/admin-DORVQfLn.d.ts +0 -340
- package/dist/chunk-2BGLZZ7T.js +0 -10
- package/dist/chunk-42D6KAQV.js +0 -2882
- package/dist/chunk-4XOW57KJ.js +0 -343
- package/dist/chunk-72KT6VWV.js +0 -1338
- package/dist/chunk-7XAOVFSN.js +0 -28
- package/dist/chunk-KJIEMSQP.js +0 -198
- package/dist/chunk-KN72XWP7.js +0 -394
- package/dist/chunk-N6THTH7P.js +0 -110
- package/dist/chunk-V3H5A3BR.js +0 -22501
- package/dist/chunk-YGRQ4Q5P.js +0 -20
- package/dist/chunk-YTUZFKB3.js +0 -78
- package/dist/core/index.d.ts +0 -28
- package/dist/core/index.js +0 -617
- package/dist/createHarness-DOLOHA62.js +0 -18
- package/dist/eval/index.d.ts +0 -247
- package/dist/eval/index.js +0 -541
- package/dist/file-search-BUgMSKZE.d.ts +0 -289
- package/dist/front/artifacts.d.ts +0 -34
- package/dist/front/artifacts.js +0 -21
- package/dist/front/index.d.ts +0 -1082
- package/dist/front/index.js +0 -12912
- package/dist/front/styles.css +0 -5380
- package/dist/harness-qHlMJhZo.d.ts +0 -276
- package/dist/middleware-CaAEXpi5.d.ts +0 -115
- package/dist/piChatEvent-DpE0lIb9.d.ts +0 -450
- package/dist/piChatSessionService-CBct-hmz.d.ts +0 -86
- package/dist/sandbox-BMhut3kh.d.ts +0 -317
- package/dist/server/agent-host/testing/compositionRouteProof.d.ts +0 -8
- package/dist/server/agent-host/testing/compositionRouteProof.js +0 -78
- package/dist/server/agent-host/testing/gatewayConformance.d.ts +0 -43
- package/dist/server/agent-host/testing/gatewayConformance.js +0 -605
- package/dist/server/index.d.ts +0 -2278
- package/dist/server/index.js +0 -274
- package/dist/server/pi-session-readability.d.ts +0 -12
- package/dist/server/pi-session-readability.js +0 -23
- package/dist/server/worker/index.d.ts +0 -68
- package/dist/server/worker/index.js +0 -377
- package/dist/share-entry-CxS6UJ_L.d.ts +0 -330
- package/dist/shared/index.d.ts +0 -2111
- package/dist/shared/index.js +0 -262
- package/dist/types-DcWoQMn8.d.ts +0 -1377
- package/docs/ACCESSIBILITY.md +0 -52
- package/docs/AGENT_GATEWAY_V0.md +0 -212
- package/docs/AGENT_PACKAGES.md +0 -90
- package/docs/API.md +0 -123
- package/docs/AUTHORING.md +0 -68
- package/docs/CSP.md +0 -41
- package/docs/ERROR_CODES.md +0 -229
- package/docs/KNOWN_LIMITATIONS.md +0 -100
- package/docs/MIGRATION.md +0 -50
- package/docs/PERFORMANCE.md +0 -68
- package/docs/PLUGINS.md +0 -138
- package/docs/README.md +0 -98
- package/docs/RISKS-MULTI-TAB.md +0 -46
- package/docs/SESSION_IDENTITY.md +0 -91
- package/docs/STYLING.md +0 -73
- package/docs/UI-SHADCN.md +0 -56
- package/docs/VERCEL_COSTS.md +0 -103
- package/docs/plans/archive/AGENT_EVAL_FRAMEWORK.md +0 -466
- package/docs/plans/archive/agent-package-spec.md +0 -1777
- package/docs/plans/archive/harness-followup-capabilities.md +0 -440
- package/docs/plans/archive/harness-tool-ui-capabilities.md +0 -144
- package/docs/plans/archive/pi-followup-history-projection.md +0 -229
- package/docs/plans/archive/pi-tools-migration.md +0 -1061
- package/docs/plans/archive/reviews/pi-followup-history-codex-review.md +0 -11
- package/docs/plans/archive/reviews/pi-followup-history-gpt-review.md +0 -64
- package/docs/plans/archive/reviews/pi-followup-history-opus-review.md +0 -43
- package/docs/plans/archive/reviews/pi-followup-history-xai-review.md +0 -43
- package/docs/plans/archive/vercel-base-snapshot-template-plan.md +0 -527
- package/docs/plans/archive/vercel-persistent-sandbox-adapter.md +0 -553
- package/docs/runtime-provisioning.md +0 -157
- package/docs/runtime.md +0 -296
- package/docs/tools.md +0 -102
package/dist/eval/index.js
DELETED
|
@@ -1,541 +0,0 @@
|
|
|
1
|
-
// src/eval/evalPrompt.ts
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
|
-
|
|
4
|
-
// src/eval/types.ts
|
|
5
|
-
var EvalAny = /* @__PURE__ */ Symbol.for("@hachej/boring-agent/eval/EvalAny");
|
|
6
|
-
function EvalRegex(re) {
|
|
7
|
-
return { __evalRegex: typeof re === "string" ? new RegExp(re) : re };
|
|
8
|
-
}
|
|
9
|
-
function isEvalRegex(value) {
|
|
10
|
-
return typeof value === "object" && value !== null && "__evalRegex" in value && value.__evalRegex instanceof RegExp;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// src/eval/matcher.ts
|
|
14
|
-
function callSatisfies(expected, actual) {
|
|
15
|
-
if (expected.tool !== actual.tool) {
|
|
16
|
-
return {
|
|
17
|
-
ok: false,
|
|
18
|
-
matchedIndex: -1,
|
|
19
|
-
reason: `tool mismatch: expected "${expected.tool}", got "${actual.tool}"`
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
if (!expected.params) {
|
|
23
|
-
return { ok: true, matchedIndex: 0 };
|
|
24
|
-
}
|
|
25
|
-
const paramsCheck = matchParams(expected.params, actual.params, expected.strict ?? false);
|
|
26
|
-
if (!paramsCheck.ok) {
|
|
27
|
-
return { ok: false, matchedIndex: -1, reason: paramsCheck.reason };
|
|
28
|
-
}
|
|
29
|
-
return { ok: true, matchedIndex: 0 };
|
|
30
|
-
}
|
|
31
|
-
function someCallMatches(expected, actual) {
|
|
32
|
-
const expectedList = Array.isArray(expected) ? expected : [expected];
|
|
33
|
-
for (const exp of expectedList) {
|
|
34
|
-
let matched = false;
|
|
35
|
-
let lastReason;
|
|
36
|
-
for (const cand of actual) {
|
|
37
|
-
const out = callSatisfies(exp, cand);
|
|
38
|
-
if (out.ok) {
|
|
39
|
-
matched = true;
|
|
40
|
-
break;
|
|
41
|
-
}
|
|
42
|
-
lastReason = out.reason;
|
|
43
|
-
}
|
|
44
|
-
if (!matched) {
|
|
45
|
-
return {
|
|
46
|
-
ok: false,
|
|
47
|
-
matchedIndex: -1,
|
|
48
|
-
reason: lastReason ? `no actual call matched expected ${formatExpected(exp)} \u2014 closest mismatch: ${lastReason}` : `no actual call matched expected ${formatExpected(exp)} (actual was empty)`
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return { ok: true, matchedIndex: 0 };
|
|
53
|
-
}
|
|
54
|
-
function firstCallMatches(expected, actual) {
|
|
55
|
-
if (actual.length === 0) {
|
|
56
|
-
return {
|
|
57
|
-
ok: false,
|
|
58
|
-
matchedIndex: -1,
|
|
59
|
-
reason: `expectFirst: no tool calls captured (LLM answered in plain text)`
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
return callSatisfies(expected, actual[0]);
|
|
63
|
-
}
|
|
64
|
-
function noToolCallMatches(actual) {
|
|
65
|
-
if (actual.length === 0) {
|
|
66
|
-
return { ok: true, matchedIndex: -1 };
|
|
67
|
-
}
|
|
68
|
-
return {
|
|
69
|
-
ok: false,
|
|
70
|
-
matchedIndex: -1,
|
|
71
|
-
reason: `expectNoToolCall: ${actual.length} tool call(s) captured: ${actual.map((c) => c.tool).join(", ")}`
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
function matchParams(expected, actual, strict, pathPrefix = "") {
|
|
75
|
-
for (const key of Object.keys(expected)) {
|
|
76
|
-
const expVal = expected[key];
|
|
77
|
-
const actVal = actual[key];
|
|
78
|
-
const path = pathPrefix ? `${pathPrefix}.${key}` : key;
|
|
79
|
-
if (expVal === EvalAny || isEvalAnySymbol(expVal)) {
|
|
80
|
-
if (actVal === void 0) {
|
|
81
|
-
return { ok: false, reason: `${path}: missing (expected !EvalAny)` };
|
|
82
|
-
}
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
if (isEvalRegex(expVal)) {
|
|
86
|
-
if (typeof actVal !== "string") {
|
|
87
|
-
return {
|
|
88
|
-
ok: false,
|
|
89
|
-
reason: `${path}: expected string for !EvalRegex, got ${typeof actVal}`
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
if (!expVal.__evalRegex.test(actVal)) {
|
|
93
|
-
return {
|
|
94
|
-
ok: false,
|
|
95
|
-
reason: `${path}: ${JSON.stringify(actVal)} does not match /${expVal.__evalRegex.source}/`
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
if (typeof expVal === "object" && expVal !== null && !Array.isArray(expVal)) {
|
|
101
|
-
if (typeof actVal !== "object" || actVal === null || Array.isArray(actVal)) {
|
|
102
|
-
return {
|
|
103
|
-
ok: false,
|
|
104
|
-
reason: `${path}: expected object, got ${describe(actVal)}`
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
const nested = matchParams(
|
|
108
|
-
expVal,
|
|
109
|
-
actVal,
|
|
110
|
-
strict,
|
|
111
|
-
path
|
|
112
|
-
);
|
|
113
|
-
if (!nested.ok) return nested;
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
if (Array.isArray(expVal)) {
|
|
117
|
-
if (!Array.isArray(actVal)) {
|
|
118
|
-
return {
|
|
119
|
-
ok: false,
|
|
120
|
-
reason: `${path}: expected array, got ${describe(actVal)}`
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
if (expVal.length !== actVal.length) {
|
|
124
|
-
return {
|
|
125
|
-
ok: false,
|
|
126
|
-
reason: `${path}: array length mismatch (expected ${expVal.length}, got ${actVal.length})`
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
for (let i = 0; i < expVal.length; i++) {
|
|
130
|
-
const elemMatch = matchParams(
|
|
131
|
-
{ item: expVal[i] },
|
|
132
|
-
{ item: actVal[i] },
|
|
133
|
-
strict,
|
|
134
|
-
`${path}[${i}]`
|
|
135
|
-
);
|
|
136
|
-
if (!elemMatch.ok) return elemMatch;
|
|
137
|
-
}
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
if (expVal !== actVal) {
|
|
141
|
-
return {
|
|
142
|
-
ok: false,
|
|
143
|
-
reason: `${path}: expected ${JSON.stringify(expVal)}, got ${JSON.stringify(actVal)}`
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
if (strict) {
|
|
148
|
-
for (const key of Object.keys(actual)) {
|
|
149
|
-
if (!(key in expected)) {
|
|
150
|
-
const path = pathPrefix ? `${pathPrefix}.${key}` : key;
|
|
151
|
-
return {
|
|
152
|
-
ok: false,
|
|
153
|
-
reason: `${path}: unexpected key (strict mode)`
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return { ok: true };
|
|
159
|
-
}
|
|
160
|
-
function isEvalAnySymbol(value) {
|
|
161
|
-
return value === EvalAny;
|
|
162
|
-
}
|
|
163
|
-
function describe(value) {
|
|
164
|
-
if (value === null) return "null";
|
|
165
|
-
if (Array.isArray(value)) return "array";
|
|
166
|
-
if (value === void 0) return "undefined";
|
|
167
|
-
return typeof value;
|
|
168
|
-
}
|
|
169
|
-
function formatExpected(exp) {
|
|
170
|
-
if (!exp.params) return `{tool: ${exp.tool}}`;
|
|
171
|
-
return `{tool: ${exp.tool}, params: ${safeStringify(exp.params)}}`;
|
|
172
|
-
}
|
|
173
|
-
function safeStringify(value) {
|
|
174
|
-
try {
|
|
175
|
-
return JSON.stringify(value, (_k, v) => {
|
|
176
|
-
if (v === EvalAny) return "<EvalAny>";
|
|
177
|
-
if (isEvalRegex(v)) return `<EvalRegex /${v.__evalRegex.source}/>`;
|
|
178
|
-
return v;
|
|
179
|
-
});
|
|
180
|
-
} catch {
|
|
181
|
-
return "<unserializable>";
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// src/eval/evalConfig.ts
|
|
186
|
-
var DEFAULT_EVAL_MODEL = { provider: "openrouter", id: "qwen/qwen3.6-plus" };
|
|
187
|
-
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
188
|
-
var DEFAULT_SUITE_TIMEOUT_MS = 5 * 6e4;
|
|
189
|
-
var DEFAULT_CONCURRENCY = 4;
|
|
190
|
-
|
|
191
|
-
// src/eval/evalPrompt.ts
|
|
192
|
-
async function evalAgentPrompt(opts) {
|
|
193
|
-
validateMutuallyExclusive(opts);
|
|
194
|
-
const sessionId = opts.sessionId ?? randomUUID();
|
|
195
|
-
const model = parseModelString(opts.model ?? DEFAULT_EVAL_MODEL);
|
|
196
|
-
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
197
|
-
const maxRetries = opts.retries ?? 0;
|
|
198
|
-
let attempts = 0;
|
|
199
|
-
let lastResult = null;
|
|
200
|
-
while (attempts <= maxRetries) {
|
|
201
|
-
attempts += 1;
|
|
202
|
-
let captured;
|
|
203
|
-
try {
|
|
204
|
-
captured = await runOnce(opts.app, {
|
|
205
|
-
agentTypeId: opts.agentTypeId,
|
|
206
|
-
sessionId: `${sessionId}-attempt-${attempts}`,
|
|
207
|
-
prompt: opts.prompt,
|
|
208
|
-
systemPrompt: opts.systemPrompt,
|
|
209
|
-
model,
|
|
210
|
-
timeoutMs,
|
|
211
|
-
headers: opts.headers
|
|
212
|
-
});
|
|
213
|
-
} catch (err) {
|
|
214
|
-
lastResult = {
|
|
215
|
-
ok: false,
|
|
216
|
-
actual: [],
|
|
217
|
-
text: "",
|
|
218
|
-
attempts,
|
|
219
|
-
reason: `transport: ${err instanceof Error ? err.message : String(err)}`
|
|
220
|
-
};
|
|
221
|
-
if (attempts > maxRetries) return lastResult;
|
|
222
|
-
continue;
|
|
223
|
-
}
|
|
224
|
-
if (captured.errorText) {
|
|
225
|
-
lastResult = {
|
|
226
|
-
ok: false,
|
|
227
|
-
actual: captured.toolCalls,
|
|
228
|
-
text: captured.text,
|
|
229
|
-
usage: captured.usage,
|
|
230
|
-
attempts,
|
|
231
|
-
reason: `stream: ${captured.errorText}`
|
|
232
|
-
};
|
|
233
|
-
if (attempts > maxRetries) return lastResult;
|
|
234
|
-
continue;
|
|
235
|
-
}
|
|
236
|
-
const matchResult = matchExpectations(opts, captured.toolCalls);
|
|
237
|
-
lastResult = {
|
|
238
|
-
ok: matchResult.ok,
|
|
239
|
-
actual: captured.toolCalls,
|
|
240
|
-
text: captured.text,
|
|
241
|
-
usage: captured.usage,
|
|
242
|
-
attempts,
|
|
243
|
-
reason: matchResult.ok ? void 0 : matchResult.reason
|
|
244
|
-
};
|
|
245
|
-
if (matchResult.ok) return lastResult;
|
|
246
|
-
}
|
|
247
|
-
return lastResult ?? {
|
|
248
|
-
ok: false,
|
|
249
|
-
actual: [],
|
|
250
|
-
text: "",
|
|
251
|
-
attempts,
|
|
252
|
-
reason: "no attempts ran"
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
function validateMutuallyExclusive(opts) {
|
|
256
|
-
const set = [
|
|
257
|
-
opts.expect !== void 0,
|
|
258
|
-
opts.expectFirst !== void 0,
|
|
259
|
-
opts.expectNoToolCall === true
|
|
260
|
-
].filter(Boolean).length;
|
|
261
|
-
if (set === 0) {
|
|
262
|
-
throw new Error(
|
|
263
|
-
"evalAgentPrompt: provide one of `expect`, `expectFirst`, or `expectNoToolCall: true`"
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
if (set > 1) {
|
|
267
|
-
throw new Error(
|
|
268
|
-
"evalAgentPrompt: `expect`, `expectFirst`, and `expectNoToolCall` are mutually exclusive"
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
function matchExpectations(opts, actual) {
|
|
273
|
-
if (opts.expectNoToolCall) {
|
|
274
|
-
return noToolCallMatches(actual);
|
|
275
|
-
}
|
|
276
|
-
if (opts.expectFirst) {
|
|
277
|
-
return firstCallMatches(opts.expectFirst, actual);
|
|
278
|
-
}
|
|
279
|
-
return someCallMatches(opts.expect, actual);
|
|
280
|
-
}
|
|
281
|
-
async function runOnce(app, opts) {
|
|
282
|
-
const sessionsPath = `/api/v1/agents/${encodeURIComponent(opts.agentTypeId)}/sessions`;
|
|
283
|
-
const created = await app.inject({
|
|
284
|
-
method: "POST",
|
|
285
|
-
url: sessionsPath,
|
|
286
|
-
headers: opts.headers,
|
|
287
|
-
payload: { requestId: `eval-create-${opts.sessionId}`, title: opts.sessionId }
|
|
288
|
-
});
|
|
289
|
-
if (created.statusCode !== 201) {
|
|
290
|
-
throw new Error(`Agent session create returned ${created.statusCode}: ${created.body.slice(0, 256)}`);
|
|
291
|
-
}
|
|
292
|
-
const sessionId = created.json().sessionId;
|
|
293
|
-
if (typeof sessionId !== "string" || !sessionId) throw new Error("Agent session create did not return sessionId");
|
|
294
|
-
const userMessage = opts.systemPrompt ? `[SYSTEM]
|
|
295
|
-
${opts.systemPrompt}
|
|
296
|
-
[/SYSTEM]
|
|
297
|
-
|
|
298
|
-
${opts.prompt}` : opts.prompt;
|
|
299
|
-
try {
|
|
300
|
-
const res = await withTimeout(
|
|
301
|
-
app.inject({
|
|
302
|
-
method: "POST",
|
|
303
|
-
url: `${sessionsPath}/${encodeURIComponent(sessionId)}/prompt`,
|
|
304
|
-
headers: opts.headers,
|
|
305
|
-
payload: {
|
|
306
|
-
requestId: `eval-${sessionId}`,
|
|
307
|
-
content: userMessage,
|
|
308
|
-
clientNonce: `eval-${opts.sessionId}`,
|
|
309
|
-
model: opts.model
|
|
310
|
-
}
|
|
311
|
-
}),
|
|
312
|
-
opts.timeoutMs,
|
|
313
|
-
`chat request exceeded ${opts.timeoutMs}ms`
|
|
314
|
-
);
|
|
315
|
-
if (res.statusCode !== 202 && res.statusCode !== 200) {
|
|
316
|
-
throw new Error(
|
|
317
|
-
`Pi chat prompt returned ${res.statusCode}: ${res.body.slice(0, 256)}`
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
const snapshot = await withTimeout(
|
|
321
|
-
waitForSettledState(app, opts, sessionId),
|
|
322
|
-
opts.timeoutMs,
|
|
323
|
-
`chat turn did not settle within ${opts.timeoutMs}ms`
|
|
324
|
-
);
|
|
325
|
-
return capturePiChatSnapshot(snapshot);
|
|
326
|
-
} finally {
|
|
327
|
-
void app.inject({
|
|
328
|
-
method: "DELETE",
|
|
329
|
-
url: `${sessionsPath}/${encodeURIComponent(sessionId)}`,
|
|
330
|
-
headers: opts.headers
|
|
331
|
-
}).catch(() => {
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
var STATE_POLL_INTERVAL_MS = 250;
|
|
336
|
-
async function waitForSettledState(app, opts, sessionId) {
|
|
337
|
-
for (; ; ) {
|
|
338
|
-
const state = await app.inject({
|
|
339
|
-
method: "GET",
|
|
340
|
-
url: `/api/v1/agents/${encodeURIComponent(opts.agentTypeId)}/sessions/${encodeURIComponent(sessionId)}/state`,
|
|
341
|
-
headers: opts.headers
|
|
342
|
-
});
|
|
343
|
-
if (state.statusCode !== 200) {
|
|
344
|
-
throw new Error(`Pi chat state returned ${state.statusCode}: ${state.body.slice(0, 256)}`);
|
|
345
|
-
}
|
|
346
|
-
const envelope = JSON.parse(state.body);
|
|
347
|
-
const snapshot = envelope.state;
|
|
348
|
-
if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot)) {
|
|
349
|
-
throw new Error("Pi chat state did not return an addressed state envelope");
|
|
350
|
-
}
|
|
351
|
-
const addressedState = snapshot;
|
|
352
|
-
if (addressedState.status !== "streaming") return addressedState;
|
|
353
|
-
await new Promise((resolve) => setTimeout(resolve, STATE_POLL_INTERVAL_MS));
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
function capturePiChatSnapshot(snapshot) {
|
|
357
|
-
const toolCalls = [];
|
|
358
|
-
const textParts = [];
|
|
359
|
-
const snapshotError = snapshot.error;
|
|
360
|
-
const errorText = typeof snapshotError?.message === "string" ? snapshotError.message : void 0;
|
|
361
|
-
const messages = Array.isArray(snapshot.messages) ? snapshot.messages : [];
|
|
362
|
-
for (const message of messages) {
|
|
363
|
-
if (typeof message !== "object" || message === null) continue;
|
|
364
|
-
const parts = Array.isArray(message.parts) ? message.parts : [];
|
|
365
|
-
for (const part of parts) {
|
|
366
|
-
if (typeof part !== "object" || part === null) continue;
|
|
367
|
-
const rec = part;
|
|
368
|
-
if (rec.type === "text" && typeof rec.text === "string") textParts.push(rec.text);
|
|
369
|
-
if (rec.type === "tool-call" && typeof rec.toolName === "string") {
|
|
370
|
-
toolCalls.push({ tool: rec.toolName, params: typeof rec.input === "object" && rec.input !== null ? rec.input : {} });
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
return { toolCalls, text: textParts.join(""), errorText };
|
|
375
|
-
}
|
|
376
|
-
function withTimeout(p, ms, label) {
|
|
377
|
-
return new Promise((resolve, reject) => {
|
|
378
|
-
const timer = setTimeout(() => reject(new Error(label)), ms);
|
|
379
|
-
p.then(
|
|
380
|
-
(v) => {
|
|
381
|
-
clearTimeout(timer);
|
|
382
|
-
resolve(v);
|
|
383
|
-
},
|
|
384
|
-
(err) => {
|
|
385
|
-
clearTimeout(timer);
|
|
386
|
-
reject(err);
|
|
387
|
-
}
|
|
388
|
-
);
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
function parseModelString(input) {
|
|
392
|
-
if (typeof input === "object") return input;
|
|
393
|
-
if (input.startsWith("claude-")) return { provider: "anthropic", id: input };
|
|
394
|
-
if (input.startsWith("gpt-")) return { provider: "openai", id: input };
|
|
395
|
-
if (input.startsWith("qwen/")) return { provider: "openrouter", id: input };
|
|
396
|
-
return { provider: "anthropic", id: input };
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// src/eval/runSuite.ts
|
|
400
|
-
import { readFile } from "node:fs/promises";
|
|
401
|
-
|
|
402
|
-
// src/eval/yamlSchema.ts
|
|
403
|
-
import { parse, Schema } from "yaml";
|
|
404
|
-
var evalSchema = new Schema({
|
|
405
|
-
customTags: [
|
|
406
|
-
{
|
|
407
|
-
tag: "!EvalAny",
|
|
408
|
-
// No value — `!EvalAny` alone resolves to the symbol.
|
|
409
|
-
identify: (value) => value === EvalAny,
|
|
410
|
-
resolve: () => EvalAny
|
|
411
|
-
},
|
|
412
|
-
{
|
|
413
|
-
tag: "!EvalRegex",
|
|
414
|
-
// Scalar value — `!EvalRegex "^chart:"` resolves to a matcher object.
|
|
415
|
-
identify: (value) => typeof value === "object" && value !== null && "__evalRegex" in value,
|
|
416
|
-
resolve: (str) => EvalRegex(str)
|
|
417
|
-
}
|
|
418
|
-
]
|
|
419
|
-
});
|
|
420
|
-
function parseFixtureYaml(text) {
|
|
421
|
-
const parsed = parse(text, { schema: evalSchema });
|
|
422
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
423
|
-
throw new Error(
|
|
424
|
-
`Eval fixture must be a YAML object with a "prompts" key. Got: ${describe2(parsed)}`
|
|
425
|
-
);
|
|
426
|
-
}
|
|
427
|
-
const obj = parsed;
|
|
428
|
-
if (!Array.isArray(obj.prompts)) {
|
|
429
|
-
throw new Error(`Eval fixture missing "prompts" array.`);
|
|
430
|
-
}
|
|
431
|
-
return obj;
|
|
432
|
-
}
|
|
433
|
-
function describe2(value) {
|
|
434
|
-
if (value === null) return "null";
|
|
435
|
-
if (value === void 0) return "undefined";
|
|
436
|
-
if (Array.isArray(value)) return "array";
|
|
437
|
-
return typeof value;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// src/eval/runSuite.ts
|
|
441
|
-
async function runEvalSuite(opts) {
|
|
442
|
-
const fixtureText = await readFile(opts.fixturesPath, "utf8");
|
|
443
|
-
const fixture = parseFixtureYaml(fixtureText);
|
|
444
|
-
const concurrency = opts.concurrency ?? DEFAULT_CONCURRENCY;
|
|
445
|
-
const suiteTimeoutMs = opts.suiteTimeoutMs ?? DEFAULT_SUITE_TIMEOUT_MS;
|
|
446
|
-
const start = Date.now();
|
|
447
|
-
const deadline = start + suiteTimeoutMs;
|
|
448
|
-
const queue = [...fixture.prompts];
|
|
449
|
-
const results = [];
|
|
450
|
-
let bailed = false;
|
|
451
|
-
async function worker() {
|
|
452
|
-
while (queue.length > 0 && !bailed) {
|
|
453
|
-
if (Date.now() > deadline) return;
|
|
454
|
-
const prompt = queue.shift();
|
|
455
|
-
if (!prompt) return;
|
|
456
|
-
const result = await runOnePrompt(opts, fixture, prompt);
|
|
457
|
-
results.push({
|
|
458
|
-
...result,
|
|
459
|
-
prompt: prompt.prompt,
|
|
460
|
-
expected: prompt
|
|
461
|
-
});
|
|
462
|
-
if (!result.ok && opts.bail) {
|
|
463
|
-
bailed = true;
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
const workers = Array.from({ length: concurrency }, () => worker());
|
|
469
|
-
await Promise.race([
|
|
470
|
-
Promise.all(workers),
|
|
471
|
-
new Promise(
|
|
472
|
-
(_, reject) => setTimeout(
|
|
473
|
-
() => reject(new Error(`suite exceeded ${suiteTimeoutMs}ms`)),
|
|
474
|
-
suiteTimeoutMs
|
|
475
|
-
)
|
|
476
|
-
)
|
|
477
|
-
]).catch((err) => {
|
|
478
|
-
results.push({
|
|
479
|
-
ok: false,
|
|
480
|
-
actual: [],
|
|
481
|
-
text: "",
|
|
482
|
-
attempts: 0,
|
|
483
|
-
reason: err instanceof Error ? err.message : String(err),
|
|
484
|
-
prompt: "(suite-level timeout)",
|
|
485
|
-
expected: { prompt: "(suite-level timeout)" }
|
|
486
|
-
});
|
|
487
|
-
});
|
|
488
|
-
const totalUsage = results.reduce(
|
|
489
|
-
(acc, r) => ({
|
|
490
|
-
input: acc.input + (r.usage?.input ?? 0),
|
|
491
|
-
output: acc.output + (r.usage?.output ?? 0)
|
|
492
|
-
}),
|
|
493
|
-
{ input: 0, output: 0 }
|
|
494
|
-
);
|
|
495
|
-
const passed = results.filter((r) => r.ok).length;
|
|
496
|
-
const total = results.length;
|
|
497
|
-
const passRate = total === 0 ? 0 : passed / total;
|
|
498
|
-
return {
|
|
499
|
-
total,
|
|
500
|
-
passed,
|
|
501
|
-
failed: total - passed,
|
|
502
|
-
passRate,
|
|
503
|
-
results,
|
|
504
|
-
totalUsage,
|
|
505
|
-
totalDurationMs: Date.now() - start,
|
|
506
|
-
allPassed: total > 0 && passed === total
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
async function runOnePrompt(opts, fixture, p) {
|
|
510
|
-
const model = opts.model ?? p.model ?? fixture.defaults?.model ?? fixture.model;
|
|
511
|
-
const retries = p.retries ?? fixture.defaults?.retries ?? 0;
|
|
512
|
-
const timeoutMs = p.timeoutMs ?? fixture.defaults?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
513
|
-
return evalAgentPrompt({
|
|
514
|
-
app: opts.app,
|
|
515
|
-
agentTypeId: opts.agentTypeId,
|
|
516
|
-
prompt: p.prompt,
|
|
517
|
-
expect: p.expect,
|
|
518
|
-
expectFirst: p.expectFirst,
|
|
519
|
-
expectNoToolCall: p.expectNoToolCall,
|
|
520
|
-
systemPrompt: fixture.systemPrompt,
|
|
521
|
-
model,
|
|
522
|
-
retries,
|
|
523
|
-
timeoutMs
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
export {
|
|
527
|
-
DEFAULT_CONCURRENCY,
|
|
528
|
-
DEFAULT_EVAL_MODEL,
|
|
529
|
-
DEFAULT_SUITE_TIMEOUT_MS,
|
|
530
|
-
DEFAULT_TIMEOUT_MS,
|
|
531
|
-
EvalAny,
|
|
532
|
-
EvalRegex,
|
|
533
|
-
callSatisfies,
|
|
534
|
-
evalAgentPrompt,
|
|
535
|
-
firstCallMatches,
|
|
536
|
-
isEvalRegex,
|
|
537
|
-
noToolCallMatches,
|
|
538
|
-
parseFixtureYaml,
|
|
539
|
-
runEvalSuite,
|
|
540
|
-
someCallMatches
|
|
541
|
-
};
|