@leostera/evalkit 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -1
- package/dist/cli.mjs +62 -10
- package/dist/cli.mjs.map +1 -1
- package/dist/{index-U6TUTBf7.d.mts → index-lOw0fmak.d.mts} +14 -5
- package/dist/index-lOw0fmak.d.mts.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/runner.d.mts +1 -1
- package/dist/src-ButXPY8P.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/index-U6TUTBf7.d.mts.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@leostera/evalkit`. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses [Semantic Versioning](https://semver.org/). Before 1.0, a minor version may include breaking changes; they are marked **Breaking**.
|
|
4
4
|
|
|
5
|
+
## [0.5.0] - 2026-10-28
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `share.headers` in `evalkit.config.ts`: extra headers for every request to the share server, for an auth proxy such as Cloudflare Access in front of it. It can be an object or a function (sync or async), is called once per `evalkit share`, and its values are never printed.
|
|
10
|
+
- `evalkit share` checks the server before uploading, in a new Server step.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- `evalkit share` no longer follows redirects or treats an HTML page as a successful upload. A redirect (for example to a login page), a 401 or 403, or a non-JSON answer stops sharing with an error naming the cause, instead of `JSON Parse error: Unrecognized token '<'` for every run.
|
|
15
|
+
|
|
5
16
|
## [0.4.0] - 2026-10-28
|
|
6
17
|
|
|
7
18
|
Commit provenance and team sharing ([RFD0006](docs/rfds/RFD0006-commit-provenance-and-share-server.md)).
|
|
@@ -74,7 +85,8 @@ Commit provenance and team sharing ([RFD0006](docs/rfds/RFD0006-commit-provenanc
|
|
|
74
85
|
- **Breaking:** the npm package moved to `@leostera/evalkit`. Replace the `@leostera-js/evalkit` dependency and update imports, including `/runner`. Eval definitions and local reports are unchanged.
|
|
75
86
|
- Dashboard workflow refinements.
|
|
76
87
|
|
|
77
|
-
[0.
|
|
88
|
+
[0.5.0]: https://github.com/leostera/evalkit/compare/0dd8717...main
|
|
89
|
+
[0.4.0]: https://github.com/leostera/evalkit/compare/5e67e72...0dd8717
|
|
78
90
|
[0.3.0]: https://github.com/leostera/evalkit/compare/38c34be...5e67e72
|
|
79
91
|
[0.2.0]: https://github.com/leostera/evalkit/compare/3f8855a...38c34be
|
|
80
92
|
[0.1.0]: https://github.com/leostera/evalkit/compare/12d5a1e...3f8855a
|
package/dist/cli.mjs
CHANGED
|
@@ -9152,29 +9152,61 @@ async function runBundle(options) {
|
|
|
9152
9152
|
}, { workspaceFiles: options.workspaceFiles });
|
|
9153
9153
|
return encodeBundle(files);
|
|
9154
9154
|
}
|
|
9155
|
-
/** The server refused
|
|
9155
|
+
/** The server refused this bundle (4xx): retrying it cannot help. */
|
|
9156
9156
|
var UploadRejected = class extends Error {};
|
|
9157
|
+
/**
|
|
9158
|
+
* The URL does not answer as an EvalKit share server (a redirect, an HTML page,
|
|
9159
|
+
* an auth proxy). Every upload would fail the same way, so sharing stops.
|
|
9160
|
+
*/
|
|
9161
|
+
var ShareServerError = class extends Error {};
|
|
9162
|
+
/** `share.headers` from the config: called once, checked, never printed. */
|
|
9163
|
+
async function resolveShareHeaders(headers) {
|
|
9164
|
+
let value;
|
|
9165
|
+
try {
|
|
9166
|
+
value = typeof headers === "function" ? await headers() : headers ?? {};
|
|
9167
|
+
} catch (error) {
|
|
9168
|
+
throw new Error(`share.headers failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
9169
|
+
}
|
|
9170
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("share.headers must be an object of header names to values");
|
|
9171
|
+
const entries = Object.entries(value);
|
|
9172
|
+
for (const [name, item] of entries) if (typeof item !== "string" || !item) throw new Error(`share.headers "${name}" must be a non-empty string`);
|
|
9173
|
+
return Object.fromEntries(entries);
|
|
9174
|
+
}
|
|
9175
|
+
/** Check that the URL is an EvalKit share server and the headers get through. */
|
|
9176
|
+
async function checkShareServer(serverUrl, headers) {
|
|
9177
|
+
const response = await fetch(new URL("/api/v1/repos", serverUrl), {
|
|
9178
|
+
headers,
|
|
9179
|
+
redirect: "manual"
|
|
9180
|
+
});
|
|
9181
|
+
await expectShareServer(response, serverUrl);
|
|
9182
|
+
await response.body?.cancel();
|
|
9183
|
+
}
|
|
9157
9184
|
/** POST one bundle, retrying network errors and 5xx responses. */
|
|
9158
9185
|
async function uploadBundle(serverUrl, bundle, options = {}) {
|
|
9159
9186
|
const attempts = options.attempts ?? 3;
|
|
9160
9187
|
let lastError;
|
|
9161
9188
|
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
9162
9189
|
try {
|
|
9163
|
-
return await postBundle(serverUrl, bundle);
|
|
9190
|
+
return await postBundle(serverUrl, bundle, options.headers ?? {});
|
|
9164
9191
|
} catch (error) {
|
|
9165
|
-
if (error instanceof UploadRejected) throw error;
|
|
9192
|
+
if (error instanceof UploadRejected || error instanceof ShareServerError) throw error;
|
|
9166
9193
|
lastError = error;
|
|
9167
9194
|
}
|
|
9168
9195
|
if (attempt < attempts) await Bun.sleep((options.delayMs ?? 500) * 2 ** (attempt - 1));
|
|
9169
9196
|
}
|
|
9170
9197
|
throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error(`Upload failed: ${String(lastError)}`);
|
|
9171
9198
|
}
|
|
9172
|
-
async function postBundle(serverUrl, bundle) {
|
|
9199
|
+
async function postBundle(serverUrl, bundle, headers) {
|
|
9173
9200
|
const response = await fetch(new URL("/api/v1/uploads", serverUrl), {
|
|
9174
9201
|
method: "POST",
|
|
9175
|
-
headers: {
|
|
9176
|
-
|
|
9202
|
+
headers: {
|
|
9203
|
+
...headers,
|
|
9204
|
+
"content-type": "application/octet-stream"
|
|
9205
|
+
},
|
|
9206
|
+
body: bundle,
|
|
9207
|
+
redirect: "manual"
|
|
9177
9208
|
});
|
|
9209
|
+
await expectShareServer(response, serverUrl);
|
|
9178
9210
|
if (response.ok) return parseUploadResult(await response.json());
|
|
9179
9211
|
const reason = await response.text();
|
|
9180
9212
|
if (response.status < 500) throw new UploadRejected(`Share server rejected the upload: ${reason}`);
|
|
@@ -9186,7 +9218,18 @@ function parseUploadResult(value) {
|
|
|
9186
9218
|
status: result.status,
|
|
9187
9219
|
url: result.url
|
|
9188
9220
|
};
|
|
9189
|
-
throw new
|
|
9221
|
+
throw new ShareServerError(`Unexpected share server response: ${JSON.stringify(value)}`);
|
|
9222
|
+
}
|
|
9223
|
+
/** Redirects and non-JSON answers come from something in front of the server. */
|
|
9224
|
+
async function expectShareServer(response, serverUrl) {
|
|
9225
|
+
if (response.status >= 300 && response.status < 400) {
|
|
9226
|
+
const location = response.headers.get("location");
|
|
9227
|
+
const target = location ? new URL(location, serverUrl).host : "elsewhere";
|
|
9228
|
+
throw new ShareServerError(`${new URL(serverUrl).host} redirected to ${target} instead of answering. If an auth proxy is in front of the share server, set share.headers in evalkit.config.ts.`);
|
|
9229
|
+
}
|
|
9230
|
+
const type = response.headers.get("content-type") ?? "";
|
|
9231
|
+
const denied = response.status === 401 || response.status === 403;
|
|
9232
|
+
if (denied || response.ok && !type.includes("json")) throw new ShareServerError(`${new URL(serverUrl).host} did not answer as an EvalKit share server (${response.status}${type ? `, ${type.split(";")[0]}` : ""}). ` + (denied ? "Check share.headers." : "Check share.url."));
|
|
9190
9233
|
}
|
|
9191
9234
|
async function shareRuns(options) {
|
|
9192
9235
|
const reports = createDashboardReportReader(options.reportRoot, options.registry);
|
|
@@ -9207,10 +9250,11 @@ async function shareRuns(options) {
|
|
|
9207
9250
|
run,
|
|
9208
9251
|
workspaceFiles: options.workspaceFiles
|
|
9209
9252
|
});
|
|
9210
|
-
const uploaded = await uploadBundle(options.serverUrl, bundle);
|
|
9253
|
+
const uploaded = await uploadBundle(options.serverUrl, bundle, { headers: options.headers });
|
|
9211
9254
|
result.bytes += bundle.byteLength;
|
|
9212
9255
|
result[uploaded.status] += 1;
|
|
9213
9256
|
} catch (error) {
|
|
9257
|
+
if (error instanceof ShareServerError) throw error;
|
|
9214
9258
|
result.failed.push({
|
|
9215
9259
|
runId: run.id,
|
|
9216
9260
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -9339,7 +9383,7 @@ scores, evidence, and candidate files (never evaluator workspaces): review it be
|
|
|
9339
9383
|
}
|
|
9340
9384
|
//#endregion
|
|
9341
9385
|
//#region ../cli/src/new-project.ts
|
|
9342
|
-
const npmDependency = "^0.
|
|
9386
|
+
const npmDependency = "^0.5.0";
|
|
9343
9387
|
const ignored = [
|
|
9344
9388
|
"node_modules/",
|
|
9345
9389
|
"_evalkit-results/",
|
|
@@ -9452,7 +9496,7 @@ async function writeStarterReadme(root, name, inPlace) {
|
|
|
9452
9496
|
}
|
|
9453
9497
|
//#endregion
|
|
9454
9498
|
//#region ../cli/src/index.ts
|
|
9455
|
-
const VERSION = "0.
|
|
9499
|
+
const VERSION = "0.5.0";
|
|
9456
9500
|
let projectRoot = process.cwd();
|
|
9457
9501
|
let reportRoot = resolve(projectRoot, "_evalkit-results");
|
|
9458
9502
|
const sandboxRoot = resolve(projectRoot, "_evalkit-sandbox");
|
|
@@ -9891,6 +9935,14 @@ const shareCommand = make$1("share", {
|
|
|
9891
9935
|
if (!selection.selected.length) throw new Error(noRunsMessage(selection, head, { all }));
|
|
9892
9936
|
const result = await shareRuns({
|
|
9893
9937
|
serverUrl,
|
|
9938
|
+
headers: await step("Server", async () => {
|
|
9939
|
+
const headers = await resolveShareHeaders(project.config.share?.headers);
|
|
9940
|
+
await checkShareServer(serverUrl, headers);
|
|
9941
|
+
return headers;
|
|
9942
|
+
}, (headers) => {
|
|
9943
|
+
const names = Object.keys(headers);
|
|
9944
|
+
return `${new URL(serverUrl).host}${names.length ? dim(` with ${names.join(", ")}`) : ""}`;
|
|
9945
|
+
}),
|
|
9894
9946
|
reportRoot,
|
|
9895
9947
|
registry,
|
|
9896
9948
|
runs: selection.selected,
|