@shardflux/sdk 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cell.d.ts CHANGED
@@ -86,6 +86,8 @@ export interface CellClientOptions {
86
86
  * Through Workspace.cell() a wake reports here too (action `wake`).
87
87
  */
88
88
  onProgress?: ProgressListener;
89
+ /** Internal: see CAPTURE_BARRIER. */
90
+ [CAPTURE_BARRIER]?: (() => Promise<unknown> | undefined) | undefined;
89
91
  }
90
92
  /** Per-request transition handling (internal to CellClient). */
91
93
  interface TransitionOptions {
@@ -93,6 +95,11 @@ interface TransitionOptions {
93
95
  wake?: boolean;
94
96
  }
95
97
  export declare const DEFAULT_TRANSITION_TIMEOUT_MS = 120000;
98
+ /**
99
+ * Internal (tool-call capture): a CellClient option whose function is awaited before every request, so calls wait for
100
+ * capture writes recorded before them (read-your-writes). Workspace.cell() sets it; the capture's own client does not.
101
+ */
102
+ export declare const CAPTURE_BARRIER: unique symbol;
96
103
  export interface RunResult {
97
104
  sessionId: string;
98
105
  exitCode: number | null;
package/dist/cell.js CHANGED
@@ -11,6 +11,11 @@ export function cellPath(template, params) {
11
11
  });
12
12
  }
13
13
  export const DEFAULT_TRANSITION_TIMEOUT_MS = 120_000;
14
+ /**
15
+ * Internal (tool-call capture): a CellClient option whose function is awaited before every request, so calls wait for
16
+ * capture writes recorded before them (read-your-writes). Workspace.cell() sets it; the capture's own client does not.
17
+ */
18
+ export const CAPTURE_BARRIER = Symbol('shardflux.captureBarrier');
14
19
  /** Wakes per call at most: a workspace that keeps being suspended again surfaces the refusal. */
15
20
  const MAX_WAKES = 3;
16
21
  const b64 = (bytes) => Buffer.from(typeof bytes === 'string' ? Buffer.from(bytes, 'utf8') : bytes).toString('base64');
