@shardflux/sdk 0.10.2 → 0.11.1

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/http.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { RetryRecord } from './progress.js';
2
- export declare const SDK_VERSION = "0.10.2";
2
+ export declare const SDK_VERSION = "0.11.1";
3
3
  export interface RequestOptions {
4
4
  query?: Record<string, string | number | boolean | undefined | null>;
5
5
  json?: unknown;
@@ -29,17 +29,13 @@ export interface HttpOptions {
29
29
  }
30
30
  export declare const defaultSleep: (ms: number) => Promise<void>;
31
31
  /**
32
- * The fetch the SDK uses when none is given. On runtimes whose bundled undici is 8.x (Node 26) it sends
33
- * `Connection: close`: undici 8.9 intermittently stalls a request on a reused keep-alive connection after the
34
- * connection sat idle for a few seconds (measured against the staging API: 20.9 s and 26.3 s stalls in 50 requests
35
- * with 0.2-12 s gaps; Node 22 / undici 6.28 max 351 ms; Node 26 with `Connection: close` max 435 ms; node:http2 on the
36
- * same load balancer and gaps max 193 ms, and the load balancer's idle timeout is 3600 s, so it is not an idle-timeout
37
- * interaction). The CLI and MCP server do the same. Other runtimes keep the runtime's keep-alive pooling.
38
- * `SHARDFLUX_HTTP_KEEPALIVE=1` disables the workaround.
32
+ * The fetch the SDK uses when none is given. On Node 26 every request uses a fresh connection (`Connection: close`),
33
+ * as in the CLI and the MCP server; other runtimes reuse connections. `SHARDFLUX_HTTP_KEEPALIVE=1` reuses connections
34
+ * on Node 26 too.
39
35
  */
40
36
  export declare function defaultFetch(env?: Record<string, string | undefined> | undefined, undiciVersion?: string | undefined): typeof fetch;
41
37
  export declare function buildUrl(base: string, path: string, query?: RequestOptions['query']): string;
42
- /** `X-Tree-Revision` (file-first workspaces, contracts §29.8): a non-negative integer, else null. */
38
+ /** `X-Tree-Revision` (file-first workspaces): a non-negative integer, else null. */
43
39
  export declare function treeRevisionOf(headers: Headers): number | null;
44
40
  /** Turns a non-2xx response into a ShardfluxApiError (or a protocol error for undocumented bodies). */
45
41
  export declare function errorFrom(res: Response, source: 'api' | 'cell'): Promise<Error>;
@@ -61,10 +57,10 @@ export declare class HttpClient {
61
57
  body: T;
62
58
  }>;
63
59
  }
64
- /** Longest server-side wait the SDK asks for (contracts §3: servers cap `Prefer: wait` at 20 s). */
60
+ /** Longest server-side wait the SDK asks for (servers cap `Prefer: wait` at 20 s). */
65
61
  export declare const SERVER_WAIT_MAX_S = 20;
66
62
  /**
67
- * One bounded-wait poll (contracts §3): GET `path` with `Prefer: wait=<s>` when `waitS` >= 1. `applied` is true only
63
+ * One bounded-wait poll: GET `path` with `Prefer: wait=<s>` when `waitS` >= 1. `applied` is true only
68
64
  * when the server says it waited (Preference-Applied); otherwise the caller keeps its own backoff.
69
65
  */
