@shardflux/sdk 0.5.0 → 0.6.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.
@@ -585,10 +585,14 @@ export interface paths {
585
585
  /**
586
586
  * Idle policy, activity and the projected automatic suspend
587
587
  * @description `idle_since` = max(`last_tool_at`, `last_attach_at`, `keepalive_until`,
588
- * `running_since`) (null when none is known; in the future while a
589
- * keepalive is active). `suspend_at` = `idle_since` + `idle_minutes` when
588
+ * `running_since`, the work horizon of running exec commands) (null when
589
+ * none is known; in the future while a keepalive or a running command is
590
+ * active). A running exec command counts as work until min(its start +
591
+ * `IDLE_COMMAND_MAX_SECONDS`, its start + `timeout_ms`), and its end is
592
+ * activity (contracts §20.6). `suspend_at` = `idle_since` + `idle_minutes` when
590
593
  * the policy is `suspend_after` and automatic suspend is enabled, else
591
- * null. Activity lags tool traffic by up to `ACTIVITY_FLUSH_MS`. Any
594
+ * null; the idle loop acts within `2 x ACTIVITY_FLUSH_MS` after it.
595
+ * Activity lags tool traffic by up to `ACTIVITY_FLUSH_MS`. Any
592
596
  * tool granted by the token authorizes the call; the tool gate does not
593
597
  * apply and the call is not tool activity.
594
598
  */
@@ -601,6 +605,49 @@ export interface paths {
601
605
  patch?: never;
602
606
  trace?: never;
603
607
  };
608
+ "/v1/workspaces/{workspace_id}/changes": {
609
+ parameters: {
610
+ query?: never;
611
+ header?: never;
612
+ path: {
613
+ workspace_id: components["parameters"]["WorkspaceId"];
614
+ };
615
+ cookie?: never;
616
+ };
617
+ /**
618
+ * The workspace's changes against its template (layered workspaces)
619
+ * @description Templates v2 (contracts §19.10). Lists what the workspace changed
620
+ * relative to its template chain: the guest walks its workspace layer's
621
+ * overlay upper directory in raw path-byte order and classifies each
622
+ * entry against the template: `added` (absent in the template),
623
+ * `modified` (a regular file or symlink that exists in both),
624
+ * `metadata` (only with `hash=true`, when the content equals the
625
+ * template's), `deleted` (a whiteout over an existing template path) and
626
+ * `replaced` (an opaque directory hiding the template's contents).
627
+ * Directories that exist in both appear only when their mode or owner
628
+ * differ, or when they are opaque; ancestors copied up because a child
629
+ * changed are not reported.
630
+ *
631
+ * Requires a tool token with the `files` tool. It is a read-only action
632
+ * (served while a snapshot holds the tool gate `read_only`) and counts as
633
+ * tool activity. `hash=true` hashes regular files of 16 MiB or less;
634
+ * larger ones get `sha256: null, hash_skipped: true`.
635
+ *
636
+ * Errors: `409 workspace_not_running` (not running; a suspended
637
+ * workspace's changes are not served), `409 conflict` with
638
+ * `details.reason = legacy_disk_layout` (a legacy workspace has no
639
+ * workspace layer) or `guest_feature_unavailable` (the template's guest
640
+ * agent lacks `layer_changes.v1`).
641
+ */
642
+ get: operations["workspaceChanges"];
643
+ put?: never;
644
+ post?: never;
645
+ delete?: never;
646
+ options?: never;
647
+ head?: never;
648
+ patch?: never;
649
+ trace?: never;
650
+ };
604
651
  }
605
652
  export type webhooks = Record<string, never>;