@@ -72,6 +77,7 @@ export class CellClient {
72
77
  workspaceId;
73
78
  tokens;
74
79
  #opts;
80
+ #barrier;
75
81
  #listener;
76
82
  #wake;
77
83
  #transitionTimeoutMs;
@@ -90,6 +96,7 @@ export class CellClient {
90
96
  this.#wake = opts.wake ?? null;
91
97
  this.#transitionTimeoutMs = opts.transitionTimeoutMs ?? DEFAULT_TRANSITION_TIMEOUT_MS;
92
98
  this.#listener = opts.onProgress;
99
+ this.#barrier = opts[CAPTURE_BARRIER];
93
100
  }
94
101
  #http(endpoint) {
95
102
  let c = this.#clients.get(endpoint);
@@ -120,6 +127,12 @@ export class CellClient {
120
127
  */
121
128
  async request(method, path, init = {}) {
122
129
  const closer = this.#closer.signal;
130
+ if (closer.aborted)
131
+ throw closer.reason;
132
+ // Read-your-writes: tool-call capture writes recorded before this call land first (bounded; never throws).
133
+ const barrier = this.#barrier?.();
134
+ if (barrier)
135
+ await barrier;
123
136
  if (closer.aborted)
124
137
  throw closer.reason;
125
138
  const { wake: wakeAllowed = true, ...reqInit } = init;
package/dist/client.d.ts CHANGED
@@ -21,6 +21,7 @@ import { UsageApi } from './usage.js';
21
21
  import { VolumesApi } from './volumes.js';
22
22
  import type { FinishedOperation, InternalLifecycleOptions, LifecycleOptions, WaitedLifecycleOptions } from './lifecycle.js';
23
23
  import type { ProgressListener } from './progress.js';
24
+ import { CaptureRegistry } from './capture.js';
24
25
  export type WorkspaceView = components['schemas']['Workspace'];
25
26
  export type Operation = components['schemas']['Operation'];
26
27
  /** persistent (kept until deleted) or session (discarded when the session ends: close(), idle timeout; contracts §19.11). */
@@ -34,6 +35,8 @@ export type UpdatePolicy = components['schemas']['UpdatePolicy'];
34
35
  /** Where the workspace's disk came from: null, a fork, or a draft state (test instances). */
35
36
  export type WorkspaceOrigin = components['schemas']['WorkspaceOrigin'];
36
37
  export type ResetWorkspaceBody = components['schemas']['ResetWorkspaceBody'];
38
+ /** GET /v1/workspaces/{id}/inputs (0.7.0): the workspace's text inputs. */
39
+ export type WorkspaceInputs = components['schemas']['WorkspaceInputs'];
37
40
  /** List filter: a lifetime or `any` (the list default is `persistent`). */
38
41
  export type LifetimeFilter = WorkspaceLifetime | 'any';
39
42
  /** List filter: a purpose or `any` (the list default is `standard`). */
@@ -126,6 +129,15 @@ export interface OpenParams {
126
129
  * ShardfluxApiError 422 (details.reason `secret_not_available`, details.names) and nothing is created or changed.
127
130
  */
128
131
  secrets?: string[];
132
+ /**
133
+ * The template version's text inputs `{NAME: value}` (0.7.0; contracts §24.3), put into the environment of every
134
+ * exec, terminal, start command and service. A new key stores each given value, else the declared default; an
135
+ * existing key replaces them all (omitted leaves them unchanged). Secret inputs are not passed here: they bind the
136
+ * stored secret of the same name. ShardfluxApiError 422 with details.reason `input_unknown` (an undeclared name),
137
+ * `input_invalid` (a secret input, or a value over 4096 bytes or with CR, LF or NUL) or `input_required`
138
+ * (details.names, details.kind).
139
+ */
140
+ inputs?: Record<string, string>;
129
141
  /**
130
142
  * `session`: the workspace is discarded when its session ends (workspace.close(), or the idle timeout); the key then
131
143
  * opens a NEW workspace. Omitted: the template version's default, else `persistent`. Immutable: reopening a live key
@@ -186,6 +198,8 @@ export interface ClientContext {
186
198
  workspaces: WorkspacesApi;
187
199
  /** The client-level progress listener (ShardfluxOptions.onProgress). */
188
200
  onProgress?: ProgressListener | undefined;
201
+ /** Tool-call captures of this client by workspace id (read-your-writes barrier, discard on delete/reset). */
202
+ captures: CaptureRegistry;
189
203
  }
190
204
  export declare class WorkspacesApi {
191
205
  #private;
@@ -289,6 +303,8 @@ export declare class WorkspacesApi {
289
303
  operation: Operation;
290
304
  workspace: Workspace;
291
305
  }>;
306
+ /** The workspace's text inputs `{NAME: value}` (0.7.0; contracts §24.3). Secret inputs are bound secrets, never listed. */
307
+ inputs(workspaceId: string): Promise<Record<string, string>>;
292
308
  operations(workspaceId: string, params?: {
293
309
  limit?: number;
294
310
  cursor?: string;
package/dist/client.js CHANGED
@@ -9,6 +9,7 @@ import { UsageApi } from "./usage.js";
9
9
  import { VolumesApi } from "./volumes.js";
10
10
  import { AFTER_WAIT, TRACE, runLifecycle } from "./lifecycle.js";
11
11
  import { Trace, combineListeners, traced } from "./progress.js";
12
+ import { CaptureRegistry } from "./capture.js";
12
13
  /**
13
14
  * The workspace a key names (contracts §19.11): the live row (deleted_at null) when there is one, since at most one live
14
15
  * workspace holds a key; otherwise the newest tombstone (ended sessions leave tombstones with the same key, and a
@@ -76,6 +77,8 @@ export class WorkspacesApi {
76
77
  body.tools = params.tools;
77
78
  if (params.secrets !== undefined)
78
79
  body.secrets = params.secrets;
80
+ if (params.inputs !== undefined)
81
+ body.inputs = params.inputs;
79
82
  if (params.lifetime !== undefined)
80
83
  body.lifetime = params.lifetime;
81
84
  const waitOpts = params.wait === false ? null : (params.wait ?? {});
@@ -299,7 +302,13 @@ export class WorkspacesApi {
299
302
  }
300
303
  #op(kind, workspaceId, json, opts) {
301
304
  const path = `/v1/workspaces/${encodeURIComponent(workspaceId)}${kind === 'delete' ? '' : `/${kind}`}`;
302
- return runLifecycle(this.#ctx(), kind, workspaceId, async (init) => (await this.#lifecycle(kind === 'delete' ? 'DELETE' : 'POST', path, json, opts.idempotencyKey, init)).operation, opts);
305
+ return runLifecycle(this.#ctx(), kind, workspaceId, async (init) => {
306
+ const { operation } = await this.#lifecycle(kind === 'delete' ? 'DELETE' : 'POST', path, json, opts.idempotencyKey, init);
307
+ // Pending tool-call capture writes would land on a deleted or wiped disk: dropped once the change is accepted.
308
+ if (kind === 'delete' || kind === 'reset')
309
+ this.#ctx().captures.discard(workspaceId, kind);
310
+ return operation;
311
+ }, opts, { settle: kind === 'suspend' || kind === 'snapshot' });
303
312
  }
304
313
  delete(workspaceId, opts = {}) {
305
314
  return this.#op('delete', workspaceId, undefined, opts);
@@ -322,8 +331,9 @@ export class WorkspacesApi {
322
331
  const operation = await runLifecycle(this.#ctx(), 'close', workspaceId, async (init) => {
323
332
  const out = await this.#lifecycle('POST', `/v1/workspaces/${encodeURIComponent(workspaceId)}/close`, undefined, opts.idempotencyKey, init);
324
333
  workspace = out.workspace;
334
+ this.#ctx().captures.discard(workspaceId, 'delete'); // the session is deleted
325
335
  return out.operation;
326
- }, opts);
336
+ }, opts, { settle: true });
327
337
  return { operation, workspace: workspace };
328
338
  }
329
339
  reset(workspaceId, opts = {}) {
@@ -335,7 +345,8 @@ export class WorkspacesApi {
335
345
  * captured briefly (`operation`, layer_snapshot); poll `build` with templates.builds.waitForBuild. Owners/admins and
336
346
  * API keys with a tool permission only.
337
347
  */
338
- saveAsTemplate(workspaceId, params) {
348
+ async saveAsTemplate(workspaceId, params) {
349
+ await this.#ctx().captures.settle(workspaceId); // captured tool calls are part of the saved layer
339
350
  return this.#http.json('POST', `/v1/workspaces/${encodeURIComponent(workspaceId)}/save-as-template`, { json: saveAsTemplateBody(params), idempotencyKey: params.idempotencyKey ?? randomId('save-') }, this.#auth);
340
351
  }
341
352
  async fork(workspaceId, target, opts = {}) {
@@ -350,9 +361,14 @@ export class WorkspacesApi {
350
361
  await trace.span('view', () => copy.refresh());
351
362
  await opts[AFTER_WAIT]?.(trace, op);
352
363
  },
353
- });
364
+ }, { settle: true });
354
365
  return { operation, workspace: copy };
355
366
  }
367
+ /** The workspace's text inputs `{NAME: value}` (0.7.0; contracts §24.3). Secret inputs are bound secrets, never listed. */
368
+ async inputs(workspaceId) {
369
+ const body = await this.#http.json('GET', `/v1/workspaces/${encodeURIComponent(workspaceId)}/inputs`, {}, this.#auth);
370
+ return body.inputs;
371
+ }
356
372
  async operations(workspaceId, params = {}) {
357
373
  const page = await this.#http.json('GET', `/v1/workspaces/${encodeURIComponent(workspaceId)}/operations`, { query: { limit: params.limit, cursor: params.cursor, state: params.state, kind: params.kind } }, this.#auth);
358
374
  return { data: page.data, nextCursor: page.next_cursor };
@@ -431,6 +447,7 @@ export class Shardflux {
431
447
  sleep,
432
448
  workspaces: this.workspaces,
433
449
  onProgress: opts.onProgress,
450
+ captures: new CaptureRegistry(),
434
451
  };
435
452
  }
436
453
  /** The authenticated principal (the API key, its organization and project). */
package/dist/egress.d.ts CHANGED
@@ -122,6 +122,24 @@ export interface WorkspaceEgressPolicy {
122
122
  /** Active organization override (null: none); when set, `effective.source` is 'organization'. */
123
123
  organization_override: OrganizationEgressOverride | null;
124
124
  enforcement: EgressEnforcement;
125
+ /**
126
+ * The template version's network ceiling (0.7.0; contracts §24.3 `defaults.egress`), or null. The host enforces
127
+ * `effective` intersected with it; a workspace policy it would narrow is refused with 422 egress_widening.
128
+ */
129
+ template_egress: {
130
+ mode: 'allowlist' | 'none';
131
+ allow_hosts: string[];
132
+ } | null;
133
+ /** What the host enforces (0.7.0): `effective` intersected with `template_egress` (equal to `effective` without one). */
134
+ effective_policy: {
135
+ mode: EgressMode;
136
+ rules: Array<{
137
+ host: string;
138
+ ports: number[];
139
+ protocols: Array<'tcp'>;
140
+ }>;
141
+ cidrs: string[];
142
+ };
125
143
  }
126
144
  export declare class EgressPolicyApi {
127
145
  #private;
package/dist/errors.d.ts CHANGED
@@ -13,8 +13,15 @@ export type ErrorCode = AppErrorCode | CellErrorCode;
13
13
  * file_list_indexing (retryable), guest_feature_unavailable; 422 validation_failed confirm_destructive_required,
14
14
  * reserved_key_prefix, invalid_defaults, update_policy_not_available, invalid_path, too_many_acknowledged_findings;
15
15
  * 403 forbidden template_dev_mode_role; 404 not_found draft_not_found, version_not_found, path_not_found.
16
+ * The template editor (contracts §24.6, 0.7.0) added: 422 validation_failed invalid_recipe, base_not_layered,
17
+ * language_unavailable, language_conflict, invalid_package, too_many_files, platform_owned_path, upload_required,
18
+ * upload_missing, upload_digest_mismatch, upload_too_large, extra_hosts_without_auto, invalid_settings,
19
+ * services_unsupported, input_required, input_unknown, input_invalid, egress_widening, reserved_session_id,
20
+ * env_collision, reserved_template_slug; 409 conflict package_index_unavailable; 404 package_not_found; the operation
21
+ * error `startup_failed` (details.reason startup_failed, service_not_ready, secrets_unavailable, secret_not_available,
22
+ * guest_feature_unavailable).
16
23
  */
17
- export type KnownErrorReason = 'workspace_not_running' | 'operation_in_progress' | 'workspace_deleted' | 'secret_not_available' | 'legacy_disk_layout' | 'not_session' | 'session_lifetime' | 'lifetime_mismatch' | 'not_resettable' | 'template_not_layered' | 'draft_exists' | 'draft_stale' | 'build_in_progress' | 'file_list_unavailable' | 'file_list_indexing' | 'guest_feature_unavailable' | 'confirm_destructive_required' | 'reserved_key_prefix' | 'invalid_defaults' | 'update_policy_not_available' | 'invalid_path' | 'too_many_acknowledged_findings' | 'template_dev_mode_role' | 'draft_not_found' | 'version_not_found' | 'path_not_found';
24
+ export type KnownErrorReason = 'invalid_recipe' | 'base_not_layered' | 'language_unavailable' | 'language_conflict' | 'invalid_package' | 'too_many_files' | 'platform_owned_path' | 'upload_required' | 'upload_missing' | 'upload_digest_mismatch' | 'upload_too_large' | 'extra_hosts_without_auto' | 'invalid_settings' | 'services_unsupported' | 'input_required' | 'input_unknown' | 'input_invalid' | 'egress_widening' | 'reserved_session_id' | 'env_collision' | 'reserved_template_slug' | 'package_index_unavailable' | 'package_not_found' | 'startup_failed' | 'service_not_ready' | 'secrets_unavailable' | 'workspace_not_running' | 'operation_in_progress' | 'workspace_deleted' | 'secret_not_available' | 'legacy_disk_layout' | 'not_session' | 'session_lifetime' | 'lifetime_mismatch' | 'not_resettable' | 'template_not_layered' | 'draft_exists' | 'draft_stale' | 'build_in_progress' | 'file_list_unavailable' | 'file_list_indexing' | 'guest_feature_unavailable' | 'confirm_destructive_required' | 'reserved_key_prefix' | 'invalid_defaults' | 'update_policy_not_available' | 'invalid_path' | 'too_many_acknowledged_findings' | 'template_dev_mode_role' | 'draft_not_found' | 'version_not_found' | 'path_not_found';
18
25
  /** A known reason, or any other string the server sends (reasons are open-ended). */
19
26
  export type ErrorReason = KnownErrorReason | (string & {});
20
27
  export interface ErrorBodyLike {
@@ -2132,6 +2132,46 @@ export interface paths {
2132
2132
  patch?: never;
2133
2133
  trace?: never;
2134
2134
  };
2135
+ "/v1/organizations/{organization_id}/template-languages": {
2136
+ parameters: {
2137
+ query?: never;
2138
+ header?: never;
2139
+ path?: never;
2140
+ cookie?: never;
2141
+ };
2142
+ /**
2143
+ * The recipe languages a base offers: version, default, whether the base already has it, and the hosts its install needs
2144
+ * @description The §24.1 language table read for the chain’s platform base of `base`, resolved as a build resolves `recipe.base` (422 validation_failed with details.field "base" and the build’s reasons: base_not_found, base_archived, base_not_published, architecture_not_supported). `included`: the base already has that version (python-node-browser: python and node), so the build installs nothing for it. A version the base has another version of is left out (a build would refuse it with language_conflict). Owners/admins and API keys with a tool permission.
2145
+ */
2146
+ get: operations["getV1OrganizationsOrganizationIdTemplateLanguages"];
2147
+ put?: never;
2148
+ post?: never;
2149
+ delete?: never;
2150
+ options?: never;
2151
+ head?: never;
2152
+ patch?: never;
2153
+ trace?: never;
2154
+ };
2155
+ "/v1/template-languages": {
2156
+ parameters: {
2157
+ query?: never;
2158
+ header?: never;
2159
+ path?: never;
2160
+ cookie?: never;
2161
+ };
2162
+ /**
2163
+ * The recipe languages a base offers: version, default, whether the base already has it, and the hosts its install needs (the API key’s organization)
2164
+ * @description The §24.1 language table read for the chain’s platform base of `base`, resolved as a build resolves `recipe.base` (422 validation_failed with details.field "base" and the build’s reasons: base_not_found, base_archived, base_not_published, architecture_not_supported). `included`: the base already has that version (python-node-browser: python and node), so the build installs nothing for it. A version the base has another version of is left out (a build would refuse it with language_conflict). Owners/admins and API keys with a tool permission.
2165
+ */
2166
+ get: operations["getV1TemplateLanguages"];
2167
+ put?: never;
2168
+ post?: never;
2169
+ delete?: never;
2170
+ options?: never;
2171
+ head?: never;
2172
+ patch?: never;
2173
+ trace?: never;
2174
+ };
2135
2175
  "/v1/organizations/{organization_id}/template-builder-availability": {
2136
2176
  parameters: {
2137
2177
  query?: never;
@@ -2330,6 +2370,26 @@ export interface paths {
2330
2370
  patch?: never;
2331
2371
  trace?: never;
2332
2372
  };
2373
+ "/api/v1/organizations/{organization_id}/template-languages": {
2374
+ parameters: {
2375
+ query?: never;
2376
+ header?: never;
2377
+ path?: never;
2378
+ cookie?: never;
2379
+ };
2380
+ /**
2381
+ * The recipe languages a base offers: version, default, whether the base already has it, and the hosts its install needs
2382
+ * @description The §24.1 language table read for the chain’s platform base of `base`, resolved as a build resolves `recipe.base` (422 validation_failed with details.field "base" and the build’s reasons: base_not_found, base_archived, base_not_published, architecture_not_supported). `included`: the base already has that version (python-node-browser: python and node), so the build installs nothing for it. A version the base has another version of is left out (a build would refuse it with language_conflict). Owners/admins and API keys with a tool permission.
2383
+ */
2384
+ get: operations["getApiV1OrganizationsOrganizationIdTemplateLanguages"];
2385
+ put?: never;
2386
+ post?: never;
2387
+ delete?: never;
2388
+ options?: never;
2389
+ head?: never;
2390
+ patch?: never;
2391
+ trace?: never;
2392
+ };
2333
2393
  "/api/v1/organizations/{organization_id}/template-builder-availability": {
2334
2394
  parameters: {
2335
2395
  query?: never;
@@ -4962,6 +5022,32 @@ export interface components {
4962
5022
  /** @description Known versions, newest first (at most 200). */
4963
5023
  versions: string[];
4964
5024
  };
5025
+ TemplateLanguages: {
5026
+ /** @description `<slug>@<version>` as asked. */
5027
+ base: string;
5028
+ /** @description The chain’s platform base (the base itself for a platform version): the table is read for it. */
5029
+ platform_base: string;
5030
+ /** @description Hosts apt installs need while an `auto` build runs (the platform base’s `build_inputs.apt_pin.hosts`, else snapshot.ubuntu.com). */
5031
+ apt_hosts: string[];
5032
+ /** @description In table order (python, node, go, rust, java), then newest version first as the table lists them. */
5033
+ data: {
5034
+ /** @enum {string} */
5035
+ id: "python" | "node" | "go" | "rust" | "java";
5036
+ /** @description Display name: Python, Node.js, Go, Rust, Java. */
5037
+ name: string;
5038
+ /** @description The version as a recipe names it (`{id, version}`): "3.12", "24", "1.27". */
5039
+ version: string;
5040
+ /** @description The version `{id}` without a version resolves to on this base. */
5041
+ default: boolean;
5042
+ /** @description The base already has this version (its manifest `tools`): the build installs nothing for it and needs no host (python still gets its /opt/venv). */
5043
+ included: boolean;
5044
+ base_version: string | null;
5045
+ /** @description Hosts its download needs while an `auto` build runs ([] when included, or when it comes from apt). */
5046
+ hosts: string[];
5047
+ /** @description apt packages it installs ([] when included). An `auto` build then also allows `apt_hosts`. */
5048
+ apt: string[];
5049
+ }[];
5050
+ };
4965
5051
  /** @description How the version was produced: a recipe build, a saved workspace (or draft), or git (reserved). */
4966
5052
  TemplateSource: {
4967
5053
  /** @enum {string} */
@@ -15102,6 +15188,91 @@ export interface operations {
15102
15188
  };
15103
15189
  };
15104
15190
  };
15191
+ getV1OrganizationsOrganizationIdTemplateLanguages: {
15192
+ parameters: {
15193
+ query: {
15194
+ /** @description `<slug>@<version>` (apt: the base whose apt index is searched). */
15195
+ base: string;
15196
+ };
15197
+ header?: never;
15198
+ path: {
15199
+ /** @description UUIDv7, lowercase canonical form. */
15200
+ organization_id: string;
15201
+ };
15202
+ cookie?: never;
15203
+ };
15204
+ requestBody?: never;
15205
+ responses: {
15206
+ /** @description Default Response */
15207
+ 200: {
15208
+ headers: {
15209
+ [name: string]: unknown;
15210
+ };
15211
+ content: {
15212
+ "application/json": components["schemas"]["TemplateLanguages"];
15213
+ };
15214
+ };
15215
+ /** @description Default Response */
15216
+ "4XX": {
15217
+ headers: {
15218
+ [name: string]: unknown;
15219
+ };
15220
+ content: {
15221
+ "application/json": components["schemas"]["ErrorBody"];
15222
+ };
15223
+ };
15224
+ /** @description Default Response */
15225
+ "5XX": {
15226
+ headers: {
15227
+ [name: string]: unknown;
15228
+ };
15229
+ content: {
15230
+ "application/json": components["schemas"]["ErrorBody"];
15231
+ };
15232
+ };
15233
+ };
15234
+ };
15235
+ getV1TemplateLanguages: {
15236
+ parameters: {
15237
+ query: {
15238
+ /** @description `<slug>@<version>` (apt: the base whose apt index is searched). */
15239
+ base: string;
15240
+ };
15241
+ header?: never;
15242
+ path?: never;
15243
+ cookie?: never;
15244
+ };
15245
+ requestBody?: never;
15246
+ responses: {
15247
+ /** @description Default Response */
15248
+ 200: {
15249
+ headers: {
15250
+ [name: string]: unknown;
15251
+ };
15252
+ content: {
15253
+ "application/json": components["schemas"]["TemplateLanguages"];
15254
+ };
15255
+ };
15256
+ /** @description Default Response */
15257
+ "4XX": {
15258
+ headers: {
15259
+ [name: string]: unknown;
15260
+ };
15261
+ content: {
15262
+ "application/json": components["schemas"]["ErrorBody"];
15263
+ };
15264
+ };
15265
+ /** @description Default Response */
15266
+ "5XX": {
15267
+ headers: {
15268
+ [name: string]: unknown;
15269
+ };
15270
+ content: {
15271
+ "application/json": components["schemas"]["ErrorBody"];
15272
+ };
15273
+ };
15274
+ };
15275
+ };
15105
15276
  getV1OrganizationsOrganizationIdTemplateBuilderAvailability: {
15106
15277
  parameters: {
15107
15278
  query?: never;
@@ -17424,6 +17595,50 @@ export interface operations {
17424
17595
  };
17425
17596
  };
17426
17597
  };
17598
+ getApiV1OrganizationsOrganizationIdTemplateLanguages: {
17599
+ parameters: {
17600
+ query: {
17601
+ /** @description `<slug>@<version>` (apt: the base whose apt index is searched). */
17602
+ base: string;
17603
+ };
17604
+ header?: never;
17605
+ path: {
17606
+ /** @description UUIDv7, lowercase canonical form. */
17607
+ organization_id: string;
17608
+ };
17609
+ cookie?: never;
17610
+ };
17611
+ requestBody?: never;
17612
+ responses: {
17613
+ /** @description Default Response */
17614
+ 200: {
17615
+ headers: {
17616
+ [name: string]: unknown;
17617
+ };
17618
+ content: {
17619
+ "application/json": components["schemas"]["TemplateLanguages"];
17620
+ };
17621
+ };
17622
+ /** @description Default Response */
17623
+ "4XX": {
17624
+ headers: {
17625
+ [name: string]: unknown;
17626
+ };
17627
+ content: {
17628
+ "application/json": components["schemas"]["ErrorBody"];
17629
+ };
17630
+ };
17631
+ /** @description Default Response */
17632
+ "5XX": {
17633
+ headers: {
17634
+ [name: string]: unknown;
17635
+ };
17636
+ content: {
17637
+ "application/json": components["schemas"]["ErrorBody"];
17638
+ };
17639
+ };
17640
+ };
17641
+ };
17427
17642
  getApiV1OrganizationsOrganizationIdTemplateBuilderAvailability: {
17428
17643
  parameters: {
17429
17644
  query?: never;
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.6.2";
2
+ export declare const SDK_VERSION = "0.7.0";
3
3
  export interface RequestOptions {
4
4
  query?: Record<string, string | number | boolean | undefined | null>;
5
5
  json?: unknown;
package/dist/http.js CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { ShardfluxApiError, ShardfluxProtocolError, isErrorBody } from "./errors.js";
8
8
  import { describeFailure } from "./progress.js";
9
- export const SDK_VERSION = '0.6.2';
9
+ export const SDK_VERSION = '0.7.0';
10
10
  export const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
11
11
  /**
12
12
  * The fetch the SDK uses when none is given. On runtimes whose bundled undici is 8.x (Node 26) it sends
package/dist/index.d.ts CHANGED
@@ -12,14 +12,18 @@
12
12
  export type { components, operations, paths } from './generated/app-api.js';
13
13
  export type { components as CellComponents, paths as CellPaths } from './generated/cell-api.js';
14
14
  export { BillingApi, Shardflux, WorkspacesApi, fetchBillingCatalog, pickByKey } from './client.js';
15
- export type { AgentSession, BillingCatalog, BillingSubscription, Caps, CheckoutSession, DiskLayout, Entitlements, FindByKeyOptions, ForkTarget, Invoice, InvoicePage, LifetimeFilter, ListParams, Me, OpenParams, OpenResponse, Operation, Page, PortalSession, PurposeFilter, ResetWorkspaceBody, ShardfluxOptions, UpdatePolicy, WaitOptions, WorkspaceLifetime, WorkspaceOrigin, WorkspacePurpose, WorkspaceView, } from './client.js';
15
+ export type { AgentSession, BillingCatalog, BillingSubscription, Caps, CheckoutSession, DiskLayout, Entitlements, FindByKeyOptions, ForkTarget, Invoice, InvoicePage, LifetimeFilter, ListParams, Me, OpenParams, OpenResponse, Operation, Page, PortalSession, PurposeFilter, ResetWorkspaceBody, ShardfluxOptions, UpdatePolicy, WaitOptions, WorkspaceInputs, WorkspaceLifetime, WorkspaceOrigin, WorkspacePurpose, WorkspaceView, } from './client.js';
16
16
  export type { FinishedOperation, LifecycleOptions, WaitedLifecycleOptions } from './lifecycle.js';
17
17
  export { formatTiming } from './progress.js';
18
18
  export type { LifecycleAction, LifecyclePhase, LifecycleTiming, ProgressEvent, ProgressListener, RetryRecord, ServerTiming, TimingOutcome, TimingPhase, } from './progress.js';
19
19
  export { UsageApi } from './usage.js';
20
20
  export type { Grants, Spend, SpendPolicy, UsageEstimate, UsageMeter, UsageSeries, UsageSeriesParams, UsageSummary } from './usage.js';
21
- export { TemplateBuildTimeoutError, TemplateBuildsApi, TemplateDraftApi, TemplatesApi, buildSettled, saveAsTemplateBody } from './templates.js';
22
- export type { BuilderAvailability, CreateDraftBody, CreateDraftParams, CreateTemplateBuildParams, CreateTestInstanceBody, DraftOpened, DraftState, OpenTestInstanceParams, OrgTemplateStorage, PublishDraftBody, PublishDraftParams, SaveAsTemplateBody, SaveAsTemplateParams, SaveAsTemplateResponse, TemplateBuild, TemplateBuildLogUrl, TemplateBuildRegistrationState, TemplateBuildState, TemplateDefaults, TemplateDefaultsInput, TemplateDetail, TemplateDiffChange, TemplateDiffEntry, TemplateDiffPage, TemplateDiffParams, TemplateDraft, TemplateDraftSummary, TemplateFileEntry, TemplateFilePage, TemplateFilesParams, TemplateFilesSummary, TemplateOwner, TemplateRecipe, TemplateSource, TemplateStorage, TemplateStorageWarning, TemplateSummary, TemplateVersion, TemplateVersionState, WaitForBuildOptions, } from './templates.js';
21
+ export { TemplateBuildTimeoutError, TemplateBuildsApi, TemplateDraftApi, TemplatePackagesApi, TemplateUploadError, TemplateUploadsApi, TemplateVersionTestInstancesApi, TemplateVersionsApi, TemplatesApi, buildSettled, saveAsTemplateBody, } from './templates.js';
22
+ export { TemplateFileError, packDirectory, parseTemplateText, readTemplateFile } from './template-file.js';
23
+ export type { PackedFile, YamlParser } from './template-file.js';
24
+ export { tarEnd, tarHeader, tarPadding } from './tar.js';
25
+ export type { TarEntry, TarEntryType } from './tar.js';
26
+ export type { BuildFromFileEvent, BuildFromFileOptions, BuildFromFileResult, BuildFromRecipeOptions, BuilderAvailability, CreateDraftBody, CreateDraftParams, CreateTemplateBuildParams, CreateTestInstanceBody, CreateVersionTestInstanceBody, CreateVersionTestInstanceParams, DraftOpened, LocalUpload, DraftState, OpenTestInstanceParams, OrgTemplateStorage, PublishDraftBody, PublishDraftParams, PutUploadOptions, SaveAsTemplateBody, SaveAsTemplateParams, SaveAsTemplateResponse, TemplateBuild, TemplateBuildLogUrl, TemplateBuildRecipeV2, TemplateBuildRegistrationState, TemplateBuildState, TemplateCategory, TemplateDefaults, TemplateDefaultsInput, TemplateDetail, TemplateDiffChange, TemplateDiffEntry, TemplateDiffPage, TemplateDiffParams, TemplateDraft, TemplateDraftSummary, TemplateEgressDefault, TemplateFileEntry, TemplateFilePage, TemplateFilesParams, TemplateFilesSummary, TemplateInput, TemplateLanguage, TemplateLanguages, TemplateOwner, TemplatePackage, TemplatePackageEcosystem, TemplatePackagePage, TemplateRecipe, TemplateRecipeV2, TemplateRecipeV2File, TemplateService, TemplateSettings, TemplateSettingsInput, TemplateSource, TemplateStartCommand, TemplateStorage, TemplateStorageWarning, TemplateSummary, TemplateUpload, TemplateUploadRequest, TemplateUploadResponse, TemplateUploadResult, TemplateVersion, TemplateVersionRecipe, TemplateVersionState, UploadData, WaitForBuildOptions, WorkspaceStartup, } from './templates.js';
23
27
  export { SecretsApi, WorkspaceSecrets } from './secrets.js';
24
28
  export type { BoundSecretStatus, CreateOrganizationSecretParams, CreateSecretParams, Secret, SecretAccessEvent, SecretPermissions, SecretScope, SecretVersion, UpdateSecretParams, WorkspaceSecretBindings, } from './secrets.js';
25
29
  export { EgressPolicyApi } from './egress.js';
@@ -36,6 +40,9 @@ export { ToolTokenManager } from './tokens.js';
36
40
  export type { ToolName, ToolToken, ToolTokenOptions } from './tokens.js';
37
41
  export { ToolArgumentError, executeToolCall, toAnthropicTools, toOpenAITools, validateArgs, workspaceTools } from './tools.js';
38
42
  export type { JsonSchema, WorkspaceTool, WorkspaceToolsOptions } from './tools.js';
43
+ export { CaptureError, ToolCallCapture, captureTool } from './capture.js';
44
+ export type { CallLike, CallRef, CaptureCall, CaptureErrorKind, CaptureEvent, CaptureFlushResult, CapturePart, CaptureSelector, CaptureSource, CaptureStats, CaptureStatus, DropReason, ToolCallCaptureOptions, WrapOptions, } from './capture.js';
45
+ export type { AiSdkAdapter, AiSdkToolEndEvent, AnthropicAdapter, ClaudeAdapter, ClaudeCaptureHooks, ClaudeHookCallback, ClaudeHookMatcher, LangChainAdapter, LangChainToolHandler, MastraAdapter, MastraAfterToolCallContext, McpAdapter, OpenAIAgentsAdapter, } from './capture-adapters.js';
39
46
  export { OperationFailedError, OperationTimeoutError, ShardfluxApiError, ShardfluxProtocolError } from './errors.js';
40
47
  export type { AppErrorCode, CellErrorCode, ErrorCode, ErrorReason, KnownErrorReason } from './errors.js';
41
48
  export { SDK_VERSION } from './http.js';
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  export { BillingApi, Shardflux, WorkspacesApi, fetchBillingCatalog, pickByKey } from "./client.js";
2
2
  export { formatTiming } from "./progress.js";
3
3
  export { UsageApi } from "./usage.js";
4
- export { TemplateBuildTimeoutError, TemplateBuildsApi, TemplateDraftApi, TemplatesApi, buildSettled, saveAsTemplateBody } from "./templates.js";
4
+ export { TemplateBuildTimeoutError, TemplateBuildsApi, TemplateDraftApi, TemplatePackagesApi, TemplateUploadError, TemplateUploadsApi, TemplateVersionTestInstancesApi, TemplateVersionsApi, TemplatesApi, buildSettled, saveAsTemplateBody, } from "./templates.js";
5
+ export { TemplateFileError, packDirectory, parseTemplateText, readTemplateFile } from "./template-file.js";
6
+ export { tarEnd, tarHeader, tarPadding } from "./tar.js";
5
7
  export { SecretsApi, WorkspaceSecrets } from "./secrets.js";
6
8
  export { EgressPolicyApi } from "./egress.js";
7
9
  export { VolumesApi } from "./volumes.js";
@@ -10,5 +12,6 @@ export { Workspace } from "./workspace.js";
10
12
  export { CellClient, DEFAULT_TRANSITION_TIMEOUT_MS, cellPath, ndjson } from "./cell.js";
11
13
  export { ToolTokenManager } from "./tokens.js";
12
14
  export { ToolArgumentError, executeToolCall, toAnthropicTools, toOpenAITools, validateArgs, workspaceTools } from "./tools.js";
15
+ export { CaptureError, ToolCallCapture, captureTool } from "./capture.js";
13
16
  export { OperationFailedError, OperationTimeoutError, ShardfluxApiError, ShardfluxProtocolError } from "./errors.js";
14
17
  export { SDK_VERSION } from "./http.js";
@@ -46,4 +46,6 @@ export declare function waitOptionsOf(opts: LifecycleOptions): WaitOptions | nul
46
46
  * Starts a lifecycle operation with `start` and, when `opts.wait` asks for it, waits for it to finish. The trace covers
47
47
  * the request, every observed state, and `AFTER_WAIT`; it ends with `done` either way.
48
48
  */
49
- export declare function runLifecycle(ctx: ClientContext, kind: LifecycleAction, workspaceId: string, start: (init: Pick<RequestOptions, 'onRetry'>) => Promise<Operation>, opts: InternalLifecycleOptions): Promise<Operation>;
49
+ export declare function runLifecycle(ctx: ClientContext, kind: LifecycleAction, workspaceId: string, start: (init: Pick<RequestOptions, 'onRetry'>) => Promise<Operation>, opts: InternalLifecycleOptions, capture?: {
50
+ settle?: boolean;
51
+ }): Promise<Operation>;
package/dist/lifecycle.js CHANGED
@@ -13,10 +13,14 @@ export function waitOptionsOf(opts) {
13
13
  * Starts a lifecycle operation with `start` and, when `opts.wait` asks for it, waits for it to finish. The trace covers
14
14
  * the request, every observed state, and `AFTER_WAIT`; it ends with `done` either way.
15
15
  */
16
- export async function runLifecycle(ctx, kind, workspaceId, start, opts) {
16
+ export async function runLifecycle(ctx, kind, workspaceId, start, opts, capture = {}) {
17
17
  const waitOpts = waitOptionsOf(opts);
18
18
  const trace = new Trace(kind, combineListeners(ctx.onProgress, opts.onProgress, waitOpts?.onProgress), { workspaceId });
19
19
  return traced(trace, async () => {
20
+ // Tool-call capture writes recorded before this call land first (snapshot, fork, suspend and close include them).
21
+ const pending = capture.settle ? ctx.captures.settle(workspaceId) : undefined;
22
+ if (pending)
23
+ await trace.span('capture_flush', () => pending);
20
24
  trace.phase('request');
21
25
  const operation = await start({ onRetry: trace.onRetry });
22
26
  trace.observe(operation);
@@ -26,8 +26,10 @@ export type LifecycleAction = 'open' | 'suspend' | 'resume' | 'snapshot' | 'fork
26
26
  * operation's `state_reason` (e.g. `no_ready_host`, `template_downloading`).
27
27
  * - `view`: reading the workspace after the operation. `token`: issuing a tool token (concurrent with `view` in open()).
28
28
  * - `busy`: a tool call waiting out `workspace_busy` (only in `tool` events).
29
+ * - `capture_flush` (0.7.0+): a lifecycle call waiting for tool-call capture writes recorded before it (only when some
30
+ * were pending).
29
31
  */
30
- export type LifecyclePhase = 'request' | 'queued' | 'capacity_pending' | 'running' | 'view' | 'token' | 'busy';
32
+ export type LifecyclePhase = 'request' | 'queued' | 'capacity_pending' | 'running' | 'view' | 'token' | 'busy' | 'capture_flush';
31
33
  export interface TimingPhase {
32
34
  phase: LifecyclePhase;
33
35
  /** The server's state_reason, or why the SDK entered the phase (`held`, `initial`, `expiring`, `invalidated`). */
package/dist/tar.d.ts ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * A small tar writer for build uploads of folders (contracts §24.2: uncompressed ustar/pax, extracted by the host's
3
+ * static tool). Pure: no Node imports, so the browser bundle can carry it.
4
+ *
5
+ * The bytes equal CPython's `tarfile.open(mode="w", format=tarfile.PAX_FORMAT)` with every member added by hand (the
6
+ * Python SDK's writer), so both SDKs upload the same bytes for the same folder and a template built from either has the
7
+ * same recipe_sha256:
8
+ * - a pax `x` header (`././@PaxHeader`) precedes a member whose name or link target is non-ASCII or longer than 100
9
+ * characters (`path`, `linkpath` records), or whose size does not fit the 11 octal digits (`size`);
10
+ * - the ustar header then holds the name encoded as ASCII with `?` for each non-ASCII code point, truncated to 100 bytes;
11
+ * - the archive ends with two zero blocks and is padded to a multiple of 10240 bytes (tarfile's RECORDSIZE).
12
+ */
13
+ export declare const TAR_BLOCK = 512;
14
+ export declare const TAR_RECORD_SIZE: number;
15
+ export type TarEntryType = 'file' | 'dir' | 'symlink';
16
+ export interface TarEntry {
17
+ /** Relative path with `/` separators; directories get their trailing `/` from the writer. */
18
+ path: string;
19
+ type: TarEntryType;
20
+ /** Permission bits (masked to 0o7777 as tarfile does). */
21
+ mode: number;
22
+ /** Bytes of a regular file (0 for directories and symlinks). */
23
+ size: number;
24
+ /** Symlink target. */
25
+ linkname?: string;
26
+ mtime?: number;
27
+ uid?: number;
28
+ gid?: number;
29
+ uname?: string;
30
+ gname?: string;
31
+ }
32
+ /** Zero padding after `size` bytes of member data. */
33
+ export declare function tarPadding(size: number): Uint8Array;
34
+ /**
35
+ * The header block(s) of one member: an optional pax extended header with its records, then the ustar header.
36
+ * Member data (a file's bytes) follows, then tarPadding(size).
37
+ */
38
+ export declare function tarHeader(entry: TarEntry): Uint8Array;
39
+ /** The end of an archive written so far (`offset` bytes): two zero blocks, then zeros up to a multiple of 10240. */
40
+ export declare function tarEnd(offset: number): Uint8Array;