@workbench-ai/workbench-core 0.0.46

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.
Files changed (72) hide show
  1. package/dist/adapter-auth.d.ts +63 -0
  2. package/dist/adapter-auth.d.ts.map +1 -0
  3. package/dist/adapter-auth.js +244 -0
  4. package/dist/execution-events.d.ts +53 -0
  5. package/dist/execution-events.d.ts.map +1 -0
  6. package/dist/execution-events.js +195 -0
  7. package/dist/execution-graph.d.ts +27 -0
  8. package/dist/execution-graph.d.ts.map +1 -0
  9. package/dist/execution-graph.js +126 -0
  10. package/dist/execution-jobs.d.ts +70 -0
  11. package/dist/execution-jobs.d.ts.map +1 -0
  12. package/dist/execution-jobs.js +229 -0
  13. package/dist/execution-outputs.d.ts +9 -0
  14. package/dist/execution-outputs.d.ts.map +1 -0
  15. package/dist/execution-outputs.js +393 -0
  16. package/dist/execution-phases.d.ts +21 -0
  17. package/dist/execution-phases.d.ts.map +1 -0
  18. package/dist/execution-phases.js +262 -0
  19. package/dist/execution-runtime-types.d.ts +35 -0
  20. package/dist/execution-runtime-types.d.ts.map +1 -0
  21. package/dist/execution-runtime-types.js +1 -0
  22. package/dist/execution-scheduler.d.ts +31 -0
  23. package/dist/execution-scheduler.d.ts.map +1 -0
  24. package/dist/execution-scheduler.js +241 -0
  25. package/dist/execution-traces.d.ts +16 -0
  26. package/dist/execution-traces.d.ts.map +1 -0
  27. package/dist/execution-traces.js +164 -0
  28. package/dist/execution-usage.d.ts +12 -0
  29. package/dist/execution-usage.d.ts.map +1 -0
  30. package/dist/execution-usage.js +433 -0
  31. package/dist/generic-spec.d.ts +113 -0
  32. package/dist/generic-spec.d.ts.map +1 -0
  33. package/dist/generic-spec.js +656 -0
  34. package/dist/index.d.ts +160 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +2858 -0
  37. package/dist/model-prices-litellm.d.ts +9674 -0
  38. package/dist/model-prices-litellm.d.ts.map +1 -0
  39. package/dist/model-prices-litellm.js +9668 -0
  40. package/dist/runtime-utils.d.ts +18 -0
  41. package/dist/runtime-utils.d.ts.map +1 -0
  42. package/dist/runtime-utils.js +108 -0
  43. package/dist/sandbox-backends/docker.d.ts +5 -0
  44. package/dist/sandbox-backends/docker.d.ts.map +1 -0
  45. package/dist/sandbox-backends/docker.js +568 -0
  46. package/dist/sandbox-backends/index.d.ts +37 -0
  47. package/dist/sandbox-backends/index.d.ts.map +1 -0
  48. package/dist/sandbox-backends/index.js +79 -0
  49. package/dist/sandbox-backends/names.d.ts +6 -0
  50. package/dist/sandbox-backends/names.d.ts.map +1 -0
  51. package/dist/sandbox-backends/names.js +14 -0
  52. package/dist/sandbox-backends/template-images.d.ts +4 -0
  53. package/dist/sandbox-backends/template-images.d.ts.map +1 -0
  54. package/dist/sandbox-backends/template-images.js +48 -0
  55. package/dist/sandbox-inputs.d.ts +27 -0
  56. package/dist/sandbox-inputs.d.ts.map +1 -0
  57. package/dist/sandbox-inputs.js +220 -0
  58. package/dist/sandbox-plane.d.ts +89 -0
  59. package/dist/sandbox-plane.d.ts.map +1 -0
  60. package/dist/sandbox-plane.js +327 -0
  61. package/dist/subject-patch.d.ts +8 -0
  62. package/dist/subject-patch.d.ts.map +1 -0
  63. package/dist/subject-patch.js +63 -0
  64. package/dist/trace-files.d.ts +18 -0
  65. package/dist/trace-files.d.ts.map +1 -0
  66. package/dist/trace-files.js +94 -0
  67. package/environments/libreoffice-agent/Dockerfile +13 -0
  68. package/environments/libreoffice-python/Dockerfile +11 -0
  69. package/environments/node-22/Dockerfile +3 -0
  70. package/environments/python-3.12/Dockerfile +8 -0
  71. package/package.json +42 -0
  72. package/worker/sandbox-adapter-runner.cjs +275 -0