606
653
  export interface components {
@@ -818,6 +865,49 @@ export interface components {
818
865
  entries: components["schemas"]["FileInfo"][];
819
866
  truncated: boolean;
820
867
  };
868
+ WorkspaceChange: {
869
+ path: string;
870
+ /** @enum {string} */
871
+ change: "added" | "modified" | "metadata" | "deleted" | "replaced";
872
+ /**
873
+ * @description The upper entry's type; for `deleted`, the template entry's type.
874
+ * @enum {string}
875
+ */
876
+ type: "file" | "dir" | "symlink" | "char" | "block" | "fifo";
877
+ /** Format: int64 */
878
+ size_bytes: number;
879
+ /** @description Octal permission bits, e.g. "0644". */
880
+ mode: string;
881
+ uid: number;
882
+ gid: number;
883
+ /** Format: date-time */
884
+ modified_at: string | null;
885
+ /** @description Only with hash=true, for regular files of 16 MiB or less. */
886
+ sha256: string | null;
887
+ hash_skipped: boolean;
888
+ link_target: string | null;
889
+ };
890
+ WorkspaceChangesSummary: {
891
+ /** Format: int64 */
892
+ added: number;
893
+ /** Format: int64 */
894
+ modified: number;
895
+ /** Format: int64 */
896
+ deleted: number;
897
+ /** Format: int64 */
898
+ replaced: number;
899
+ /**
900
+ * Format: int64
901
+ * @description Sum of the sizes of added and modified regular files.
902
+ */
903
+ bytes: number;
904
+ };
905
+ WorkspaceChangesPage: {
906
+ data: components["schemas"]["WorkspaceChange"][];
907
+ /** @description Opaque; null when there are no more changes under path_prefix. */
908
+ next_cursor: string | null;
909
+ summary?: components["schemas"]["WorkspaceChangesSummary"];
910
+ };
821
911
  FileWriteResult: {
822
912
  path: string;
823
913
  /** Format: int64 */
@@ -927,15 +1017,25 @@ export interface components {
927
1017
  keepalive_until: string;
928
1018
  };
929
1019
  IdlePolicy: {
930
- /** @enum {string} */
931
- mode: "hot" | "suspend_after";
932
- /** @description Set when mode is suspend_after. */
1020
+ /**
1021
+ * @description adaptive = the learned timeout (contracts §20.7, the default); suspend_after = a fixed timeout;
1022
+ * never (or the legacy hot) = no automatic suspend.
1023
+ * @enum {string}
1024
+ */
1025
+ mode: "hot" | "suspend_after" | "adaptive" | "never";
1026
+ /** @description Set when mode is suspend_after (rounded up from idle_seconds). */
933
1027
  idle_minutes: number | null;
1028
+ /** @description The fixed timeout in seconds when mode is suspend_after. */
1029
+ idle_seconds?: number | null;
1030
+ /** @description The timeout applied to the current idle period (adaptive or fixed); null while none applies. */
1031
+ timeout_seconds?: number | null;
1032
+ /** @description What the timeout comes from, e.g. "learned from 37 idle periods (active-hours)", "template prior", "default". */
1033
+ basis?: string | null;
934
1034
  /**
935
- * @description workspace = the workspace's own policy; default = the cell default (IDLE_DEFAULT_MODE / IDLE_DEFAULT_MINUTES).
1035
+ * @description workspace = the workspace's own policy; template = the template's default; default = the cell default (IDLE_DEFAULT_MODE).
936
1036
  * @enum {string}
937
1037
  */
938
- source: "workspace" | "default";
1038
+ source: "workspace" | "template" | "default";
939
1039
  };
940
1040
  IdleAutoSuspend: {
941
1041
  /** @description Policy is suspend_after and automatic suspend was not disabled (e.g. after repeated failures). */
@@ -1829,4 +1929,36 @@ export interface operations {
1829
1929
  default: components["responses"]["Error"];
1830
1930
  };
1831
1931
  };
1932
+ workspaceChanges: {
1933
+ parameters: {
1934
+ query?: {
1935
+ /** @description Absolute path; only entries at or below it. */
1936
+ path_prefix?: string;
1937
+ limit?: number;
1938
+ /** @description `next_cursor` of the previous page. */
1939
+ cursor?: string;
1940
+ hash?: boolean;
1941
+ /** @description Also return totals over everything under path_prefix. */
1942
+ summary?: boolean;
1943
+ };
1944
+ header?: never;
1945
+ path: {
1946
+ workspace_id: components["parameters"]["WorkspaceId"];
1947
+ };
1948
+ cookie?: never;
1949
+ };
1950
+ requestBody?: never;
1951
+ responses: {
1952
+ /** @description One page of changes, in raw path-byte order. */
1953
+ 200: {
1954
+ headers: {
1955
+ [name: string]: unknown;
1956
+ };
1957
+ content: {
1958
+ "application/json": components["schemas"]["WorkspaceChangesPage"];
1959
+ };
1960
+ };
1961
+ default: components["responses"]["Error"];
1962
+ };
1963
+ };
1832
1964
  }
package/dist/http.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- export declare const SDK_VERSION = "0.5.0";
1
+ import type { RetryRecord } from './progress.js';
2
+ export declare const SDK_VERSION = "0.6.0";
2
3
  export interface RequestOptions {
3
4
  query?: Record<string, string | number | boolean | undefined | null>;
4
5
  json?: unknown;
@@ -10,6 +11,8 @@ export interface RequestOptions {
10
11
  signal?: AbortSignal;
11
12
  /** Override the client's default request timeout (ms); 0 disables it (streams). */
12
13
  timeoutMs?: number;
14
+ /** Called before each retry of a transient failure (lifecycle timing records these). */
15
+ onRetry?: (retry: Omit<RetryRecord, 'atMs'>) => void;
13
16
  }
14
17
  export interface HttpOptions {
15
18
  baseUrl: string;
@@ -21,6 +24,16 @@ export interface HttpOptions {
21
24
  sleep?: (ms: number) => Promise<void>;
22
25
  }
23
26
  export declare const defaultSleep: (ms: number) => Promise<void>;
27
+ /**
28
+ * The fetch the SDK uses when none is given. On runtimes whose bundled undici is 8.x (Node 26) it sends
29
+ * `Connection: close`: undici 8.9 intermittently stalls a request on a reused keep-alive connection after the
30
+ * connection sat idle for a few seconds (measured against the staging API: 20.9 s and 26.3 s stalls in 50 requests
31
+ * 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
32
+ * 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
33
+ * interaction). The CLI and MCP server do the same. Other runtimes keep the runtime's keep-alive pooling.
34
+ * `SHARDFLUX_HTTP_KEEPALIVE=1` disables the workaround.
35
+ */
36
+ export declare function defaultFetch(env?: Record<string, string | undefined> | undefined, undiciVersion?: string | undefined): typeof fetch;
24
37
  export declare function buildUrl(base: string, path: string, query?: RequestOptions['query']): string;
25
38
  /** Turns a non-2xx response into a ShardfluxApiError (or a protocol error for undocumented bodies). */
26
39
  export declare function errorFrom(res: Response, source: 'api' | 'cell'): Promise<Error>;
@@ -30,10 +43,26 @@ export declare class HttpClient {
30
43
  /** Performs the request and returns the raw Response (2xx), throwing structured errors otherwise. */
31
44
  raw(method: string, path: string, init?: RequestOptions, authorization?: string): Promise<Response>;
32
45
  json<T>(method: string, path: string, init?: RequestOptions, authorization?: string): Promise<T>;
46
+ /** Like json() but also returns the status and the response headers (bounded waits read Preference-Applied). */
47
+ jsonWithHeaders<T>(method: string, path: string, init?: RequestOptions, authorization?: string): Promise<{
48
+ status: number;
49
+ headers: Headers;
50
+ body: T;
51
+ }>;
33
52
  /** Like json() but also returns the HTTP status (open() distinguishes 200 from 202). */
34
53
  jsonWithStatus<T>(method: string, path: string, init?: RequestOptions, authorization?: string): Promise<{
35
54
  status: number;
36
55
  body: T;
37
56
  }>;
38
57
  }
58
+ /** Longest server-side wait the SDK asks for (contracts §3: servers cap `Prefer: wait` at 20 s). */
59
+ export declare const SERVER_WAIT_MAX_S = 20;
60
+ /**
61
+ * One bounded-wait poll (contracts §3): GET `path` with `Prefer: wait=<s>` when `waitS` >= 1. `applied` is true only
62
+ * when the server says it waited (Preference-Applied); otherwise the caller keeps its own backoff.
63
+ */
64
+ export declare function pollWithWait<T>(http: HttpClient, path: string, authorization: string, waitS: number, signal?: AbortSignal, onRetry?: RequestOptions['onRetry']): Promise<{
65
+ body: T;
66
+ applied: boolean;
67
+ }>;
39
68
  export declare function randomId(prefix?: string): string;
package/dist/http.js CHANGED
@@ -5,8 +5,29 @@
5
5
  * requests carrying an Idempotency-Key).
6
6
  */
7
7
  import { ShardfluxApiError, ShardfluxProtocolError, isErrorBody } from "./errors.js";
8
- export const SDK_VERSION = '0.5.0';
8
+ import { describeFailure } from "./progress.js";
9
+ export const SDK_VERSION = '0.6.0';
9
10
  export const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
11
+ /**
12
+ * The fetch the SDK uses when none is given. On runtimes whose bundled undici is 8.x (Node 26) it sends
13
+ * `Connection: close`: undici 8.9 intermittently stalls a request on a reused keep-alive connection after the
14
+ * connection sat idle for a few seconds (measured against the staging API: 20.9 s and 26.3 s stalls in 50 requests
15
+ * 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
16
+ * 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
17
+ * interaction). The CLI and MCP server do the same. Other runtimes keep the runtime's keep-alive pooling.
18
+ * `SHARDFLUX_HTTP_KEEPALIVE=1` disables the workaround.
19
+ */
20
+ export function defaultFetch(env = globalThis.process?.env, undiciVersion = globalThis.process?.versions?.undici) {
21
+ const base = (input, init) => fetch(input, init);
22
+ const major = Number((undiciVersion ?? '').split('.')[0]);
23
+ if (!(major >= 8) || env?.SHARDFLUX_HTTP_KEEPALIVE === '1')
24
+ return base;
25
+ return (input, init) => {
26
+ const headers = new Headers(init?.headers);
27
+ headers.set('connection', 'close');
28
+ return fetch(input, { ...init, headers });
29
+ };
30
+ }
10
31
  export function buildUrl(base, path, query) {
11
32
  const u = new URL(base.replace(/\/+$/, '') + path);
12
33
  for (const [k, v] of Object.entries(query ?? {})) {
@@ -76,7 +97,9 @@ export class HttpClient {
76
97
  throw err;
77
98
  if (!retriable || attempt >= this.opts.maxRetries)
78
99
  throw err;
79
- await sleep(Math.min(2_000, 200 * 2 ** attempt));
100
+ const delayMs = Math.min(2_000, 200 * 2 ** attempt);
101
+ init.onRetry?.({ request: `${method} ${path}`, attempt: attempt + 1, cause: describeFailure(err, timeoutMs), delayMs });
102
+ await sleep(delayMs);
80
103
  continue;
81
104
  }
82
105
  if (res.ok)
@@ -87,7 +110,9 @@ export class HttpClient {
87
110
  if (!retriable || !retryableError || attempt >= this.opts.maxRetries)
88
111
  throw error;
89
112
  const hinted = error instanceof ShardfluxApiError ? error.retryAfterSeconds : undefined;
90
- await sleep(Math.min(5_000, hinted !== undefined ? hinted * 1000 : 200 * 2 ** attempt));
113
+ const delayMs = Math.min(5_000, hinted !== undefined ? hinted * 1000 : 200 * 2 ** attempt);
114
+ init.onRetry?.({ request: `${method} ${path}`, attempt: attempt + 1, cause: describeFailure(error), delayMs });
115
+ await sleep(delayMs);
91
116
  }
92
117
  }
93
118
  async json(method, path, init = {}, authorization) {
@@ -104,6 +129,17 @@ export class HttpClient {
104
129
  throw new ShardfluxProtocolError(`${method} ${path}: response is not JSON`, res.status);
105
130
  }
106
131
  }
132
+ /** Like json() but also returns the status and the response headers (bounded waits read Preference-Applied). */
133
+ async jsonWithHeaders(method, path, init = {}, authorization) {
134
+ const res = await this.raw(method, path, init, authorization);
135
+ const text = await res.text();
136
+ try {
137
+ return { status: res.status, headers: res.headers, body: (text.length === 0 ? undefined : JSON.parse(text)) };
138
+ }
139
+ catch {
140
+ throw new ShardfluxProtocolError(`${method} ${path}: response is not JSON`, res.status);
141
+ }
142
+ }
107
143
  /** Like json() but also returns the HTTP status (open() distinguishes 200 from 202). */
108
144
  async jsonWithStatus(method, path, init = {}, authorization) {
109
145
  const res = await this.raw(method, path, init, authorization);
@@ -116,6 +152,24 @@ export class HttpClient {
116
152
  }
117
153
  }
118
154
  }
155
+ /** Longest server-side wait the SDK asks for (contracts §3: servers cap `Prefer: wait` at 20 s). */
156
+ export const SERVER_WAIT_MAX_S = 20;
157
+ /**
158
+ * One bounded-wait poll (contracts §3): GET `path` with `Prefer: wait=<s>` when `waitS` >= 1. `applied` is true only
159
+ * when the server says it waited (Preference-Applied); otherwise the caller keeps its own backoff.
160
+ */
161
+ export async function pollWithWait(http, path, authorization, waitS, signal, onRetry) {
162
+ const s = Math.min(SERVER_WAIT_MAX_S, Math.floor(waitS));
163
+ const init = { ...(signal ? { signal } : {}), ...(onRetry ? { onRetry } : {}) };
164
+ if (s >= 1) {
165
+ init.headers = { prefer: `wait=${s}` };
166
+ // The held response may take the whole wait: the request timeout must outlast it.
167
+ init.timeoutMs = Math.max(http.opts.timeoutMs, s * 1000 + 10_000);
168
+ }
169
+ const res = await http.jsonWithHeaders('GET', path, init, authorization);
170
+ const pa = res.headers.get('preference-applied');
171
+ return { body: res.body, applied: s >= 1 && pa !== null && /\bwait\s*=/i.test(pa) };
172
+ }
119
173
  export function randomId(prefix = '') {
120
174
  return `${prefix}${crypto.randomUUID()}`;
121
175
  }
package/dist/index.d.ts CHANGED
@@ -11,14 +11,17 @@
11
11
  */
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
- export { BillingApi, Shardflux, WorkspacesApi, fetchBillingCatalog } from './client.js';
15
- export type { AgentSession, BillingCatalog, BillingSubscription, Caps, CheckoutSession, Entitlements, Invoice, InvoicePage, ListParams, Me, OpenParams, OpenResponse, Operation, Page, PortalSession, ShardfluxOptions, WaitOptions, WorkspaceView, } from './client.js';
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';
16
+ export type { FinishedOperation, LifecycleOptions, WaitedLifecycleOptions } from './lifecycle.js';
17
+ export { formatTiming } from './progress.js';
18
+ export type { LifecycleAction, LifecyclePhase, LifecycleTiming, ProgressEvent, ProgressListener, RetryRecord, ServerTiming, TimingOutcome, TimingPhase, } from './progress.js';
16
19
  export { UsageApi } from './usage.js';
17
20
  export type { Grants, Spend, SpendPolicy, UsageEstimate, UsageMeter, UsageSeries, UsageSeriesParams, UsageSummary } from './usage.js';
18
- export { TemplateBuildTimeoutError, TemplateBuildsApi, TemplatesApi, buildSettled } from './templates.js';
19
- export type { BuilderAvailability, CreateTemplateBuildParams, TemplateBuild, TemplateBuildLogUrl, TemplateBuildRegistrationState, TemplateBuildState, TemplateDetail, TemplateOwner, TemplateRecipe, TemplateSummary, TemplateVersion, TemplateVersionState, WaitForBuildOptions, } from './templates.js';
20
- export { SecretsApi } from './secrets.js';
21
- export type { CreateSecretParams, Secret, SecretPermissions, SecretScope, SecretVersion, UpdateSecretParams } from './secrets.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';
23
+ export { SecretsApi, WorkspaceSecrets } from './secrets.js';
24
+ export type { BoundSecretStatus, CreateOrganizationSecretParams, CreateSecretParams, Secret, SecretAccessEvent, SecretPermissions, SecretScope, SecretVersion, UpdateSecretParams, WorkspaceSecretBindings, } from './secrets.js';
22
25
  export { EgressPolicyApi } from './egress.js';
23
26
  export type { EffectiveEgressPolicy, EgressEnforcement, EgressMode, EgressPolicyInput, EgressPolicyVersion, EgressRule, EgressRuleInput, OrganizationEgressOverride, ProjectEgressPolicy, WorkspaceEgressPolicy, } from './egress.js';
24
27
  export { VolumesApi } from './volumes.js';
@@ -26,12 +29,13 @@ export type { AttachVolumeParams, AttachmentResult, CreateVolumeParams, DeleteVo
26
29
  export { AuditApi, auditQuery, parseAuditNdjson } from './audit.js';
27
30
  export type { AuditActorType, AuditEvent, AuditFilters, AuditListParams, AuditOutcome, AuditSource } from './audit.js';
28
31
  export { Workspace } from './workspace.js';
29
- export { CellClient, cellPath, ndjson } from './cell.js';
30
- export type { BrowserContent, BrowserContentRequest, BrowserScreenshotRequest, CellClientOptions, ExecSession, ExecStartRequest, FileInfo, FileList, FileWriteResult, GitResult, GitStatus, OutputEvent, ProcessList, PtyOpenRequest, PtySession, RunOptions, RunResult, Signal, } from './cell.js';
32
+ export type { WakeOptions } from './workspace.js';
33
+ export { CellClient, DEFAULT_TRANSITION_TIMEOUT_MS, cellPath, ndjson } from './cell.js';
34
+ export type { BrowserContent, BrowserContentRequest, BrowserScreenshotRequest, CellClientOptions, ExecSession, ExecStartRequest, FileInfo, FileList, FileWriteResult, GitResult, GitStatus, OutputEvent, ProcessList, PtyOpenRequest, PtySession, RunOptions, RunResult, Signal, WorkspaceChange, WorkspaceChangeKind, WorkspaceChangesPage, WorkspaceChangesParams, WorkspaceChangesSummary, } from './cell.js';
31
35
  export { ToolTokenManager } from './tokens.js';
32
36
  export type { ToolName, ToolToken, ToolTokenOptions } from './tokens.js';
33
37
  export { ToolArgumentError, executeToolCall, toAnthropicTools, toOpenAITools, validateArgs, workspaceTools } from './tools.js';
34
38
  export type { JsonSchema, WorkspaceTool, WorkspaceToolsOptions } from './tools.js';
35
39
  export { OperationFailedError, OperationTimeoutError, ShardfluxApiError, ShardfluxProtocolError } from './errors.js';
36
- export type { AppErrorCode, CellErrorCode, ErrorCode } from './errors.js';
40
+ export type { AppErrorCode, CellErrorCode, ErrorCode, ErrorReason, KnownErrorReason } from './errors.js';
37
41
  export { SDK_VERSION } from './http.js';
package/dist/index.js CHANGED
@@ -1,12 +1,13 @@
1
- export { BillingApi, Shardflux, WorkspacesApi, fetchBillingCatalog } from "./client.js";
1
+ export { BillingApi, Shardflux, WorkspacesApi, fetchBillingCatalog, pickByKey } from "./client.js";
2
+ export { formatTiming } from "./progress.js";
2
3
  export { UsageApi } from "./usage.js";
3
- export { TemplateBuildTimeoutError, TemplateBuildsApi, TemplatesApi, buildSettled } from "./templates.js";
4
- export { SecretsApi } from "./secrets.js";
4
+ export { TemplateBuildTimeoutError, TemplateBuildsApi, TemplateDraftApi, TemplatesApi, buildSettled, saveAsTemplateBody } from "./templates.js";
5
+ export { SecretsApi, WorkspaceSecrets } from "./secrets.js";
5
6
  export { EgressPolicyApi } from "./egress.js";
6
7
  export { VolumesApi } from "./volumes.js";
7
8
  export { AuditApi, auditQuery, parseAuditNdjson } from "./audit.js";
8
9
  export { Workspace } from "./workspace.js";
9
- export { CellClient, cellPath, ndjson } from "./cell.js";
10
+ export { CellClient, DEFAULT_TRANSITION_TIMEOUT_MS, cellPath, ndjson } from "./cell.js";
10
11
  export { ToolTokenManager } from "./tokens.js";
11
12
  export { ToolArgumentError, executeToolCall, toAnthropicTools, toOpenAITools, validateArgs, workspaceTools } from "./tools.js";
12
13
  export { OperationFailedError, OperationTimeoutError, ShardfluxApiError, ShardfluxProtocolError } from "./errors.js";
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Lifecycle calls (suspend, resume, snapshot, fork, delete, close, reset): "requested" or "finished".
3
+ *
4
+ * Every lifecycle endpoint answers once the operation is accepted, while it is usually still `queued`. By default the
5
+ * SDK returns that operation: the call resolves when the change is REQUESTED. With `wait`, it waits for the operation
6
+ * and resolves when the change has FINISHED, returning the succeeded operation (typed FinishedOperation) and, on a
7
+ * workspace handle, the refreshed view. Both paths are traced (see progress.ts).
8
+ */
9
+ import type { ClientContext, Operation, WaitOptions } from './client.js';
10
+ import type { RequestOptions } from './http.js';
11
+ import { Trace } from './progress.js';
12
+ import type { LifecycleAction, ProgressListener } from './progress.js';
13
+ /** An operation that has finished successfully: what a lifecycle call with `wait` resolves to. */
14
+ export type FinishedOperation = Operation & {
15
+ state: 'succeeded';
16
+ completed_at: string;
17
+ };
18
+ export interface LifecycleOptions {
19
+ idempotencyKey?: string;
20
+ /**
21
+ * Default (false): resolve once the change is requested; the returned operation is usually still `queued`.
22
+ * `true` or WaitOptions: resolve once it has finished (the operation `succeeded`); throws OperationFailedError when it
23
+ * fails and OperationTimeoutError after `timeoutMs` (default 5 minutes; the operation continues server side).
24
+ */
25
+ wait?: boolean | WaitOptions;
26
+ /** Progress events of this call (phases, retries, and `done` with its timing). */
27
+ onProgress?: ProgressListener;
28
+ }
29
+ /** Lifecycle options the options of a waited call narrow to. */
30
+ export type WaitedLifecycleOptions = LifecycleOptions & {
31
+ wait: true | WaitOptions;
32
+ };
33
+ /** Internal: the trace a wait continues instead of starting its own (open(), wake() and waited lifecycle calls). */
34
+ export declare const TRACE: unique symbol;
35
+ /** Internal: work a workspace handle does after the operation finished, inside the same trace (refreshing its view). */
36
+ export declare const AFTER_WAIT: unique symbol;
37
+ export type InternalWaitOptions = WaitOptions & {
38
+ [TRACE]?: Trace;
39
+ };
40
+ export type InternalLifecycleOptions = LifecycleOptions & {
41
+ [AFTER_WAIT]?: (trace: Trace, operation: Operation) => Promise<void>;
42
+ };
43
+ export declare function waitOptionsOf(opts: LifecycleOptions): WaitOptions | null;
44
+ /**
45
+ * Starts a lifecycle operation with `start` and, when `opts.wait` asks for it, waits for it to finish. The trace covers
46
+ * the request, every observed state, and `AFTER_WAIT`; it ends with `done` either way.
47
+ */
48
+ export declare function runLifecycle(ctx: ClientContext, kind: LifecycleAction, workspaceId: string, start: (init: Pick<RequestOptions, 'onRetry'>) => Promise<Operation>, opts: InternalLifecycleOptions): Promise<Operation>;
@@ -0,0 +1,33 @@
1
+ import { OperationFailedError } from "./errors.js";
2
+ import { Trace, combineListeners, traced } from "./progress.js";
3
+ /** Internal: the trace a wait continues instead of starting its own (open(), wake() and waited lifecycle calls). */
4
+ export const TRACE = Symbol('shardflux.trace');
5
+ /** Internal: work a workspace handle does after the operation finished, inside the same trace (refreshing its view). */
6
+ export const AFTER_WAIT = Symbol('shardflux.afterWait');
7
+ export function waitOptionsOf(opts) {
8
+ if (opts.wait === undefined || opts.wait === false)
9
+ return null;
10
+ return opts.wait === true ? {} : opts.wait;
11
+ }
12
+ /**
13
+ * Starts a lifecycle operation with `start` and, when `opts.wait` asks for it, waits for it to finish. The trace covers
14
+ * the request, every observed state, and `AFTER_WAIT`; it ends with `done` either way.
15
+ */
16
+ export async function runLifecycle(ctx, kind, workspaceId, start, opts) {
17
+ const waitOpts = waitOptionsOf(opts);
18
+ const trace = new Trace(kind, combineListeners(ctx.onProgress, opts.onProgress, waitOpts?.onProgress), { workspaceId });
19
+ return traced(trace, async () => {
20
+ trace.phase('request');
21
+ const operation = await start({ onRetry: trace.onRetry });
22
+ trace.observe(operation);
23
+ if (!waitOpts)
24
+ return operation;
25
+ let final = operation;
26
+ if (operation.state === 'failed' || operation.state === 'canceled')
27
+ throw new OperationFailedError(operation);
28
+ if (operation.state !== 'succeeded')
29
+ final = await ctx.workspaces.waitForOperation(operation.id, { ...waitOpts, [TRACE]: trace });
30
+ await opts[AFTER_WAIT]?.(trace, final);
31
+ return final;
32
+ });
33
+ }