@workbench-ai/workbench 0.0.65 → 0.0.67

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/index.js CHANGED
@@ -5,7 +5,8 @@ import { createRequire } from "node:module";
5
5
  import os from "node:os";
6
6
  import path from "node:path";
7
7
  import { Writable } from "node:stream";
8
- import { createBaselineCandidateJob as createRuntimeBaselineCandidateJob, evaluationScorecardId, evaluationMeanMetrics, executeWorkbenchExecutionJob, engineResolveBindingForSpec, filterOptimizerTraceJobsForCaseIds, filterCandidateSourceFiles, formatWorkbenchCaseSelector, formatWorkbenchSelectionPolicy, workbenchCaseSelectorUsesAllCases, workbenchExecutionPurpose, workbenchRunExecutionFingerprint, createWorkbenchAdapterAuthBundle, createOptimizerTraceInputFiles, DOCKER_SANDBOX_BACKEND, localWorkbenchAdapterAuthStore, materializeWorkbenchRunResult, normalizeSurfaceFiles, planWorkbenchExecutionJobsForPurpose, runWorkbenchExecutionDag, resolveEngineCaseExecutionConfig, resolveWorkbenchResolvedSourceYaml, runtimeResources, validateWorkbenchRunEnvelope, parseWorkbenchAdapterAuthTarget, workbenchEngineCaseIdsForImproveEvaluation, workbenchEngineCaseIdsForSelector, workbenchImproveOptimizeSelector, workbenchImproveSelectionPolicy, workbenchProjectSourceFingerprint, workbenchRuntimeBundleFingerprint, workbenchRuntimeExplicitActiveId, } from "@workbench-ai/workbench-core";
8
+ import { gzipSync } from "node:zlib";
9
+ import { createBaselineCandidateJob as createRuntimeBaselineCandidateJob, evaluationScorecardId, evaluationMeanMetrics, executeWorkbenchExecutionJob, engineResolveBindingForSpec, filterOptimizerTraceJobsForCaseIds, filterCandidateSourceFiles, formatWorkbenchCaseSelector, formatWorkbenchSelectionPolicy, workbenchCaseSelectorUsesAllCases, workbenchExecutionPurpose, workbenchRunExecutionFingerprint, createWorkbenchAdapterAuthBundle, createOptimizerTraceInputFiles, DOCKER_SANDBOX_BACKEND, localWorkbenchAdapterAuthStore, materializeWorkbenchRunResult, normalizeRelativePath, normalizeSurfaceFiles, isSurfaceSnapshotFile, jsonRecord, planWorkbenchExecutionJobsForPurpose, runWorkbenchExecutionDag, resolveEngineCaseExecutionConfig, resolveWorkbenchResolvedSourceYaml, runtimeResources, validateWorkbenchRunEnvelope, parseWorkbenchAdapterAuthTarget, workbenchEngineCaseIdsForImproveEvaluation, workbenchEngineCaseIdsForSelector, workbenchImproveOptimizeSelector, workbenchImproveSelectionPolicy, workbenchProjectSourceFingerprint, workbenchRuntimeCandidateIdentityForExchange, workbenchRuntimeBundleFingerprint, workbenchRuntimeProjectedActiveId, } from "@workbench-ai/workbench-core";
9
10
  import { assertWorkbenchAdapterOperationResultOk, collectWorkbenchAdapterAuthRequirements, normalizeWorkbenchAdapterOperationRequest, readWorkbenchAdapterOperationResult, workbenchAdapterOperationCommand, workbenchAdapterOperationResultPath, withDefaultWorkbenchAdapterAuthProfiles as applyDefaultWorkbenchAdapterAuthProfiles, } from "@workbench-ai/workbench-protocol";
10
11
  import { builtinLocalTraceAdapter, builtinLocalTraceAdapters, sortLocalTraceRefs, } from "@workbench-ai/workbench-built-in-adapters/local-traces";
11
12
  import { commandUsage, REMOTE_WATCH_LIFECYCLE_NOTE, LOCAL_DEV_OPEN_LIFECYCLE_NOTE, rootUsage, } from "./command-model.js";
@@ -34,7 +35,32 @@ class WorkbenchApiRequestError extends Error {
34
35
  }
35
36
  }
36
37
  const API_REQUEST_MAX_ATTEMPTS = 3;
38
+ const API_REQUEST_GZIP_THRESHOLD_BYTES = 1024 * 1024;
37
39
  const DEFAULT_BASE_URL = "https://v2.workbench.ai";
