@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.
- package/CHANGELOG.md +63 -0
- package/README.md +228 -24
- package/dist/cell.d.ts +67 -2
- package/dist/cell.js +98 -8
- package/dist/client.d.ts +148 -22
- package/dist/client.js +232 -29
- package/dist/errors.d.ts +20 -0
- package/dist/errors.js +10 -0
- package/dist/generated/app-api.d.ts +10223 -5857
- package/dist/generated/cell-api.d.ts +140 -8
- package/dist/http.d.ts +30 -1
- package/dist/http.js +57 -3
- package/dist/index.d.ts +13 -9
- package/dist/index.js +5 -4
- package/dist/lifecycle.d.ts +48 -0
- package/dist/lifecycle.js +33 -0
- package/dist/progress.d.ts +166 -0
- package/dist/progress.js +238 -0
- package/dist/secrets.d.ts +83 -6
- package/dist/secrets.js +53 -1
- package/dist/templates.d.ts +279 -7
- package/dist/templates.js +216 -4
- package/dist/tokens.d.ts +8 -3
- package/dist/tokens.js +25 -13
- package/dist/tools.d.ts +17 -0
- package/dist/tools.js +5 -1
- package/dist/workspace.d.ts +112 -20
- package/dist/workspace.js +176 -10
- package/package.json +2 -1
package/dist/cell.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ShardfluxApiError, ShardfluxProtocolError } from "./errors.js";
|
|
2
2
|
import { HttpClient, defaultSleep, randomId } from "./http.js";
|
|
3
|
+
import { describeFailure, emitTo } from "./progress.js";
|
|
3
4
|
/** Fills a path template that must exist in cell-api.yaml. */
|
|
4
5
|
export function cellPath(template, params) {
|
|
5
6
|
return template.replace(/\{([a-z_]+)\}/g, (_, name) => {
|
|
@@ -9,6 +10,9 @@ export function cellPath(template, params) {
|
|
|
9
10
|
return encodeURIComponent(String(v));
|
|
10
11
|
});
|
|
11
12
|
}
|
|
13
|
+
export const DEFAULT_TRANSITION_TIMEOUT_MS = 120_000;
|
|
14
|
+
/** Wakes per call at most: a workspace that keeps being suspended again surfaces the refusal. */
|
|
15
|
+
const MAX_WAKES = 3;
|
|
12
16
|
const b64 = (bytes) => Buffer.from(typeof bytes === 'string' ? Buffer.from(bytes, 'utf8') : bytes).toString('base64');
|
|
13
17
|
const unb64 = (s) => (s ? new Uint8Array(Buffer.from(s, 'base64')) : new Uint8Array());
|
|
14
18
|
/** Parses an NDJSON byte stream into objects (tolerates CRLF and a final unterminated line). */
|
|
@@ -68,7 +72,11 @@ export class CellClient {
|
|
|
68
72
|
workspaceId;
|
|
69
73
|
tokens;
|
|
70
74
|
#opts;
|
|
75
|
+
#listener;
|
|
76
|
+
#wake;
|
|
77
|
+
#transitionTimeoutMs;
|
|
71
78
|
#clients = new Map();
|
|
79
|
+
#closer = new AbortController();
|
|
72
80
|
constructor(workspaceId, tokens, opts = {}) {
|
|
73
81
|
this.workspaceId = workspaceId;
|
|
74
82
|
this.tokens = tokens;
|
|
@@ -79,6 +87,9 @@ export class CellClient {
|
|
|
79
87
|
maxRetries: opts.maxRetries ?? 2,
|
|
80
88
|
sleep: opts.sleep ?? defaultSleep,
|
|
81
89
|
};
|
|
90
|
+
this.#wake = opts.wake ?? null;
|
|
91
|
+
this.#transitionTimeoutMs = opts.transitionTimeoutMs ?? DEFAULT_TRANSITION_TIMEOUT_MS;
|
|
92
|
+
this.#listener = opts.onProgress;
|
|
82
93
|
}
|
|
83
94
|
#http(endpoint) {
|
|
84
95
|
let c = this.#clients.get(endpoint);
|
|
@@ -88,18 +99,71 @@ export class CellClient {
|
|
|
88
99
|
}
|
|
89
100
|
return c;
|
|
90
101
|
}
|
|
91
|
-
/**
|
|
102
|
+
/** True after close(): every request (and stream) of this client is aborted. */
|
|
103
|
+
get closed() {
|
|
104
|
+
return this.#closer.signal.aborted;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Aborts every in-flight and future request of this client, including exec output streams and PTY reads (commands
|
|
108
|
+
* keep running in the workspace: nothing is canceled there). Workspace.close() calls it.
|
|
109
|
+
*/
|
|
110
|
+
close() {
|
|
111
|
+
if (!this.#closer.signal.aborted)
|
|
112
|
+
this.#closer.abort(new DOMException('The workspace handle was closed.', 'AbortError'));
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* One authorized request. Refreshes the token once on stale_epoch / 401. Lifecycle transitions (contracts §20.4),
|
|
116
|
+
* bounded in total by `transitionTimeoutMs`: a call refused with `workspace_busy` is retried after `Retry-After`; one
|
|
117
|
+
* refused with `workspace_not_running` (or whose token cannot be minted because the workspace is not running) wakes
|
|
118
|
+
* the workspace through `wake`, given the time left, and is retried with a fresh token, at most 3 wakes per call.
|
|
119
|
+
* Refused calls were never executed, so retrying is safe. When the budget is spent the refusal surfaces.
|
|
120
|
+
*/
|
|
92
121
|
async request(method, path, init = {}) {
|
|
93
|
-
|
|
94
|
-
|
|
122
|
+
const closer = this.#closer.signal;
|
|
123
|
+
if (closer.aborted)
|
|
124
|
+
throw closer.reason;
|
|
125
|
+
const { wake: wakeAllowed = true, ...reqInit } = init;
|
|
126
|
+
const signal = reqInit.signal ? AbortSignal.any([reqInit.signal, closer]) : closer;
|
|
127
|
+
const deadline = Date.now() + this.#transitionTimeoutMs;
|
|
128
|
+
const t0 = performance.now();
|
|
129
|
+
const listener = this.#listener;
|
|
130
|
+
const emit = listener
|
|
131
|
+
? (e) => emitTo([listener], { action: 'tool', workspaceId: this.workspaceId, operationId: null, atMs: Math.round((performance.now() - t0) * 10) / 10, ...e })
|
|
132
|
+
: undefined;
|
|
133
|
+
const retry = (cause, delayMs, attempt) => emit?.({ type: 'retry', retry: { atMs: Math.round((performance.now() - t0) * 10) / 10, request: `${method} ${path}`, attempt, cause, delayMs } });
|
|
134
|
+
let refreshed = false;
|
|
135
|
+
let wakes = 0;
|
|
136
|
+
let attempts = 0;
|
|
137
|
+
for (;;) {
|
|
95
138
|
try {
|
|
96
|
-
|
|
139
|
+
const token = await this.tokens.get(listener);
|
|
140
|
+
return await this.#http(token.cell_endpoint).raw(method, path, { ...reqInit, signal, ...(emit ? { onRetry: (r) => retry(r.cause, r.delayMs, (attempts += 1)) } : {}) }, `Bearer ${token.token}`);
|
|
97
141
|
}
|
|
98
142
|
catch (err) {
|
|
99
|
-
|
|
100
|
-
if (!refreshable || attempt > 0)
|
|
143
|
+
if (!(err instanceof ShardfluxApiError) || signal.aborted)
|
|
101
144
|
throw err;
|
|
102
|
-
|
|
145
|
+
if ((err.code === 'stale_epoch' || (err.status === 401 && err.source === 'cell')) && !refreshed) {
|
|
146
|
+
refreshed = true;
|
|
147
|
+
this.tokens.invalidate();
|
|
148
|
+
retry(err.code === 'stale_epoch' ? 'stale_epoch (the workspace moved or resumed; new token)' : `${describeFailure(err)} (new token)`, 0, (attempts += 1));
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const left = deadline - Date.now();
|
|
152
|
+
if (err.code === 'workspace_busy' && left > 0) {
|
|
153
|
+
emit?.({ type: 'phase', phase: 'busy', reason: err.reason ?? 'workspace_busy' });
|
|
154
|
+
await this.#opts.sleep(Math.min(left, 5_000, Math.max(250, (err.retryAfterSeconds ?? 1) * 1000)));
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const notRunning = err.code === 'workspace_not_running' || (err.code === 'conflict' && err.reason === 'workspace_not_running');
|
|
158
|
+
if (notRunning && wakeAllowed && this.#wake && wakes < MAX_WAKES && left > 0) {
|
|
159
|
+
wakes += 1;
|
|
160
|
+
if ((await this.#wake(left, signal)) === false)
|
|
161
|
+
throw err; // running per the API: nothing to wait for
|
|
162
|
+
this.tokens.invalidate();
|
|
163
|
+
refreshed = false;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
throw err;
|
|
103
167
|
}
|
|
104
168
|
}
|
|
105
169
|
}
|
|
@@ -130,6 +194,8 @@ export class CellClient {
|
|
|
130
194
|
/** Output events from byte offsets (NDJSON). `follow` keeps the stream open until `exit`. */
|
|
131
195
|
output: async (sessionId, opts = {}) => {
|
|
132
196
|
const res = await this.request('GET', this.#p('/v1/workspaces/{workspace_id}/exec/{session_id}/output', { session_id: sessionId }), {
|
|
197
|
+
// Following never wakes: a workspace suspended under a running command stays suspended (explicit suspend).
|
|
198
|
+
wake: opts.follow === false,
|
|
133
199
|
query: { stdout_offset: opts.stdoutOffset ?? 0, stderr_offset: opts.stderrOffset ?? 0, follow: opts.follow ?? true },
|
|
134
200
|
accept: 'application/x-ndjson',
|
|
135
201
|
timeoutMs: opts.follow === false ? undefined : 0,
|
|
@@ -223,7 +289,7 @@ export class CellClient {
|
|
|
223
289
|
}
|
|
224
290
|
}
|
|
225
291
|
catch (e) {
|
|
226
|
-
if (opts.signal?.aborted)
|
|
292
|
+
if (opts.signal?.aborted || this.closed)
|
|
227
293
|
throw e;
|
|
228
294
|
const transient = !(e instanceof ShardfluxApiError) || e.retryable;
|
|
229
295
|
if (!transient || reconnects >= maxReconnects)
|
|
@@ -234,6 +300,8 @@ export class CellClient {
|
|
|
234
300
|
break;
|
|
235
301
|
}
|
|
236
302
|
// Stream ended without `exit` (gateway restart, idle proxy, network): resume from offsets.
|
|
303
|
+
if (this.closed)
|
|
304
|
+
throw this.#closer.signal.reason;
|
|
237
305
|
reconnects += 1;
|
|
238
306
|
if (reconnects > maxReconnects)
|
|
239
307
|
throw new ShardfluxProtocolError(`exec ${sessionId}: output stream kept dropping`, 0);
|
|
@@ -288,6 +356,7 @@ export class CellClient {
|
|
|
288
356
|
let session = null;
|
|
289
357
|
let exited = false;
|
|
290
358
|
await new Promise((resolve, reject) => {
|
|
359
|
+
const onClose = () => finish(this.#closer.signal.reason instanceof Error ? this.#closer.signal.reason : new Error('closed'));
|
|
291
360
|
const ws = new WS(url, { headers: { authorization: `Bearer ${token}` } });
|
|
292
361
|
let quiet;
|
|
293
362
|
const total = setTimeout(() => finish(), opts.timeoutMs ?? 5_000);
|
|
@@ -295,6 +364,7 @@ export class CellClient {
|
|
|
295
364
|
clearTimeout(total);
|
|
296
365
|
if (quiet)
|
|
297
366
|
clearTimeout(quiet);
|
|
367
|
+
this.#closer.signal.removeEventListener('abort', onClose);
|
|
298
368
|
try {
|
|
299
369
|
ws.close(1000);
|
|
300
370
|
}
|
|
@@ -333,6 +403,10 @@ export class CellClient {
|
|
|
333
403
|
});
|
|
334
404
|
ws.addEventListener('error', () => finish(new ShardfluxProtocolError('pty attach WebSocket failed', 0)));
|
|
335
405
|
ws.addEventListener('close', () => finish());
|
|
406
|
+
if (this.closed)
|
|
407
|
+
onClose();
|
|
408
|
+
else
|
|
409
|
+
this.#closer.signal.addEventListener('abort', onClose, { once: true });
|
|
336
410
|
});
|
|
337
411
|
return { output: sink.text(), nextOffset: next, session, exited };
|
|
338
412
|
},
|
|
@@ -394,6 +468,22 @@ export class CellClient {
|
|
|
394
468
|
mkdir: (path, opts = {}) => this.#json('POST', this.#p('/v1/workspaces/{workspace_id}/files/mkdir'), { json: { path, ...(opts.parents !== undefined ? { parents: opts.parents } : {}), ...(opts.mode !== undefined ? { mode: opts.mode } : {}) } }),
|
|
395
469
|
move: (from, to, opts = {}) => this.#json('POST', this.#p('/v1/workspaces/{workspace_id}/files/move'), { json: { from, to, ...(opts.overwrite !== undefined ? { overwrite: opts.overwrite } : {}) } }),
|
|
396
470
|
};
|
|
471
|
+
// ---- changes against the template (layered workspaces, contracts §19.10) ------------------
|
|
472
|
+
/** One page of the workspace's changes against its template (needs the `files` tool). */
|
|
473
|
+
changes(params = {}) {
|
|
474
|
+
return this.#json('GET', this.#p('/v1/workspaces/{workspace_id}/changes'), {
|
|
475
|
+
query: { path_prefix: params.pathPrefix, limit: params.limit, cursor: params.cursor, hash: params.hash, summary: params.summary },
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
/** Every change under `pathPrefix`, following next_cursor. */
|
|
479
|
+
async *changesAll(params = {}) {
|
|
480
|
+
let cursor;
|
|
481
|
+
do {
|
|
482
|
+
const page = await this.changes({ ...params, ...(cursor === undefined ? {} : { cursor }) });
|
|
483
|
+
yield* page.data;
|
|
484
|
+
cursor = page.next_cursor ?? undefined;
|
|
485
|
+
} while (cursor !== undefined);
|
|
486
|
+
}
|
|
397
487
|
// ---- git -------------------------------------------------------------------------------
|
|
398
488
|
git = {
|
|
399
489
|
clone: (req) => this.#json('POST', this.#p('/v1/workspaces/{workspace_id}/git/clone'), { json: req, timeoutMs: (req.timeout_ms ?? 600_000) + 30_000 }),
|
package/dist/client.d.ts
CHANGED
|
@@ -16,10 +16,28 @@ import { AuditApi } from './audit.js';
|
|
|
16
16
|
import { EgressPolicyApi } from './egress.js';
|
|
17
17
|
import { SecretsApi } from './secrets.js';
|
|
18
18
|
import { TemplatesApi } from './templates.js';
|
|
19
|
+
import type { SaveAsTemplateParams, SaveAsTemplateResponse } from './templates.js';
|
|
19
20
|
import { UsageApi } from './usage.js';
|
|
20
21
|
import { VolumesApi } from './volumes.js';
|
|
22
|
+
import type { FinishedOperation, InternalLifecycleOptions, LifecycleOptions, WaitedLifecycleOptions } from './lifecycle.js';
|
|
23
|
+
import type { ProgressListener } from './progress.js';
|
|
21
24
|
export type WorkspaceView = components['schemas']['Workspace'];
|
|
22
25
|
export type Operation = components['schemas']['Operation'];
|
|
26
|
+
/** persistent (kept until deleted) or session (discarded when the session ends: close(), idle timeout; contracts §19.11). */
|
|
27
|
+
export type WorkspaceLifetime = components['schemas']['WorkspaceLifetime'];
|
|
28
|
+
/** legacy (one disk) or layered (the template chain read-only plus a workspace layer; contracts §19.2). */
|
|
29
|
+
export type DiskLayout = components['schemas']['DiskLayout'];
|
|
30
|
+
/** standard, template_draft (a template's dev-mode draft) or template_test (a test instance of a draft state). */
|
|
31
|
+
export type WorkspacePurpose = components['schemas']['WorkspacePurpose'];
|
|
32
|
+
/** Reserved (T2): always `pinned` in T1. */
|
|
33
|
+
export type UpdatePolicy = components['schemas']['UpdatePolicy'];
|
|
34
|
+
/** Where the workspace's disk came from: null, a fork, or a draft state (test instances). */
|
|
35
|
+
export type WorkspaceOrigin = components['schemas']['WorkspaceOrigin'];
|
|
36
|
+
export type ResetWorkspaceBody = components['schemas']['ResetWorkspaceBody'];
|
|
37
|
+
/** List filter: a lifetime or `any` (the list default is `persistent`). */
|
|
38
|
+
export type LifetimeFilter = WorkspaceLifetime | 'any';
|
|
39
|
+
/** List filter: a purpose or `any` (the list default is `standard`). */
|
|
40
|
+
export type PurposeFilter = WorkspacePurpose | 'any';
|
|
23
41
|
type JsonOf<R> = R extends {
|
|
24
42
|
content: {
|
|
25
43
|
'application/json': infer T;
|
|
@@ -48,6 +66,7 @@ export interface ShardfluxOptions {
|
|
|
48
66
|
apiKey: string;
|
|
49
67
|
/** Default https://api.shardflux.dev (override with `baseUrl`). */
|
|
50
68
|
baseUrl?: string;
|
|
69
|
+
/** Default: the runtime's fetch, with `Connection: close` on Node 26 (undici 8 keep-alive stalls; see defaultFetch in http.ts). */
|
|
51
70
|
fetch?: typeof fetch;
|
|
52
71
|
userAgent?: string;
|
|
53
72
|
/** Per-request timeout (ms), default 30 s. */
|
|
@@ -56,19 +75,36 @@ export interface ShardfluxOptions {
|
|
|
56
75
|
maxRetries?: number;
|
|
57
76
|
/** Injected for tests; defaults to setTimeout. */
|
|
58
77
|
sleep?: (ms: number) => Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Progress of every traced call made through this client (open, lifecycle calls, waits, wakes, tool tokens, and
|
|
80
|
+
* retries and busy waits of tool calls): for logs or telemetry. Per-call `onProgress` listeners get their own events too.
|
|
81
|
+
*/
|
|
82
|
+
onProgress?: ProgressListener;
|
|
59
83
|
}
|
|
60
84
|
export interface Caps {
|
|
61
85
|
cpu_millis?: number;
|
|
62
86
|
memory_mib?: number;
|
|
63
87
|
disk_gib?: number;
|
|
64
88
|
}
|
|
89
|
+
export interface ForkTarget {
|
|
90
|
+
key: string;
|
|
91
|
+
caps?: Caps;
|
|
92
|
+
lifetime?: WorkspaceLifetime;
|
|
93
|
+
}
|
|
65
94
|
export interface WaitOptions {
|
|
66
95
|
/** Give up waiting after this long (default 300 000 ms); the operation continues server side. */
|
|
67
96
|
timeoutMs?: number;
|
|
68
|
-
/** First poll delay (default 250 ms); doubles up to `maxPollIntervalMs` with jitter. */
|
|
97
|
+
/** First poll delay (default 250 ms); doubles up to `maxPollIntervalMs` with jitter. Used when the server does not wait. */
|
|
69
98
|
pollIntervalMs?: number;
|
|
70
99
|
maxPollIntervalMs?: number;
|
|
100
|
+
/**
|
|
101
|
+
* Ask the server to hold each poll until the state changes (`Prefer: wait`, contracts §3; default true). A server
|
|
102
|
+
* that does not wait answers at once and the SDK falls back to the backoff above.
|
|
103
|
+
*/
|
|
104
|
+
serverWait?: boolean;
|
|
71
105
|
signal?: AbortSignal;
|
|
106
|
+
/** Progress while waiting: each observed state (queued, capacity_pending, running with its reason), retries, and `done` with the timing. */
|
|
107
|
+
onProgress?: ProgressListener;
|
|
72
108
|
}
|
|
73
109
|
export interface OpenParams {
|
|
74
110
|
key: string;
|
|
@@ -80,10 +116,28 @@ export interface OpenParams {
|
|
|
80
116
|
agentLabel?: string;
|
|
81
117
|
/** Tools to request in tool tokens (subset of the key's permissions); default all permitted. */
|
|
82
118
|
tools?: ToolName[];
|
|
119
|
+
/**
|
|
120
|
+
* Secret names to bind to the workspace (max 50): injected into every exec and PTY start. Sets the binding of a
|
|
121
|
+
* new key and replaces it on an existing key; omitted leaves it unchanged. An unknown or unusable name is
|
|
122
|
+
* ShardfluxApiError 422 (details.reason `secret_not_available`, details.names) and nothing is created or changed.
|
|
123
|
+
*/
|
|
124
|
+
secrets?: string[];
|
|
125
|
+
/**
|
|
126
|
+
* `session`: the workspace is discarded when its session ends (workspace.close(), or the idle timeout); the key then
|
|
127
|
+
* opens a NEW workspace. Omitted: the template version's default, else `persistent`. Immutable: reopening a live key
|
|
128
|
+
* with another value is ShardfluxApiError 409 (details.reason `lifetime_mismatch`).
|
|
129
|
+
*/
|
|
130
|
+
lifetime?: WorkspaceLifetime;
|
|
83
131
|
/** `false`: return immediately (possibly not ready). Default: wait until ready. */
|
|
84
132
|
wait?: false | WaitOptions;
|
|
85
133
|
/** Defaults to a fresh key per open() call so transport retries replay instead of duplicating. */
|
|
86
134
|
idempotencyKey?: string;
|
|
135
|
+
/**
|
|
136
|
+
* Progress of the open: the request, each operation state observed while waiting (with the server's reason), the
|
|
137
|
+
* view read and first tool token, and retries. The final `done` event carries the timing, also on
|
|
138
|
+
* `workspace.lastTiming` (and on the error's `timing` when the open fails).
|
|
139
|
+
*/
|
|
140
|
+
onProgress?: ProgressListener;
|
|
87
141
|
}
|
|
88
142
|
export interface ListParams {
|
|
89
143
|
state?: WorkspaceView['observed_state'];
|
|
@@ -92,9 +146,28 @@ export interface ListParams {
|
|
|
92
146
|
projectId?: string;
|
|
93
147
|
organizationId?: string;
|
|
94
148
|
includeDeleted?: boolean;
|
|
149
|
+
/** Default (server side) `persistent`: sessions are hidden unless `session` or `any`. */
|
|
150
|
+
lifetime?: LifetimeFilter;
|
|
151
|
+
/** Default (server side) `standard`: drafts and test instances are hidden unless named or `any`. */
|
|
152
|
+
purpose?: PurposeFilter;
|
|
95
153
|
limit?: number;
|
|
96
154
|
cursor?: string;
|
|
97
155
|
}
|
|
156
|
+
export interface FindByKeyOptions {
|
|
157
|
+
/** Also consider tombstones (deleted workspaces and ended sessions) when no live workspace has the key (default true). */
|
|
158
|
+
includeDeleted?: boolean;
|
|
159
|
+
projectId?: string;
|
|
160
|
+
organizationId?: string;
|
|
161
|
+
agentLabel?: string;
|
|
162
|
+
tools?: ToolName[];
|
|
163
|
+
signal?: AbortSignal;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* The workspace a key names (contracts §19.11): the live row (deleted_at null) when there is one, since at most one live
|
|
167
|
+
* workspace holds a key; otherwise the newest tombstone (ended sessions leave tombstones with the same key, and a
|
|
168
|
+
* deleted persistent key keeps its tombstone). Null when no row has exactly this key. Rows need not be sorted.
|
|
169
|
+
*/
|
|
170
|
+
export declare function pickByKey<T extends Pick<WorkspaceView, 'id' | 'workspace_key' | 'deleted_at'>>(rows: Iterable<T>, key: string): T | null;
|
|
98
171
|
export interface Page<T> {
|
|
99
172
|
data: T[];
|
|
100
173
|
nextCursor: string | null;
|
|
@@ -107,6 +180,8 @@ export interface ClientContext {
|
|
|
107
180
|
userAgent: string;
|
|
108
181
|
sleep: (ms: number) => Promise<void>;
|
|
109
182
|
workspaces: WorkspacesApi;
|
|
183
|
+
/** The client-level progress listener (ShardfluxOptions.onProgress). */
|
|
184
|
+
onProgress?: ProgressListener | undefined;
|
|
110
185
|
}
|
|
111
186
|
export declare class WorkspacesApi {
|
|
112
187
|
#private;
|
|
@@ -115,12 +190,17 @@ export declare class WorkspacesApi {
|
|
|
115
190
|
* Opens a workspace by key: creates it from the template's latest published version on first
|
|
116
191
|
* use, reconnects (or resumes) afterwards; never resets an existing workspace. Waits until it is
|
|
117
192
|
* ready unless `wait: false`; on timeout throws OperationTimeoutError carrying the operation id.
|
|
193
|
+
* The timing of the open (client phases, retries, the operation's server timing) is on `workspace.lastTiming`, on
|
|
194
|
+
* `onProgress` as it happens, and on the error's `timing` when the open fails.
|
|
118
195
|
*/
|
|
119
196
|
open(params: OpenParams): Promise<Workspace>;
|
|
120
197
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
198
|
+
* Waits for an operation: each GET /v1/operations/{id} asks the server to hold the response until the state changes
|
|
199
|
+
* (`Prefer: wait`, at most 20 s, contracts §3), so completion is seen within one notification of the commit. A server
|
|
200
|
+
* that does not wait is polled with bounded exponential backoff (+-20 % jitter). Resolves when the operation
|
|
201
|
+
* succeeds; throws OperationFailedError when it fails or is canceled, OperationTimeoutError after `timeoutMs` (the
|
|
202
|
+
* 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.
|
|
124
204
|
*/
|
|
125
205
|
waitForOperation(operationId: string, opts?: WaitOptions): Promise<Operation>;
|
|
126
206
|
/** One operation (GET /v1/operations/{id}); lifecycle operations stay pollable after a workspace is deleted. */
|
|
@@ -134,26 +214,72 @@ export declare class WorkspacesApi {
|
|
|
134
214
|
list(params?: ListParams): Promise<Page<Workspace>>;
|
|
135
215
|
/** Iterates every page. */
|
|
136
216
|
listAll(params?: Omit<ListParams, 'cursor'>): AsyncGenerator<Workspace>;
|
|
137
|
-
/**
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
217
|
+
/**
|
|
218
|
+
* Looks a workspace up by its exact key across every lifetime and purpose (`lifetime=any&purpose=any`), preferring the
|
|
219
|
+
* live workspace over tombstones of ended sessions or deleted workspaces with the same key (see pickByKey). Null when
|
|
220
|
+
* no workspace of the project has the key. This is the lookup the CLI and the MCP server use.
|
|
221
|
+
*/
|
|
222
|
+
findByKey(key: string, opts?: FindByKeyOptions): Promise<Workspace | null>;
|
|
223
|
+
/**
|
|
224
|
+
* Deletes the workspace: tombstoned at once (tool access revoked), storage cleaned up by the operation. Resolves when
|
|
225
|
+
* the delete is requested; with `wait`, when it has finished.
|
|
226
|
+
*/
|
|
227
|
+
delete(workspaceId: string, opts: WaitedLifecycleOptions): Promise<FinishedOperation>;
|
|
228
|
+
delete(workspaceId: string, opts?: LifecycleOptions): Promise<Operation>;
|
|
229
|
+
/**
|
|
230
|
+
* Suspends the workspace (memory and processes checkpointed). Resolves when the suspend is REQUESTED: the returned
|
|
231
|
+
* operation is usually still `queued`. Pass `{ wait: true }` to resolve once it has FINISHED (`succeeded`).
|
|
232
|
+
*/
|
|
233
|
+
suspend(workspaceId: string, opts: WaitedLifecycleOptions): Promise<FinishedOperation>;
|
|
234
|
+
suspend(workspaceId: string, opts?: LifecycleOptions): Promise<Operation>;
|
|
235
|
+
/** Resumes a suspended workspace. Resolves when the resume is requested; with `wait`, once the workspace runs. */
|
|
236
|
+
resume(workspaceId: string, opts: WaitedLifecycleOptions): Promise<FinishedOperation>;
|
|
237
|
+
resume(workspaceId: string, opts?: LifecycleOptions): Promise<Operation>;
|
|
238
|
+
/** Takes a snapshot. Resolves when it is requested; with `wait`, once it is taken. */
|
|
239
|
+
snapshot(workspaceId: string, opts: WaitedLifecycleOptions & {
|
|
240
|
+
label?: string;
|
|
241
|
+
}): Promise<FinishedOperation>;
|
|
242
|
+
snapshot(workspaceId: string, opts?: LifecycleOptions & {
|
|
148
243
|
label?: string;
|
|
149
|
-
idempotencyKey?: string;
|
|
150
244
|
}): Promise<Operation>;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
245
|
+
/**
|
|
246
|
+
* Ends a session workspace now (contracts §19.11): the workspace is deleted exactly like delete() (ended_reason
|
|
247
|
+
* closed) and returns the `delete` operation (input.reason session_closed); the key then opens a NEW workspace.
|
|
248
|
+
* Idempotent. A persistent workspace is ShardfluxApiError 409 (details.reason `not_session`): use
|
|
249
|
+
* `workspace.close()`, which calls this only for sessions. With `wait`, resolves once the delete has finished.
|
|
250
|
+
*/
|
|
251
|
+
close(workspaceId: string, opts: WaitedLifecycleOptions): Promise<FinishedOperation>;
|
|
252
|
+
close(workspaceId: string, opts?: LifecycleOptions): Promise<Operation>;
|
|
253
|
+
/** close() plus the workspace view after the close (a tombstone). */
|
|
254
|
+
closeWithView(workspaceId: string, opts?: InternalLifecycleOptions): Promise<{
|
|
255
|
+
operation: Operation;
|
|
256
|
+
workspace: WorkspaceView;
|
|
257
|
+
}>;
|
|
258
|
+
/**
|
|
259
|
+
* Resets a layered workspace to its template (contracts §19.12): every change in the workspace layer is wiped; key,
|
|
260
|
+
* id, template version, caps, secret bindings and volume attachments stay. Running: restarted on a blank layer
|
|
261
|
+
* (processes are gone; old tool tokens get 409 stale_epoch and the SDK refreshes them). Suspended: stays suspended and
|
|
262
|
+
* boots blank on the next resume. Returns the `reset` operation; its result names the recovery checkpoint (restorable
|
|
263
|
+
* for 7 days). Errors: 409 legacy_disk_layout, not_resettable, operation_in_progress.
|
|
264
|
+
*/
|
|
265
|
+
reset(workspaceId: string, opts: WaitedLifecycleOptions): Promise<FinishedOperation>;
|
|
266
|
+
reset(workspaceId: string, opts?: LifecycleOptions): Promise<Operation>;
|
|
267
|
+
/**
|
|
268
|
+
* Saves a layered workspace as the next version of an organization template (contracts §19.8). A running workspace is
|
|
269
|
+
* captured briefly (`operation`, layer_snapshot); poll `build` with templates.builds.waitForBuild. Owners/admins and
|
|
270
|
+
* API keys with a tool permission only.
|
|
271
|
+
*/
|
|
272
|
+
saveAsTemplate(workspaceId: string, params: SaveAsTemplateParams): Promise<SaveAsTemplateResponse>;
|
|
273
|
+
/**
|
|
274
|
+
* Forks into a new key. `lifetime` is the fork's own (default persistent): forking a session is how it is kept.
|
|
275
|
+
* Resolves when the fork is requested (the copy's handle is returned at once); with `wait`, once the copy exists,
|
|
276
|
+
* with its handle refreshed.
|
|
277
|
+
*/
|
|
278
|
+
fork(workspaceId: string, target: ForkTarget, opts: WaitedLifecycleOptions): Promise<{
|
|
279
|
+
operation: FinishedOperation;
|
|
280
|
+
workspace: Workspace;
|
|
281
|
+
}>;
|
|
282
|
+
fork(workspaceId: string, target: ForkTarget, opts?: LifecycleOptions): Promise<{
|
|
157
283
|
operation: Operation;
|
|
158
284
|
workspace: Workspace;
|
|
159
285
|
}>;
|