@cursor/july 0.1.12 → 0.1.15
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/dist/bin/agent-serve.js +32 -9
- package/dist/channels/github/api.d.ts.map +1 -1
- package/dist/channels/github/api.js +11 -4
- package/dist/channels/github/cursor-account.d.ts +21 -1
- package/dist/channels/github/cursor-account.d.ts.map +1 -1
- package/dist/channels/github/cursor-account.js +29 -1
- package/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +1 -1
- package/dist/channels/slack/api.d.ts +9 -0
- package/dist/channels/slack/api.d.ts.map +1 -1
- package/dist/channels/slack/api.js +40 -7
- package/dist/channels/slack/eval-directive.d.ts +3 -0
- package/dist/channels/slack/eval-directive.d.ts.map +1 -1
- package/dist/channels/slack/eval-directive.js +20 -1
- package/dist/channels/slack/post-update-delivery.d.ts +85 -0
- package/dist/channels/slack/post-update-delivery.d.ts.map +1 -0
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.Dh_JkJjI.js → app.DpdGZTLe.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.D2d8CfFS.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.CptQNLkR.js → VPLocalSearchBox.IB-0QqIT.js} +1 -1
- package/dist/docs/assets/chunks/{theme.DWflH-LQ.js → theme.-fYBoNtR.js} +2 -2
- package/dist/docs/assets/{evals.md.DAgEc_hL.js → evals.md.DYOjkRCX.js} +10 -4
- package/dist/docs/assets/{evals.md.DAgEc_hL.lean.js → evals.md.DYOjkRCX.lean.js} +1 -1
- package/dist/docs/assets/{reference_cli.md.B74NrVzb.js → reference_cli.md.DnYfr5V2.js} +4 -1
- package/dist/docs/assets/{reference_http-api.md.Dx_nmDG6.js → reference_http-api.md.BncLd3PZ.js} +2 -2
- package/dist/docs/assets/{reference_http-api.md.Dx_nmDG6.lean.js → reference_http-api.md.BncLd3PZ.lean.js} +1 -1
- package/dist/docs/building-with-agents.html +3 -3
- package/dist/docs/concepts.html +3 -3
- package/dist/docs/deployment.html +3 -3
- package/dist/docs/evals.html +13 -7
- package/dist/docs/example-agents/approval-buddy.html +3 -3
- package/dist/docs/example-agents/benny.html +3 -3
- package/dist/docs/example-agents/bugbot.html +3 -3
- package/dist/docs/example-agents/codebase-wiki.html +3 -3
- package/dist/docs/example-agents/codeowners-review.html +3 -3
- package/dist/docs/example-agents/concierge.html +3 -3
- package/dist/docs/example-agents/fsd.html +3 -3
- package/dist/docs/example-agents/index.html +3 -3
- package/dist/docs/example-agents/knowledge-base.html +3 -3
- package/dist/docs/example-agents/oncall.html +3 -3
- package/dist/docs/example-agents/security-reviewer.html +3 -3
- package/dist/docs/example-agents/slack-agent.html +3 -3
- package/dist/docs/example-agents/weather-agent.html +3 -3
- package/dist/docs/guides/agent-to-agent.html +3 -3
- package/dist/docs/guides/cloud-runtime.html +3 -3
- package/dist/docs/guides/github.html +3 -3
- package/dist/docs/guides/human-in-the-loop.html +3 -3
- package/dist/docs/guides/mcp-oauth.html +3 -3
- package/dist/docs/guides/slack.html +3 -3
- package/dist/docs/guides/webhooks.html +3 -3
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +3 -3
- package/dist/docs/index.html +3 -3
- package/dist/docs/quickstart.html +3 -3
- package/dist/docs/reference/agent-config.html +3 -3
- package/dist/docs/reference/channels.html +3 -3
- package/dist/docs/reference/cli.html +8 -5
- package/dist/docs/reference/connections.html +3 -3
- package/dist/docs/reference/hooks.html +3 -3
- package/dist/docs/reference/http-api.html +5 -5
- package/dist/docs/reference/instructions.html +3 -3
- package/dist/docs/reference/playground.html +3 -3
- package/dist/docs/reference/project-layout.html +3 -3
- package/dist/docs/reference/prompt.html +3 -3
- package/dist/docs/reference/schedules.html +3 -3
- package/dist/docs/reference/sessions.html +3 -3
- package/dist/docs/reference/skills.html +3 -3
- package/dist/docs/reference/subagents.html +3 -3
- package/dist/docs/reference/tools.html +3 -3
- package/dist/docs/scaffolding-agents.html +3 -3
- package/dist/docs/storage.html +3 -3
- package/dist/docs/troubleshooting.html +3 -3
- package/dist/evals.d.ts +49 -2
- package/dist/evals.d.ts.map +1 -1
- package/dist/evals.js +53 -16
- package/dist/internal/cli-ax.d.ts +4 -0
- package/dist/internal/cli-ax.d.ts.map +1 -1
- package/dist/internal/cli-ax.js +166 -33
- package/dist/internal/cli-deploy.d.ts +8 -0
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +92 -12
- package/dist/internal/cli-mcp-oauth.d.ts +14 -2
- package/dist/internal/cli-mcp-oauth.d.ts.map +1 -1
- package/dist/internal/cli-mcp-oauth.js +95 -2
- package/dist/internal/cursor/backend-client.d.ts +34 -0
- package/dist/internal/cursor/backend-client.d.ts.map +1 -1
- package/dist/internal/cursor/backend-client.js +73 -9
- package/dist/internal/cursor/credentials.d.ts +7 -1
- package/dist/internal/cursor/credentials.d.ts.map +1 -1
- package/dist/internal/cursor/credentials.js +9 -3
- package/dist/internal/cursor-account-mcp-auth.d.ts +55 -0
- package/dist/internal/cursor-account-mcp-auth.d.ts.map +1 -0
- package/dist/internal/cursor-account-mcp-auth.js +175 -0
- package/dist/internal/deploy-client.d.ts +6 -0
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +13 -0
- package/dist/internal/eval-run-store.d.ts +15 -0
- package/dist/internal/eval-run-store.d.ts.map +1 -1
- package/dist/internal/eval-run-store.js +94 -8
- package/dist/internal/eval-runner.d.ts +14 -0
- package/dist/internal/eval-runner.d.ts.map +1 -1
- package/dist/internal/eval-runner.js +111 -22
- package/dist/internal/evals-client.d.ts +9 -1
- package/dist/internal/evals-client.d.ts.map +1 -1
- package/dist/internal/evals-client.js +44 -1
- package/dist/internal/http-channel.d.ts.map +1 -1
- package/dist/internal/http-channel.js +74 -7
- package/dist/internal/map-pool.d.ts +6 -1
- package/dist/internal/map-pool.d.ts.map +1 -1
- package/dist/internal/map-pool.js +8 -1
- package/dist/internal/mcp-host.d.ts +13 -0
- package/dist/internal/mcp-host.d.ts.map +1 -1
- package/dist/internal/mcp-host.js +68 -13
- package/dist/internal/persistence-coordinator.d.ts +127 -0
- package/dist/internal/persistence-coordinator.d.ts.map +1 -0
- package/dist/internal/run-client.d.ts +5 -1
- package/dist/internal/run-client.d.ts.map +1 -1
- package/dist/internal/run-client.js +26 -5
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +57 -4
- package/dist/internal/session-engine.d.ts +13 -0
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +49 -0
- package/dist/internal/storage-coordinator.d.ts +7 -0
- package/dist/internal/storage-coordinator.d.ts.map +1 -1
- package/dist/internal/storage-coordinator.js +29 -4
- package/dist/persistence.d.ts +184 -0
- package/dist/persistence.d.ts.map +1 -0
- package/dist/playground/assets/{index-1dzluIcW.js → index-BVMOoiek.js} +44 -44
- package/dist/playground/assets/index-DYmP4b--.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/evals.md +46 -11
- package/docs/reference/cli.md +3 -0
- package/docs/reference/http-api.md +2 -1
- package/package.json +1 -1
- package/skills/evals/SKILL.md +10 -0
- package/src/bin/agent-serve.ts +38 -8
- package/src/channels/github/api.ts +12 -1
- package/src/channels/github/cursor-account.ts +51 -1
- package/src/channels/github/github-channel.ts +4 -1
- package/src/channels/slack/api.ts +57 -8
- package/src/channels/slack/eval-directive.ts +24 -1
- package/src/evals.ts +112 -25
- package/src/internal/cli-ax.ts +195 -16
- package/src/internal/cli-deploy.ts +140 -13
- package/src/internal/cli-mcp-oauth.ts +122 -3
- package/src/internal/cursor/backend-client.ts +104 -1
- package/src/internal/cursor/credentials.ts +10 -2
- package/src/internal/cursor-account-mcp-auth.ts +241 -0
- package/src/internal/deploy-client.ts +16 -0
- package/src/internal/eval-run-store.ts +97 -6
- package/src/internal/eval-runner.ts +129 -18
- package/src/internal/evals-client.ts +82 -0
- package/src/internal/http-channel.ts +87 -6
- package/src/internal/map-pool.ts +8 -1
- package/src/internal/mcp-host.ts +74 -16
- package/src/internal/run-client.ts +32 -1
- package/src/internal/server.ts +69 -2
- package/src/internal/session-engine.ts +58 -0
- package/src/internal/storage-coordinator.ts +32 -6
- package/src/types.ts +10 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.Er_R50vY.js +0 -1
- package/dist/internal/json-dir-store.js +0 -100
- package/dist/playground/assets/index-kZjeJs1v.css +0 -1
- /package/dist/docs/assets/{reference_cli.md.B74NrVzb.lean.js → reference_cli.md.DnYfr5V2.lean.js} +0 -0
|
@@ -331,10 +331,16 @@ export async function postMessage(args: {
|
|
|
331
331
|
text: string;
|
|
332
332
|
threadTs?: string;
|
|
333
333
|
blocks?: unknown[];
|
|
334
|
-
|
|
334
|
+
/**
|
|
335
|
+
* Skip `markdown_text` and post with plain `text` only (Deploy Buddy /
|
|
336
|
+
* host-report pattern). Prefer this for large structured bodies — Slack
|
|
337
|
+
* `markdown_text` hard-caps at 12k while `text` allows ~40k.
|
|
338
|
+
*/
|
|
339
|
+
plainText?: boolean;
|
|
340
|
+
}): Promise<{ ts: string; channel: string }> {
|
|
335
341
|
// Slack treats markdown_text as mutually exclusive with text and blocks
|
|
336
342
|
// (error: markdown_text_conflict). Prefer markdown when we have no
|
|
337
|
-
// blocks; with Block Kit, use text as body/fallback.
|
|
343
|
+
// blocks; with Block Kit or plainText, use text as body/fallback.
|
|
338
344
|
const base: Record<string, unknown> = {
|
|
339
345
|
channel: args.channel,
|
|
340
346
|
unfurl_links: false,
|
|
@@ -344,16 +350,17 @@ export async function postMessage(args: {
|
|
|
344
350
|
base.thread_ts = args.threadTs;
|
|
345
351
|
}
|
|
346
352
|
const hasBlocks = args.blocks !== undefined && args.blocks.length > 0;
|
|
353
|
+
const usePlainText = args.plainText === true || hasBlocks;
|
|
347
354
|
|
|
348
355
|
let response: Record<string, unknown>;
|
|
349
|
-
if (
|
|
356
|
+
if (usePlainText) {
|
|
350
357
|
response = await callSlackApi({
|
|
351
358
|
botToken: args.botToken,
|
|
352
359
|
operation: "chat.postMessage",
|
|
353
360
|
body: {
|
|
354
361
|
...base,
|
|
355
362
|
text: args.text,
|
|
356
|
-
blocks: args.blocks,
|
|
363
|
+
...(hasBlocks ? { blocks: args.blocks } : {}),
|
|
357
364
|
},
|
|
358
365
|
});
|
|
359
366
|
} else {
|
|
@@ -366,11 +373,14 @@ export async function postMessage(args: {
|
|
|
366
373
|
},
|
|
367
374
|
});
|
|
368
375
|
// Older workspaces / scopes may not accept markdown_text — retry with text.
|
|
376
|
+
// Also fall back when markdown_text hits its 12k limit (msg_too_long) while
|
|
377
|
+
// plain text still allows up to ~40k.
|
|
369
378
|
if (
|
|
370
379
|
response.ok !== true &&
|
|
371
380
|
(response.error === "markdown_text_conflict" ||
|
|
372
381
|
response.error === "invalid_arguments" ||
|
|
373
382
|
response.error === "invalid_blocks" ||
|
|
383
|
+
response.error === "msg_too_long" ||
|
|
374
384
|
SLACK_UNSUPPORTED_ERRORS.has(String(response.error ?? "")))
|
|
375
385
|
) {
|
|
376
386
|
response = await callSlackApi({
|
|
@@ -388,8 +398,43 @@ export async function postMessage(args: {
|
|
|
388
398
|
`Slack chat.postMessage failed: ${String(response.error ?? "unknown_error")}`
|
|
389
399
|
);
|
|
390
400
|
}
|
|
401
|
+
// Slack requires thread_ts as a string. A numeric ts (JSON number) silently
|
|
402
|
+
// drops threading server-side — never coerce via Number.
|
|
391
403
|
const ts = typeof response.ts === "string" ? response.ts : "";
|
|
392
|
-
|
|
404
|
+
if (ts === "") {
|
|
405
|
+
throw new Error(
|
|
406
|
+
`Slack chat.postMessage ok but missing string ts (got ${typeof response.ts})`
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
// When we asked to thread, confirm Slack honored it — but only when the
|
|
410
|
+
// response includes a thread_ts. Some proxy / customize-username payloads
|
|
411
|
+
// omit `message.thread_ts` even for successful threaded posts; treating
|
|
412
|
+
// that as failure retries (or deletes) replies and leaves empty threads.
|
|
413
|
+
const requestedThreadTs = args.threadTs;
|
|
414
|
+
if (requestedThreadTs !== undefined && requestedThreadTs !== "") {
|
|
415
|
+
const message = response.message;
|
|
416
|
+
if (
|
|
417
|
+
typeof message === "object" &&
|
|
418
|
+
message !== null &&
|
|
419
|
+
!Array.isArray(message)
|
|
420
|
+
) {
|
|
421
|
+
const applied =
|
|
422
|
+
typeof (message as { thread_ts?: unknown }).thread_ts === "string"
|
|
423
|
+
? (message as { thread_ts: string }).thread_ts
|
|
424
|
+
: "";
|
|
425
|
+
if (applied !== "" && applied !== requestedThreadTs) {
|
|
426
|
+
throw new Error(
|
|
427
|
+
`Slack chat.postMessage ignored thread_ts (requested ${requestedThreadTs}, got ${applied})`
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// Slack returns the resolved channel ID (C…) even when the request used a name.
|
|
433
|
+
const channel =
|
|
434
|
+
typeof response.channel === "string" && response.channel !== ""
|
|
435
|
+
? response.channel
|
|
436
|
+
: args.channel;
|
|
437
|
+
return { ts, channel };
|
|
393
438
|
}
|
|
394
439
|
|
|
395
440
|
/** Replace an existing message (e.g. strip Approve/Deny after a decision). */
|
|
@@ -399,20 +444,22 @@ export async function updateMessage(args: {
|
|
|
399
444
|
ts: string;
|
|
400
445
|
text: string;
|
|
401
446
|
blocks?: unknown[];
|
|
447
|
+
/** Skip `markdown_text` and update with plain `text` only. */
|
|
448
|
+
plainText?: boolean;
|
|
402
449
|
}): Promise<SoftSlackResult> {
|
|
403
450
|
const base: Record<string, unknown> = {
|
|
404
451
|
channel: args.channel,
|
|
405
452
|
ts: args.ts,
|
|
406
453
|
};
|
|
407
|
-
if (args.blocks !== undefined) {
|
|
408
|
-
// Blocks force the legacy text path (same as postMessage).
|
|
454
|
+
if (args.blocks !== undefined || args.plainText === true) {
|
|
455
|
+
// Blocks / plainText force the legacy text path (same as postMessage).
|
|
409
456
|
return callSlackApiSoft({
|
|
410
457
|
botToken: args.botToken,
|
|
411
458
|
operation: "chat.update",
|
|
412
459
|
body: {
|
|
413
460
|
...base,
|
|
414
461
|
text: args.text,
|
|
415
|
-
blocks: args.blocks,
|
|
462
|
+
...(args.blocks !== undefined ? { blocks: args.blocks } : {}),
|
|
416
463
|
},
|
|
417
464
|
});
|
|
418
465
|
}
|
|
@@ -425,11 +472,13 @@ export async function updateMessage(args: {
|
|
|
425
472
|
},
|
|
426
473
|
});
|
|
427
474
|
// Older workspaces / scopes may not accept markdown_text — retry with text.
|
|
475
|
+
// Also fall back when markdown_text hits its 12k limit (msg_too_long).
|
|
428
476
|
if (
|
|
429
477
|
!result.ok &&
|
|
430
478
|
(result.error === "markdown_text_conflict" ||
|
|
431
479
|
result.error === "invalid_arguments" ||
|
|
432
480
|
result.error === "invalid_blocks" ||
|
|
481
|
+
result.error === "msg_too_long" ||
|
|
433
482
|
SLACK_UNSUPPORTED_ERRORS.has(String(result.error ?? "")))
|
|
434
483
|
) {
|
|
435
484
|
result = await callSlackApiSoft({
|
|
@@ -26,6 +26,7 @@ export type SlackEvalDirective =
|
|
|
26
26
|
| { kind: "help" }
|
|
27
27
|
| { kind: "list" }
|
|
28
28
|
| { kind: "status" }
|
|
29
|
+
| { kind: "cancel"; runId: string }
|
|
29
30
|
| { kind: "run"; filterIds?: string[]; tags?: string[] };
|
|
30
31
|
|
|
31
32
|
function escapeRegExp(value: string): string {
|
|
@@ -89,6 +90,13 @@ export function parseSlackEvalDirective(
|
|
|
89
90
|
if (rest.length === 1 && head === "status") {
|
|
90
91
|
return { kind: "status" };
|
|
91
92
|
}
|
|
93
|
+
if (head === "cancel") {
|
|
94
|
+
const runId = rest[1];
|
|
95
|
+
if (rest.length !== 2 || runId === undefined || runId.length === 0) {
|
|
96
|
+
return { kind: "help" };
|
|
97
|
+
}
|
|
98
|
+
return { kind: "cancel", runId };
|
|
99
|
+
}
|
|
92
100
|
|
|
93
101
|
const filterIds: string[] = [];
|
|
94
102
|
const tags: string[] = [];
|
|
@@ -152,6 +160,7 @@ export function formatSlackEvalHelp(): string {
|
|
|
152
160
|
"• `eval --tag <tag>` — run evals with a tag (`-t` also works)",
|
|
153
161
|
"• `eval list` — list available evals",
|
|
154
162
|
"• `eval status` — show the active or latest eval run",
|
|
163
|
+
"• `eval cancel <evalId>` — cancel a running eval batch",
|
|
155
164
|
"• `eval help` — this message",
|
|
156
165
|
].join("\n");
|
|
157
166
|
}
|
|
@@ -291,6 +300,20 @@ export async function handleSlackEvalDirective(args: {
|
|
|
291
300
|
return;
|
|
292
301
|
}
|
|
293
302
|
|
|
303
|
+
if (directive.kind === "cancel") {
|
|
304
|
+
try {
|
|
305
|
+
const run = await evals.cancel(directive.runId);
|
|
306
|
+
await safePost(
|
|
307
|
+
binding,
|
|
308
|
+
`Cancelled Eval ID \`${run.runId}\`.\n${formatSlackEvalRunSummary(run)}`
|
|
309
|
+
);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
312
|
+
await safePost(binding, `Failed to cancel eval: ${message}`);
|
|
313
|
+
}
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
294
317
|
await safeTyping(binding, "Starting evals…");
|
|
295
318
|
let run: EvalRunSnapshot;
|
|
296
319
|
try {
|
|
@@ -323,7 +346,7 @@ export async function handleSlackEvalDirective(args: {
|
|
|
323
346
|
|
|
324
347
|
await safePost(
|
|
325
348
|
binding,
|
|
326
|
-
`Started
|
|
349
|
+
`Started Eval ID \`${run.runId}\` (${run.summary.total} case(s)). Cancel with \`eval cancel ${run.runId}\`.`
|
|
327
350
|
);
|
|
328
351
|
|
|
329
352
|
while (run.status === "running") {
|
package/src/evals.ts
CHANGED
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
|
|
49
49
|
import { normalizePositiveInt } from "./internal/bounded-int.js";
|
|
50
50
|
import { brandDefinition } from "./internal/brand.js";
|
|
51
|
-
import type { SessionEvent } from "./types.js";
|
|
51
|
+
import type { AgentCloudOptions, SessionEvent } from "./types.js";
|
|
52
52
|
|
|
53
53
|
export type Expectation =
|
|
54
54
|
| { kind: "includes"; value: string | RegExp }
|
|
@@ -93,6 +93,12 @@ export interface EvalSendOptions {
|
|
|
93
93
|
workspaceDir?: string;
|
|
94
94
|
/** Files seeded into the session workspace before the first turn. */
|
|
95
95
|
workspaceFiles?: Record<string, string>;
|
|
96
|
+
/**
|
|
97
|
+
* Per-session cloud options merged over the agent's static `cloud` config
|
|
98
|
+
* (create only). Use this to attach a pinned fixture repo for cloud evals
|
|
99
|
+
* without putting it on the agent's default `cloud.repos`.
|
|
100
|
+
*/
|
|
101
|
+
cloud?: AgentCloudOptions;
|
|
96
102
|
}
|
|
97
103
|
|
|
98
104
|
export interface EvalTestContext {
|
|
@@ -103,6 +109,14 @@ export interface EvalTestContext {
|
|
|
103
109
|
readonly sessionId: string | undefined;
|
|
104
110
|
/** All events captured so far across turns. */
|
|
105
111
|
readonly events: SessionEvent[];
|
|
112
|
+
/**
|
|
113
|
+
* 1-based index of this run when {@link EvalConfig.iterations} /
|
|
114
|
+
* {@link EvalDatapoint.iterations} expands a datapoint. Always `1` when
|
|
115
|
+
* iterations is unset or `1`.
|
|
116
|
+
*/
|
|
117
|
+
readonly iteration: number;
|
|
118
|
+
/** Total iterations for this datapoint (default `1`). */
|
|
119
|
+
readonly iterations: number;
|
|
106
120
|
/** Gate: run did not fail. */
|
|
107
121
|
succeeded(): void;
|
|
108
122
|
/** Gate: at least one completed call to `name`. */
|
|
@@ -115,8 +129,17 @@ export interface EvalTestContext {
|
|
|
115
129
|
check(value: unknown, assertion: Expectation): void;
|
|
116
130
|
/** Debug line (surfaced under `agent-serve eval --verbose`). */
|
|
117
131
|
log(message: string): void;
|
|
132
|
+
/**
|
|
133
|
+
* Structured score for the playground (and CLI). Values appear on the
|
|
134
|
+
* case card; agents can further customize rendering via
|
|
135
|
+
* `agent/playground/evals.tsx`.
|
|
136
|
+
*/
|
|
137
|
+
metric(name: string, value: string | number): void;
|
|
118
138
|
}
|
|
119
139
|
|
|
140
|
+
/** Hard ceiling for {@link EvalConfig.iterations} / case `iterations`. */
|
|
141
|
+
export const EVAL_MAX_ITERATIONS = 100;
|
|
142
|
+
|
|
120
143
|
/** One runnable datapoint inside a multi-case {@link defineEval} file. */
|
|
121
144
|
export interface EvalDatapoint {
|
|
122
145
|
/**
|
|
@@ -128,6 +151,13 @@ export interface EvalDatapoint {
|
|
|
128
151
|
description?: string;
|
|
129
152
|
tags?: string[];
|
|
130
153
|
timeoutMs?: number;
|
|
154
|
+
/**
|
|
155
|
+
* How many times to run this datapoint (default `1`, or the file-level
|
|
156
|
+
* {@link EvalConfig.iterations}). When greater than 1, discovery expands
|
|
157
|
+
* to `<fileId>/<id>/1` … `<fileId>/<id>/<n>` so each iteration is its own
|
|
158
|
+
* concurrent case.
|
|
159
|
+
*/
|
|
160
|
+
iterations?: number;
|
|
131
161
|
test: (t: EvalTestContext) => Promise<void>;
|
|
132
162
|
}
|
|
133
163
|
|
|
@@ -139,6 +169,11 @@ export interface EvalConfig {
|
|
|
139
169
|
description?: string;
|
|
140
170
|
tags?: string[];
|
|
141
171
|
timeoutMs?: number;
|
|
172
|
+
/**
|
|
173
|
+
* Default iterations for every datapoint in this file (default `1`).
|
|
174
|
+
* Case-level {@link EvalDatapoint.iterations} overrides this.
|
|
175
|
+
*/
|
|
176
|
+
iterations?: number;
|
|
142
177
|
/** Single-datapoint shorthand. Case id = file path under `evals/`. */
|
|
143
178
|
test?: (t: EvalTestContext) => Promise<void>;
|
|
144
179
|
/** Multiple datapoints in this file. Each id = `<fileId>/<case.id>`. */
|
|
@@ -150,6 +185,10 @@ export interface EvalCaseDefinition {
|
|
|
150
185
|
description?: string;
|
|
151
186
|
tags?: string[];
|
|
152
187
|
timeoutMs?: number;
|
|
188
|
+
/** Total iterations configured for the authored datapoint. */
|
|
189
|
+
iterations?: number;
|
|
190
|
+
/** 1-based iteration index for this expanded runnable case. */
|
|
191
|
+
iteration?: number;
|
|
153
192
|
test: (t: EvalTestContext) => Promise<void>;
|
|
154
193
|
readonly __agentServe: "eval";
|
|
155
194
|
}
|
|
@@ -161,6 +200,13 @@ export type EvalDefinition = EvalConfig & { readonly __agentServe: "eval" };
|
|
|
161
200
|
* `evals/weather.eval.ts` → `weather`. With a single `test`, that is also
|
|
162
201
|
* the case id. With `cases`, each datapoint is `<fileId>/<case.id>`.
|
|
163
202
|
*/
|
|
203
|
+
export function normalizeEvalIterations(value: unknown): number {
|
|
204
|
+
return normalizePositiveInt(value, {
|
|
205
|
+
label: "defineEval: iterations",
|
|
206
|
+
max: EVAL_MAX_ITERATIONS,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
164
210
|
export function defineEval(config: EvalConfig): EvalDefinition {
|
|
165
211
|
const hasTest = typeof config.test === "function";
|
|
166
212
|
const hasCases = config.cases !== undefined;
|
|
@@ -170,6 +216,9 @@ export function defineEval(config: EvalConfig): EvalDefinition {
|
|
|
170
216
|
if (!hasTest && !hasCases) {
|
|
171
217
|
throw new Error("defineEval requires test(t) or a non-empty cases array");
|
|
172
218
|
}
|
|
219
|
+
if (config.iterations !== undefined) {
|
|
220
|
+
normalizeEvalIterations(config.iterations);
|
|
221
|
+
}
|
|
173
222
|
if (hasCases) {
|
|
174
223
|
if (!Array.isArray(config.cases) || config.cases.length === 0) {
|
|
175
224
|
throw new Error("defineEval: cases must be a non-empty array");
|
|
@@ -188,6 +237,9 @@ export function defineEval(config: EvalConfig): EvalDefinition {
|
|
|
188
237
|
`defineEval: cases[${JSON.stringify(datapoint.id)}] requires test(t)`
|
|
189
238
|
);
|
|
190
239
|
}
|
|
240
|
+
if (datapoint.iterations !== undefined) {
|
|
241
|
+
normalizeEvalIterations(datapoint.iterations);
|
|
242
|
+
}
|
|
191
243
|
}
|
|
192
244
|
}
|
|
193
245
|
return brandDefinition("eval", config);
|
|
@@ -207,44 +259,75 @@ function validateDatapointId(id: unknown): asserts id is string {
|
|
|
207
259
|
/**
|
|
208
260
|
* Expand a file-level definition into runnable datapoints.
|
|
209
261
|
* Exported for tests and tooling.
|
|
262
|
+
*
|
|
263
|
+
* When `iterations` > 1, each authored datapoint expands to
|
|
264
|
+
* `<fileId>/<case.id>/1` … `<fileId>/<case.id>/<n>` (single-test files
|
|
265
|
+
* expand to `<fileId>/1` … `<fileId>/<n>`).
|
|
210
266
|
*/
|
|
211
267
|
export function expandEvalDefinition(
|
|
212
268
|
fileId: string,
|
|
213
269
|
definition: EvalDefinition
|
|
214
270
|
): Array<{ id: string; definition: EvalCaseDefinition }> {
|
|
215
271
|
if (definition.cases !== undefined) {
|
|
216
|
-
return definition.cases.
|
|
272
|
+
return definition.cases.flatMap((datapoint) => {
|
|
217
273
|
const description = datapoint.description ?? definition.description;
|
|
218
274
|
const timeoutMs = datapoint.timeoutMs ?? definition.timeoutMs;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
275
|
+
const iterations = normalizeEvalIterations(
|
|
276
|
+
datapoint.iterations ?? definition.iterations ?? 1
|
|
277
|
+
);
|
|
278
|
+
return expandIterations({
|
|
279
|
+
baseId: `${fileId}/${datapoint.id}`,
|
|
280
|
+
description,
|
|
281
|
+
tags: datapoint.tags ?? definition.tags,
|
|
282
|
+
timeoutMs,
|
|
283
|
+
iterations,
|
|
284
|
+
test: datapoint.test,
|
|
285
|
+
});
|
|
228
286
|
});
|
|
229
287
|
}
|
|
230
288
|
if (typeof definition.test !== "function") {
|
|
231
289
|
throw new Error(`defineEval(${fileId}): missing test(t)`);
|
|
232
290
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
291
|
+
const iterations = normalizeEvalIterations(definition.iterations ?? 1);
|
|
292
|
+
return expandIterations({
|
|
293
|
+
baseId: fileId,
|
|
294
|
+
description: definition.description,
|
|
295
|
+
tags: definition.tags,
|
|
296
|
+
timeoutMs: definition.timeoutMs,
|
|
297
|
+
iterations,
|
|
298
|
+
test: definition.test,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function expandIterations(args: {
|
|
303
|
+
baseId: string;
|
|
304
|
+
description?: string;
|
|
305
|
+
tags?: string[];
|
|
306
|
+
timeoutMs?: number;
|
|
307
|
+
iterations: number;
|
|
308
|
+
test: (t: EvalTestContext) => Promise<void>;
|
|
309
|
+
}): Array<{ id: string; definition: EvalCaseDefinition }> {
|
|
310
|
+
const out: Array<{ id: string; definition: EvalCaseDefinition }> = [];
|
|
311
|
+
for (let iteration = 1; iteration <= args.iterations; iteration++) {
|
|
312
|
+
const id =
|
|
313
|
+
args.iterations === 1 ? args.baseId : `${args.baseId}/${iteration}`;
|
|
314
|
+
const description =
|
|
315
|
+
args.iterations === 1 || args.description === undefined
|
|
316
|
+
? args.description
|
|
317
|
+
: `${args.description} (iteration ${iteration}/${args.iterations})`;
|
|
318
|
+
out.push({
|
|
319
|
+
id,
|
|
236
320
|
definition: brandDefinition("eval", {
|
|
237
|
-
...(
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
: {}),
|
|
244
|
-
test: definition.test,
|
|
321
|
+
...(description !== undefined ? { description } : {}),
|
|
322
|
+
...(args.tags !== undefined ? { tags: args.tags } : {}),
|
|
323
|
+
...(args.timeoutMs !== undefined ? { timeoutMs: args.timeoutMs } : {}),
|
|
324
|
+
iterations: args.iterations,
|
|
325
|
+
iteration,
|
|
326
|
+
test: args.test,
|
|
245
327
|
}),
|
|
246
|
-
}
|
|
247
|
-
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
return out;
|
|
248
331
|
}
|
|
249
332
|
|
|
250
333
|
/**
|
|
@@ -272,7 +355,7 @@ export const EVAL_DEFAULT_MAX_PLAYGROUND_RUNS = 20;
|
|
|
272
355
|
export const EVAL_MAX_PLAYGROUND_RUNS = 500;
|
|
273
356
|
|
|
274
357
|
export type EvalCaseRunStatus = "pending" | "running" | "done";
|
|
275
|
-
export type EvalBatchStatus = "running" | "completed" | "failed";
|
|
358
|
+
export type EvalBatchStatus = "running" | "completed" | "failed" | "cancelled";
|
|
276
359
|
|
|
277
360
|
/** One case inside a playground / HTTP eval batch snapshot. */
|
|
278
361
|
export interface EvalCaseSnapshot {
|
|
@@ -287,6 +370,8 @@ export interface EvalCaseSnapshot {
|
|
|
287
370
|
durationMs?: number;
|
|
288
371
|
error?: string;
|
|
289
372
|
logs?: string[];
|
|
373
|
+
/** Structured scores from {@link EvalTestContext.metric}. */
|
|
374
|
+
metrics?: Record<string, string | number>;
|
|
290
375
|
inputs?: string[];
|
|
291
376
|
finalText?: string;
|
|
292
377
|
tools?: string[];
|
|
@@ -356,6 +441,8 @@ export interface HostEvalsApi {
|
|
|
356
441
|
activeRunId?: string;
|
|
357
442
|
}>;
|
|
358
443
|
start(input?: HostStartEvalRunInput): Promise<EvalRunSnapshot>;
|
|
444
|
+
/** Cancel a running batch by Eval ID (`runId`). */
|
|
445
|
+
cancel(runId: string): Promise<EvalRunSnapshot>;
|
|
359
446
|
}
|
|
360
447
|
|
|
361
448
|
export interface EvalConfigFile {
|