70
66
  export declare function pollWithWait<T>(http: HttpClient, path: string, authorization: string, waitS: number, signal?: AbortSignal, onRetry?: RequestOptions['onRetry']): Promise<{
package/dist/http.js CHANGED
@@ -8,18 +8,15 @@
8
8
  */
9
9
  import { ShardfluxApiError, ShardfluxProtocolError, apiError, isErrorBody } from "./errors.js";
10
10
  import { describeFailure } from "./progress.js";
11
- export const SDK_VERSION = '0.10.2';
11
+ export const SDK_VERSION = '0.11.1';
12
12
  export const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
13
13
  /**
14
- * The fetch the SDK uses when none is given. On runtimes whose bundled undici is 8.x (Node 26) it sends
15
- * `Connection: close`: undici 8.9 intermittently stalls a request on a reused keep-alive connection after the
16
- * connection sat idle for a few seconds (measured against the staging API: 20.9 s and 26.3 s stalls in 50 requests
17
- * with 0.2-12 s gaps; Node 22 / undici 6.28 max 351 ms; Node 26 with `Connection: close` max 435 ms; node:http2 on the
18
- * same load balancer and gaps max 193 ms, and the load balancer's idle timeout is 3600 s, so it is not an idle-timeout
19
- * interaction). The CLI and MCP server do the same. Other runtimes keep the runtime's keep-alive pooling.
20
- * `SHARDFLUX_HTTP_KEEPALIVE=1` disables the workaround.
14
+ * The fetch the SDK uses when none is given. On Node 26 every request uses a fresh connection (`Connection: close`),
15
+ * as in the CLI and the MCP server; other runtimes reuse connections. `SHARDFLUX_HTTP_KEEPALIVE=1` reuses connections
16
+ * on Node 26 too.
21
17
  */
22
18
  export function defaultFetch(env = globalThis.process?.env, undiciVersion = globalThis.process?.versions?.undici) {
19
+ // Why a fresh connection on Node 26 (its bundled HTTP client, measured): docs/progress/startup-latency.md.
23
20
  const base = (input, init) => fetch(input, init);
24
21
  const major = Number((undiciVersion ?? '').split('.')[0]);
25
22
  if (!(major >= 8) || env?.SHARDFLUX_HTTP_KEEPALIVE === '1')
@@ -45,7 +42,7 @@ function retryAfterSeconds(res) {
45
42
  const n = Number(v);
46
43
  return Number.isFinite(n) && n >= 0 ? n : undefined;
47
44
  }
48
- /** `X-Tree-Revision` (file-first workspaces, contracts §29.8): a non-negative integer, else null. */
45
+ /** `X-Tree-Revision` (file-first workspaces): a non-negative integer, else null. */
49
46
  export function treeRevisionOf(headers) {
50
47
  const v = headers.get('x-tree-revision');
51
48
  if (v === null || !/^\d{1,19}$/.test(v))
@@ -171,10 +168,10 @@ export class HttpClient {
171
168
  }
172
169
  }
173
170
  }
174
- /** Longest server-side wait the SDK asks for (contracts §3: servers cap `Prefer: wait` at 20 s). */
171
+ /** Longest server-side wait the SDK asks for (servers cap `Prefer: wait` at 20 s). */
175
172
  export const SERVER_WAIT_MAX_S = 20;
176
173
  /**
177
- * One bounded-wait poll (contracts §3): GET `path` with `Prefer: wait=<s>` when `waitS` >= 1. `applied` is true only
174
+ * One bounded-wait poll: GET `path` with `Prefer: wait=<s>` when `waitS` >= 1. `applied` is true only
178
175
  * when the server says it waited (Preference-Applied); otherwise the caller keeps its own backoff.
179
176
  */
180
177
  export async function pollWithWait(http, path, authorization, waitS, signal, onRetry) {
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * const workspace = await cloud.workspaces.open({ key: `${customerId}/${projectId}`, template: 'python-node-browser' });
7
7
  * await agent.run({ input: userMessage, tools: workspaceTools(workspace) });
8
8
  *
9
- * File-first workspaces (`mode: 'file_first'`, contracts §29) keep a versioned file tree and run each command as an
9
+ * File-first workspaces (`mode: 'file_first'`) keep a versioned file tree and run each command as an
10
10
  * execution: `await workspace.executions.run(['bash', '-lc', 'pytest -q'])`.
11
11
  *
12
12
  * Types come from the committed OpenAPI documents: application API
@@ -34,7 +34,7 @@ export type WaitedLifecycleOptions = LifecycleOptions & {
34
34
  };
35
35
  /**
36
36
  * `resume()` options (0.9.0). With `wait`, the server holds the resume until the workspace runs and returns a tool token
37
- * with it (contracts §22.6): `agentLabel` and `tools` choose that token (defaults: a workspace handle's own, those given
37
+ * with it: `agentLabel` and `tools` choose that token (defaults: a workspace handle's own, those given
38
38
  * to open(); `workspaces.resume(id)` otherwise the key's tools and label `default`). A handle keeps it for its `cell()`
39
39
  * clients of the same label and tools; `workspaces.resume(id)` only attributes it.
40
40
  */
@@ -50,7 +50,7 @@ export declare const TRACE: unique symbol;
50
50
  /** Internal: work a workspace handle does after the operation finished, inside the same trace (refreshing its view). */
51
51
  export declare const AFTER_WAIT: unique symbol;
52
52
  /**
53
- * Internal: a workspace handle's part in a held resume (contracts §22.6): the tool token to ask for, and how it takes the
53
+ * Internal: a workspace handle's part in a held resume: the tool token to ask for, and how it takes the
54
54
  * running workspace and that token from a 200 (instead of reading the view and fetching a token afterwards).
55
55
  */
56
56
  export declare const HELD_RESUME: unique symbol;
package/dist/lifecycle.js CHANGED
@@ -5,7 +5,7 @@ export const TRACE = Symbol('shardflux.trace');
5
5
  /** Internal: work a workspace handle does after the operation finished, inside the same trace (refreshing its view). */
6
6
  export const AFTER_WAIT = Symbol('shardflux.afterWait');
7
7
  /**
8
- * Internal: a workspace handle's part in a held resume (contracts §22.6): the tool token to ask for, and how it takes the
8
+ * Internal: a workspace handle's part in a held resume: the tool token to ask for, and how it takes the
9
9
  * running workspace and that token from a 200 (instead of reading the view and fetching a token afterwards).
10
10
  */
11
11
  export const HELD_RESUME = Symbol('shardflux.heldResume');
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Lifecycle timing and progress. A slow open (0.8 s one day, 34 s the next) should say where the time went without a
3
- * packet capture: waiting for a host, the cell booting or restoring the VM, the network between the caller and the API,
4
- * the first tool token, or retries.
2
+ * Lifecycle timing and progress. Every open, resume and wake says where its time went, without a packet capture: the
3
+ * queue, the cell booting or restoring the VM, the network between the caller and the API, the first tool token, or
4
+ * retries.
5
5
  *
6
6
  * A Trace follows one SDK call (open, a lifecycle call with `wait`, a wake, a token fetch). It records two clocks and
7
7
  * never mixes them:
@@ -56,8 +56,8 @@ export interface ServerTiming {
56
56
  kind: Operation['kind'];
57
57
  state: Operation['state'];
58
58
  /**
59
- * created_at → started_at: waiting until the cell began running the operation, including any wait for a host with
60
- * capacity (started_at is set when the operation first enters `running`). Null when the API does not report
59
+ * created_at → started_at: waiting until the cell began running the operation, including any time in
60
+ * `capacity_pending` (started_at is set when the operation first enters `running`). Null when the API does not report
61
61
  * started_at or the operation has not run yet. An operation that finished without running (e.g. a failed dependency)
62
62
  * has started_at = completed_at, so all of its time shows here.
63
63
  */
@@ -70,8 +70,25 @@ export interface ServerTiming {
70
70
  startPath: string | null;
71
71
  /** `result.warm_fallback`: why a start that could be warm booted instead. */
72
72
  warmFallback: string | null;
73
- /** `result.resume_path`: `local_cache`, `prestaged` (copied to this host ahead of the resume, contracts §23) or `download` (the checkpoint had to be fetched first). */
73
+ /**
74
+ * `result.resume_path`: `local_cache`, `prestaged` (copied to this host ahead of the resume),
75
+ * `download` (the checkpoint had to be fetched first), `cold_boot` (0.11.0+: the saved disk was booted after a
76
+ * platform runtime change; processes restarted; see `memoryRestored`) or `reset_blank_layer` (the first start after a
77
+ * reset).
78
+ */
74
79
  resumePath: string | null;
80
+ /**
81
+ * `result.memory_restored` (0.11.0+): `true` when memory and running processes came back from the checkpoint;
82
+ * `false` when the workspace booted instead (`cold_boot`, `reset_blank_layer`): its files are as of the suspend, but
83
+ * every process was restarted, as after a reboot. `null` when the result does not say (an older API, or not a
84
+ * resume). Always set by `serverTiming()`; optional only so timings built by hand still type-check.
85
+ */
86
+ memoryRestored?: boolean | null;
87
+ /**
88
+ * `result.cold_boot_reason` (0.11.0+), with `resumePath` `cold_boot`: why the memory could not be restored, e.g.
89
+ * `runtime_changed` (the platform's VM runtime changed after the suspend). Null otherwise.
90
+ */
91
+ coldBootReason?: string | null;
75
92
  /** `result.boot_to_ready_ms`: VM start until the guest agent answered. */
76
93
  bootToReadyMs: number | null;
77
94
  /** `result.host_timings_ms`: the host's own steps (restore: load, after_restore, ready, …). */
@@ -106,8 +123,8 @@ interface EventBase {
106
123
  }
107
124
  /**
108
125
  * - `phase`: a phase began (live progress: "capacity_pending: no_ready_host"). `tool` phases come from tool calls.
109
- * A `capacity_pending` phase carries `deadlineAt` (0.6.2+, when the API reports it): when the start gives up waiting
110
- * for a host and fails with `capacity_unavailable` (retryable; nothing was started).
126
+ * A `capacity_pending` phase carries `deadlineAt` (0.6.2+, when the API reports it): the start's deadline, past which
127
+ * it fails with `capacity_unavailable` (retryable; nothing was started).
111
128
  * - `retry`: a request is retried after a transient failure (also emitted for tool calls, action `tool`).
112
129
  * - `done`: the call ended, successfully or not, with its full timing.
113
130
  */
@@ -129,9 +146,9 @@ export declare function emitTo(listeners: ReadonlyArray<ProgressListener | undef
129
146
  /** Combines listeners (client-level and per call) into one; undefined when there are none. */
130
147
  export declare function combineListeners(...listeners: Array<ProgressListener | undefined>): ProgressListener | undefined;
131
148
  /**
132
- * When a start waiting in `capacity_pending` gives up: the operation's `error.details.deadline_at` (RFC 3339). A start no
133
- * host admits by then fails with `capacity_unavailable` (retryable; nothing was started). Null in any other state, or
134
- * from an API that does not report it.
149
+ * The deadline of a start in `capacity_pending`: the operation's `error.details.deadline_at` (RFC 3339). A start still
150
+ * queued then fails with `capacity_unavailable` (retryable; nothing was started). Null in any other state, or from an
151
+ * API that does not report it.
135
152
  */
136
153
  export declare function capacityDeadlineOf(op: Operation): string | null;
137
154
  /** Server timing from an operation as GET /v1/operations/{id} returns it. */
@@ -152,7 +169,7 @@ export declare class Trace {
152
169
  get finished(): LifecycleTiming | null;
153
170
  /**
154
171
  * Enters a sequential phase (closing the current one). The same phase and reason again is not a new phase.
155
- * `deadlineAt`: when a `capacity_pending` start gives up (added to the event only).
172
+ * `deadlineAt`: the deadline of a `capacity_pending` start (added to the event only).
156
173
  */
157
174
  phase(phase: LifecyclePhase, reason?: string | null, deadlineAt?: string | null): void;
158
175
  /** Runs `fn` as a phase that may overlap others (open() reads the view and issues the token together). */
@@ -168,13 +185,16 @@ export declare class Trace {
168
185
  /** Runs `fn` under `trace` and ends the trace either way (the error keeps its timing). */
169
186
  export declare function traced<T>(trace: Trace, fn: () => Promise<T>): Promise<T>;
170
187
  /**
171
- * A human-readable account of a timing, for logs and bug reports:
188
+ * A human-readable account of a timing, for logs and bug reports. A resume in production:
189
+ *
190
+ * resume 413 ms, succeeded (workspace <id>, operation <id>)
191
+ * client: request 218 ms → queued 195 ms
192
+ * server: queued 51 ms, ran 290 ms, total 341 ms; resume from local_cache, boot to ready 211 ms, host disk 0 ms, load 6 ms, ready 62 ms, total 211 ms, after_restore 36 ms
193
+ * outside the server: 72 ms
172
194
  *
173
- * open 34.18 s, succeeded (workspace <id>, operation <id>)
174
- * client: request 20.01 s (held) → capacity_pending 13.52 s (no_ready_host) → running 590 ms → view 42 ms ∥ token 61 ms
175
- * server: queued 33.40 s, ran 620 ms, total 34.02 s; start warm, boot to ready 79 ms
176
- * outside the server: 161 ms
177
- * retries: 1 (POST /v1/workspaces/open, HTTP 503 unavailable, after 200 ms)
195
+ * A call that retried a request adds `retries: <n> (<request>, <cause>, after <delay>)`.
196
+ * A resume that booted the workspace instead of restoring its memory (0.11.0+) says so in the server line:
197
+ * `resume from cold_boot: processes restarted (runtime_changed)`.
178
198
  */
179
199
  export declare function formatTiming(t: LifecycleTiming): string;
180
200
  export {};
package/dist/progress.js CHANGED
@@ -31,9 +31,9 @@ function diffMs(from, to) {
31
31
  const str = (v) => (typeof v === 'string' && v.length > 0 ? v : null);
32
32
  const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null);
33
33
  /**
34
- * When a start waiting in `capacity_pending` gives up: the operation's `error.details.deadline_at` (RFC 3339). A start no
35
- * host admits by then fails with `capacity_unavailable` (retryable; nothing was started). Null in any other state, or
36
- * from an API that does not report it.
34
+ * The deadline of a start in `capacity_pending`: the operation's `error.details.deadline_at` (RFC 3339). A start still
35
+ * queued then fails with `capacity_unavailable` (retryable; nothing was started). Null in any other state, or from an
36
+ * API that does not report it.
37
37
  */
38
38
  export function capacityDeadlineOf(op) {
39
39
  if (op.state !== 'capacity_pending')
@@ -63,6 +63,9 @@ export function serverTiming(op) {
63
63
  startPath: str(r.start_path),
64
64
  warmFallback: str(r.warm_fallback),
65
65
  resumePath: str(r.resume_path),
66
+ // Unknown (null) unless the result says so: a result without the field is never read as "not restored".
67
+ memoryRestored: typeof r.memory_restored === 'boolean' ? r.memory_restored : null,
68
+ coldBootReason: str(r.cold_boot_reason),
66
69
  bootToReadyMs: num(r.boot_to_ready_ms),
67
70
  hostTimingsMs,
68
71
  };
@@ -128,7 +131,7 @@ export class Trace {
128
131
  }
129
132
  /**
130
133
  * Enters a sequential phase (closing the current one). The same phase and reason again is not a new phase.
131
- * `deadlineAt`: when a `capacity_pending` start gives up (added to the event only).
134
+ * `deadlineAt`: the deadline of a `capacity_pending` start (added to the event only).
132
135
  */
133
136
  phase(phase, reason = null, deadlineAt = null) {
134
137
  if (this.#timing)
@@ -215,13 +218,16 @@ export async function traced(trace, fn) {
215
218
  }
216
219
  const fmt = (ms) => (ms === null ? '?' : ms < 1000 ? `${Math.round(ms)} ms` : `${(ms / 1000).toFixed(2)} s`);
217
220
  /**
218
- * A human-readable account of a timing, for logs and bug reports:
221
+ * A human-readable account of a timing, for logs and bug reports. A resume in production:
219
222
  *
220
- * open 34.18 s, succeeded (workspace <id>, operation <id>)
221
- * client: request 20.01 s (held) → capacity_pending 13.52 s (no_ready_host) → running 590 ms → view 42 ms ∥ token 61 ms
222
- * server: queued 33.40 s, ran 620 ms, total 34.02 s; start warm, boot to ready 79 ms
223
- * outside the server: 161 ms
224
- * retries: 1 (POST /v1/workspaces/open, HTTP 503 unavailable, after 200 ms)
223
+ * resume 413 ms, succeeded (workspace <id>, operation <id>)
224
+ * client: request 218 ms → queued 195 ms
225
+ * server: queued 51 ms, ran 290 ms, total 341 ms; resume from local_cache, boot to ready 211 ms, host disk 0 ms, load 6 ms, ready 62 ms, total 211 ms, after_restore 36 ms
226
+ * outside the server: 72 ms
227
+ *
228
+ * A call that retried a request adds `retries: <n> (<request>, <cause>, after <delay>)`.
229
+ * A resume that booted the workspace instead of restoring its memory (0.11.0+) says so in the server line:
230
+ * `resume from cold_boot: processes restarted (runtime_changed)`.
225
231
  */
226
232
  export function formatTiming(t) {
227
233
  const ids = [t.workspaceId ? `workspace ${t.workspaceId}` : null, t.operationId ? `operation ${t.operationId}` : null].filter(Boolean).join(', ');
@@ -242,7 +248,7 @@ export function formatTiming(t) {
242
248
  const parts = [s.queuedMs !== null ? `queued ${fmt(s.queuedMs)}` : null, s.runMs !== null ? `ran ${fmt(s.runMs)}` : null, s.totalMs !== null ? `total ${fmt(s.totalMs)}` : `still ${s.state}`];
243
249
  const how = [
244
250
  s.startPath ? `start ${s.startPath}${s.warmFallback ? ` (warm fallback: ${s.warmFallback})` : ''}` : null,
245
- s.resumePath ? `resume from ${s.resumePath}` : null,
251
+ s.resumePath ? `resume from ${s.resumePath}${s.memoryRestored === false ? `: processes restarted${s.coldBootReason ? ` (${s.coldBootReason})` : ''}` : ''}` : null,
246
252
  s.bootToReadyMs !== null ? `boot to ready ${fmt(s.bootToReadyMs)}` : null,
247
253
  s.hostTimingsMs ? `host ${Object.entries(s.hostTimingsMs).map(([k, v]) => `${k} ${fmt(v)}`).join(', ')}` : null,
248
254
  ].filter(Boolean);
package/dist/tar.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * A small tar writer for build uploads of folders (contracts §24.2: uncompressed ustar/pax, extracted by the host's
2
+ * A small tar writer for build uploads of folders (uncompressed ustar/pax, extracted by the host's
3
3
  * static tool). Pure: no Node imports, so the browser bundle can carry it.
4
4
  *
5
5
  * The bytes equal CPython's `tarfile.open(mode="w", format=tarfile.PAX_FORMAT)` with every member added by hand (the
package/dist/tar.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * A small tar writer for build uploads of folders (contracts §24.2: uncompressed ustar/pax, extracted by the host's
2
+ * A small tar writer for build uploads of folders (uncompressed ustar/pax, extracted by the host's
3
3
  * static tool). Pure: no Node imports, so the browser bundle can carry it.
4
4
  *
5
5
  * The bytes equal CPython's `tarfile.open(mode="w", format=tarfile.PAX_FORMAT)` with every member added by hand (the
@@ -22,7 +22,7 @@ export declare class TemplateFileError extends Error {
22
22
  readonly path: string | undefined;
23
23
  constructor(message: string, path?: string);
24
24
  }
25
- /** Largest single upload (contracts §24.2: 5 GiB, one presigned PUT). */
25
+ /** Largest single upload (5 GiB, one presigned PUT). */
26
26
  export declare const UPLOAD_BYTES_MAX = 5368709120;
27
27
  /** Most entries a folder tar may have (the host's extraction limit, §24.2). */
28
28
  export declare const TAR_ENTRIES_MAX = 200000;
@@ -33,7 +33,7 @@ export class TemplateFileError extends Error {
33
33
  this.path = path;
34
34
  }
35
35
  }
36
- /** Largest single upload (contracts §24.2: 5 GiB, one presigned PUT). */
36
+ /** Largest single upload (5 GiB, one presigned PUT). */
37
37
  export const UPLOAD_BYTES_MAX = 5_368_709_120;
38
38
  /** Most entries a folder tar may have (the host's extraction limit, §24.2). */
39
39
  export const TAR_ENTRIES_MAX = 200_000;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Template registry, custom template builds (recipe v1 Dockerfiles and recipe v2, contracts §24.1), build uploads
2
+ * Template registry, custom template builds (recipe v1 Dockerfiles and recipe v2), build uploads
3
3
  * (§24.2), recipe export, version test instances, package search, the version file tree and diff, and template dev
4
4
  * mode (drafts and test instances) over the application API (/v1). Registry and build types are written by hand and
5
5
  * checked against the generated contract in type-checks.ts; the Templates v2 and template editor types alias the
@@ -15,7 +15,7 @@ import type { YamlParser } from './template-file.js';
15
15
  import type { ToolName } from './tokens.js';
16
16
  import { Workspace } from './workspace.js';
17
17
  type S = components['schemas'];
18
- /** Recipe v2 (contracts §24.1): the `recipe` of a build, the export's `recipe` and the document of template.yaml. */
18
+ /** Recipe v2: the `recipe` of a build, the export's `recipe` and the document of template.yaml. */
19
19
  export type TemplateRecipeV2 = S['TemplateRecipeV2'];
20
20
  /** One `build.files[]` entry: an upload (`upload: "sha256:<hex>"`), or in template.yaml a local path (`from`). */
21
21
  export type TemplateRecipeV2File = NonNullable<TemplateRecipeV2['build']['files']>[number];
@@ -40,7 +40,7 @@ export type TemplateVersionRecipe = S['TemplateVersionRecipe'];
40
40
  export type TemplatePackage = S['TemplatePackage'];
41
41
  export type TemplatePackagePage = S['TemplatePackagePage'];
42
42
  export type TemplatePackageEcosystem = 'apt' | 'pip' | 'npm';
43
- /** The languages a base offers a recipe v2 (contracts §24.6 `GET …/template-languages`): the language table for its platform base. */
43
+ /** The languages a base offers a recipe v2 (`GET …/template-languages`): the language table for its platform base. */
44
44
  export type TemplateLanguages = S['TemplateLanguages'];
45
45
  export type TemplateLanguage = TemplateLanguages['data'][number];
46
46
  export type CreateVersionTestInstanceBody = S['CreateVersionTestInstanceBody'];
@@ -52,18 +52,18 @@ export type TemplateBuildRecipeV2 = Extract<NonNullable<S['TemplateBuild']['reci
52
52
  }>;
53
53
  /** Start commands and services of a workspace's version (§24.4); null when it has none. */
54
54
  export type WorkspaceStartup = S['WorkspaceStartup'];
55
- /** Manifest v2 `defaults` of a version (contracts §19.7). */
55
+ /** Manifest v2 `defaults` of a version. */
56
56
  export type TemplateDefaults = S['TemplateDefaults'];
57
57
  /** How a version was produced: a recipe build, a saved workspace (or draft publish), or git (reserved). */
58
58
  export type TemplateSource = S['TemplateSource'];
59
59
  /** The version's file list state (the tree and diff routes read it). */
60
60
  export type TemplateFilesSummary = S['TemplateFilesSummary'];
61
- /** Storage of a template version (contracts §19.13). */
61
+ /** Storage of a template version. */
62
62
  export type TemplateStorage = S['TemplateStorage'];
63
63
  export type TemplateStorageWarning = S['TemplateStorageWarning'];
64
64
  /** Template storage of an organization (counts toward retained_state_gib). */
65
65
  export type OrgTemplateStorage = S['OrgTemplateStorage'];
66
- /** One inode path of a template version (contracts §19.10). */
66
+ /** One inode path of a template version. */
67
67
  export type TemplateFileEntry = S['TemplateFileEntry'];
68
68
  /** One directory level of a version's tree (keyset-paginated). */
69
69
  export type TemplateFilePage = S['TemplateFilePage'];
@@ -72,7 +72,7 @@ export type TemplateDiffEntry = S['TemplateDiffEntry'];
72
72
  export type TemplateDiffChange = TemplateDiffEntry['change'];
73
73
  /** One page of a version diff; the first page (no cursor) carries `summary`. */
74
74
  export type TemplateDiffPage = S['TemplateDiffPage'];
75
- /** The live draft of an organization template (contracts §19.9). */
75
+ /** The live draft of an organization template. */
76
76
  export type TemplateDraft = S['TemplateDraft'];
77
77
  /** A disk-only capture of the draft (a test instance or a publish starts from one). */
78
78
  export type DraftState = S['DraftState'];
@@ -164,7 +164,7 @@ export interface TemplateVersion {
164
164
  rootfs_sha256: string | null;
165
165
  } | null;
166
166
  defaults: TemplateDefaults;
167
- /** What a workspace of this version gets when it opens (0.7.0; contracts §24.3). `defaults` equals `settings.defaults`. */
167
+ /** What a workspace of this version gets when it opens (0.7.0). `defaults` equals `settings.defaults`. */
168
168
  settings: TemplateSettings;
169
169
  /** The file list the tree and diff read (`loaded` when files() and diff() can answer). */
170
170
  files: TemplateFilesSummary;
@@ -199,7 +199,7 @@ export interface TemplateSummary {
199
199
  created_at: string;
200
200
  archived_at: string | null;
201
201
  shadowed_by_organization_template: boolean;
202
- /** Mutable template-level defaults (contracts §20.5, §20.7): the size of a start without caps.memory_mib, and the idle policy of a workspace that sets none. */
202
+ /** Mutable template-level defaults: the size of a start without caps.memory_mib, and the idle policy of a workspace that sets none. */
203
203
  defaults: {
204
204
  memory_mib: number | null;
205
205
  idle_policy: string | null;
@@ -347,7 +347,7 @@ export interface TemplateBuild {
347
347
  };
348
348
  publishable: boolean;
349
349
  builder_availability: BuilderAvailability;
350
- /** `recipe` (Dockerfile dialect) or `workspace` (save-as-template, draft publish; contracts §19.8). */
350
+ /** `recipe` (Dockerfile dialect) or `workspace` (save-as-template, draft publish). */
351
351
  source_kind: 'recipe' | 'workspace';
352
352
  source_workspace_id: string | null;
353
353
  /** The captured checkpoint (null until the capture operation succeeded). */
@@ -385,7 +385,7 @@ export interface CreateTemplateBuildParams {
385
385
  displayName?: string;
386
386
  /**
387
387
  * Recipe v1 (a Dockerfile) or recipe v2 (0.7.0; `schema: "shardflux.template-recipe.v2"`: languages, packages,
388
- * uploaded files, steps, auto network and settings; contracts §24.1). Recipe v2 files reference uploads
388
+ * uploaded files, steps, auto network and settings). Recipe v2 files reference uploads
389
389
  * (`templates.uploads.put()`); `buildFromFile()` / `buildFromRecipe()` upload local `from` paths for you.
390
390
  */
391
391
  recipe: TemplateRecipe | TemplateRecipeV2;
@@ -411,7 +411,7 @@ export interface WaitForBuildOptions {
411
411
  /** Backoff used when the server does not hold the poll (default 1 s doubling to 10 s). */
412
412
  pollIntervalMs?: number;
413
413
  maxPollIntervalMs?: number;
414
- /** Ask the server to hold each poll until the build changes (`Prefer: wait`, contracts §3; default true). */
414
+ /** Ask the server to hold each poll until the build changes (`Prefer: wait`; default true). */
415
415
  serverWait?: boolean;
416
416
  signal?: AbortSignal;
417
417
  /** Called with each build view the wait observes whose state or registration changed (0.7.0), the settled one included. */
@@ -429,7 +429,7 @@ export interface SaveAsTemplateResponse {
429
429
  operation: Operation | null;
430
430
  build: TemplateBuild;
431
431
  }
432
- /** Save-as-template (contracts §19.8): everything in the workspace, minus the sf-scrub.v1 list, becomes one new org layer. */
432
+ /** Save-as-template: everything in the workspace, minus the sf-scrub.v1 list, becomes one new org layer. */
433
433
  export interface SaveAsTemplateParams {
434
434
  /** Organization template to save into (created when absent; platform slugs are refused with platform_template_slug). */
435
435
  templateSlug: string;
@@ -440,7 +440,7 @@ export interface SaveAsTemplateParams {
440
440
  /** Defaults of the new version (default: the source version's, else persistent). */
441
441
  defaults?: TemplateDefaultsInput;
442
442
  /**
443
- * Settings of the new version (0.7.0; contracts §24.3): each field given replaces that field of the source version's
443
+ * Settings of the new version (0.7.0): each field given replaces that field of the source version's
444
444
  * settings, each field left out is carried forward. `settings.defaults` together with `defaults` is 422 invalid_settings.
445
445
  */
446
446
  settings?: TemplateSettingsInput;
@@ -536,7 +536,7 @@ export interface DraftOpened {
536
536
  operation: Operation | null;
537
537
  }
538
538
  /**
539
- * Template dev mode for one organization template (contracts §19.9): the single live draft (a layered, persistent
539
+ * Template dev mode for one organization template: the single live draft (a layered, persistent
540
540
  * workspace on the draft base), its captured states, disposable test instances (session workspaces on a copy of a
541
541
  * state) and publishing the draft as the next version. Mutations need build access (owners/admins, API keys with a
542
542
  * tool permission); others get 403 forbidden with details.reason template_dev_mode_role.
@@ -654,7 +654,7 @@ export declare class TemplateUploadError extends Error {
654
654
  constructor(sha256: string, status: number, code: string | null, detail?: string);
655
655
  }
656
656
  /**
657
- * Build uploads (contracts §24.2): files and folders a recipe v2 copies into the template, stored once per
657
+ * Build uploads: files and folders a recipe v2 copies into the template, stored once per
658
658
  * organization and content (SHA-256). Needs build access (API keys with a tool permission). Uploads count toward the
659
659
  * organization's template storage while they exist; one nothing references is deleted 7 days later.
660
660
  */
@@ -795,7 +795,7 @@ export interface CreateVersionTestInstanceParams {
795
795
  idempotencyKey?: string;
796
796
  }
797
797
  /**
798
- * Test instances of a version (contracts §24.6): a session workspace on a registered version of the organization's
798
+ * Test instances of a version: a session workspace on a registered version of the organization's
799
799
  * template, published or not, so a build can be tried before it is published. Owners, admins and API keys with a
800
800
  * tool permission (403 template_dev_mode_role otherwise).
801
801
  */
@@ -805,7 +805,7 @@ export declare class TemplateVersionTestInstancesApi {
805
805
  /** Opens one (202; waits until ready unless `wait: false`). It ends with close() or when idle. */
806
806
  create(slug: string, version: number, params?: CreateVersionTestInstanceParams): Promise<Workspace>;
807
807
  }
808
- /** Package names for the editor's pickers (contracts §24.6): apt (a base's index), pip (names only) and npm. */
808
+ /** Package names for the editor's pickers: apt (a base's index), pip (names only) and npm. */
809
809
  export declare class TemplatePackagesApi {
810
810
  #private;
811
811
  constructor(ctx: () => ClientContext);
@@ -848,7 +848,7 @@ export declare class TemplatesApi {
848
848
  organizationId?: string;
849
849
  }): Promise<TemplateLanguages>;
850
850
  /**
851
- * Node only: builds a template from template.yaml (or a .json file with the same document; contracts §24.1). The
851
+ * Node only: builds a template from template.yaml (or a .json file with the same document). The
852
852
  * file is recipe v2; each `build.files[]` entry may name a local `from` path (relative to the file): folders are
853
853
  * packed as the reproducible tar, files are sent as they are, both uploaded unless the organization already has
854
854
  * them, and the build is created (and awaited with `wait`). YAML needs the optional `yaml` package or `parseYaml`.
@@ -885,7 +885,7 @@ export declare class TemplatesApi {
885
885
  owner?: TemplateOwner;
886
886
  }): Promise<TemplateDetail | null>;
887
887
  /**
888
- * One directory level of a version's file tree (contracts §19.10), sorted by name bytes. 409 conflict with
888
+ * One directory level of a version's file tree, sorted by name bytes. 409 conflict with
889
889
  * details.reason file_list_unavailable (no file list) or file_list_indexing (retryable); 404 path_not_found or
890
890
  * version_not_found; 422 invalid_path. File contents are not served: open a draft or test instance for that.
891
891
  */
@@ -901,7 +901,7 @@ export declare class TemplatesApi {
901
901
  diff(slug: string, params: TemplateDiffParams): Promise<TemplateDiffPage>;
902
902
  /** Every diff entry, following next_cursor. */
903
903
  diffAll(slug: string, params: Omit<TemplateDiffParams, 'cursor'>): AsyncGenerator<TemplateDiffEntry>;
904
- /** Dev mode of one organization template: its draft, states, test instances and publish (contracts §19.9). */
904
+ /** Dev mode of one organization template: its draft, states, test instances and publish. */
905
905
  draft(slug: string, params?: {
906
906
  organizationId?: string;
907
907
  }): TemplateDraftApi;
package/dist/templates.js CHANGED
@@ -53,7 +53,7 @@ async function openedWorkspace(ctx, res, p) {
53
53
  return { workspace: new Workspace(ctx, view, wrap), operation };
54
54
  }
55
55
  /**
56
- * Template dev mode for one organization template (contracts §19.9): the single live draft (a layered, persistent
56
+ * Template dev mode for one organization template: the single live draft (a layered, persistent
57
57
  * workspace on the draft base), its captured states, disposable test instances (session workspaces on a copy of a
58
58
  * state) and publishing the draft as the next version. Mutations need build access (owners/admins, API keys with a
59
59
  * tool permission); others get 403 forbidden with details.reason template_dev_mode_role.
@@ -369,7 +369,7 @@ async function sendUpload(ctx, meta, body, organizationId, signal) {
369
369
  return { upload: again.body.upload, ref, uploaded: true };
370
370
  }
371
371
  /**
372
- * Build uploads (contracts §24.2): files and folders a recipe v2 copies into the template, stored once per
372
+ * Build uploads: files and folders a recipe v2 copies into the template, stored once per
373
373
  * organization and content (SHA-256). Needs build access (API keys with a tool permission). Uploads count toward the
374
374
  * organization's template storage while they exist; one nothing references is deleted 7 days later.
375
375
  */
@@ -458,7 +458,7 @@ function uploadLocal(ctx, src, organizationId, signal) {
458
458
  return sendUpload(ctx, { sha256: src.sha256, size: src.size, kind: src.kind }, { open: () => fileBody(src.uploadPath), replayable: true }, organizationId, signal);
459
459
  }
460
460
  const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
461
- // ---- recipe export, version test instances, package search (contracts §24.6) --------------------------------------------
461
+ // ---- recipe export, version test instances, package search --------------------------------------------
462
462
  export class TemplateVersionsApi {
463
463
  #ctx;
464
464
  constructor(ctx) {
@@ -475,7 +475,7 @@ export class TemplateVersionsApi {
475
475
  }
476
476
  }
477
477
  /**
478
- * Test instances of a version (contracts §24.6): a session workspace on a registered version of the organization's
478
+ * Test instances of a version: a session workspace on a registered version of the organization's
479
479
  * template, published or not, so a build can be tried before it is published. Owners, admins and API keys with a
480
480
  * tool permission (403 template_dev_mode_role otherwise).
481
481
  */
@@ -504,7 +504,7 @@ export class TemplateVersionTestInstancesApi {
504
504
  return (await openedWorkspace(ctx, res, params)).workspace;
505
505
  }
506
506
  }
507
- /** Package names for the editor's pickers (contracts §24.6): apt (a base's index), pip (names only) and npm. */
507
+ /** Package names for the editor's pickers: apt (a base's index), pip (names only) and npm. */
508
508
  export class TemplatePackagesApi {
509
509
  #ctx;
510
510
  constructor(ctx) {
@@ -574,7 +574,7 @@ export class TemplatesApi {
574
574
  return this.#organization;
575
575
  }
576
576
  /**
577
- * Node only: builds a template from template.yaml (or a .json file with the same document; contracts §24.1). The
577
+ * Node only: builds a template from template.yaml (or a .json file with the same document). The
578
578
  * file is recipe v2; each `build.files[]` entry may name a local `from` path (relative to the file): folders are
579
579
  * packed as the reproducible tar, files are sent as they are, both uploaded unless the organization already has
580
580
  * them, and the build is created (and awaited with `wait`). YAML needs the optional `yaml` package or `parseYaml`.
@@ -705,7 +705,7 @@ export class TemplatesApi {
705
705
  }
706
706
  }
707
707
  /**
708
- * One directory level of a version's file tree (contracts §19.10), sorted by name bytes. 409 conflict with
708
+ * One directory level of a version's file tree, sorted by name bytes. 409 conflict with
709
709
  * details.reason file_list_unavailable (no file list) or file_list_indexing (retryable); 404 path_not_found or
710
710
  * version_not_found; 422 invalid_path. File contents are not served: open a draft or test instance for that.
711
711
  */
@@ -741,7 +741,7 @@ export class TemplatesApi {
741
741
  cursor = page.next_cursor ?? undefined;
742
742
  } while (cursor !== undefined);
743
743
  }
744
- /** Dev mode of one organization template: its draft, states, test instances and publish (contracts §19.9). */
744
+ /** Dev mode of one organization template: its draft, states, test instances and publish. */
745
745
  draft(slug, params = {}) {
746
746
  return new TemplateDraftApi(this.#ctx, slug, params.organizationId);
747
747
  }
package/dist/tools.d.ts CHANGED
@@ -49,7 +49,7 @@ export declare function validateArgs(schema: JsonSchema, value: unknown, path?:
49
49
  export interface WorkspaceToolsOptions {
50
50
  /**
51
51
  * Tool permissions to expose (default: the tools of the workspace's last token, else all). A file-first workspace
52
- * (`workspace.mode`, contracts §29) gets only the `exec` and `files` tools: `exec` runs each command as an execution
52
+ * (`workspace.mode`) gets only the `exec` and `files` tools: `exec` runs each command as an execution
53
53
  * (a fresh VM on the workspace's files; the result adds `execution_id`, `state`, `changed` and `tree_revision`), and
54
54
  * the process, terminal, git and browser tools are not offered because nothing runs between executions.
55
55
  */
@@ -70,7 +70,7 @@ export interface WorkspaceToolsOptions {
70
70
  * Send `workspace.hint()` when a tool call starts, without waiting for it (default true; 0.9.0+), so a parked
71
71
  * workspace is being restored while the call is prepared. Pass false when you call `workspace.hint()` yourself
72
72
  * earlier (e.g. when the model starts streaming a tool call). `read_file`, `list_files` and `search_files` never send
73
- * it: a sleeping workspace serves them from its disk without waking (contracts §26.4), and the hint would wake a
73
+ * it: a sleeping workspace serves them from its disk without waking, and the hint would wake a
74
74
  * suspended workspace (or restore a hibernated one) that the read does not need.
75
75
  */
76
76
  hint?: boolean;
@@ -81,7 +81,7 @@ export interface WorkspaceToolsOptions {
81
81
  mode?: WorkspaceMode;
82
82
  /**
83
83
  * File-first workspaces: called with the execution id before the `exec` tool sends its execution (0.9.0+). An
84
- * execution cannot be canceled, so a caller that stops waiting (an aborted signal) can still fetch its result later
84
+ * execution runs to completion, so a caller that stops waiting (an aborted signal) can still fetch its result later
85
85
  * with `workspace.executions.get(id)`.
86
86
  */
87
87
  onExecution?: (executionId: string) => void;