@shardflux/sdk 0.6.1 → 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/CHANGELOG.md +105 -1
- package/README.md +216 -11
- package/dist/capture-adapters.d.ts +178 -0
- package/dist/capture-adapters.js +432 -0
- package/dist/capture-serialize.d.ts +97 -0
- package/dist/capture-serialize.js +481 -0
- package/dist/capture-text.d.ts +14 -0
- package/dist/capture-text.js +13 -0
- package/dist/capture.d.ts +288 -0
- package/dist/capture.js +1326 -0
- package/dist/cell.d.ts +7 -0
- package/dist/cell.js +13 -0
- package/dist/client.d.ts +24 -2
- package/dist/client.js +24 -5
- package/dist/egress.d.ts +18 -0
- package/dist/errors.d.ts +21 -2
- package/dist/errors.js +19 -2
- package/dist/generated/app-api.d.ts +2900 -193
- package/dist/http.d.ts +1 -1
- package/dist/http.js +1 -1
- package/dist/index.d.ts +10 -3
- package/dist/index.js +4 -1
- package/dist/lifecycle.d.ts +5 -2
- package/dist/lifecycle.js +5 -1
- package/dist/progress.d.ts +17 -3
- package/dist/progress.js +18 -4
- package/dist/tar.d.ts +40 -0
- package/dist/tar.js +150 -0
- package/dist/template-file.d.ts +92 -0
- package/dist/template-file.js +326 -0
- package/dist/templates.d.ts +318 -9
- package/dist/templates.js +432 -13
- package/dist/tools.d.ts +6 -13
- package/dist/tools.js +23 -3
- package/dist/workspace.d.ts +28 -3
- package/dist/workspace.js +52 -3
- package/package.json +24 -2
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`). */
|
|
@@ -92,7 +95,11 @@ export interface ForkTarget {
|
|
|
92
95
|
lifetime?: WorkspaceLifetime;
|
|
93
96
|
}
|
|
94
97
|
export interface WaitOptions {
|
|
95
|
-
/**
|
|
98
|
+
/**
|
|
99
|
+
* Give up waiting after this long (default 300 000 ms); the operation continues server side. A start waiting for
|
|
100
|
+
* capacity (`capacity_pending`) does so until its deadline (`error.details.deadline_at`, 15 minutes after it was
|
|
101
|
+
* created) and then fails with `capacity_unavailable` (retryable; nothing was started).
|
|
102
|
+
*/
|
|
96
103
|
timeoutMs?: number;
|
|
97
104
|
/** First poll delay (default 250 ms); doubles up to `maxPollIntervalMs` with jitter. Used when the server does not wait. */
|
|
98
105
|
pollIntervalMs?: number;
|
|
@@ -122,6 +129,15 @@ export interface OpenParams {
|
|
|
122
129
|
* ShardfluxApiError 422 (details.reason `secret_not_available`, details.names) and nothing is created or changed.
|
|
123
130
|
*/
|
|
124
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>;
|
|
125
141
|
/**
|
|
126
142
|
* `session`: the workspace is discarded when its session ends (workspace.close(), or the idle timeout); the key then
|
|
127
143
|
* opens a NEW workspace. Omitted: the template version's default, else `persistent`. Immutable: reopening a live key
|
|
@@ -182,6 +198,8 @@ export interface ClientContext {
|
|
|
182
198
|
workspaces: WorkspacesApi;
|
|
183
199
|
/** The client-level progress listener (ShardfluxOptions.onProgress). */
|
|
184
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;
|
|
185
203
|
}
|
|
186
204
|
export declare class WorkspacesApi {
|
|
187
205
|
#private;
|
|
@@ -200,7 +218,9 @@ export declare class WorkspacesApi {
|
|
|
200
218
|
* that does not wait is polled with bounded exponential backoff (+-20 % jitter). Resolves when the operation
|
|
201
219
|
* succeeds; throws OperationFailedError when it fails or is canceled, OperationTimeoutError after `timeoutMs` (the
|
|
202
220
|
* operation keeps running and can be awaited again). Both errors carry the wait's `timing`; `onProgress` sees each
|
|
203
|
-
* state change (queued, capacity_pending, running, with the server's reason) as it is observed.
|
|
221
|
+
* state change (queued, capacity_pending, running, with the server's reason) as it is observed. A start waiting in
|
|
222
|
+
* capacity_pending gives up at its deadline (`deadlineAt` on the phase event) and fails with `capacity_unavailable`
|
|
223
|
+
* (`err.retryable` true: nothing was started, retry later); the SDK does not retry it.
|
|
204
224
|
*/
|
|
205
225
|
waitForOperation(operationId: string, opts?: WaitOptions): Promise<Operation>;
|
|
206
226
|
/** One operation (GET /v1/operations/{id}); lifecycle operations stay pollable after a workspace is deleted. */
|
|
@@ -283,6 +303,8 @@ export declare class WorkspacesApi {
|
|
|
283
303
|
operation: Operation;
|
|
284
304
|
workspace: Workspace;
|
|
285
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>>;
|
|
286
308
|
operations(workspaceId: string, params?: {
|
|
287
309
|
limit?: number;
|
|
288
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 ?? {});
|
|
@@ -170,7 +173,9 @@ export class WorkspacesApi {
|
|
|
170
173
|
* that does not wait is polled with bounded exponential backoff (+-20 % jitter). Resolves when the operation
|
|
171
174
|
* succeeds; throws OperationFailedError when it fails or is canceled, OperationTimeoutError after `timeoutMs` (the
|
|
172
175
|
* operation keeps running and can be awaited again). Both errors carry the wait's `timing`; `onProgress` sees each
|
|
173
|
-
* state change (queued, capacity_pending, running, with the server's reason) as it is observed.
|
|
176
|
+
* state change (queued, capacity_pending, running, with the server's reason) as it is observed. A start waiting in
|
|
177
|
+
* capacity_pending gives up at its deadline (`deadlineAt` on the phase event) and fails with `capacity_unavailable`
|
|
178
|
+
* (`err.retryable` true: nothing was started, retry later); the SDK does not retry it.
|
|
174
179
|
*/
|
|
175
180
|
async waitForOperation(operationId, opts = {}) {
|
|
176
181
|
const inherited = opts[TRACE];
|
|
@@ -297,7 +302,13 @@ export class WorkspacesApi {
|
|
|
297
302
|
}
|
|
298
303
|
#op(kind, workspaceId, json, opts) {
|
|
299
304
|
const path = `/v1/workspaces/${encodeURIComponent(workspaceId)}${kind === 'delete' ? '' : `/${kind}`}`;
|
|
300
|
-
return runLifecycle(this.#ctx(), kind, workspaceId, async (init) =>
|
|
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' });
|
|
301
312
|
}
|
|
302
313
|
delete(workspaceId, opts = {}) {
|
|
303
314
|
return this.#op('delete', workspaceId, undefined, opts);
|
|
@@ -320,8 +331,9 @@ export class WorkspacesApi {
|
|
|
320
331
|
const operation = await runLifecycle(this.#ctx(), 'close', workspaceId, async (init) => {
|
|
321
332
|
const out = await this.#lifecycle('POST', `/v1/workspaces/${encodeURIComponent(workspaceId)}/close`, undefined, opts.idempotencyKey, init);
|
|
322
333
|
workspace = out.workspace;
|
|
334
|
+
this.#ctx().captures.discard(workspaceId, 'delete'); // the session is deleted
|
|
323
335
|
return out.operation;
|
|
324
|
-
}, opts);
|
|
336
|
+
}, opts, { settle: true });
|
|
325
337
|
return { operation, workspace: workspace };
|
|
326
338
|
}
|
|
327
339
|
reset(workspaceId, opts = {}) {
|
|
@@ -333,7 +345,8 @@ export class WorkspacesApi {
|
|
|
333
345
|
* captured briefly (`operation`, layer_snapshot); poll `build` with templates.builds.waitForBuild. Owners/admins and
|
|
334
346
|
* API keys with a tool permission only.
|
|
335
347
|
*/
|
|
336
|
-
saveAsTemplate(workspaceId, params) {
|
|
348
|
+
async saveAsTemplate(workspaceId, params) {
|
|
349
|
+
await this.#ctx().captures.settle(workspaceId); // captured tool calls are part of the saved layer
|
|
337
350
|
return this.#http.json('POST', `/v1/workspaces/${encodeURIComponent(workspaceId)}/save-as-template`, { json: saveAsTemplateBody(params), idempotencyKey: params.idempotencyKey ?? randomId('save-') }, this.#auth);
|
|
338
351
|
}
|
|
339
352
|
async fork(workspaceId, target, opts = {}) {
|
|
@@ -348,9 +361,14 @@ export class WorkspacesApi {
|
|
|
348
361
|
await trace.span('view', () => copy.refresh());
|
|
349
362
|
await opts[AFTER_WAIT]?.(trace, op);
|
|
350
363
|
},
|
|
351
|
-
});
|
|
364
|
+
}, { settle: true });
|
|
352
365
|
return { operation, workspace: copy };
|
|
353
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
|
+
}
|
|
354
372
|
async operations(workspaceId, params = {}) {
|
|
355
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);
|
|
356
374
|
return { data: page.data, nextCursor: page.next_cursor };
|
|
@@ -429,6 +447,7 @@ export class Shardflux {
|
|
|
429
447
|
sleep,
|
|
430
448
|
workspaces: this.workspaces,
|
|
431
449
|
onProgress: opts.onProgress,
|
|
450
|
+
captures: new CaptureRegistry(),
|
|
432
451
|
};
|
|
433
452
|
}
|
|
434
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 {
|
|
@@ -53,13 +60,19 @@ type Operation = AppComponents['schemas']['Operation'];
|
|
|
53
60
|
/**
|
|
54
61
|
* Waiting for an operation ran out of time. The operation keeps running server side:
|
|
55
62
|
* resume with `cloud.workspaces.waitForOperation(err.operationId)` or call open() again
|
|
56
|
-
* (it returns the same operation while it is active).
|
|
63
|
+
* (it returns the same operation while it is active). A start still waiting for capacity
|
|
64
|
+
* (`capacity_pending`) gives up at `deadlineAt` and then fails with `capacity_unavailable`.
|
|
57
65
|
*/
|
|
58
66
|
export declare class OperationTimeoutError extends Error {
|
|
59
67
|
readonly operationId: string;
|
|
60
68
|
readonly workspaceId: string | null;
|
|
61
69
|
readonly lastState: Operation['state'];
|
|
62
70
|
readonly lastReason: string | null;
|
|
71
|
+
/**
|
|
72
|
+
* When the operation was last `capacity_pending`: when it gives up waiting for a host (`error.details.deadline_at`,
|
|
73
|
+
* RFC 3339) and fails with `capacity_unavailable`. Null in other states or from an API that does not report it.
|
|
74
|
+
*/
|
|
75
|
+
readonly deadlineAt: string | null;
|
|
63
76
|
readonly waitedMs: number;
|
|
64
77
|
/** Where the time went: client phases, retries and the operation's own server timing so far. */
|
|
65
78
|
timing: LifecycleTiming | undefined;
|
|
@@ -70,6 +83,12 @@ export declare class OperationFailedError extends Error {
|
|
|
70
83
|
readonly operation: Operation;
|
|
71
84
|
readonly operationId: string;
|
|
72
85
|
readonly errorCode: string | null;
|
|
86
|
+
/**
|
|
87
|
+
* `operation.error.retryable` (false when absent): the same request may succeed later. `capacity_unavailable` is
|
|
88
|
+
* retryable: no host could admit the start before its deadline, nothing was started, retry later. The SDK never
|
|
89
|
+
* retries a failed operation itself.
|
|
90
|
+
*/
|
|
91
|
+
readonly retryable: boolean;
|
|
73
92
|
/** Where the time went before the operation failed. */
|
|
74
93
|
timing: LifecycleTiming | undefined;
|
|
75
94
|
constructor(op: Operation);
|
package/dist/errors.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { capacityDeadlineOf } from "./progress.js";
|
|
1
2
|
export function isErrorBody(v) {
|
|
2
3
|
if (typeof v !== 'object' || v === null || !('error' in v))
|
|
3
4
|
return false;
|
|
@@ -45,23 +46,32 @@ export class ShardfluxProtocolError extends Error {
|
|
|
45
46
|
/**
|
|
46
47
|
* Waiting for an operation ran out of time. The operation keeps running server side:
|
|
47
48
|
* resume with `cloud.workspaces.waitForOperation(err.operationId)` or call open() again
|
|
48
|
-
* (it returns the same operation while it is active).
|
|
49
|
+
* (it returns the same operation while it is active). A start still waiting for capacity
|
|
50
|
+
* (`capacity_pending`) gives up at `deadlineAt` and then fails with `capacity_unavailable`.
|
|
49
51
|
*/
|
|
50
52
|
export class OperationTimeoutError extends Error {
|
|
51
53
|
operationId;
|
|
52
54
|
workspaceId;
|
|
53
55
|
lastState;
|
|
54
56
|
lastReason;
|
|
57
|
+
/**
|
|
58
|
+
* When the operation was last `capacity_pending`: when it gives up waiting for a host (`error.details.deadline_at`,
|
|
59
|
+
* RFC 3339) and fails with `capacity_unavailable`. Null in other states or from an API that does not report it.
|
|
60
|
+
*/
|
|
61
|
+
deadlineAt;
|
|
55
62
|
waitedMs;
|
|
56
63
|
/** Where the time went: client phases, retries and the operation's own server timing so far. */
|
|
57
64
|
timing = undefined;
|
|
58
65
|
constructor(op, waitedMs) {
|
|
59
|
-
|
|
66
|
+
const deadlineAt = capacityDeadlineOf(op);
|
|
67
|
+
const after = deadlineAt ? `it keeps waiting for a host server side until ${deadlineAt} and fails with capacity_unavailable if none admits it by then.` : 'it continues server side.';
|
|
68
|
+
super(`Operation ${op.id} (${op.kind}) is still ${op.state}${op.state_reason ? ` (${op.state_reason})` : ''} after ${Math.round(waitedMs)} ms; ${after}`);
|
|
60
69
|
this.name = 'OperationTimeoutError';
|
|
61
70
|
this.operationId = op.id;
|
|
62
71
|
this.workspaceId = op.workspace_id;
|
|
63
72
|
this.lastState = op.state;
|
|
64
73
|
this.lastReason = op.state_reason ?? null;
|
|
74
|
+
this.deadlineAt = deadlineAt;
|
|
65
75
|
this.waitedMs = waitedMs;
|
|
66
76
|
}
|
|
67
77
|
}
|
|
@@ -70,6 +80,12 @@ export class OperationFailedError extends Error {
|
|
|
70
80
|
operation;
|
|
71
81
|
operationId;
|
|
72
82
|
errorCode;
|
|
83
|
+
/**
|
|
84
|
+
* `operation.error.retryable` (false when absent): the same request may succeed later. `capacity_unavailable` is
|
|
85
|
+
* retryable: no host could admit the start before its deadline, nothing was started, retry later. The SDK never
|
|
86
|
+
* retries a failed operation itself.
|
|
87
|
+
*/
|
|
88
|
+
retryable;
|
|
73
89
|
/** Where the time went before the operation failed. */
|
|
74
90
|
timing = undefined;
|
|
75
91
|
constructor(op) {
|
|
@@ -79,5 +95,6 @@ export class OperationFailedError extends Error {
|
|
|
79
95
|
this.operation = op;
|
|
80
96
|
this.operationId = op.id;
|
|
81
97
|
this.errorCode = code;
|
|
98
|
+
this.retryable = op.error?.retryable === true;
|
|
82
99
|
}
|
|
83
100
|
}
|