@cursor/july 0.1.12 → 0.1.14
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/github/instrument.d.ts +20 -0
- package/dist/channels/github/instrument.d.ts.map +1 -0
- 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/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.Dh_JkJjI.js → app.CKFFLL4Z.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DGWkvXJc.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.CptQNLkR.js → VPLocalSearchBox.BXk19JBN.js} +1 -1
- package/dist/docs/assets/chunks/{theme.DWflH-LQ.js → theme.BXSPBMyn.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 +41 -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 +163 -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 +87 -8
- package/dist/internal/eval-runner.d.ts +12 -0
- package/dist/internal/eval-runner.d.ts.map +1 -1
- package/dist/internal/eval-runner.js +97 -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 +43 -0
- 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/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/playground/assets/index-CuXb4j4V.css +1 -0
- package/dist/playground/assets/{index-1dzluIcW.js → index-DeyYpOit.js} +46 -46
- 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/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 +104 -25
- package/src/internal/cli-ax.ts +192 -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 +89 -6
- package/src/internal/eval-runner.ts +113 -18
- package/src/internal/evals-client.ts +81 -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/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`. */
|
|
@@ -117,6 +131,9 @@ export interface EvalTestContext {
|
|
|
117
131
|
log(message: string): void;
|
|
118
132
|
}
|
|
119
133
|
|
|
134
|
+
/** Hard ceiling for {@link EvalConfig.iterations} / case `iterations`. */
|
|
135
|
+
export const EVAL_MAX_ITERATIONS = 100;
|
|
136
|
+
|
|
120
137
|
/** One runnable datapoint inside a multi-case {@link defineEval} file. */
|
|
121
138
|
export interface EvalDatapoint {
|
|
122
139
|
/**
|
|
@@ -128,6 +145,13 @@ export interface EvalDatapoint {
|
|
|
128
145
|
description?: string;
|
|
129
146
|
tags?: string[];
|
|
130
147
|
timeoutMs?: number;
|
|
148
|
+
/**
|
|
149
|
+
* How many times to run this datapoint (default `1`, or the file-level
|
|
150
|
+
* {@link EvalConfig.iterations}). When greater than 1, discovery expands
|
|
151
|
+
* to `<fileId>/<id>/1` … `<fileId>/<id>/<n>` so each iteration is its own
|
|
152
|
+
* concurrent case.
|
|
153
|
+
*/
|
|
154
|
+
iterations?: number;
|
|
131
155
|
test: (t: EvalTestContext) => Promise<void>;
|
|
132
156
|
}
|
|
133
157
|
|
|
@@ -139,6 +163,11 @@ export interface EvalConfig {
|
|
|
139
163
|
description?: string;
|
|
140
164
|
tags?: string[];
|
|
141
165
|
timeoutMs?: number;
|
|
166
|
+
/**
|
|
167
|
+
* Default iterations for every datapoint in this file (default `1`).
|
|
168
|
+
* Case-level {@link EvalDatapoint.iterations} overrides this.
|
|
169
|
+
*/
|
|
170
|
+
iterations?: number;
|
|
142
171
|
/** Single-datapoint shorthand. Case id = file path under `evals/`. */
|
|
143
172
|
test?: (t: EvalTestContext) => Promise<void>;
|
|
144
173
|
/** Multiple datapoints in this file. Each id = `<fileId>/<case.id>`. */
|
|
@@ -150,6 +179,10 @@ export interface EvalCaseDefinition {
|
|
|
150
179
|
description?: string;
|
|
151
180
|
tags?: string[];
|
|
152
181
|
timeoutMs?: number;
|
|
182
|
+
/** Total iterations configured for the authored datapoint. */
|
|
183
|
+
iterations?: number;
|
|
184
|
+
/** 1-based iteration index for this expanded runnable case. */
|
|
185
|
+
iteration?: number;
|
|
153
186
|
test: (t: EvalTestContext) => Promise<void>;
|
|
154
187
|
readonly __agentServe: "eval";
|
|
155
188
|
}
|
|
@@ -161,6 +194,13 @@ export type EvalDefinition = EvalConfig & { readonly __agentServe: "eval" };
|
|
|
161
194
|
* `evals/weather.eval.ts` → `weather`. With a single `test`, that is also
|
|
162
195
|
* the case id. With `cases`, each datapoint is `<fileId>/<case.id>`.
|
|
163
196
|
*/
|
|
197
|
+
export function normalizeEvalIterations(value: unknown): number {
|
|
198
|
+
return normalizePositiveInt(value, {
|
|
199
|
+
label: "defineEval: iterations",
|
|
200
|
+
max: EVAL_MAX_ITERATIONS,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
164
204
|
export function defineEval(config: EvalConfig): EvalDefinition {
|
|
165
205
|
const hasTest = typeof config.test === "function";
|
|
166
206
|
const hasCases = config.cases !== undefined;
|
|
@@ -170,6 +210,9 @@ export function defineEval(config: EvalConfig): EvalDefinition {
|
|
|
170
210
|
if (!hasTest && !hasCases) {
|
|
171
211
|
throw new Error("defineEval requires test(t) or a non-empty cases array");
|
|
172
212
|
}
|
|
213
|
+
if (config.iterations !== undefined) {
|
|
214
|
+
normalizeEvalIterations(config.iterations);
|
|
215
|
+
}
|
|
173
216
|
if (hasCases) {
|
|
174
217
|
if (!Array.isArray(config.cases) || config.cases.length === 0) {
|
|
175
218
|
throw new Error("defineEval: cases must be a non-empty array");
|
|
@@ -188,6 +231,9 @@ export function defineEval(config: EvalConfig): EvalDefinition {
|
|
|
188
231
|
`defineEval: cases[${JSON.stringify(datapoint.id)}] requires test(t)`
|
|
189
232
|
);
|
|
190
233
|
}
|
|
234
|
+
if (datapoint.iterations !== undefined) {
|
|
235
|
+
normalizeEvalIterations(datapoint.iterations);
|
|
236
|
+
}
|
|
191
237
|
}
|
|
192
238
|
}
|
|
193
239
|
return brandDefinition("eval", config);
|
|
@@ -207,44 +253,75 @@ function validateDatapointId(id: unknown): asserts id is string {
|
|
|
207
253
|
/**
|
|
208
254
|
* Expand a file-level definition into runnable datapoints.
|
|
209
255
|
* Exported for tests and tooling.
|
|
256
|
+
*
|
|
257
|
+
* When `iterations` > 1, each authored datapoint expands to
|
|
258
|
+
* `<fileId>/<case.id>/1` … `<fileId>/<case.id>/<n>` (single-test files
|
|
259
|
+
* expand to `<fileId>/1` … `<fileId>/<n>`).
|
|
210
260
|
*/
|
|
211
261
|
export function expandEvalDefinition(
|
|
212
262
|
fileId: string,
|
|
213
263
|
definition: EvalDefinition
|
|
214
264
|
): Array<{ id: string; definition: EvalCaseDefinition }> {
|
|
215
265
|
if (definition.cases !== undefined) {
|
|
216
|
-
return definition.cases.
|
|
266
|
+
return definition.cases.flatMap((datapoint) => {
|
|
217
267
|
const description = datapoint.description ?? definition.description;
|
|
218
268
|
const timeoutMs = datapoint.timeoutMs ?? definition.timeoutMs;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
269
|
+
const iterations = normalizeEvalIterations(
|
|
270
|
+
datapoint.iterations ?? definition.iterations ?? 1
|
|
271
|
+
);
|
|
272
|
+
return expandIterations({
|
|
273
|
+
baseId: `${fileId}/${datapoint.id}`,
|
|
274
|
+
description,
|
|
275
|
+
tags: datapoint.tags ?? definition.tags,
|
|
276
|
+
timeoutMs,
|
|
277
|
+
iterations,
|
|
278
|
+
test: datapoint.test,
|
|
279
|
+
});
|
|
228
280
|
});
|
|
229
281
|
}
|
|
230
282
|
if (typeof definition.test !== "function") {
|
|
231
283
|
throw new Error(`defineEval(${fileId}): missing test(t)`);
|
|
232
284
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
285
|
+
const iterations = normalizeEvalIterations(definition.iterations ?? 1);
|
|
286
|
+
return expandIterations({
|
|
287
|
+
baseId: fileId,
|
|
288
|
+
description: definition.description,
|
|
289
|
+
tags: definition.tags,
|
|
290
|
+
timeoutMs: definition.timeoutMs,
|
|
291
|
+
iterations,
|
|
292
|
+
test: definition.test,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function expandIterations(args: {
|
|
297
|
+
baseId: string;
|
|
298
|
+
description?: string;
|
|
299
|
+
tags?: string[];
|
|
300
|
+
timeoutMs?: number;
|
|
301
|
+
iterations: number;
|
|
302
|
+
test: (t: EvalTestContext) => Promise<void>;
|
|
303
|
+
}): Array<{ id: string; definition: EvalCaseDefinition }> {
|
|
304
|
+
const out: Array<{ id: string; definition: EvalCaseDefinition }> = [];
|
|
305
|
+
for (let iteration = 1; iteration <= args.iterations; iteration++) {
|
|
306
|
+
const id =
|
|
307
|
+
args.iterations === 1 ? args.baseId : `${args.baseId}/${iteration}`;
|
|
308
|
+
const description =
|
|
309
|
+
args.iterations === 1 || args.description === undefined
|
|
310
|
+
? args.description
|
|
311
|
+
: `${args.description} (iteration ${iteration}/${args.iterations})`;
|
|
312
|
+
out.push({
|
|
313
|
+
id,
|
|
236
314
|
definition: brandDefinition("eval", {
|
|
237
|
-
...(
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
: {}),
|
|
244
|
-
test: definition.test,
|
|
315
|
+
...(description !== undefined ? { description } : {}),
|
|
316
|
+
...(args.tags !== undefined ? { tags: args.tags } : {}),
|
|
317
|
+
...(args.timeoutMs !== undefined ? { timeoutMs: args.timeoutMs } : {}),
|
|
318
|
+
iterations: args.iterations,
|
|
319
|
+
iteration,
|
|
320
|
+
test: args.test,
|
|
245
321
|
}),
|
|
246
|
-
}
|
|
247
|
-
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
return out;
|
|
248
325
|
}
|
|
249
326
|
|
|
250
327
|
/**
|
|
@@ -272,7 +349,7 @@ export const EVAL_DEFAULT_MAX_PLAYGROUND_RUNS = 20;
|
|
|
272
349
|
export const EVAL_MAX_PLAYGROUND_RUNS = 500;
|
|
273
350
|
|
|
274
351
|
export type EvalCaseRunStatus = "pending" | "running" | "done";
|
|
275
|
-
export type EvalBatchStatus = "running" | "completed" | "failed";
|
|
352
|
+
export type EvalBatchStatus = "running" | "completed" | "failed" | "cancelled";
|
|
276
353
|
|
|
277
354
|
/** One case inside a playground / HTTP eval batch snapshot. */
|
|
278
355
|
export interface EvalCaseSnapshot {
|
|
@@ -356,6 +433,8 @@ export interface HostEvalsApi {
|
|
|
356
433
|
activeRunId?: string;
|
|
357
434
|
}>;
|
|
358
435
|
start(input?: HostStartEvalRunInput): Promise<EvalRunSnapshot>;
|
|
436
|
+
/** Cancel a running batch by Eval ID (`runId`). */
|
|
437
|
+
cancel(runId: string): Promise<EvalRunSnapshot>;
|
|
359
438
|
}
|
|
360
439
|
|
|
361
440
|
export interface EvalConfigFile {
|