@hachej/boring-agent 0.1.110 → 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.
Files changed (156) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +262 -51
  3. package/dist/agents.d.ts +75 -0
  4. package/dist/agents.js +91 -0
  5. package/dist/approval.d.ts +71 -0
  6. package/dist/approval.js +126 -0
  7. package/dist/artifacts.d.ts +53 -0
  8. package/dist/artifacts.js +129 -0
  9. package/dist/ask-user.d.ts +40 -0
  10. package/dist/ask-user.js +134 -0
  11. package/dist/attachment.d.ts +5 -0
  12. package/dist/attachment.js +4 -0
  13. package/dist/browser-task.d.ts +32 -0
  14. package/dist/browser-task.js +119 -0
  15. package/dist/chat-transport.d.ts +80 -0
  16. package/dist/chat-transport.js +306 -0
  17. package/dist/contracts.d.ts +40 -0
  18. package/dist/contracts.js +1 -0
  19. package/dist/conversations.d.ts +136 -0
  20. package/dist/conversations.js +366 -0
  21. package/dist/definitions.d.ts +37 -0
  22. package/dist/definitions.js +99 -0
  23. package/dist/delivery-input.d.ts +35 -0
  24. package/dist/delivery-input.js +56 -0
  25. package/dist/delivery.d.ts +33 -0
  26. package/dist/delivery.js +154 -0
  27. package/dist/durable-wait.d.ts +23 -0
  28. package/dist/durable-wait.js +79 -0
  29. package/dist/file-convert.d.ts +46 -0
  30. package/dist/file-convert.js +159 -0
  31. package/dist/file-guard.d.ts +50 -0
  32. package/dist/file-guard.js +171 -0
  33. package/dist/file-types.d.ts +17 -0
  34. package/dist/file-types.js +95 -0
  35. package/dist/gateway-provider.d.ts +24 -0
  36. package/dist/gateway-provider.js +42 -0
  37. package/dist/git.d.ts +34 -0
  38. package/dist/git.js +79 -0
  39. package/dist/harness-pool.d.ts +38 -0
  40. package/dist/harness-pool.js +134 -0
  41. package/dist/index.d.ts +4 -0
  42. package/dist/index.js +1 -0
  43. package/dist/json-schema-tool.d.ts +25 -0
  44. package/dist/json-schema-tool.js +19 -0
  45. package/dist/memory/optchat-core.d.ts +118 -0
  46. package/dist/memory/optchat-core.js +317 -0
  47. package/dist/memory/optchat.d.ts +156 -0
  48. package/dist/memory/optchat.js +432 -0
  49. package/dist/mentions.d.ts +56 -0
  50. package/dist/mentions.js +140 -0
  51. package/dist/metering.d.ts +183 -0
  52. package/dist/metering.js +443 -0
  53. package/dist/model-gateway.d.ts +86 -0
  54. package/dist/model-gateway.js +241 -0
  55. package/dist/native-version.d.ts +9 -0
  56. package/dist/native-version.js +7 -0
  57. package/dist/native.d.ts +4 -0
  58. package/dist/native.js +1 -0
  59. package/dist/presentation.d.ts +17 -0
  60. package/dist/presentation.js +39 -0
  61. package/dist/projection-format.d.ts +68 -0
  62. package/dist/projection-format.js +118 -0
  63. package/dist/projection-window.d.ts +9 -0
  64. package/dist/projection-window.js +93 -0
  65. package/dist/projection.d.ts +22 -0
  66. package/dist/projection.js +153 -0
  67. package/dist/question-response.d.ts +20 -0
  68. package/dist/question-response.js +75 -0
  69. package/dist/questions.d.ts +74 -0
  70. package/dist/questions.js +199 -0
  71. package/dist/self-evolving.d.ts +55 -0
  72. package/dist/self-evolving.js +239 -0
  73. package/dist/skills.d.ts +15 -0
  74. package/dist/skills.js +51 -0
  75. package/dist/tool-evidence.d.ts +14 -0
  76. package/dist/tool-evidence.js +54 -0
  77. package/dist/validation.d.ts +38 -0
  78. package/dist/validation.js +68 -0
  79. package/dist/workspaces.d.ts +107 -0
  80. package/dist/workspaces.js +212 -0
  81. package/package.json +145 -150
  82. package/dist/DebugDrawer-JNA43SVR.js +0 -8
  83. package/dist/admin-DORVQfLn.d.ts +0 -340
  84. package/dist/chunk-2BGLZZ7T.js +0 -10
  85. package/dist/chunk-42D6KAQV.js +0 -2882
  86. package/dist/chunk-4XOW57KJ.js +0 -343
  87. package/dist/chunk-72KT6VWV.js +0 -1338
  88. package/dist/chunk-7XAOVFSN.js +0 -28
  89. package/dist/chunk-KJIEMSQP.js +0 -198
  90. package/dist/chunk-KN72XWP7.js +0 -394
  91. package/dist/chunk-N6THTH7P.js +0 -110
  92. package/dist/chunk-V3H5A3BR.js +0 -22501
  93. package/dist/chunk-YGRQ4Q5P.js +0 -20
  94. package/dist/chunk-YTUZFKB3.js +0 -78
  95. package/dist/core/index.d.ts +0 -28
  96. package/dist/core/index.js +0 -617
  97. package/dist/createHarness-DOLOHA62.js +0 -18
  98. package/dist/eval/index.d.ts +0 -247
  99. package/dist/eval/index.js +0 -541
  100. package/dist/file-search-BUgMSKZE.d.ts +0 -289
  101. package/dist/front/artifacts.d.ts +0 -34
  102. package/dist/front/artifacts.js +0 -21
  103. package/dist/front/index.d.ts +0 -1082
  104. package/dist/front/index.js +0 -12912
  105. package/dist/front/styles.css +0 -5380
  106. package/dist/harness-qHlMJhZo.d.ts +0 -276
  107. package/dist/middleware-CaAEXpi5.d.ts +0 -115
  108. package/dist/piChatEvent-DpE0lIb9.d.ts +0 -450
  109. package/dist/piChatSessionService-CBct-hmz.d.ts +0 -86
  110. package/dist/sandbox-BMhut3kh.d.ts +0 -317
  111. package/dist/server/agent-host/testing/compositionRouteProof.d.ts +0 -8
  112. package/dist/server/agent-host/testing/compositionRouteProof.js +0 -78
  113. package/dist/server/agent-host/testing/gatewayConformance.d.ts +0 -43
  114. package/dist/server/agent-host/testing/gatewayConformance.js +0 -605
  115. package/dist/server/index.d.ts +0 -2278
  116. package/dist/server/index.js +0 -274
  117. package/dist/server/pi-session-readability.d.ts +0 -12
  118. package/dist/server/pi-session-readability.js +0 -23
  119. package/dist/server/worker/index.d.ts +0 -68
  120. package/dist/server/worker/index.js +0 -377
  121. package/dist/share-entry-CxS6UJ_L.d.ts +0 -330
  122. package/dist/shared/index.d.ts +0 -2111
  123. package/dist/shared/index.js +0 -262
  124. package/dist/types-DcWoQMn8.d.ts +0 -1377
  125. package/docs/ACCESSIBILITY.md +0 -52
  126. package/docs/AGENT_GATEWAY_V0.md +0 -212
  127. package/docs/AGENT_PACKAGES.md +0 -90
  128. package/docs/API.md +0 -123
  129. package/docs/AUTHORING.md +0 -68
  130. package/docs/CSP.md +0 -41
  131. package/docs/ERROR_CODES.md +0 -229
  132. package/docs/KNOWN_LIMITATIONS.md +0 -100
  133. package/docs/MIGRATION.md +0 -50
  134. package/docs/PERFORMANCE.md +0 -68
  135. package/docs/PLUGINS.md +0 -138
  136. package/docs/README.md +0 -98
  137. package/docs/RISKS-MULTI-TAB.md +0 -46
  138. package/docs/SESSION_IDENTITY.md +0 -91
  139. package/docs/STYLING.md +0 -73
  140. package/docs/UI-SHADCN.md +0 -56
  141. package/docs/VERCEL_COSTS.md +0 -103
  142. package/docs/plans/archive/AGENT_EVAL_FRAMEWORK.md +0 -466
  143. package/docs/plans/archive/agent-package-spec.md +0 -1777
  144. package/docs/plans/archive/harness-followup-capabilities.md +0 -440
  145. package/docs/plans/archive/harness-tool-ui-capabilities.md +0 -144
  146. package/docs/plans/archive/pi-followup-history-projection.md +0 -229
  147. package/docs/plans/archive/pi-tools-migration.md +0 -1061
  148. package/docs/plans/archive/reviews/pi-followup-history-codex-review.md +0 -11
  149. package/docs/plans/archive/reviews/pi-followup-history-gpt-review.md +0 -64
  150. package/docs/plans/archive/reviews/pi-followup-history-opus-review.md +0 -43
  151. package/docs/plans/archive/reviews/pi-followup-history-xai-review.md +0 -43
  152. package/docs/plans/archive/vercel-base-snapshot-template-plan.md +0 -527
  153. package/docs/plans/archive/vercel-persistent-sandbox-adapter.md +0 -553
  154. package/docs/runtime-provisioning.md +0 -157
  155. package/docs/runtime.md +0 -296
  156. package/docs/tools.md +0 -102
@@ -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
- };