40
+ const AUTH_COMMAND_HANDLERS = {
41
+ connect: authConnect,
42
+ disconnect: authDisconnect,
43
+ };
44
+ const ADAPTERS_COMMAND_HANDLERS = {
45
+ create: adaptersCreate,
46
+ inspect: adaptersInspect,
47
+ list: adaptersList,
48
+ test: adaptersTest,
49
+ };
50
+ const TRACES_COMMAND_HANDLERS = {
51
+ collect: localTraceCollect,
52
+ list: localTraceList,
53
+ show: localTraceShow,
54
+ };
55
+ const TWO_SEGMENT_HELP_COMMANDS = {
56
+ adapters: ["create", "list", "inspect", "test"],
57
+ auth: [],
58
+ candidates: ["list", "show", "files", "preview"],
59
+ evaluations: ["list", "show"],
60
+ executions: ["trace"],
61
+ runs: ["list", "show"],
62
+ traces: ["collect", "list", "show"],
63
+ };
38
64
  export async function runCli(argv, io = {
39
65
  stdin: process.stdin,
40
66
  stdout: process.stdout,
@@ -162,34 +188,12 @@ export async function runCli(argv, io = {
162
188
  }
163
189
  function commandPathForHelp(argv) {
164
190
  const positionals = argv.filter((arg) => arg !== "--help" && arg !== "-h" && !arg.startsWith("--"));
165
- if (positionals[0] === "adapters" &&
166
- ["create", "list", "inspect", "test"].includes(positionals[1] ?? "")) {
191
+ const command = positionals[0] ?? "";
192
+ const subcommands = TWO_SEGMENT_HELP_COMMANDS[command];
193
+ if (subcommands && (subcommands.length === 0 || subcommands.includes(positionals[1] ?? ""))) {
167
194
  return positionals.slice(0, 2).join(" ");
168
195
  }
169
- if (positionals[0] === "traces" &&
170
- ["collect", "list", "show"].includes(positionals[1] ?? "")) {
171
- return positionals.slice(0, 2).join(" ");
172
- }
173
- if (positionals[0] === "auth") {
174
- return positionals.slice(0, 2).join(" ");
175
- }
176
- if (positionals[0] === "runs" &&
177
- ["list", "show"].includes(positionals[1] ?? "")) {
178
- return positionals.slice(0, 2).join(" ");
179
- }
180
- if (positionals[0] === "evaluations" &&
181
- ["list", "show"].includes(positionals[1] ?? "")) {
182
- return positionals.slice(0, 2).join(" ");
183
- }
184
- if (positionals[0] === "executions" &&
185
- ["trace"].includes(positionals[1] ?? "")) {
186
- return positionals.slice(0, 2).join(" ");
187
- }
188
- if (positionals[0] === "candidates" &&
189
- ["list", "show", "files", "preview"].includes(positionals[1] ?? "")) {
190
- return positionals.slice(0, 2).join(" ");
191
- }
192
- return positionals[0] ?? "";
196
+ return command;
193
197
  }
194
198
  function extractRemoteFlag(argv) {
195
199
  let enabled = false;
@@ -762,6 +766,11 @@ async function localRun(argv, io, runtimeOptions) {
762
766
  detail: { budget, samples, strategy: "greedy", optimizeOn: optimizeOnLabel, selectBy: selectByLabel },
763
767
  }),
764
768
  ];
769
+ const runInput = localRunInput({
770
+ benchmarkFingerprint,
771
+ sourceYaml: projectSource.specSource,
772
+ engineResolveFiles,
773
+ });
765
774
  const runningRun = {
766
775
  id: runId,
767
776
  workflow: "improve",
@@ -784,6 +793,7 @@ async function localRun(argv, io, runtimeOptions) {
784
793
  executionFingerprint,
785
794
  activeCandidateId: snapshot.activeId,
786
795
  outputCandidateId: null,
796
+ input: runInput,
787
797
  };
788
798
  snapshot = upsertLocalRun(snapshot, runningRun, events);
789
799
  await saveLocalArchive(workspace, snapshot);
@@ -952,6 +962,7 @@ async function localRun(argv, io, runtimeOptions) {
952
962
  outcome: failedJobCount > 0 ? "error" : "ok",
953
963
  activeCandidateId: snapshot.activeId,
954
964
  outputCandidateId: outputCandidateId ?? snapshot.activeId,
965
+ input: runInput,
955
966
  };
956
967
  events.push(createLocalEvent("run_finished", finishedAt, {
957
968
  runId,
@@ -1243,6 +1254,11 @@ async function localEvaluateCandidate(argv, io, runtimeOptions) {
1243
1254
  candidateId: evaluatedCandidateId,
1244
1255
  detail: { samples, strategy: "direct" },
1245
1256
  });
1257
+ const runInput = localRunInput({
1258
+ benchmarkFingerprint,
1259
+ sourceYaml: projectSource.specSource,
1260
+ engineResolveFiles,
1261
+ });
1246
1262
  const runningRun = {
1247
1263
  id: runId,
1248
1264
  workflow: "eval",
@@ -1263,6 +1279,7 @@ async function localEvaluateCandidate(argv, io, runtimeOptions) {
1263
1279
  executionFingerprint,
1264
1280
  activeCandidateId: activeCandidateIdBeforeEval,
1265
1281
  outputCandidateId: evaluatedCandidateId,
1282
+ input: runInput,
1266
1283
  };
1267
1284
  snapshot = upsertLocalRun(snapshot, runningRun, [runStartedEvent]);
1268
1285
  await saveLocalArchive(workspace, snapshot);
@@ -1348,7 +1365,7 @@ async function localEvaluateCandidate(argv, io, runtimeOptions) {
1348
1365
  durationMs: Math.max(0, Date.parse(finishedAt) - Date.parse(startedAt)),
1349
1366
  },
1350
1367
  });
1351
- snapshot = upsertLocalRun(snapshot, {
1368
+ const finishedRun = {
1352
1369
  id: runId,
1353
1370
  workflow: "eval",
1354
1371
  benchmarkFingerprint,
@@ -1372,7 +1389,9 @@ async function localEvaluateCandidate(argv, io, runtimeOptions) {
1372
1389
  outcome: currentRunFailedJobCount > 0 ? "error" : "ok",
1373
1390
  activeCandidateId,
1374
1391
  outputCandidateId: evaluatedCandidateId,
1375
- }, [runFinishedEvent]);
1392
+ input: runInput,
1393
+ };
1394
+ snapshot = upsertLocalRun(snapshot, finishedRun, [runFinishedEvent]);
1376
1395
  await saveLocalJobs(workspace, currentRunJobs);
1377
1396
  await saveLocalArchive(workspace, snapshot);
1378
1397
  const evaluation = materialized.evaluations[0] ?? null;
@@ -1576,8 +1595,7 @@ function localDevOpenUrl(baseUrl, snapshot, runId) {
1576
1595
  if (!evaluation) {
1577
1596
  return new URL("candidates", baseUrl).toString();
1578
1597
  }
1579
- const params = new URLSearchParams({ evaluation: evaluation.id });
1580
- return new URL(`candidates/${encodeURIComponent(evaluation.candidateId)}?${params.toString()}`, baseUrl).toString();
1598
+ return new URL(`evaluations/${encodeURIComponent(evaluation.id)}`, baseUrl).toString();
1581
1599
  }
1582
1600
  async function readLocalBenchmarkFingerprint(workspace) {
1583
1601
  return localBenchmarkFingerprint(await readLocalProjectSource(workspace));
@@ -1606,6 +1624,13 @@ function authoredBenchmarkSourceFiles(projectSource) {
1606
1624
  executable: false,
1607
1625
  }];
1608
1626
  }
1627
+ function localRunInput(input) {
1628
+ return {
1629
+ benchmarkFingerprint: input.benchmarkFingerprint,
1630
+ sourceYaml: input.sourceYaml,
1631
+ engineResolveFiles: normalizeSurfaceFiles(input.engineResolveFiles),
1632
+ };
1633
+ }
1609
1634
  function shellQuote(value) {
1610
1635
  return `'${value.replace(/'/gu, "'\\''")}'`;
1611
1636
  }
@@ -1871,11 +1896,16 @@ async function localCandidatePreview(argv, io) {
1871
1896
  const inspection = localInspectionFromParsed(parsed);
1872
1897
  const snapshot = await inspection.snapshot();
1873
1898
  const candidateId = readCandidateIdFlag(parsed, snapshot);
1874
- const preview = await inspection.candidatePreview({
1899
+ const requestedPath = requireFlag(parsed, "path");
1900
+ const surface = await inspection.candidateFileSurface({
1875
1901
  id: candidateId,
1876
- path: requireFlag(parsed, "path"),
1902
+ path: requestedPath,
1877
1903
  view: readPreviewMode(parsed),
1878
1904
  });
1905
+ const preview = surface.preview;
1906
+ if (!preview || preview.path !== normalizeRelativePath(requestedPath)) {
1907
+ throw new UsageError(`File not found: ${requestedPath}`);
1908
+ }
1879
1909
  const content = preview.source?.content ?? preview.rendered_html ?? preview.diff ?? "";
1880
1910
  const outputPath = asOptionalString(parsed.flags.output);
1881
1911
  if (outputPath && outputPath !== "-") {
@@ -2010,46 +2040,21 @@ async function localDiagnose(argv, io) {
2010
2040
  return 0;
2011
2041
  }
2012
2042
  async function runAuthCommand(argv, io) {
2013
- const command = argv[0];
2014
- const rest = argv.slice(1);
2015
- switch (command) {
2016
- case "connect":
2017
- return await authConnect(rest, io);
2018
- case "disconnect":
2019
- return await authDisconnect(rest, io);
2020
- default:
2021
- throw new UsageError(`Unknown command: auth ${argv.join(" ")}`);
2022
- }
2043
+ return await runSubCommand("auth", AUTH_COMMAND_HANDLERS, argv, io);
2023
2044
  }
2024
2045
  async function runAdaptersCommand(argv, io) {
2025
- const command = argv[0];
2026
- const rest = argv.slice(1);
2027
- switch (command) {
2028
- case "create":
2029
- return await adaptersCreate(rest, io);
2030
- case "list":
2031
- return await adaptersList(rest, io);
2032
- case "inspect":
2033
- return await adaptersInspect(rest, io);
2034
- case "test":
2035
- return await adaptersTest(rest, io);
2036
- default:
2037
- throw new UsageError(`Unknown command: adapters ${argv.join(" ")}`);
2038
- }
2046
+ return await runSubCommand("adapters", ADAPTERS_COMMAND_HANDLERS, argv, io);
2039
2047
  }
2040
2048
  async function runTracesCommand(argv, io) {
2041
- const command = argv[0];
2042
- const rest = argv.slice(1);
2043
- switch (command) {
2044
- case "collect":
2045
- return await localTraceCollect(rest, io);
2046
- case "list":
2047
- return await localTraceList(rest, io);
2048
- case "show":
2049
- return await localTraceShow(rest, io);
2050
- default:
2051
- throw new UsageError(`Unknown command: traces ${argv.join(" ")}`);
2049
+ return await runSubCommand("traces", TRACES_COMMAND_HANDLERS, argv, io);
2050
+ }
2051
+ async function runSubCommand(group, handlers, argv, io) {
2052
+ const command = argv[0] ?? "";
2053
+ const handler = handlers[command];
2054
+ if (!handler) {
2055
+ throw new UsageError(`Unknown command: ${group} ${argv.join(" ")}`);
2052
2056
  }
2057
+ return await handler(argv.slice(1), io);
2053
2058
  }
2054
2059
  const DEFAULT_LOCAL_TRACE_LIMIT = 3;
2055
2060
  const LOCAL_TRACE_WINDOW_FLAGS = new Set(["providers", "since", "workspace", "limit", "json"]);
@@ -3057,7 +3062,7 @@ function adapterAuthRecord(value) {
3057
3062
  }
3058
3063
  async function pushBenchmark(argv, io) {
3059
3064
  const parsed = parseArgs(argv);
3060
- rejectUnknownFlags(parsed, new Set(["dir", "visibility", "dry-run", "json"]));
3065
+ rejectUnknownFlags(parsed, new Set(["dir", "visibility", "dry-run", "force", "json"]));
3061
3066
  const dir = resolveSourceDir(parsed);
3062
3067
  const source = await readLocalProjectSource(dir);
3063
3068
  const origin = await readWorkbenchOrigin(dir);
@@ -3065,17 +3070,18 @@ async function pushBenchmark(argv, io) {
3065
3070
  const visibility = readOptionalBenchmarkVisibility(parsed.flags.visibility);
3066
3071
  const createVisibility = visibility ?? "public";
3067
3072
  const dryRun = parsed.flags["dry-run"] === true;
3073
+ const force = parsed.flags.force === true;
3068
3074
  const runtime = await exportLocalRuntimeBundle(dir, {
3069
3075
  currentBenchmarkFingerprint: localBenchmarkFingerprint(source),
3070
3076
  });
3071
3077
  const localRuntimeFingerprint = workbenchRuntimeBundleFingerprint(runtime);
3072
- const state = localProjectState({
3073
- source,
3074
- runtime,
3075
- origin,
3076
- visibility: createVisibility,
3077
- });
3078
3078
  if (!origin) {
3079
+ const state = localProjectState({
3080
+ source,
3081
+ runtime,
3082
+ origin,
3083
+ visibility: createVisibility,
3084
+ });
3079
3085
  if (dryRun) {
3080
3086
  writeOutput({
3081
3087
  ok: true,
@@ -3123,30 +3129,135 @@ async function pushBenchmark(argv, io) {
3123
3129
  if (!projectId) {
3124
3130
  throw new UsageError("Missing remote benchmark. Run workbench push from a source directory.");
3125
3131
  }
3132
+ const remoteProject = force || dryRun
3133
+ ? await readOriginProjectIfPresent({ baseUrl, origin })
3134
+ : null;
3126
3135
  if (dryRun) {
3127
- const remoteProject = await verifyLinkedPushDryRunTarget({
3128
- baseUrl,
3129
- origin,
3130
- projectId,
3136
+ const state = localProjectState({
3137
+ source,
3138
+ runtime,
3139
+ origin: remoteProject
3140
+ ? { ...origin, projectId: remoteProject.id }
3141
+ : origin,
3142
+ visibility: visibility ?? remoteProject?.visibility ?? createVisibility,
3131
3143
  });
3144
+ if (!remoteProject && !force) {
3145
+ throw new WorkbenchApiRequestError(404, `Workbench benchmark not found: ${origin.remote}`, "");
3146
+ }
3147
+ if (!remoteProject && force) {
3148
+ await assertForceCreateAllowed({ baseUrl, origin, sourceName: source.spec.name });
3149
+ }
3150
+ const action = force && !remoteProject ? "create" : force ? "replace" : "update";
3132
3151
  writeOutput({
3133
3152
  ok: true,
3134
3153
  dryRun: true,
3135
- action: "update",
3154
+ action,
3136
3155
  dir,
3137
3156
  baseUrl,
3138
- benchmarkId: projectId,
3157
+ benchmarkId: remoteProject?.id ?? projectId,
3139
3158
  remote: origin.remote,
3140
- benchmark: remoteProjectSummaryForOutput(remoteProject),
3159
+ ...(remoteProject ? { benchmark: remoteProjectSummaryForOutput(remoteProject) } : {}),
3141
3160
  benchmarkName: source.spec.name,
3142
- visibility: visibility ?? "unchanged",
3161
+ visibility: visibility ?? (remoteProject ? remoteProject.visibility ?? "unchanged" : createVisibility),
3143
3162
  sourceFileCount: sourceFileCount(source),
3144
3163
  runtime: runtimeBundleStats(runtime),
3145
3164
  sourceFingerprint: state.source.fingerprint,
3146
3165
  runtimeFingerprint: localRuntimeFingerprint,
3147
- }, parsed, io, () => `Would push ${sourceFileCount(source)} source file(s) and runtime history to ${origin.remote}.`);
3166
+ }, parsed, io, () => force && !remoteProject
3167
+ ? `Would create ${origin.remote} from local project state.`
3168
+ : force
3169
+ ? `Would replace ${origin.remote} with local project state.`
3170
+ : `Would push ${sourceFileCount(source)} source file(s) and runtime history to ${origin.remote}.`);
3171
+ return 0;
3172
+ }
3173
+ if (force) {
3174
+ const targetOrigin = remoteProject
3175
+ ? { ...origin, projectId: remoteProject.id }
3176
+ : null;
3177
+ const state = localProjectState({
3178
+ source,
3179
+ runtime,
3180
+ origin: targetOrigin,
3181
+ visibility: visibility ?? remoteProject?.visibility ?? createVisibility,
3182
+ });
3183
+ if (!remoteProject) {
3184
+ await assertForceCreateAllowed({ baseUrl, origin, sourceName: source.spec.name });
3185
+ const { project, origin: nextOrigin, result } = await createRemoteBenchmarkFromState({
3186
+ baseUrl,
3187
+ dir,
3188
+ state,
3189
+ });
3190
+ writeOutput({
3191
+ ok: true,
3192
+ action: "create",
3193
+ benchmark: project,
3194
+ visibility: project.visibility ?? createVisibility,
3195
+ origin: nextOrigin,
3196
+ source: result.source,
3197
+ runtime: result.runtime.stats,
3198
+ urls: buildWorkbenchResourceUrls({
3199
+ baseUrl,
3200
+ projectId: project.id,
3201
+ ...originRemoteUrlParts(nextOrigin),
3202
+ }),
3203
+ }, parsed, io, (record) => {
3204
+ const value = record;
3205
+ return [
3206
+ `Pushed ${value.origin.remote} (${value.origin.projectId}).`,
3207
+ `Open benchmark: ${value.urls.benchmark}`,
3208
+ ].join("\n");
3209
+ });
3210
+ return 0;
3211
+ }
3212
+ const response = await apiRequest(projectApiPath(remoteProject.id, "/state?force=true"), {
3213
+ method: "PUT",
3214
+ body: state,
3215
+ }, baseUrl);
3216
+ assertAcceptedForceReplacement({
3217
+ expected: state,
3218
+ actual: response.state,
3219
+ });
3220
+ const responseProject = remoteProjectSummaryFromState(response.state);
3221
+ const publishedProject = await applyRequestedProjectVisibility({
3222
+ baseUrl,
3223
+ projectId: responseProject.id,
3224
+ responseProject,
3225
+ visibility,
3226
+ });
3227
+ const applied = await acceptPushedProjectStateToLocal({
3228
+ dir,
3229
+ baseUrl,
3230
+ state: response.state,
3231
+ });
3232
+ writeOutput({
3233
+ ok: true,
3234
+ action: "replace",
3235
+ changed: true,
3236
+ benchmark: publishedProject,
3237
+ visibility: visibility ?? publishedProject.visibility ?? "unchanged",
3238
+ origin: applied.origin,
3239
+ source: response.source,
3240
+ runtime: response.runtime.stats,
3241
+ urls: buildWorkbenchResourceUrls({
3242
+ baseUrl,
3243
+ projectId: publishedProject.id ?? responseProject.id,
3244
+ ...originRemoteUrlParts(applied.origin),
3245
+ }),
3246
+ }, parsed, io, (record) => {
3247
+ const value = record;
3248
+ return [
3249
+ `Replaced ${value.origin.remote} (${value.origin.projectId}).`,
3250
+ `Open benchmark: ${value.urls.benchmark}`,
3251
+ ].join("\n");
3252
+ });
3148
3253
  return 0;
3149
3254
  }
3255
+ const state = localProjectState({
3256
+ source,
3257
+ runtime,
3258
+ origin,
3259
+ visibility: createVisibility,
3260
+ });
3150
3261
  const response = await apiRequest(projectApiPath(projectId, "/state"), {
3151
3262
  method: "PUT",
3152
3263
  body: state,
@@ -3186,18 +3297,62 @@ async function pushBenchmark(argv, io) {
3186
3297
  });
3187
3298
  return 0;
3188
3299
  }
3189
- async function verifyLinkedPushDryRunTarget(args) {
3190
- const response = await apiRequest(projectApiPath(args.projectId), {}, args.baseUrl);
3191
- const expected = parseOriginRemote(args.origin);
3192
- const actualOwner = response.benchmark.ownerUsername;
3193
- const actualProject = response.benchmark.name;
3194
- if (actualOwner !== expected.owner || actualProject !== expected.project) {
3195
- const actualRemote = actualOwner && actualProject
3196
- ? `${actualOwner}/${actualProject}`
3197
- : "unknown";
3198
- throw new UsageError(`Workbench origin points to ${args.origin.remote}, but ${args.projectId} resolved to ${actualRemote}.`);
3300
+ async function readOriginProjectIfPresent(args) {
3301
+ try {
3302
+ const response = await apiRequest(publicProjectApiPath(parseOriginRemote(args.origin)), {}, args.baseUrl);
3303
+ return response.benchmark;
3199
3304
  }
3200
- return response.benchmark;
3305
+ catch (error) {
3306
+ if (error instanceof WorkbenchApiRequestError && error.status === 404) {
3307
+ return null;
3308
+ }
3309
+ throw error;
3310
+ }
3311
+ }
3312
+ async function assertForceCreateAllowed(args) {
3313
+ const target = parseOriginRemote(args.origin);
3314
+ if (args.sourceName !== target.project) {
3315
+ throw new UsageError(`Local benchmark name ${args.sourceName} does not match force push target ${args.origin.remote}.`);
3316
+ }
3317
+ const expectedOwner = target.owner;
3318
+ const profileStatus = await readWorkbenchProfileStatus(await loadConfig(), args.baseUrl);
3319
+ const actualOwner = profileStatus.profile?.username ?? null;
3320
+ if (!profileStatus.authenticated || actualOwner !== expectedOwner) {
3321
+ throw new WorkbenchApiRequestError(404, `Workbench benchmark not found or not writable: ${args.origin.remote}`, "");
3322
+ }
3323
+ }
3324
+ function assertAcceptedForceReplacement(args) {
3325
+ const expectedSourceFingerprint = args.expected.source.fingerprint ??
3326
+ workbenchProjectSourceFingerprint(args.expected.source);
3327
+ const actualSourceFingerprint = args.actual.source.fingerprint ??
3328
+ workbenchProjectSourceFingerprint(args.actual.source);
3329
+ if (actualSourceFingerprint !== expectedSourceFingerprint) {
3330
+ throw new Error(`Workbench force push did not replace remote source. Expected ${expectedSourceFingerprint}, received ${actualSourceFingerprint}.`);
3331
+ }
3332
+ const expectedRuntime = runtimeReplacementSignature(args.expected.runtime);
3333
+ const actualRuntime = runtimeReplacementSignature(args.actual.runtime);
3334
+ if (JSON.stringify(actualRuntime) !== JSON.stringify(expectedRuntime)) {
3335
+ throw new Error(`Workbench force push did not replace remote runtime. Remote returned different record identity.`);
3336
+ }
3337
+ }
3338
+ function runtimeReplacementSignature(runtime) {
3339
+ return {
3340
+ activeId: runtime.activeId ?? null,
3341
+ candidates: runtime.candidates
3342
+ .map(workbenchRuntimeCandidateIdentityForExchange)
3343
+ .sort((left, right) => left.id.localeCompare(right.id)),
3344
+ candidateFiles: runtimeFileGroupSignature(runtime.candidateFiles, (group) => group.candidateId),
3345
+ evaluations: runtime.evaluations.map((evaluation) => evaluation.id).sort(),
3346
+ runs: runtime.runs.map((run) => run.id).sort(),
3347
+ jobs: runtime.jobs.map((job) => job.id).sort(),
3348
+ executionFiles: runtimeFileGroupSignature(runtime.executionFiles, (group) => group.jobId),
3349
+ events: runtime.events.map((event) => event.id).sort(),
3350
+ };
3351
+ }
3352
+ function runtimeFileGroupSignature(groups, idForGroup) {
3353
+ return groups
3354
+ .map((group) => `${idForGroup(group)}:${group.files.map((file) => file.path).sort().join("\n")}`)
3355
+ .sort();
3201
3356
  }
3202
3357
  function remoteProjectSummaryForOutput(project) {
3203
3358
  return {
@@ -3974,7 +4129,7 @@ function buildWorkbenchResourceUrls(target, refs = {}) {
3974
4129
  ? evaluationScorecardId(refs.runId, refs.candidateId)
3975
4130
  : null;
3976
4131
  urls.candidateEvaluation = evaluationId
3977
- ? `${benchmark}/candidates/${encodeURIComponent(refs.candidateId)}?evaluation=${encodeURIComponent(evaluationId)}`
4132
+ ? `${benchmark}/evaluations/${encodeURIComponent(evaluationId)}`
3978
4133
  : `${benchmark}/candidates/${encodeURIComponent(refs.candidateId)}`;
3979
4134
  }
3980
4135
  return urls;
@@ -4087,10 +4242,10 @@ function localProjectState(args) {
4087
4242
  };
4088
4243
  }
4089
4244
  function projectStateRuntimeStats(state) {
4090
- const activeId = workbenchRuntimeExplicitActiveId({
4245
+ const activeId = workbenchRuntimeProjectedActiveId({
4091
4246
  candidates: state.runtime.candidates,
4247
+ evaluations: state.runtime.evaluations,
4092
4248
  runs: state.runtime.runs,
4093
- preferredActiveId: state.runtime.activeId ?? null,
4094
4249
  benchmarkFingerprint: projectStateBenchmarkFingerprint(state.source),
4095
4250
  });
4096
4251
  return runtimeBundleStats({
@@ -4420,11 +4575,11 @@ function selectWorkbenchBaseUrl(input = {}) {
4420
4575
  input.configBaseUrl ??
4421
4576
  DEFAULT_BASE_URL);
4422
4577
  }
4423
- async function readWorkbenchProfileStatus(config) {
4578
+ async function readWorkbenchProfileStatus(config, baseUrlOverride) {
4424
4579
  if (!config.accessToken) {
4425
4580
  return { authenticated: false, profile: null };
4426
4581
  }
4427
- const baseUrl = selectWorkbenchBaseUrl({ configBaseUrl: config.baseUrl });
4582
+ const baseUrl = baseUrlOverride ?? selectWorkbenchBaseUrl({ configBaseUrl: config.baseUrl });
4428
4583
  try {
4429
4584
  const response = await fetch(`${baseUrl}/api/workbench/profile`, {
4430
4585
  headers: {
@@ -4455,6 +4610,7 @@ async function apiRequest(apiPath, options = {}, baseUrlOverride) {
4455
4610
  : selectWorkbenchBaseUrl({ configBaseUrl: config.baseUrl });
4456
4611
  const method = options.method ?? "GET";
4457
4612
  const canRetry = method === "GET";
4613
+ const requestBody = encodeJsonRequestBody(options.body);
4458
4614
  let lastError = null;
4459
4615
  for (let attempt = 1; attempt <= API_REQUEST_MAX_ATTEMPTS; attempt += 1) {
4460
4616
  let response;
@@ -4462,12 +4618,12 @@ async function apiRequest(apiPath, options = {}, baseUrlOverride) {
4462
4618
  response = await fetch(`${baseUrl}${apiPath}`, {
4463
4619
  method,
4464
4620
  headers: {
4465
- "content-type": "application/json",
4621
+ ...requestBody.headers,
4466
4622
  ...(config.accessToken
4467
4623
  ? { authorization: `Bearer ${config.accessToken}` }
4468
4624
  : {}),
4469
4625
  },
4470
- body: options.body == null ? undefined : JSON.stringify(options.body),
4626
+ body: requestBody.body,
4471
4627
  });
4472
4628
  }
4473
4629
  catch (error) {
@@ -4493,6 +4649,22 @@ async function apiRequest(apiPath, options = {}, baseUrlOverride) {
4493
4649
  }
4494
4650
  throw lastError instanceof Error ? lastError : new Error(String(lastError ?? "Workbench API request failed."));
4495
4651
  }
4652
+ function encodeJsonRequestBody(body) {
4653
+ if (body == null) {
4654
+ return { headers: { "content-type": "application/json" } };
4655
+ }
4656
+ const text = JSON.stringify(body);
4657
+ if (Buffer.byteLength(text) < API_REQUEST_GZIP_THRESHOLD_BYTES) {
4658
+ return { body: text, headers: { "content-type": "application/json" } };
4659
+ }
4660
+ return {
4661
+ body: gzipSync(text),
4662
+ headers: {
4663
+ "content-encoding": "gzip",
4664
+ "content-type": "application/json",
4665
+ },
4666
+ };
4667
+ }
4496
4668
  function apiRequestRetryDelayMs(attempt) {
4497
4669
  return 250 * attempt;
4498
4670
  }
@@ -5068,28 +5240,12 @@ async function resolveLocalProjectForExecution(workspace, source) {
5068
5240
  };
5069
5241
  }
5070
5242
  function completedJobOutputFiles(job) {
5071
- const output = asJsonRecord(job.output);
5243
+ const output = jsonRecord(job.output);
5072
5244
  const files = Array.isArray(output.files)
5073
5245
  ? output.files.filter(isSurfaceSnapshotFile)
5074
5246
  : [];
5075
5247
  return normalizeSurfaceFiles(files);
5076
5248
  }
5077
- function asJsonRecord(value) {
5078
- return value && typeof value === "object" && !Array.isArray(value)
5079
- ? value
5080
- : {};
5081
- }
5082
- function isSurfaceSnapshotFile(value) {
5083
- const record = asJsonRecord(value);
5084
- return (typeof record.path === "string" &&
5085
- typeof record.content === "string" &&
5086
- (record.kind === undefined ||
5087
- record.kind === "text" ||
5088
- record.kind === "binary") &&
5089
- (record.encoding === undefined ||
5090
- record.encoding === "utf8" ||
5091
- record.encoding === "base64"));
5092
- }
5093
5249
  function createLocalEvent(type, at, event) {
5094
5250
  return {
5095
5251
  id: `evt_${Math.random().toString(36).slice(2, 10)}_${Date.now().toString(36)}`,
@@ -1 +1 @@
1
- {"version":3,"file":"local-archive.d.ts","sourceRoot":"","sources":["../src/local-archive.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"local-archive.d.ts","sourceRoot":"","sources":["../src/local-archive.ts"],"names":[],"mappings":"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"}