@@ -0,0 +1,35 @@
1
+ import type { HostedWorkbenchEnvironmentVersion, HostedWorkbenchJob, Json, SurfaceSnapshotFile, WorkbenchAdapterInvocation } from "@workbench-ai/workbench-contract";
2
+ import type { GenericRunSpec, WorkbenchEngineCase } from "./generic-spec.ts";
3
+ import type { WorkbenchExecutionProgressTarget } from "./execution-events.ts";
4
+ import type { WorkbenchAdapterAuthBundle } from "./adapter-auth.ts";
5
+ import type { WorkbenchAdapterOperation, WorkbenchAdapterManifest } from "@workbench-ai/workbench-protocol";
6
+ export interface WorkbenchExecutionRuntimeInput {
7
+ job: HostedWorkbenchJob;
8
+ spec: GenericRunSpec;
9
+ environmentVersion?: Pick<HostedWorkbenchEnvironmentVersion, "id" | "imageRef" | "sourceHash" | "spec">;
10
+ environmentDockerfile?: string;
11
+ baseFiles: readonly SurfaceSnapshotFile[];
12
+ engineResolveFiles: readonly SurfaceSnapshotFile[];
13
+ engineCases: readonly WorkbenchEngineCase[];
14
+ traceFiles?: readonly SurfaceSnapshotFile[];
15
+ now?: string;
16
+ adapterAuthProfiles?: readonly WorkbenchAdapterAuthBundle[];
17
+ adapterManifests?: readonly WorkbenchAdapterManifest[];
18
+ adapterAuthRoot?: string;
19
+ adapterAuthRequest?: Json;
20
+ adapterAuthEnv?: Record<string, string>;
21
+ progress?: WorkbenchExecutionProgressTarget;
22
+ runtimeRegistry?: string;
23
+ pullImages?: boolean;
24
+ workdir?: string;
25
+ workspaceRoot?: string;
26
+ }
27
+ export interface WorkbenchWorkloadPhaseCommand {
28
+ kind: "optimizer" | "runner" | "engine";
29
+ label: string;
30
+ operation: WorkbenchAdapterOperation;
31
+ adapter?: WorkbenchAdapterInvocation;
32
+ command?: string;
33
+ okExitCodes?: number[];
34
+ }
35
+ //# sourceMappingURL=execution-runtime-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execution-runtime-types.d.ts","sourceRoot":"","sources":["../src/execution-runtime-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iCAAiC,EACjC,kBAAkB,EAClB,IAAI,EACJ,mBAAmB,EACnB,0BAA0B,EAC3B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,KAAK,EACV,cAAc,EACd,mBAAmB,EACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EACV,gCAAgC,EACjC,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EACV,0BAA0B,EAC3B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EACV,yBAAyB,EACzB,wBAAwB,EACzB,MAAM,kCAAkC,CAAC;AAE1C,MAAM,WAAW,8BAA8B;IAC7C,GAAG,EAAE,kBAAkB,CAAC;IACxB,IAAI,EAAE,cAAc,CAAC;IACrB,kBAAkB,CAAC,EAAE,IAAI,CAAC,iCAAiC,EAAE,IAAI,GAAG,UAAU,GAAG,YAAY,GAAG,MAAM,CAAC,CAAC;IACxG,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,SAAS,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC1C,kBAAkB,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACnD,WAAW,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC5C,UAAU,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC5C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,mBAAmB,CAAC,EAAE,SAAS,0BAA0B,EAAE,CAAC;IAC5D,gBAAgB,CAAC,EAAE,SAAS,wBAAwB,EAAE,CAAC;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,IAAI,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,QAAQ,CAAC,EAAE,gCAAgC,CAAC;IAC5C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,6BAA6B;IAC5C,IAAI,EAAE,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,yBAAyB,CAAC;IACrC,OAAO,CAAC,EAAE,0BAA0B,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ import type { HostedWorkbenchJob } from "@workbench-ai/workbench-contract";
2
+ import { type SandboxProviderHostCost, type SandboxProviderRequestedResources, type WorkbenchSandboxProviderName } from "./sandbox-backends/index.ts";
3
+ export interface WorkbenchExecutionDagCapacity {
4
+ cpu: number;
5
+ memoryGb: number;
6
+ diskGb: number;
7
+ }
8
+ export interface WorkbenchExecutionDagResult {
9
+ jobs: HostedWorkbenchJob[];
10
+ maxConcurrency: number;
11
+ startedJobCount: number;
12
+ cancelledJobCount: number;
13
+ }
14
+ export interface WorkbenchExecutionDagRunInput {
15
+ jobs: readonly HostedWorkbenchJob[];
16
+ capacity: WorkbenchExecutionDagCapacity;
17
+ sandboxProvider: WorkbenchSandboxProviderName;
18
+ executeJob: (job: HostedWorkbenchJob) => Promise<HostedWorkbenchJob>;
19
+ now?: () => string;
20
+ onJobQueued?: (job: HostedWorkbenchJob) => void;
21
+ onJobStarted?: (job: HostedWorkbenchJob) => void;
22
+ onJobFinished?: (job: HostedWorkbenchJob) => void;
23
+ }
24
+ export declare function runWorkbenchExecutionDag(args: WorkbenchExecutionDagRunInput): Promise<WorkbenchExecutionDagResult>;
25
+ export declare function workbenchJobDependencies(job: HostedWorkbenchJob): string[];
26
+ export declare function workbenchJobResources(job: HostedWorkbenchJob): SandboxProviderRequestedResources;
27
+ export declare function workbenchJobHostCost(job: HostedWorkbenchJob, provider: WorkbenchSandboxProviderName): SandboxProviderHostCost;
28
+ export declare function addCapacity(left: WorkbenchExecutionDagCapacity, right: WorkbenchExecutionDagCapacity): WorkbenchExecutionDagCapacity;
29
+ export declare function subtractCapacity(left: WorkbenchExecutionDagCapacity, right: WorkbenchExecutionDagCapacity): WorkbenchExecutionDagCapacity;
30
+ export declare function capacityFits(available: WorkbenchExecutionDagCapacity, cost: WorkbenchExecutionDagCapacity): boolean;
31
+ //# sourceMappingURL=execution-scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execution-scheduler.d.ts","sourceRoot":"","sources":["../src/execution-scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAEnB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EAEL,KAAK,uBAAuB,EAC5B,KAAK,iCAAiC,EACtC,KAAK,4BAA4B,EAClC,MAAM,6BAA6B,CAAC;AAErC,MAAM,WAAW,6BAA6B;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,kBAAkB,EAAE,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,6BAA6B;IAC5C,IAAI,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACpC,QAAQ,EAAE,6BAA6B,CAAC;IACxC,eAAe,EAAE,4BAA4B,CAAC;IAC9C,UAAU,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACrE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAChD,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACjD,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,IAAI,CAAC;CACnD;AASD,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,6BAA6B,GAClC,OAAO,CAAC,2BAA2B,CAAC,CAuLtC;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,kBAAkB,GAAG,MAAM,EAAE,CAM1E;AAED,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,kBAAkB,GACtB,iCAAiC,CASnC;AAED,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,kBAAkB,EACvB,QAAQ,EAAE,4BAA4B,GACrC,uBAAuB,CAEzB;AAED,wBAAgB,WAAW,CACzB,IAAI,EAAE,6BAA6B,EACnC,KAAK,EAAE,6BAA6B,GACnC,6BAA6B,CAM/B;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,6BAA6B,EACnC,KAAK,EAAE,6BAA6B,GACnC,6BAA6B,CAM/B;AAED,wBAAgB,YAAY,CAC1B,SAAS,EAAE,6BAA6B,EACxC,IAAI,EAAE,6BAA6B,GAClC,OAAO,CAIT"}
@@ -0,0 +1,241 @@
1
+ import { sandboxProviderAdmissionForResources, } from "./sandbox-backends/index.js";
2
+ const RESOURCE_EPSILON = 1e-9;
3
+ export async function runWorkbenchExecutionDag(args) {
4
+ assertPositiveCapacity(args.capacity);
5
+ const now = args.now ?? (() => new Date().toISOString());
6
+ const jobsById = new Map();
7
+ const pending = new Map();
8
+ const terminal = new Map();
9
+ const running = new Map();
10
+ const dependencies = new Map();
11
+ const results = new Map();
12
+ const originalOrder = args.jobs.map((job) => job.id);
13
+ let activeCost = emptyCapacity();
14
+ let maxConcurrency = 0;
15
+ let startedJobCount = 0;
16
+ let cancelledJobCount = 0;
17
+ for (const job of args.jobs) {
18
+ if (jobsById.has(job.id)) {
19
+ throw new Error(`Execution DAG includes duplicate job id ${job.id}.`);
20
+ }
21
+ jobsById.set(job.id, job);
22
+ }
23
+ for (const job of args.jobs) {
24
+ const jobDependencies = workbenchJobDependencies(job);
25
+ dependencies.set(job.id, jobDependencies);
26
+ for (const dependencyId of jobDependencies) {
27
+ if (!jobsById.has(dependencyId)) {
28
+ throw new Error(`Job ${job.id} depends on unknown job ${dependencyId}.`);
29
+ }
30
+ }
31
+ if (isTerminalJob(job)) {
32
+ terminal.set(job.id, job);
33
+ results.set(job.id, job);
34
+ continue;
35
+ }
36
+ if (job.status !== "queued") {
37
+ throw new Error(`Job ${job.id} has unsupported initial DAG status ${job.status}.`);
38
+ }
39
+ pending.set(job.id, job);
40
+ args.onJobQueued?.(job);
41
+ }
42
+ while (pending.size > 0 || running.size > 0) {
43
+ const progressed = startReadyJobs();
44
+ if (running.size === 0) {
45
+ if (pending.size === 0) {
46
+ break;
47
+ }
48
+ const ready = readyPendingJobs();
49
+ if (ready.length > 0) {
50
+ const blocked = ready[0];
51
+ throw new Error(`Job ${blocked.id} requires ${formatCapacity(workbenchJobHostCost(blocked, args.sandboxProvider))}, ` +
52
+ `which exceeds available dev capacity ${formatCapacity(args.capacity)}.`);
53
+ }
54
+ throw new Error(`Execution DAG cannot make progress; remaining jobs have cyclic or blocked dependencies: ${[...pending.keys()].join(", ")}.`);
55
+ }
56
+ if (!progressed || pending.size > 0) {
57
+ await Promise.race([...running.values()].map((entry) => entry.promise));
58
+ }
59
+ }
60
+ return {
61
+ jobs: originalOrder.map((jobId) => {
62
+ const result = results.get(jobId);
63
+ if (!result) {
64
+ throw new Error(`Execution DAG finished without result for job ${jobId}.`);
65
+ }
66
+ return result;
67
+ }),
68
+ maxConcurrency,
69
+ startedJobCount,
70
+ cancelledJobCount,
71
+ };
72
+ function startReadyJobs() {
73
+ let progressed = false;
74
+ for (const job of [...pending.values()]) {
75
+ const dependencyStatus = dependencyTerminalStatus(job);
76
+ if (dependencyStatus === "blocked") {
77
+ continue;
78
+ }
79
+ if (dependencyStatus !== "ready") {
80
+ cancelPendingJob(job, dependencyStatus);
81
+ progressed = true;
82
+ continue;
83
+ }
84
+ const cost = workbenchJobHostCost(job, args.sandboxProvider);
85
+ const available = subtractCapacity(args.capacity, activeCost);
86
+ if (!capacityFits(available, cost)) {
87
+ continue;
88
+ }
89
+ pending.delete(job.id);
90
+ activeCost = addCapacity(activeCost, cost);
91
+ const startedAt = now();
92
+ const runningJob = {
93
+ ...job,
94
+ status: "running",
95
+ startedAt,
96
+ updatedAt: startedAt,
97
+ };
98
+ startedJobCount += 1;
99
+ maxConcurrency = Math.max(maxConcurrency, running.size + 1);
100
+ args.onJobStarted?.(runningJob);
101
+ const promise = finishJob(runningJob, cost);
102
+ running.set(job.id, { cost, promise });
103
+ progressed = true;
104
+ }
105
+ return progressed;
106
+ }
107
+ function readyPendingJobs() {
108
+ return [...pending.values()].filter((job) => dependencyTerminalStatus(job) === "ready");
109
+ }
110
+ function dependencyTerminalStatus(job) {
111
+ const jobDependencies = dependencies.get(job.id) ?? [];
112
+ let blocked = false;
113
+ for (const dependencyId of jobDependencies) {
114
+ const dependency = terminal.get(dependencyId);
115
+ if (!dependency) {
116
+ blocked = true;
117
+ continue;
118
+ }
119
+ if (dependency.status === "failed") {
120
+ return "failed";
121
+ }
122
+ if (dependency.status === "cancelled") {
123
+ return "cancelled";
124
+ }
125
+ if (dependency.status !== "succeeded") {
126
+ blocked = true;
127
+ }
128
+ }
129
+ return blocked ? "blocked" : "ready";
130
+ }
131
+ function cancelPendingJob(job, dependencyStatus) {
132
+ pending.delete(job.id);
133
+ const finishedAt = now();
134
+ const cancelled = {
135
+ ...job,
136
+ status: "cancelled",
137
+ updatedAt: finishedAt,
138
+ finishedAt,
139
+ error: `Dependency ${dependencyStatus}.`,
140
+ };
141
+ cancelledJobCount += 1;
142
+ terminal.set(job.id, cancelled);
143
+ results.set(job.id, cancelled);
144
+ args.onJobFinished?.(cancelled);
145
+ }
146
+ async function finishJob(runningJob, cost) {
147
+ let completed;
148
+ try {
149
+ completed = await args.executeJob(runningJob);
150
+ }
151
+ catch (error) {
152
+ const finishedAt = now();
153
+ completed = {
154
+ ...runningJob,
155
+ status: "failed",
156
+ updatedAt: finishedAt,
157
+ finishedAt,
158
+ error: error instanceof Error ? error.message : String(error),
159
+ };
160
+ }
161
+ finally {
162
+ activeCost = subtractCapacity(activeCost, cost);
163
+ running.delete(runningJob.id);
164
+ }
165
+ terminal.set(runningJob.id, completed);
166
+ results.set(runningJob.id, completed);
167
+ args.onJobFinished?.(completed);
168
+ }
169
+ }
170
+ export function workbenchJobDependencies(job) {
171
+ const input = jsonRecord(job.input);
172
+ const dependsOn = input.dependsOn;
173
+ return Array.isArray(dependsOn)
174
+ ? dependsOn.filter((entry) => typeof entry === "string")
175
+ : [];
176
+ }
177
+ export function workbenchJobResources(job) {
178
+ const resources = jsonRecord(jsonRecord(jsonRecord(job.input).execution).policy).resources;
179
+ const record = jsonRecord(resources);
180
+ return {
181
+ cpu: readPositiveResource(record.cpu, job.id, "resources.cpu"),
182
+ memoryGb: readPositiveResource(record.memoryGb, job.id, "resources.memoryGb"),
183
+ diskGb: readPositiveResource(record.diskGb, job.id, "resources.diskGb"),
184
+ timeoutMinutes: readPositiveResource(record.timeoutMinutes, job.id, "resources.timeoutMinutes"),
185
+ };
186
+ }
187
+ export function workbenchJobHostCost(job, provider) {
188
+ return sandboxProviderAdmissionForResources(provider, workbenchJobResources(job)).hostCost;
189
+ }
190
+ export function addCapacity(left, right) {
191
+ return {
192
+ cpu: left.cpu + right.cpu,
193
+ memoryGb: left.memoryGb + right.memoryGb,
194
+ diskGb: left.diskGb + right.diskGb,
195
+ };
196
+ }
197
+ export function subtractCapacity(left, right) {
198
+ return {
199
+ cpu: left.cpu - right.cpu,
200
+ memoryGb: left.memoryGb - right.memoryGb,
201
+ diskGb: left.diskGb - right.diskGb,
202
+ };
203
+ }
204
+ export function capacityFits(available, cost) {
205
+ return available.cpu + RESOURCE_EPSILON >= cost.cpu &&
206
+ available.memoryGb + RESOURCE_EPSILON >= cost.memoryGb &&
207
+ available.diskGb + RESOURCE_EPSILON >= cost.diskGb;
208
+ }
209
+ function emptyCapacity() {
210
+ return { cpu: 0, memoryGb: 0, diskGb: 0 };
211
+ }
212
+ function isTerminalJob(job) {
213
+ return job.status === "succeeded" || job.status === "failed" || job.status === "cancelled";
214
+ }
215
+ function assertPositiveCapacity(capacity) {
216
+ readPositiveNumber(capacity.cpu, "capacity.cpu");
217
+ readPositiveNumber(capacity.memoryGb, "capacity.memoryGb");
218
+ readPositiveNumber(capacity.diskGb, "capacity.diskGb");
219
+ }
220
+ function readPositiveResource(value, jobId, label) {
221
+ const number = typeof value === "number" ? value : Number(value);
222
+ if (!Number.isFinite(number) || number <= 0) {
223
+ throw new Error(`Job ${jobId} ${label} must be a positive number.`);
224
+ }
225
+ return number;
226
+ }
227
+ function readPositiveNumber(value, label) {
228
+ const number = typeof value === "number" ? value : Number(value);
229
+ if (!Number.isFinite(number) || number <= 0) {
230
+ throw new Error(`${label} must be a positive number.`);
231
+ }
232
+ return number;
233
+ }
234
+ function jsonRecord(value) {
235
+ return value && typeof value === "object" && !Array.isArray(value)
236
+ ? value
237
+ : {};
238
+ }
239
+ function formatCapacity(capacity) {
240
+ return `${capacity.cpu} CPU, ${capacity.memoryGb} GiB memory, ${capacity.diskGb} GiB disk`;
241
+ }
@@ -0,0 +1,16 @@
1
+ import type { HostedWorkbenchJob, WorkbenchExecutionTrace } from "@workbench-ai/workbench-contract";
2
+ export interface WorkbenchTraceMergeJob {
3
+ id: string;
4
+ trace: WorkbenchExecutionTrace;
5
+ }
6
+ export declare function finalizeWorkbenchExecutionTraceForJob(args: {
7
+ job: HostedWorkbenchJob;
8
+ stageId: string;
9
+ trace: WorkbenchExecutionTrace;
10
+ }): WorkbenchExecutionTrace;
11
+ export declare function mergeWorkbenchExecutionTracesByJob(args: {
12
+ traceIdPrefix: string;
13
+ stageId?: string | null;
14
+ jobs: readonly WorkbenchTraceMergeJob[];
15
+ }): WorkbenchExecutionTrace;
16
+ //# sourceMappingURL=execution-traces.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execution-traces.d.ts","sourceRoot":"","sources":["../src/execution-traces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAElB,uBAAuB,EACxB,MAAM,kCAAkC,CAAC;AAE1C,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,uBAAuB,CAAC;CAChC;AAED,wBAAgB,qCAAqC,CAAC,IAAI,EAAE;IAC1D,GAAG,EAAE,kBAAkB,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,uBAAuB,CAAC;CAChC,GAAG,uBAAuB,CAsD1B;AAED,wBAAgB,kCAAkC,CAAC,IAAI,EAAE;IACvD,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,SAAS,sBAAsB,EAAE,CAAC;CACzC,GAAG,uBAAuB,CAoC1B"}
@@ -0,0 +1,164 @@
1
+ export function finalizeWorkbenchExecutionTraceForJob(args) {
2
+ const status = traceStatusForJob(args.job.status);
3
+ const terminal = isTerminalTraceStatus(status);
4
+ const attempt = Math.max(1, args.job.attempt || 1);
5
+ const startedAt = args.job.startedAt ?? args.job.createdAt;
6
+ const endedAt = terminal
7
+ ? args.job.finishedAt ?? args.job.updatedAt ?? startedAt
8
+ : null;
9
+ const spans = args.trace.spans.map((span) => ({
10
+ ...span,
11
+ stage_id: args.stageId,
12
+ stage_run_index: null,
13
+ status: terminal && span.status === "running" ? status : span.status,
14
+ ended_at: terminal && !span.ended_at ? endedAt : span.ended_at,
15
+ }));
16
+ const events = args.trace.events.map((event) => ({
17
+ ...event,
18
+ stage_id: args.stageId,
19
+ stage_run_index: null,
20
+ }));
21
+ const summaries = args.trace.summaries.map((summary) => finalizeTraceSummary({
22
+ summary,
23
+ job: args.job,
24
+ status,
25
+ terminal,
26
+ attempt: Math.max(1, summary.attempt_number || attempt),
27
+ stageId: args.stageId,
28
+ startedAt,
29
+ endedAt,
30
+ spans,
31
+ }));
32
+ if (!summaries.some((summary) => sameSummaryStage(summary, attempt, args.stageId))) {
33
+ summaries.push(finalizeTraceSummary({
34
+ summary: null,
35
+ job: args.job,
36
+ status,
37
+ terminal,
38
+ attempt,
39
+ stageId: args.stageId,
40
+ startedAt,
41
+ endedAt,
42
+ spans,
43
+ }));
44
+ }
45
+ return {
46
+ trace_id: args.trace.trace_id,
47
+ spans,
48
+ events,
49
+ summaries,
50
+ };
51
+ }
52
+ export function mergeWorkbenchExecutionTracesByJob(args) {
53
+ const spans = [];
54
+ const events = [];
55
+ const summaries = [];
56
+ for (const job of args.jobs) {
57
+ const prefix = sanitizeTraceComponent(job.id);
58
+ spans.push(...job.trace.spans.map((span) => ({
59
+ ...span,
60
+ id: `${prefix}:${span.id}`,
61
+ parent_id: span.parent_id ? `${prefix}:${span.parent_id}` : null,
62
+ stage_id: args.stageId ?? span.stage_id,
63
+ stage_run_index: null,
64
+ attributes: withTraceJobId(span.attributes, job.id),
65
+ })));
66
+ events.push(...job.trace.events.map((event) => ({
67
+ ...event,
68
+ id: `${prefix}:${event.id}`,
69
+ span_id: `${prefix}:${event.span_id}`,
70
+ stage_id: args.stageId ?? event.stage_id,
71
+ stage_run_index: null,
72
+ attributes: withTraceJobId(event.attributes, job.id),
73
+ })));
74
+ summaries.push(...job.trace.summaries.map((summary) => ({
75
+ ...summary,
76
+ stage_id: args.stageId ?? summary.stage_id,
77
+ stage_run_index: null,
78
+ })));
79
+ }
80
+ return {
81
+ trace_id: `${args.traceIdPrefix}-${args.jobs.map((job) => sanitizeTraceComponent(job.id)).join("-")}`,
82
+ spans: spans.sort(compareTraceSpans),
83
+ events: events.sort(compareTraceEvents),
84
+ summaries: summaries.sort(compareTraceSummaries),
85
+ };
86
+ }
87
+ function withTraceJobId(attributes, jobId) {
88
+ return {
89
+ ...attributes,
90
+ job_id: jobId,
91
+ };
92
+ }
93
+ function compareTraceSpans(left, right) {
94
+ return (left.started_at.localeCompare(right.started_at) ||
95
+ left.id.localeCompare(right.id));
96
+ }
97
+ function compareTraceEvents(left, right) {
98
+ return left.at.localeCompare(right.at) || left.id.localeCompare(right.id);
99
+ }
100
+ function compareTraceSummaries(left, right) {
101
+ return (left.started_at.localeCompare(right.started_at) ||
102
+ traceSummaryKey(left).localeCompare(traceSummaryKey(right)));
103
+ }
104
+ function traceSummaryKey(summary) {
105
+ return [
106
+ summary.attempt_number,
107
+ summary.stage_id ?? "attempt",
108
+ summary.stage_run_index ?? "none",
109
+ ].join(":");
110
+ }
111
+ function sanitizeTraceComponent(value) {
112
+ return value.replaceAll(/[^A-Za-z0-9_-]+/g, "-");
113
+ }
114
+ function finalizeTraceSummary(args) {
115
+ const startedAt = args.summary?.started_at ?? args.startedAt;
116
+ const endedAt = args.terminal ? args.summary?.ended_at ?? args.endedAt : args.summary?.ended_at ?? null;
117
+ return {
118
+ attempt_number: args.attempt,
119
+ stage_id: args.stageId,
120
+ stage_run_index: null,
121
+ status: args.terminal ? args.status : args.summary?.status ?? args.status,
122
+ started_at: startedAt,
123
+ ended_at: endedAt,
124
+ duration_ms: endedAt
125
+ ? durationBetween(startedAt, endedAt)
126
+ : args.summary?.duration_ms ?? 0,
127
+ tool_call_count: args.summary?.tool_call_count ??
128
+ args.spans.filter((span) => span.kind === "tool_call").length,
129
+ input_tokens: args.summary?.input_tokens ?? null,
130
+ output_tokens: args.summary?.output_tokens ?? null,
131
+ usage: args.summary?.usage ?? null,
132
+ final_output_present: args.summary?.final_output_present ??
133
+ Boolean(args.job.output),
134
+ error_message: args.summary?.error_message ??
135
+ (typeof args.job.error === "string" ? args.job.error : null),
136
+ };
137
+ }
138
+ function sameSummaryStage(summary, attempt, stageId) {
139
+ return summary.attempt_number === attempt &&
140
+ (summary.stage_id == null || summary.stage_id === stageId) &&
141
+ summary.stage_run_index == null;
142
+ }
143
+ function traceStatusForJob(status) {
144
+ if (status === "succeeded") {
145
+ return "completed";
146
+ }
147
+ if (status === "failed") {
148
+ return "failed";
149
+ }
150
+ if (status === "cancelled") {
151
+ return "canceled";
152
+ }
153
+ return "running";
154
+ }
155
+ function isTerminalTraceStatus(status) {
156
+ return status === "completed" || status === "failed" || status === "canceled";
157
+ }
158
+ function durationBetween(startedAt, endedAt) {
159
+ const start = Date.parse(startedAt);
160
+ const end = Date.parse(endedAt);
161
+ return Number.isFinite(start) && Number.isFinite(end)
162
+ ? Math.max(0, end - start)
163
+ : 0;
164
+ }
@@ -0,0 +1,12 @@
1
+ import type { EvaluationUsageStats, ExecutionRole, ExecutionUsage, UsageSummary } from "@workbench-ai/workbench-contract";
2
+ export declare function extractExecutionUsageFromTrace(trace: unknown, provider: {
3
+ model?: string;
4
+ }, providerId: string, events?: readonly unknown[]): UsageSummary | undefined;
5
+ export declare function assignUsageRole(role: ExecutionRole, usage: UsageSummary | undefined): UsageSummary | undefined;
6
+ export declare function usageSummaryFromExecutionUsage(usage: ExecutionUsage | undefined): UsageSummary | undefined;
7
+ export declare function completeUsageSummary(usage: UsageSummary | undefined): UsageSummary | undefined;
8
+ export declare function normalizeUsageSummary(value: unknown): UsageSummary | undefined;
9
+ export declare function mergeUsageSummaries(summaries: readonly (UsageSummary | undefined)[]): UsageSummary | undefined;
10
+ export declare function mergeUsageRoles(roles: Partial<Record<ExecutionRole, UsageSummary | undefined>>): UsageSummary | undefined;
11
+ export declare function usageStats(summaries: readonly UsageSummary[]): EvaluationUsageStats | undefined;
12
+ //# sourceMappingURL=execution-usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execution-usage.d.ts","sourceRoot":"","sources":["../src/execution-usage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EACpB,aAAa,EACb,cAAc,EAId,YAAY,EACb,MAAM,kCAAkC,CAAC;AAiC1C,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAC5B,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,SAAS,OAAO,EAAO,GAC9B,YAAY,GAAG,SAAS,CAa1B;AAED,wBAAgB,eAAe,CAC7B,IAAI,EAAE,aAAa,EACnB,KAAK,EAAE,YAAY,GAAG,SAAS,GAC9B,YAAY,GAAG,SAAS,CAQ1B;AAED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,cAAc,GAAG,SAAS,GAChC,YAAY,GAAG,SAAS,CAE1B;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,YAAY,GAAG,SAAS,GAC9B,YAAY,GAAG,SAAS,CAmB1B;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CAY9E;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,SAAS,CAAC,YAAY,GAAG,SAAS,CAAC,EAAE,GAC/C,YAAY,GAAG,SAAS,CAc1B;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,YAAY,GAAG,SAAS,CAAC,CAAC,GAC9D,YAAY,GAAG,SAAS,CAS1B;AAED,wBAAgB,UAAU,CACxB,SAAS,EAAE,SAAS,YAAY,EAAE,GACjC,oBAAoB,GAAG,SAAS,CAQlC"}