@cursor/july 0.1.5 → 0.1.7
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/ab.d.ts +8 -95
- package/dist/ab.d.ts.map +1 -1
- package/dist/ab.js +9 -150
- package/dist/bin/agent-serve.js +41 -8
- 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 +4 -4
- package/dist/docs/assets/{app.DabPG-io.js → app.COTN7wgo.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.B7UcKvIn.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.jmyr0bU0.js → VPLocalSearchBox.BW3TBdT0.js} +1 -1
- package/dist/docs/assets/chunks/{theme.DysN9-VN.js → theme.BEJW0vE7.js} +2 -2
- package/dist/docs/assets/deployment.md.BtfEsc9S.js +55 -0
- package/dist/docs/assets/deployment.md.BtfEsc9S.lean.js +1 -0
- package/dist/docs/assets/example-agents_approval-buddy.md.8R5phXb5.js +10 -0
- package/dist/docs/assets/example-agents_approval-buddy.md.8R5phXb5.lean.js +1 -0
- package/dist/docs/assets/example-agents_benny.md.B0gjhI-p.js +7 -0
- package/dist/docs/assets/example-agents_benny.md.B0gjhI-p.lean.js +1 -0
- package/dist/docs/assets/example-agents_bugbot.md.DelIdhxB.js +11 -0
- package/dist/docs/assets/example-agents_bugbot.md.DelIdhxB.lean.js +1 -0
- package/dist/docs/assets/example-agents_codebase-wiki.md.DC6sgwn0.js +8 -0
- package/dist/docs/assets/example-agents_codebase-wiki.md.DC6sgwn0.lean.js +1 -0
- package/dist/docs/assets/example-agents_codeowners-review.md.Ku_tG2RY.js +8 -0
- package/dist/docs/assets/example-agents_codeowners-review.md.Ku_tG2RY.lean.js +1 -0
- package/dist/docs/assets/example-agents_concierge.md.4rQTSMXt.js +23 -0
- package/dist/docs/assets/example-agents_concierge.md.4rQTSMXt.lean.js +1 -0
- package/dist/docs/assets/example-agents_fsd.md.CzgUrDfi.js +15 -0
- package/dist/docs/assets/example-agents_fsd.md.CzgUrDfi.lean.js +1 -0
- package/dist/docs/assets/example-agents_index.md.CRqJlnIf.js +2 -0
- package/dist/docs/assets/example-agents_index.md.CRqJlnIf.lean.js +1 -0
- package/dist/docs/assets/example-agents_knowledge-base.md.BPJiVueF.js +11 -0
- package/dist/docs/assets/example-agents_knowledge-base.md.BPJiVueF.lean.js +1 -0
- package/dist/docs/assets/example-agents_security-reviewer.md.D2rtwDTO.js +19 -0
- package/dist/docs/assets/example-agents_security-reviewer.md.D2rtwDTO.lean.js +1 -0
- package/dist/docs/assets/example-agents_slack-agent.md.buLbgvBf.js +5 -0
- package/dist/docs/assets/example-agents_slack-agent.md.buLbgvBf.lean.js +1 -0
- package/dist/docs/assets/example-agents_weather-agent.md.C9Qv-W0o.js +24 -0
- package/dist/docs/assets/example-agents_weather-agent.md.C9Qv-W0o.lean.js +1 -0
- package/dist/docs/assets/index.md.COiu-1jL.js +20 -0
- package/dist/docs/assets/{index.md.Cylk70gg.lean.js → index.md.COiu-1jL.lean.js} +1 -1
- package/dist/docs/assets/reference_cli.md.D189RBCH.js +60 -0
- package/dist/docs/assets/reference_cli.md.D189RBCH.lean.js +1 -0
- package/dist/docs/building-with-agents.html +4 -4
- package/dist/docs/concepts.html +4 -4
- package/dist/docs/deployment.html +58 -17
- package/dist/docs/evals.html +4 -4
- package/dist/docs/example-agents/approval-buddy.html +34 -0
- package/dist/docs/example-agents/benny.html +31 -0
- package/dist/docs/example-agents/bugbot.html +35 -0
- package/dist/docs/example-agents/codebase-wiki.html +32 -0
- package/dist/docs/example-agents/codeowners-review.html +32 -0
- package/dist/docs/example-agents/concierge.html +47 -0
- package/dist/docs/example-agents/fsd.html +39 -0
- package/dist/docs/example-agents/index.html +26 -0
- package/dist/docs/example-agents/knowledge-base.html +35 -0
- package/dist/docs/example-agents/security-reviewer.html +43 -0
- package/dist/docs/example-agents/slack-agent.html +29 -0
- package/dist/docs/example-agents/weather-agent.html +48 -0
- package/dist/docs/guides/agent-to-agent.html +4 -4
- package/dist/docs/guides/cloud-runtime.html +5 -5
- package/dist/docs/guides/github.html +4 -4
- package/dist/docs/guides/human-in-the-loop.html +4 -4
- package/dist/docs/guides/slack.html +4 -4
- package/dist/docs/guides/webhooks.html +4 -4
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +4 -4
- package/dist/docs/index.html +7 -7
- package/dist/docs/quickstart.html +4 -4
- package/dist/docs/reference/agent-config.html +4 -4
- package/dist/docs/reference/channels.html +4 -4
- package/dist/docs/reference/cli.html +52 -30
- package/dist/docs/reference/connections.html +4 -4
- package/dist/docs/reference/hooks.html +4 -4
- package/dist/docs/reference/http-api.html +4 -4
- package/dist/docs/reference/instructions.html +4 -4
- package/dist/docs/reference/playground.html +4 -4
- package/dist/docs/reference/project-layout.html +4 -4
- package/dist/docs/reference/schedules.html +4 -4
- package/dist/docs/reference/sessions.html +4 -4
- package/dist/docs/reference/skills.html +4 -4
- package/dist/docs/reference/subagents.html +4 -4
- package/dist/docs/reference/tools.html +4 -4
- package/dist/docs/scaffolding-agents.html +4 -4
- package/dist/docs/storage.html +4 -4
- package/dist/docs/troubleshooting.html +4 -4
- package/dist/evals.d.ts +5 -62
- package/dist/evals.d.ts.map +1 -1
- package/dist/evals.js +3 -66
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/internal/ab-collector.d.ts +7 -5
- package/dist/internal/ab-collector.d.ts.map +1 -1
- package/dist/internal/ab-collector.js +3 -14
- package/dist/internal/ab-snapshot.d.ts +2 -4
- package/dist/internal/ab-snapshot.d.ts.map +1 -1
- package/dist/internal/cli-ax.d.ts +33 -5
- package/dist/internal/cli-ax.d.ts.map +1 -1
- package/dist/internal/cli-ax.js +428 -87
- package/dist/internal/cli-deploy.js +1 -1
- package/dist/internal/discovery.js +3 -3
- package/dist/internal/eval-run-store.d.ts +35 -30
- package/dist/internal/eval-run-store.d.ts.map +1 -1
- package/dist/internal/eval-run-store.js +88 -100
- package/dist/internal/evals-client.d.ts +96 -0
- package/dist/internal/evals-client.d.ts.map +1 -0
- package/dist/internal/evals-client.js +262 -0
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +1 -0
- package/dist/internal/persistence-coordinator.d.ts +127 -0
- package/dist/internal/persistence-coordinator.d.ts.map +1 -0
- package/dist/internal/playground-proxy.d.ts +5 -5
- package/dist/internal/playground-proxy.js +3 -3
- package/dist/internal/resolve-prod-target.d.ts +30 -0
- package/dist/internal/resolve-prod-target.d.ts.map +1 -1
- package/dist/internal/resolve-prod-target.js +74 -2
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +16 -5
- package/dist/internal/session-engine.d.ts +1 -2
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +14 -31
- package/dist/internal/storage-coordinator.d.ts +16 -15
- package/dist/internal/storage-coordinator.d.ts.map +1 -1
- package/dist/internal/storage-coordinator.js +73 -80
- package/dist/persistence.d.ts +184 -0
- package/dist/persistence.d.ts.map +1 -0
- package/dist/playground/assets/cursor-icons-16-CQ50JpfO.woff2 +0 -0
- package/dist/playground/assets/index-72vCOBWO.js +86 -0
- package/dist/playground/assets/index-BjnMwYoR.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage.d.ts +51 -10
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +27 -10
- package/docs/README.md +34 -5
- package/docs/deployment.md +352 -149
- package/docs/example-agents/approval-buddy.md +270 -0
- package/docs/example-agents/benny.md +186 -0
- package/docs/example-agents/bugbot.md +231 -0
- package/docs/example-agents/codebase-wiki.md +174 -0
- package/docs/example-agents/codeowners-review.md +195 -0
- package/docs/example-agents/concierge.md +205 -0
- package/docs/example-agents/fsd.md +330 -0
- package/docs/example-agents/index.md +102 -0
- package/docs/example-agents/knowledge-base.md +171 -0
- package/docs/example-agents/security-reviewer.md +296 -0
- package/docs/example-agents/slack-agent.md +146 -0
- package/docs/example-agents/weather-agent.md +302 -0
- package/docs/reference/cli.md +546 -147
- package/package.json +1 -1
- package/src/ab.ts +9 -261
- package/src/bin/agent-serve.ts +46 -7
- package/src/evals.ts +5 -119
- package/src/index.ts +2 -0
- package/src/internal/ab-collector.ts +12 -22
- package/src/internal/ab-snapshot.ts +2 -4
- package/src/internal/cli-ax.ts +551 -104
- package/src/internal/cli-deploy.ts +1 -1
- package/src/internal/discovery.ts +2 -2
- package/src/internal/eval-run-store.ts +91 -100
- package/src/internal/evals-client.ts +431 -0
- package/src/internal/init-project.ts +1 -0
- package/src/internal/playground-proxy.ts +5 -5
- package/src/internal/resolve-prod-target.ts +101 -3
- package/src/internal/server.ts +17 -3
- package/src/internal/session-engine.ts +9 -29
- package/src/internal/storage-coordinator.ts +109 -101
- package/src/storage.ts +79 -14
- package/dist/docs/assets/chunks/@localSearchIndexroot.QwK5BtEH.js +0 -1
- package/dist/docs/assets/deployment.md.DTKwE15Z.js +0 -14
- package/dist/docs/assets/deployment.md.DTKwE15Z.lean.js +0 -1
- package/dist/docs/assets/index.md.Cylk70gg.js +0 -20
- package/dist/docs/assets/reference_cli.md.Bv6pOxcF.js +0 -38
- package/dist/docs/assets/reference_cli.md.Bv6pOxcF.lean.js +0 -1
- package/dist/internal/json-dir-store.js +0 -100
- package/dist/playground/assets/cursor-icons-outline-BxTT_FVJ.woff2 +0 -0
- package/dist/playground/assets/index-BEauYlII.css +0 -1
- package/dist/playground/assets/index-BtM0wEGg.js +0 -319
- package/src/internal/json-dir-store.ts +0 -109
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for playground eval batches (`/v1/dev/evals/runs`).
|
|
3
|
+
*
|
|
4
|
+
* Used by `agentkit eval --prod` (or `--url`) so the batch runs on the serve
|
|
5
|
+
* process and is mirrored to `defineStorage` when an `evals` table is
|
|
6
|
+
* configured. Without those flags, eval keeps the in-process ephemeral path
|
|
7
|
+
* in `cli-ax.ts`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { EvalRunSnapshot } from "../evals.js";
|
|
11
|
+
import { matchesEvalIdFilter } from "../evals.js";
|
|
12
|
+
import type { EvalRunResult } from "./eval-runner.js";
|
|
13
|
+
import { mergeRequestHeaders, type RequestAuth } from "./request-headers.js";
|
|
14
|
+
|
|
15
|
+
const DEFAULT_POLL_MS = 1_000;
|
|
16
|
+
|
|
17
|
+
export type RemoteEvalErrorCode =
|
|
18
|
+
| "no_matching_evals"
|
|
19
|
+
| "eval_run_in_progress"
|
|
20
|
+
| "unknown_eval_run"
|
|
21
|
+
| "start_failed"
|
|
22
|
+
| "poll_failed"
|
|
23
|
+
| "aborted";
|
|
24
|
+
|
|
25
|
+
export class RemoteEvalError extends Error {
|
|
26
|
+
constructor(
|
|
27
|
+
message: string,
|
|
28
|
+
readonly status?: number,
|
|
29
|
+
readonly code?: RemoteEvalErrorCode,
|
|
30
|
+
readonly activeRunId?: string
|
|
31
|
+
) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = "RemoteEvalError";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface RemoteEvalListItem {
|
|
38
|
+
id: string;
|
|
39
|
+
fileId: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
tags?: string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface RunRemoteEvalsOptions extends RequestAuth {
|
|
45
|
+
baseUrl: string;
|
|
46
|
+
filterIds?: string[];
|
|
47
|
+
tags?: string[];
|
|
48
|
+
/** Per-case timeout override forwarded to the serve process. */
|
|
49
|
+
timeoutMs?: number;
|
|
50
|
+
/** Ask the serve harness to emit verbose case logs (server-side). */
|
|
51
|
+
verbose?: boolean;
|
|
52
|
+
fetchImpl?: typeof fetch;
|
|
53
|
+
/** Poll interval while the batch is `running`. */
|
|
54
|
+
pollMs?: number;
|
|
55
|
+
/** Abort the wait (not the remote batch). */
|
|
56
|
+
signal?: AbortSignal;
|
|
57
|
+
/** Called each time the polled snapshot changes. */
|
|
58
|
+
onSnapshot?: (snap: EvalRunSnapshot) => void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Start a batch on the target serve via `POST /v1/dev/evals/runs`, poll until
|
|
63
|
+
* terminal, and return CLI-shaped case results.
|
|
64
|
+
*/
|
|
65
|
+
export async function runRemoteEvals(
|
|
66
|
+
options: RunRemoteEvalsOptions
|
|
67
|
+
): Promise<{ snapshot: EvalRunSnapshot; results: EvalRunResult[] }> {
|
|
68
|
+
const baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
69
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
70
|
+
const auth: RequestAuth = {
|
|
71
|
+
bearerToken: options.bearerToken,
|
|
72
|
+
headers: options.headers,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const started = await startRemoteEvalRun({
|
|
76
|
+
baseUrl,
|
|
77
|
+
auth,
|
|
78
|
+
fetchImpl,
|
|
79
|
+
filterIds: options.filterIds,
|
|
80
|
+
tags: options.tags,
|
|
81
|
+
timeoutMs: options.timeoutMs,
|
|
82
|
+
verbose: options.verbose,
|
|
83
|
+
signal: options.signal,
|
|
84
|
+
});
|
|
85
|
+
options.onSnapshot?.(started);
|
|
86
|
+
|
|
87
|
+
const snapshot = await pollRemoteEvalRun({
|
|
88
|
+
baseUrl,
|
|
89
|
+
runId: started.runId,
|
|
90
|
+
auth,
|
|
91
|
+
fetchImpl,
|
|
92
|
+
pollMs: options.pollMs ?? DEFAULT_POLL_MS,
|
|
93
|
+
signal: options.signal,
|
|
94
|
+
onSnapshot: options.onSnapshot,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return { snapshot, results: snapshotToEvalRunResults(snapshot) };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** List datapoints from `GET /v1/dev/evals` (same surface a remote run uses). */
|
|
101
|
+
export async function listRemoteEvals(args: {
|
|
102
|
+
baseUrl: string;
|
|
103
|
+
auth?: RequestAuth;
|
|
104
|
+
fetchImpl?: typeof fetch;
|
|
105
|
+
filterIds?: string[];
|
|
106
|
+
tags?: string[];
|
|
107
|
+
signal?: AbortSignal;
|
|
108
|
+
}): Promise<RemoteEvalListItem[]> {
|
|
109
|
+
const baseUrl = args.baseUrl.replace(/\/$/, "");
|
|
110
|
+
const fetchImpl = args.fetchImpl ?? fetch;
|
|
111
|
+
let resp: Response;
|
|
112
|
+
try {
|
|
113
|
+
resp = await fetchImpl(`${baseUrl}/v1/dev/evals`, {
|
|
114
|
+
headers: mergeRequestHeaders(args.auth),
|
|
115
|
+
signal: args.signal,
|
|
116
|
+
});
|
|
117
|
+
} catch (error) {
|
|
118
|
+
throw new RemoteEvalError(
|
|
119
|
+
error instanceof Error ? error.message : "Could not list evals.",
|
|
120
|
+
undefined,
|
|
121
|
+
"start_failed"
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
if (!resp.ok) {
|
|
125
|
+
throw new RemoteEvalError(
|
|
126
|
+
`Could not list evals (HTTP ${resp.status}).`,
|
|
127
|
+
resp.status,
|
|
128
|
+
"start_failed"
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
const body = (await resp.json()) as {
|
|
132
|
+
evals?: Array<{
|
|
133
|
+
id?: unknown;
|
|
134
|
+
fileId?: unknown;
|
|
135
|
+
description?: unknown;
|
|
136
|
+
tags?: unknown;
|
|
137
|
+
}>;
|
|
138
|
+
};
|
|
139
|
+
const listed: RemoteEvalListItem[] = [];
|
|
140
|
+
for (const row of body.evals ?? []) {
|
|
141
|
+
if (typeof row.id !== "string" || row.id === "") {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (typeof row.fileId !== "string" || row.fileId === "") {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const tags = Array.isArray(row.tags)
|
|
148
|
+
? row.tags.filter((t): t is string => typeof t === "string")
|
|
149
|
+
: undefined;
|
|
150
|
+
listed.push({
|
|
151
|
+
id: row.id,
|
|
152
|
+
fileId: row.fileId,
|
|
153
|
+
...(typeof row.description === "string"
|
|
154
|
+
? { description: row.description }
|
|
155
|
+
: {}),
|
|
156
|
+
...(tags === undefined || tags.length === 0 ? {} : { tags }),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let selected = listed;
|
|
161
|
+
if (args.filterIds !== undefined && args.filterIds.length > 0) {
|
|
162
|
+
selected = selected.filter((e) =>
|
|
163
|
+
args.filterIds!.some((f) => matchesEvalIdFilter(e.id, f))
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
if (args.tags !== undefined && args.tags.length > 0) {
|
|
167
|
+
selected = selected.filter((e) =>
|
|
168
|
+
(e.tags ?? []).some((t) => args.tags!.includes(t))
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
return selected;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export async function startRemoteEvalRun(args: {
|
|
175
|
+
baseUrl: string;
|
|
176
|
+
auth?: RequestAuth;
|
|
177
|
+
fetchImpl?: typeof fetch;
|
|
178
|
+
filterIds?: string[];
|
|
179
|
+
tags?: string[];
|
|
180
|
+
timeoutMs?: number;
|
|
181
|
+
verbose?: boolean;
|
|
182
|
+
signal?: AbortSignal;
|
|
183
|
+
}): Promise<EvalRunSnapshot> {
|
|
184
|
+
const baseUrl = args.baseUrl.replace(/\/$/, "");
|
|
185
|
+
const fetchImpl = args.fetchImpl ?? fetch;
|
|
186
|
+
const body: {
|
|
187
|
+
filterIds?: string[];
|
|
188
|
+
tags?: string[];
|
|
189
|
+
timeoutMs?: number;
|
|
190
|
+
verbose?: boolean;
|
|
191
|
+
} = {};
|
|
192
|
+
if (args.filterIds !== undefined && args.filterIds.length > 0) {
|
|
193
|
+
body.filterIds = args.filterIds;
|
|
194
|
+
}
|
|
195
|
+
if (args.tags !== undefined && args.tags.length > 0) {
|
|
196
|
+
body.tags = args.tags;
|
|
197
|
+
}
|
|
198
|
+
if (args.timeoutMs !== undefined) {
|
|
199
|
+
body.timeoutMs = args.timeoutMs;
|
|
200
|
+
}
|
|
201
|
+
if (args.verbose === true) {
|
|
202
|
+
body.verbose = true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let resp: Response;
|
|
206
|
+
try {
|
|
207
|
+
resp = await fetchImpl(`${baseUrl}/v1/dev/evals/runs`, {
|
|
208
|
+
method: "POST",
|
|
209
|
+
headers: {
|
|
210
|
+
"content-type": "application/json",
|
|
211
|
+
...mergeRequestHeaders(args.auth),
|
|
212
|
+
},
|
|
213
|
+
body: JSON.stringify(body),
|
|
214
|
+
signal: args.signal,
|
|
215
|
+
});
|
|
216
|
+
} catch (error) {
|
|
217
|
+
throw new RemoteEvalError(
|
|
218
|
+
error instanceof Error ? error.message : "Could not start eval run.",
|
|
219
|
+
undefined,
|
|
220
|
+
"start_failed"
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const json = (await resp.json().catch(() => ({}))) as {
|
|
225
|
+
ok?: boolean;
|
|
226
|
+
error?: string;
|
|
227
|
+
message?: string;
|
|
228
|
+
activeRunId?: string;
|
|
229
|
+
runId?: string;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
if (json.error === "no_matching_evals") {
|
|
233
|
+
throw new RemoteEvalError(
|
|
234
|
+
"No matching evals found.",
|
|
235
|
+
404,
|
|
236
|
+
"no_matching_evals"
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
if (resp.status === 409 || json.error === "eval_run_in_progress") {
|
|
240
|
+
throw new RemoteEvalError(
|
|
241
|
+
json.activeRunId !== undefined && json.activeRunId !== ""
|
|
242
|
+
? `An eval run is already in progress (${json.activeRunId}).`
|
|
243
|
+
: "An eval run is already in progress.",
|
|
244
|
+
409,
|
|
245
|
+
"eval_run_in_progress",
|
|
246
|
+
typeof json.activeRunId === "string" ? json.activeRunId : undefined
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
if (!resp.ok) {
|
|
250
|
+
const detail =
|
|
251
|
+
typeof json.message === "string" && json.message !== ""
|
|
252
|
+
? json.message
|
|
253
|
+
: typeof json.error === "string" && json.error !== ""
|
|
254
|
+
? json.error
|
|
255
|
+
: `HTTP ${resp.status}`;
|
|
256
|
+
throw new RemoteEvalError(
|
|
257
|
+
`Could not start eval run: ${detail}`,
|
|
258
|
+
resp.status,
|
|
259
|
+
"start_failed"
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
if (typeof json.runId !== "string" || json.runId === "") {
|
|
263
|
+
throw new RemoteEvalError(
|
|
264
|
+
"Eval start response missing runId.",
|
|
265
|
+
undefined,
|
|
266
|
+
"start_failed"
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
return json as EvalRunSnapshot;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** One-shot `GET /v1/dev/evals/runs/:runId` (no polling). */
|
|
273
|
+
export async function getRemoteEvalRun(args: {
|
|
274
|
+
baseUrl: string;
|
|
275
|
+
runId: string;
|
|
276
|
+
auth?: RequestAuth;
|
|
277
|
+
fetchImpl?: typeof fetch;
|
|
278
|
+
signal?: AbortSignal;
|
|
279
|
+
}): Promise<EvalRunSnapshot> {
|
|
280
|
+
const baseUrl = args.baseUrl.replace(/\/$/, "");
|
|
281
|
+
const fetchImpl = args.fetchImpl ?? fetch;
|
|
282
|
+
let resp: Response;
|
|
283
|
+
try {
|
|
284
|
+
resp = await fetchImpl(
|
|
285
|
+
`${baseUrl}/v1/dev/evals/runs/${encodeURIComponent(args.runId)}`,
|
|
286
|
+
{
|
|
287
|
+
headers: mergeRequestHeaders(args.auth),
|
|
288
|
+
signal: args.signal,
|
|
289
|
+
}
|
|
290
|
+
);
|
|
291
|
+
} catch (error) {
|
|
292
|
+
throw new RemoteEvalError(
|
|
293
|
+
error instanceof Error ? error.message : "Could not fetch eval run.",
|
|
294
|
+
undefined,
|
|
295
|
+
"poll_failed"
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (resp.status === 404) {
|
|
300
|
+
throw new RemoteEvalError(
|
|
301
|
+
`Unknown eval run ${args.runId}.`,
|
|
302
|
+
404,
|
|
303
|
+
"unknown_eval_run"
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
if (!resp.ok) {
|
|
307
|
+
throw new RemoteEvalError(
|
|
308
|
+
`Could not fetch eval run (HTTP ${resp.status}).`,
|
|
309
|
+
resp.status,
|
|
310
|
+
"poll_failed"
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
return (await resp.json()) as EvalRunSnapshot;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Recent batches from `GET /v1/dev/evals/runs`. */
|
|
317
|
+
export async function listRemoteEvalRuns(args: {
|
|
318
|
+
baseUrl: string;
|
|
319
|
+
auth?: RequestAuth;
|
|
320
|
+
fetchImpl?: typeof fetch;
|
|
321
|
+
signal?: AbortSignal;
|
|
322
|
+
}): Promise<{ runs: EvalRunSnapshot[]; activeRunId?: string }> {
|
|
323
|
+
const baseUrl = args.baseUrl.replace(/\/$/, "");
|
|
324
|
+
const fetchImpl = args.fetchImpl ?? fetch;
|
|
325
|
+
let resp: Response;
|
|
326
|
+
try {
|
|
327
|
+
resp = await fetchImpl(`${baseUrl}/v1/dev/evals/runs`, {
|
|
328
|
+
headers: mergeRequestHeaders(args.auth),
|
|
329
|
+
signal: args.signal,
|
|
330
|
+
});
|
|
331
|
+
} catch (error) {
|
|
332
|
+
throw new RemoteEvalError(
|
|
333
|
+
error instanceof Error ? error.message : "Could not list eval runs.",
|
|
334
|
+
undefined,
|
|
335
|
+
"poll_failed"
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
if (!resp.ok) {
|
|
339
|
+
throw new RemoteEvalError(
|
|
340
|
+
`Could not list eval runs (HTTP ${resp.status}).`,
|
|
341
|
+
resp.status,
|
|
342
|
+
"poll_failed"
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
const body = (await resp.json()) as {
|
|
346
|
+
runs?: unknown;
|
|
347
|
+
activeRunId?: unknown;
|
|
348
|
+
};
|
|
349
|
+
const runs = Array.isArray(body.runs)
|
|
350
|
+
? (body.runs.filter(
|
|
351
|
+
(r): r is EvalRunSnapshot =>
|
|
352
|
+
r !== null &&
|
|
353
|
+
typeof r === "object" &&
|
|
354
|
+
typeof (r as EvalRunSnapshot).runId === "string"
|
|
355
|
+
) as EvalRunSnapshot[])
|
|
356
|
+
: [];
|
|
357
|
+
return {
|
|
358
|
+
runs,
|
|
359
|
+
...(typeof body.activeRunId === "string" && body.activeRunId !== ""
|
|
360
|
+
? { activeRunId: body.activeRunId }
|
|
361
|
+
: {}),
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export async function pollRemoteEvalRun(args: {
|
|
366
|
+
baseUrl: string;
|
|
367
|
+
runId: string;
|
|
368
|
+
auth?: RequestAuth;
|
|
369
|
+
fetchImpl?: typeof fetch;
|
|
370
|
+
pollMs?: number;
|
|
371
|
+
signal?: AbortSignal;
|
|
372
|
+
onSnapshot?: (snap: EvalRunSnapshot) => void;
|
|
373
|
+
}): Promise<EvalRunSnapshot> {
|
|
374
|
+
const pollMs = args.pollMs ?? DEFAULT_POLL_MS;
|
|
375
|
+
let lastJson = "";
|
|
376
|
+
|
|
377
|
+
for (;;) {
|
|
378
|
+
if (args.signal?.aborted === true) {
|
|
379
|
+
throw new RemoteEvalError("Eval wait aborted.", undefined, "aborted");
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const snap = await getRemoteEvalRun(args);
|
|
383
|
+
const serialized = JSON.stringify(snap);
|
|
384
|
+
if (serialized !== lastJson) {
|
|
385
|
+
lastJson = serialized;
|
|
386
|
+
args.onSnapshot?.(snap);
|
|
387
|
+
}
|
|
388
|
+
if (snap.status !== "running") {
|
|
389
|
+
return snap;
|
|
390
|
+
}
|
|
391
|
+
await sleep(pollMs, args.signal);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export function snapshotToEvalRunResults(
|
|
396
|
+
snapshot: EvalRunSnapshot
|
|
397
|
+
): EvalRunResult[] {
|
|
398
|
+
return snapshot.cases.map((c) => ({
|
|
399
|
+
id: c.id,
|
|
400
|
+
path: c.fileId,
|
|
401
|
+
...(c.description === undefined ? {} : { description: c.description }),
|
|
402
|
+
ok: c.ok === true,
|
|
403
|
+
assertions: c.assertions ?? [],
|
|
404
|
+
...(c.sessionId === undefined ? {} : { sessionId: c.sessionId }),
|
|
405
|
+
inputs: c.inputs ?? [],
|
|
406
|
+
...(c.finalText === undefined ? {} : { finalText: c.finalText }),
|
|
407
|
+
...(c.tools === undefined ? {} : { tools: c.tools }),
|
|
408
|
+
...(c.toolCalls === undefined ? {} : { toolCalls: c.toolCalls }),
|
|
409
|
+
...(c.error === undefined ? {} : { error: c.error }),
|
|
410
|
+
logs: c.logs ?? [],
|
|
411
|
+
durationMs: c.durationMs ?? 0,
|
|
412
|
+
}));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function sleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
416
|
+
return new Promise((resolve, reject) => {
|
|
417
|
+
if (signal?.aborted === true) {
|
|
418
|
+
reject(new RemoteEvalError("Eval wait aborted.", undefined, "aborted"));
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
const timer = setTimeout(() => {
|
|
422
|
+
signal?.removeEventListener("abort", onAbort);
|
|
423
|
+
resolve();
|
|
424
|
+
}, ms);
|
|
425
|
+
const onAbort = (): void => {
|
|
426
|
+
clearTimeout(timer);
|
|
427
|
+
reject(new RemoteEvalError("Eval wait aborted.", undefined, "aborted"));
|
|
428
|
+
};
|
|
429
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
430
|
+
});
|
|
431
|
+
}
|
|
@@ -441,6 +441,7 @@ Evals:
|
|
|
441
441
|
Required: evals/evals.config.ts with defineEvalConfig({ maxConcurrency })
|
|
442
442
|
(hard-capped at 200 due to model provider request limits)
|
|
443
443
|
Run: ${CLI_COMMAND_NAME} eval [--json] [evalId...]
|
|
444
|
+
(pass --prod to run against the hosted deployment)
|
|
444
445
|
|
|
445
446
|
See AGENTS.md in the ${PACKAGE_NAME} package for the full loop.
|
|
446
447
|
`);
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
* request-targets are rejected so injected headers never leave the configured
|
|
15
15
|
* upstream.
|
|
16
16
|
*
|
|
17
|
-
* When `refreshUpstream` is provided (
|
|
18
|
-
* re-minted shortly before `expiresAt` and once on upstream
|
|
19
|
-
* playground session does not require restarting the CLI.
|
|
17
|
+
* When `refreshUpstream` is provided (e.g. short-lived upstream tokens),
|
|
18
|
+
* credentials are re-minted shortly before `expiresAt` and once on upstream
|
|
19
|
+
* 401 so a long playground session does not require restarting the CLI.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import { randomBytes } from "node:crypto";
|
|
@@ -84,8 +84,8 @@ export interface StartPlaygroundProxyArgs {
|
|
|
84
84
|
/** Initial engineAccess expiry (ISO); enables proactive refresh. */
|
|
85
85
|
expiresAt?: string;
|
|
86
86
|
/**
|
|
87
|
-
* Re-mint upstream URL + headers
|
|
88
|
-
*
|
|
87
|
+
* Re-mint upstream URL + headers. Single-flighted across concurrent browser
|
|
88
|
+
* requests; called near expiry and once on 401.
|
|
89
89
|
*/
|
|
90
90
|
refreshUpstream?: () => Promise<PlaygroundUpstreamAccess>;
|
|
91
91
|
/** Bind port; `0` = ephemeral. */
|
|
@@ -48,6 +48,29 @@ export interface ResolvedProdTarget {
|
|
|
48
48
|
expiresAt: string;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Thrown when a hosted deployment cannot be resolved. `notFound` is true
|
|
53
|
+
* when the deployment does not exist, so callers can suggest deploying
|
|
54
|
+
* (or running locally) instead of surfacing a bare API error.
|
|
55
|
+
*/
|
|
56
|
+
export class ProdTargetError extends Error {
|
|
57
|
+
constructor(
|
|
58
|
+
message: string,
|
|
59
|
+
readonly options: { slug: string; notFound?: boolean } = { slug: "" }
|
|
60
|
+
) {
|
|
61
|
+
super(message);
|
|
62
|
+
this.name = "ProdTargetError";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
get slug(): string {
|
|
66
|
+
return this.options.slug;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get notFound(): boolean {
|
|
70
|
+
return this.options.notFound === true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
51
74
|
/**
|
|
52
75
|
* Look up a running deployment and return its engineAccess URL/headers.
|
|
53
76
|
* Throws {@link Error} with a CLI-ready message on failure.
|
|
@@ -65,8 +88,10 @@ export async function resolveProdTarget(
|
|
|
65
88
|
err,
|
|
66
89
|
});
|
|
67
90
|
if (typeof auth === "number") {
|
|
91
|
+
// Not "--prod": `eval` resolves the hosted deployment by default,
|
|
92
|
+
// without any flag.
|
|
68
93
|
throw new Error(
|
|
69
|
-
`Could not resolve Cursor credentials/team for
|
|
94
|
+
`Could not resolve Cursor credentials/team for the hosted deployment (see above).`
|
|
70
95
|
);
|
|
71
96
|
}
|
|
72
97
|
|
|
@@ -83,8 +108,9 @@ export async function resolveProdTarget(
|
|
|
83
108
|
});
|
|
84
109
|
} catch (error) {
|
|
85
110
|
if (error instanceof DeployApiError) {
|
|
86
|
-
throw new
|
|
87
|
-
`Deployment "${slug}" (team ${auth.teamId}): ${error.message}
|
|
111
|
+
throw new ProdTargetError(
|
|
112
|
+
`Deployment "${slug}" (team ${auth.teamId}): ${error.message}`,
|
|
113
|
+
{ slug, notFound: error.status === 404 }
|
|
88
114
|
);
|
|
89
115
|
}
|
|
90
116
|
throw error;
|
|
@@ -143,6 +169,78 @@ export async function resolveProdTarget(
|
|
|
143
169
|
};
|
|
144
170
|
}
|
|
145
171
|
|
|
172
|
+
export interface ResolvedProdAliasPlayground {
|
|
173
|
+
/** Hosted playground URL on the API alias (SPA owns alias-token sign-in). */
|
|
174
|
+
playgroundUrl: string;
|
|
175
|
+
slug: string;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Look up a running deployment and return its stable alias playground URL.
|
|
180
|
+
* Does not mint `engineAccess` — the browser signs in with the alias token.
|
|
181
|
+
* Throws {@link Error} with a CLI-ready message on failure.
|
|
182
|
+
*/
|
|
183
|
+
export async function resolveProdAliasPlaygroundUrl(
|
|
184
|
+
options: ResolveProdTargetOptions & { sessionId?: string }
|
|
185
|
+
): Promise<ResolvedProdAliasPlayground> {
|
|
186
|
+
const err =
|
|
187
|
+
options.err ?? ((text: string) => void process.stderr.write(text));
|
|
188
|
+
const slug = resolveProdSlug(options);
|
|
189
|
+
const auth = await resolveDeployAuth({
|
|
190
|
+
team: options.team,
|
|
191
|
+
apiKey: options.apiKey,
|
|
192
|
+
fetchImpl: options.fetchImpl,
|
|
193
|
+
err,
|
|
194
|
+
});
|
|
195
|
+
if (typeof auth === "number") {
|
|
196
|
+
throw new Error(
|
|
197
|
+
`Could not resolve Cursor credentials/team for the hosted deployment (see above).`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let detail: DeploymentDetail;
|
|
202
|
+
try {
|
|
203
|
+
detail = await auth.client.getDeployment({
|
|
204
|
+
teamId: auth.teamId,
|
|
205
|
+
slug,
|
|
206
|
+
});
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (error instanceof DeployApiError) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
`Deployment "${slug}" (team ${auth.teamId}): ${error.message}`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const statusOk = detail.status === "running" || detail.status === "degraded";
|
|
217
|
+
if (!statusOk) {
|
|
218
|
+
const lastError =
|
|
219
|
+
detail.lastError === null || detail.lastError === undefined
|
|
220
|
+
? ""
|
|
221
|
+
: ` ${sanitizeCustomerFacingDetail(detail.lastError)}`;
|
|
222
|
+
throw new Error(
|
|
223
|
+
`Deployment "${slug}" is ${detail.status}, not running.${lastError}\n` +
|
|
224
|
+
`Deploy or wait: \`${CLI} deploy --slug ${slug}\` / \`${CLI} deployment ${slug}\``
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const aliasBase = auth.client.aliasUrl(detail.engineAlias).replace(/\/$/, "");
|
|
229
|
+
const base = `${aliasBase}/playground`;
|
|
230
|
+
const sessionId =
|
|
231
|
+
options.sessionId !== undefined && options.sessionId.trim() !== ""
|
|
232
|
+
? options.sessionId.trim()
|
|
233
|
+
: undefined;
|
|
234
|
+
const playgroundUrl =
|
|
235
|
+
sessionId === undefined
|
|
236
|
+
? base
|
|
237
|
+
: `${base}?sessionId=${encodeURIComponent(sessionId)}`;
|
|
238
|
+
|
|
239
|
+
err(`${stderrPalette().dim(`Using hosted deployment "${slug}".`)}\n`);
|
|
240
|
+
|
|
241
|
+
return { playgroundUrl, slug };
|
|
242
|
+
}
|
|
243
|
+
|
|
146
244
|
/** Strip obvious hosting internals from customer-facing error fragments. */
|
|
147
245
|
function sanitizeCustomerFacingDetail(message: string): string {
|
|
148
246
|
return message
|
package/src/internal/server.ts
CHANGED
|
@@ -362,9 +362,8 @@ export async function startServer(
|
|
|
362
362
|
const evalRuns = new EvalRunStore(
|
|
363
363
|
project.rootDir,
|
|
364
364
|
logger,
|
|
365
|
-
//
|
|
366
|
-
|
|
367
|
-
engine.storage?.asEvalRunPersistence()
|
|
365
|
+
// Playground eval history mirrors onto the defineStorage evals table.
|
|
366
|
+
engine.storage?.evalRuns()
|
|
368
367
|
);
|
|
369
368
|
// Eval history restores with the same startup step as sessions (reads
|
|
370
369
|
// also hydrate lazily, but doing it here surfaces store problems at
|
|
@@ -738,6 +737,9 @@ export async function startServer(
|
|
|
738
737
|
for (const runtime of runtimes) {
|
|
739
738
|
runtime.scheduleRunner.stop();
|
|
740
739
|
runtime.reminderRunner.stop();
|
|
740
|
+
// Before the engines close their storage coordinators, so the
|
|
741
|
+
// interrupted-batch write can still reach the sink.
|
|
742
|
+
runtime.evalRuns.close();
|
|
741
743
|
}
|
|
742
744
|
for (const stop of channelStopHooks) {
|
|
743
745
|
try {
|
|
@@ -1080,6 +1082,8 @@ function mountAgentRoutes(
|
|
|
1080
1082
|
const body = await readJsonObject(request);
|
|
1081
1083
|
const filterIds = asStringArray(body?.filterIds);
|
|
1082
1084
|
const tags = asStringArray(body?.tags);
|
|
1085
|
+
const timeoutMs = asPositiveInt(body?.timeoutMs);
|
|
1086
|
+
const verbose = body?.verbose === true;
|
|
1083
1087
|
const authHeader = request.headers.get("authorization");
|
|
1084
1088
|
const bearerToken = authHeader?.toLowerCase().startsWith("bearer ")
|
|
1085
1089
|
? authHeader.slice("bearer ".length).trim()
|
|
@@ -1091,6 +1095,8 @@ function mountAgentRoutes(
|
|
|
1091
1095
|
...(bearerToken === undefined || bearerToken === ""
|
|
1092
1096
|
? {}
|
|
1093
1097
|
: { bearerToken }),
|
|
1098
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
1099
|
+
...(verbose ? { verbose: true } : {}),
|
|
1094
1100
|
});
|
|
1095
1101
|
return jsonResponse(snap, { status: 202 });
|
|
1096
1102
|
} catch (error) {
|
|
@@ -1894,6 +1900,14 @@ function asStringArray(value: unknown): string[] | undefined {
|
|
|
1894
1900
|
return out.length === 0 ? undefined : out;
|
|
1895
1901
|
}
|
|
1896
1902
|
|
|
1903
|
+
function asPositiveInt(value: unknown): number | undefined {
|
|
1904
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
1905
|
+
return undefined;
|
|
1906
|
+
}
|
|
1907
|
+
const n = Math.floor(value);
|
|
1908
|
+
return n > 0 ? n : undefined;
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1897
1911
|
async function assertReminderReadable(input: {
|
|
1898
1912
|
engine: SessionEngine;
|
|
1899
1913
|
reminder: ReminderInfo;
|