@shardflux/sdk 0.11.0 → 0.12.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 +68 -27
- package/README.md +123 -61
- package/dist/account.d.ts +3 -3
- package/dist/account.js +2 -2
- package/dist/cell.d.ts +36 -21
- package/dist/cell.js +49 -20
- package/dist/client.d.ts +56 -35
- package/dist/client.js +89 -21
- package/dist/egress.d.ts +3 -3
- package/dist/errors.d.ts +51 -22
- package/dist/errors.js +61 -16
- package/dist/executions.d.ts +2 -2
- package/dist/feedback.d.ts +3 -3
- package/dist/feedback.js +1 -1
- package/dist/generated/app-api.d.ts +1579 -30
- package/dist/generated/cell-api.d.ts +66 -0
- package/dist/http.d.ts +7 -11
- package/dist/http.js +32 -21
- package/dist/index.d.ts +10 -5
- package/dist/index.js +4 -2
- package/dist/lifecycle.d.ts +19 -2
- package/dist/lifecycle.js +9 -2
- package/dist/progress.d.ts +83 -22
- package/dist/progress.js +71 -10
- package/dist/tar.d.ts +1 -1
- package/dist/tar.js +1 -1
- package/dist/template-file.d.ts +1 -1
- package/dist/template-file.js +1 -1
- package/dist/templates.d.ts +21 -21
- package/dist/templates.js +8 -8
- package/dist/tools.d.ts +3 -3
- package/dist/tools.js +4 -4
- package/dist/version-check.d.ts +1 -1
- package/dist/volumes.d.ts +1 -1
- package/dist/workspace.d.ts +42 -27
- package/dist/workspace.js +45 -22
- package/package.json +4 -1
package/dist/client.d.ts
CHANGED
|
@@ -22,15 +22,15 @@ import type { SaveAsTemplateParams, SaveAsTemplateResponse } from './templates.j
|
|
|
22
22
|
import { UsageApi } from './usage.js';
|
|
23
23
|
import { VolumesApi } from './volumes.js';
|
|
24
24
|
import type { VersionCheckOption } from './version-check.js';
|
|
25
|
-
import type { FinishedOperation, InternalLifecycleOptions, LifecycleOptions, ResumeOptions, WaitedLifecycleOptions, WaitedResumeOptions } from './lifecycle.js';
|
|
25
|
+
import type { FinishedOperation, InternalLifecycleOptions, LifecycleOptions, ResumeOptions, SuspendOptions, WaitedLifecycleOptions, WaitedResumeOptions, WaitedSuspendOptions } from './lifecycle.js';
|
|
26
26
|
import type { ProgressListener } from './progress.js';
|
|
27
27
|
import { CaptureRegistry } from './capture.js';
|
|
28
28
|
import type { FeedbackReceipt, SendFeedbackParams } from './feedback.js';
|
|
29
29
|
export type WorkspaceView = components['schemas']['Workspace'];
|
|
30
30
|
export type Operation = components['schemas']['Operation'];
|
|
31
|
-
/** persistent (kept until deleted) or session (discarded when the session ends: close(), idle timeout
|
|
31
|
+
/** persistent (kept until deleted) or session (discarded when the session ends: close(), idle timeout). */
|
|
32
32
|
export type WorkspaceLifetime = components['schemas']['WorkspaceLifetime'];
|
|
33
|
-
/** legacy (one disk) or layered (the template chain read-only plus a workspace layer
|
|
33
|
+
/** legacy (one disk) or layered (the template chain read-only plus a workspace layer). */
|
|
34
34
|
export type DiskLayout = components['schemas']['DiskLayout'];
|
|
35
35
|
/** standard, template_draft (a template's dev-mode draft) or template_test (a test instance of a draft state). */
|
|
36
36
|
export type WorkspacePurpose = components['schemas']['WorkspacePurpose'];
|
|
@@ -56,7 +56,7 @@ type Ok<Op> = Op extends {
|
|
|
56
56
|
[K in keyof R]: K extends 200 | 201 | 202 ? JsonOf<R[K]> : never;
|
|
57
57
|
}[keyof R] : never;
|
|
58
58
|
export type OpenResponse = Ok<operations['postV1WorkspacesOpen']>;
|
|
59
|
-
/** POST /v1/workspaces/{id}/resume: the held 200
|
|
59
|
+
/** POST /v1/workspaces/{id}/resume: the held 200 or the lifecycle 202. */
|
|
60
60
|
export type ResumeResponse = Ok<operations['postV1WorkspacesWorkspaceIdResume']>;
|
|
61
61
|
/** What a resume request answered (0.9.0; `WorkspacesApi.requestResume`). */
|
|
62
62
|
export interface ResumeAnswer {
|
|
@@ -95,7 +95,7 @@ export type PortalSession = Ok<operations['postApiV1OrganizationsOrganizationIdB
|
|
|
95
95
|
export type InvoicePage = Ok<operations['getApiV1OrganizationsOrganizationIdBillingInvoices']>;
|
|
96
96
|
export type Invoice = InvoicePage['data'][number];
|
|
97
97
|
/**
|
|
98
|
-
* A pending suspend-when-idle request (0.10.0
|
|
98
|
+
* A pending suspend-when-idle request (0.10.0): once the workspace has been idle for `after_seconds`
|
|
99
99
|
* (counted from the later of its last work and `requested_at`), it is suspended; `not_before` = requested_at +
|
|
100
100
|
* after_seconds is the earliest.
|
|
101
101
|
*/
|
|
@@ -103,7 +103,7 @@ export type SuspendRequest = components['schemas']['SuspendRequest'];
|
|
|
103
103
|
/** POST /v1/workspaces/{id}/suspend-when-idle (202). */
|
|
104
104
|
export type SuspendWhenIdleResponse = Ok<operations['postV1WorkspacesWorkspaceIdSuspendWhenIdle']>;
|
|
105
105
|
export interface SuspendWhenIdleOptions {
|
|
106
|
-
/** Seconds the workspace must stay idle before it is suspended: an integer from
|
|
106
|
+
/** Seconds the workspace must stay idle before it is suspended: an integer from 0 (as soon as it is idle) to 3600 (the API refuses others with 422 validation_failed). */
|
|
107
107
|
afterSeconds: number;
|
|
108
108
|
/** Replays the stored response for a repeated request (default: a fresh key per call, so transport retries replay). */
|
|
109
109
|
idempotencyKey?: string;
|
|
@@ -121,7 +121,7 @@ export interface ShardfluxOptions {
|
|
|
121
121
|
apiKey: string;
|
|
122
122
|
/** Default https://api.shardflux.dev (override with `baseUrl`). */
|
|
123
123
|
baseUrl?: string;
|
|
124
|
-
/** Default:
|
|
124
|
+
/** Default: pooled HTTP/1.1 on Node 26+, native fetch on other runtimes (see defaultFetch in http.ts). */
|
|
125
125
|
fetch?: typeof fetch;
|
|
126
126
|
userAgent?: string;
|
|
127
127
|
/** Per-request timeout (ms), default 30 s. */
|
|
@@ -136,7 +136,7 @@ export interface ShardfluxOptions {
|
|
|
136
136
|
*/
|
|
137
137
|
onProgress?: ProgressListener;
|
|
138
138
|
/**
|
|
139
|
-
* The automatic version check (0.9.0
|
|
139
|
+
* The automatic version check (0.9.0): after the first successful API response of the process, a
|
|
140
140
|
* background GET /v1/client-versions (3 s timeout, errors swallowed) emits a `ShardfluxUpdateWarning` when this
|
|
141
141
|
* package is outdated or unsupported. Default true (`@shardflux/sdk` at SDK_VERSION); tools built on the SDK pass
|
|
142
142
|
* their own `{ package, version }` or `false`. `SHARDFLUX_NO_UPDATE_CHECK=1` or `NO_UPDATE_NOTIFIER=1` turn it off.
|
|
@@ -155,16 +155,16 @@ export interface ForkTarget {
|
|
|
155
155
|
}
|
|
156
156
|
export interface WaitOptions {
|
|
157
157
|
/**
|
|
158
|
-
* Give up waiting after this long (default 300 000 ms); the operation continues server side. A start
|
|
159
|
-
*
|
|
160
|
-
*
|
|
158
|
+
* Give up waiting after this long (default 300 000 ms); the operation continues server side. A queued start
|
|
159
|
+
* (`capacity_pending`) has a deadline (`error.details.deadline_at`, 15 minutes after it was created); past it, it
|
|
160
|
+
* fails with `capacity_unavailable` (retryable; nothing was started).
|
|
161
161
|
*/
|
|
162
162
|
timeoutMs?: number;
|
|
163
163
|
/** First poll delay (default 250 ms); doubles up to `maxPollIntervalMs` with jitter. Used when the server does not wait. */
|
|
164
164
|
pollIntervalMs?: number;
|
|
165
165
|
maxPollIntervalMs?: number;
|
|
166
166
|
/**
|
|
167
|
-
* Ask the server to hold each poll until the state changes (`Prefer: wait
|
|
167
|
+
* Ask the server to hold each poll until the state changes (`Prefer: wait`; default true). A server
|
|
168
168
|
* that does not wait answers at once and the SDK falls back to the backoff above.
|
|
169
169
|
*/
|
|
170
170
|
serverWait?: boolean;
|
|
@@ -172,7 +172,11 @@ export interface WaitOptions {
|
|
|
172
172
|
/** Progress while waiting: each observed state (queued, capacity_pending, running with its reason), retries, and `done` with the timing. */
|
|
173
173
|
onProgress?: ProgressListener;
|
|
174
174
|
}
|
|
175
|
+
export type IdlePolicy = 'adaptive' | 'never' | `fixed:${number}`;
|
|
175
176
|
export interface OpenParams {
|
|
177
|
+
/** Searchable metadata; supplied labels replace the existing map. */
|
|
178
|
+
labels?: Record<string, string>;
|
|
179
|
+
idlePolicy?: IdlePolicy;
|
|
176
180
|
key: string;
|
|
177
181
|
template: string;
|
|
178
182
|
caps?: Caps;
|
|
@@ -189,7 +193,7 @@ export interface OpenParams {
|
|
|
189
193
|
*/
|
|
190
194
|
secrets?: string[];
|
|
191
195
|
/**
|
|
192
|
-
* The template version's text inputs `{NAME: value}` (0.7.0
|
|
196
|
+
* The template version's text inputs `{NAME: value}` (0.7.0), put into the environment of every
|
|
193
197
|
* exec, terminal, start command and service. A new key stores each given value, else the declared default; an
|
|
194
198
|
* existing key replaces them all (omitted leaves them unchanged). Secret inputs are not passed here: they bind the
|
|
195
199
|
* stored secret of the same name. ShardfluxApiError 422 with details.reason `input_unknown` (an undeclared name),
|
|
@@ -204,7 +208,7 @@ export interface OpenParams {
|
|
|
204
208
|
*/
|
|
205
209
|
lifetime?: WorkspaceLifetime;
|
|
206
210
|
/**
|
|
207
|
-
* `file_first` (0.9.0
|
|
211
|
+
* `file_first` (0.9.0): the workspace is a versioned file tree with no VM between executions. It is
|
|
208
212
|
* ready at once (no operation: the open answers with a tool token), never suspended, and runs commands as executions
|
|
209
213
|
* (`workspace.executions.run()`): a fresh VM on the latest tree whose changed files become the next tree revision;
|
|
210
214
|
* nothing else survives an execution. Needs a layered template version (409 `layout_unsupported` otherwise) and is
|
|
@@ -225,6 +229,8 @@ export interface OpenParams {
|
|
|
225
229
|
onProgress?: ProgressListener;
|
|
226
230
|
}
|
|
227
231
|
export interface ListParams {
|
|
232
|
+
/** All supplied labels must match exactly. */
|
|
233
|
+
labels?: Record<string, string>;
|
|
228
234
|
state?: WorkspaceView['observed_state'];
|
|
229
235
|
desiredState?: WorkspaceView['desired_state'];
|
|
230
236
|
keyPrefix?: string;
|
|
@@ -248,7 +254,7 @@ export interface FindByKeyOptions {
|
|
|
248
254
|
signal?: AbortSignal;
|
|
249
255
|
}
|
|
250
256
|
/**
|
|
251
|
-
* The workspace a key names
|
|
257
|
+
* The workspace a key names: the live row (deleted_at null) when there is one, since at most one live
|
|
252
258
|
* workspace holds a key; otherwise the newest tombstone (ended sessions leave tombstones with the same key, and a
|
|
253
259
|
* deleted persistent key keeps its tombstone). Null when no row has exactly this key. Rows need not be sorted.
|
|
254
260
|
*/
|
|
@@ -283,15 +289,24 @@ export declare class WorkspacesApi {
|
|
|
283
289
|
open(params: OpenParams): Promise<Workspace>;
|
|
284
290
|
/**
|
|
285
291
|
* Waits for an operation: each GET /v1/operations/{id} asks the server to hold the response until the state changes
|
|
286
|
-
* (`Prefer: wait`, at most 20 s
|
|
292
|
+
* (`Prefer: wait`, at most 20 s), so completion is seen within one notification of the commit. A server
|
|
287
293
|
* that does not wait is polled with bounded exponential backoff (+-20 % jitter). Resolves when the operation
|
|
288
294
|
* succeeds; throws OperationFailedError when it fails or is canceled, OperationTimeoutError after `timeoutMs` (the
|
|
289
295
|
* operation keeps running and can be awaited again). Both errors carry the wait's `timing`; `onProgress` sees each
|
|
290
|
-
* state change (queued, capacity_pending, running, with the server's reason) as it is observed. A start
|
|
291
|
-
* capacity_pending
|
|
292
|
-
* (`err.retryable` true: nothing was started
|
|
296
|
+
* state change (queued, capacity_pending, running, with the server's reason) as it is observed. A start in
|
|
297
|
+
* capacity_pending that passes its deadline (`deadlineAt` on the phase event) fails with `capacity_unavailable`
|
|
298
|
+
* (`err.retryable` true: nothing was started; send it again); the SDK does not retry it.
|
|
293
299
|
*/
|
|
294
300
|
waitForOperation(operationId: string, opts?: WaitOptions): Promise<Operation>;
|
|
301
|
+
/**
|
|
302
|
+
* Waits for the durable copy of a succeeded suspend or fork (0.12.0+): resolves with the operation once
|
|
303
|
+
* `result.durable` is true, typically within a second of the suspend. An operation already durable (or one whose result
|
|
304
|
+
* predates the field) resolves at once without a request. Polls GET /v1/operations/{id} (`pollIntervalMs`, default
|
|
305
|
+
* 250 ms, doubling up to `maxPollIntervalMs`, default 1 000 ms). Throws DurabilityLostError when the copy cannot be
|
|
306
|
+
* made (`durability.state` `lost`), OperationFailedError if the operation did not succeed, and OperationTimeoutError
|
|
307
|
+
* (`durable: true`) after `timeoutMs` (default 300 000 ms; the copy continues server side). `signal` aborts the wait.
|
|
308
|
+
*/
|
|
309
|
+
waitForDurable(operation: string | Operation, opts?: WaitOptions): Promise<FinishedOperation>;
|
|
295
310
|
/** One operation (GET /v1/operations/{id}); lifecycle operations stay pollable after a workspace is deleted. */
|
|
296
311
|
getOperation(operationId: string, opts?: {
|
|
297
312
|
signal?: AbortSignal;
|
|
@@ -300,6 +315,10 @@ export declare class WorkspacesApi {
|
|
|
300
315
|
agentLabel?: string;
|
|
301
316
|
tools?: ToolName[];
|
|
302
317
|
}): Promise<Workspace>;
|
|
318
|
+
/** Replace labels. An empty map clears them. */
|
|
319
|
+
setLabels(workspaceId: string, labels: Record<string, string>): Promise<Workspace>;
|
|
320
|
+
/** null clears the override, restoring the template or platform policy. */
|
|
321
|
+
setIdlePolicy(workspaceId: string, idlePolicy: IdlePolicy | null): Promise<Workspace>;
|
|
303
322
|
list(params?: ListParams): Promise<Page<Workspace>>;
|
|
304
323
|
/** Iterates every page. */
|
|
305
324
|
listAll(params?: Omit<ListParams, 'cursor'>): AsyncGenerator<Workspace>;
|
|
@@ -317,13 +336,15 @@ export declare class WorkspacesApi {
|
|
|
317
336
|
delete(workspaceId: string, opts?: LifecycleOptions): Promise<Operation>;
|
|
318
337
|
/**
|
|
319
338
|
* Suspends the workspace (memory and processes checkpointed). Resolves when the suspend is REQUESTED: the returned
|
|
320
|
-
* operation is usually still `queued`. Pass `{ wait: true }` to resolve once it has FINISHED (`succeeded`)
|
|
339
|
+
* operation is usually still `queued`. Pass `{ wait: true }` to resolve once it has FINISHED (`succeeded`): the
|
|
340
|
+
* workspace is sealed on its host, typically in a few hundred ms, and `result.durable` turns true when the copy lands
|
|
341
|
+
* in durable storage, typically within a second. `{ durable: true }` (0.12.0+) resolves only then (see SuspendOptions).
|
|
321
342
|
*/
|
|
322
|
-
suspend(workspaceId: string, opts:
|
|
323
|
-
suspend(workspaceId: string, opts?:
|
|
343
|
+
suspend(workspaceId: string, opts: WaitedSuspendOptions): Promise<FinishedOperation>;
|
|
344
|
+
suspend(workspaceId: string, opts?: SuspendOptions): Promise<Operation>;
|
|
324
345
|
/**
|
|
325
346
|
* Resumes a suspended workspace. Resolves when the resume is requested; with `wait`, once the workspace runs. With
|
|
326
|
-
* `wait` (0.9.0) the request is held by the server until the workspace runs (
|
|
347
|
+
* `wait` (0.9.0) the request is held by the server until the workspace runs (one request, timing phase
|
|
327
348
|
* `request` with reason `held`); a server that does not hold it answers at once and the operation is polled.
|
|
328
349
|
* `serverWait: false` polls only. `agentLabel`/`tools` choose the tool token the held answer carries (attribution
|
|
329
350
|
* only here; `workspace.resume()` keeps it). A workspace that is already running is ShardfluxApiError 409 `conflict`
|
|
@@ -333,7 +354,7 @@ export declare class WorkspacesApi {
|
|
|
333
354
|
resume(workspaceId: string, opts?: ResumeOptions): Promise<Operation>;
|
|
334
355
|
/**
|
|
335
356
|
* One `POST /v1/workspaces/{id}/resume` (0.9.0), held for up to `waitS` seconds when that is at least 1 (`Prefer:
|
|
336
|
-
* wait
|
|
357
|
+
* wait`), asking for the tool token of `agentLabel`/`tools`. `ready` only for a 200 the server says it
|
|
337
358
|
* held (`Preference-Applied`); every other answer is the operation to wait for, so a server without the held resume
|
|
338
359
|
* works unchanged. Refusals (409 operation_in_progress, already_running without the hold, ...) throw as usual.
|
|
339
360
|
* `Workspace.wake()` and `resume({ wait })` use it; most callers want those.
|
|
@@ -347,7 +368,7 @@ export declare class WorkspacesApi {
|
|
|
347
368
|
label?: string;
|
|
348
369
|
}): Promise<Operation>;
|
|
349
370
|
/**
|
|
350
|
-
* Suspends the workspace once it has been idle for `afterSeconds` (0.9.0
|
|
371
|
+
* Suspends the workspace once it has been idle for `afterSeconds` (0.9.0): meant for the end of an
|
|
351
372
|
* agent turn, so the workspace stops using RAM soon after instead of waiting out its idle policy. The idle time
|
|
352
373
|
* counts from the later of the workspace's last work and this request. A command still running, an attached exec or
|
|
353
374
|
* terminal stream, or a keepalive postpones the suspend until `afterSeconds` after it ends; a tool call after the
|
|
@@ -357,7 +378,7 @@ export declare class WorkspacesApi {
|
|
|
357
378
|
* Resolves with the recorded `suspendRequest`, or, when a suspend is already in progress, with that `operation` and
|
|
358
379
|
* nothing recorded. Tool-call capture writes recorded before the call land first (a later write would count as the
|
|
359
380
|
* next turn). Errors: ShardfluxApiError 409 with reason `not_running`, `operation_in_progress`, `session_lifetime` or
|
|
360
|
-
* `workspace_deleted`; 422 `validation_failed` for `afterSeconds` outside
|
|
381
|
+
* `workspace_deleted`; 422 `validation_failed` for `afterSeconds` outside 0..3600.
|
|
361
382
|
*
|
|
362
383
|
* await cloud.workspaces.suspendWhenIdle(workspace.id, { afterSeconds: 60 });
|
|
363
384
|
*/
|
|
@@ -369,7 +390,7 @@ export declare class WorkspacesApi {
|
|
|
369
390
|
*/
|
|
370
391
|
cancelSuspendWhenIdle(workspaceId: string): Promise<Workspace>;
|
|
371
392
|
/**
|
|
372
|
-
* Ends a session workspace now
|
|
393
|
+
* Ends a session workspace now: the workspace is deleted exactly like delete() (ended_reason
|
|
373
394
|
* closed) and returns the `delete` operation (input.reason session_closed); the key then opens a NEW workspace.
|
|
374
395
|
* Idempotent. A persistent workspace is ShardfluxApiError 409 (details.reason `not_session`): use
|
|
375
396
|
* `workspace.close()`, which calls this only for sessions. With `wait`, resolves once the delete has finished.
|
|
@@ -382,7 +403,7 @@ export declare class WorkspacesApi {
|
|
|
382
403
|
workspace: WorkspaceView;
|
|
383
404
|
}>;
|
|
384
405
|
/**
|
|
385
|
-
* Resets a layered workspace to its template
|
|
406
|
+
* Resets a layered workspace to its template: every change in the workspace layer is wiped; key,
|
|
386
407
|
* id, template version, caps, secret bindings and volume attachments stay. Running: restarted on a blank layer
|
|
387
408
|
* (processes are gone; old tool tokens get 409 stale_epoch and the SDK refreshes them). Suspended: stays suspended and
|
|
388
409
|
* boots blank on the next resume. Returns the `reset` operation; its result names the recovery checkpoint (restorable
|
|
@@ -391,7 +412,7 @@ export declare class WorkspacesApi {
|
|
|
391
412
|
reset(workspaceId: string, opts: WaitedLifecycleOptions): Promise<FinishedOperation>;
|
|
392
413
|
reset(workspaceId: string, opts?: LifecycleOptions): Promise<Operation>;
|
|
393
414
|
/**
|
|
394
|
-
* Saves a layered workspace as the next version of an organization template
|
|
415
|
+
* Saves a layered workspace as the next version of an organization template. A running workspace is
|
|
395
416
|
* captured briefly (`operation`, layer_snapshot); poll `build` with templates.builds.waitForBuild. Owners/admins and
|
|
396
417
|
* API keys with a tool permission only.
|
|
397
418
|
*/
|
|
@@ -409,7 +430,7 @@ export declare class WorkspacesApi {
|
|
|
409
430
|
operation: Operation;
|
|
410
431
|
workspace: Workspace;
|
|
411
432
|
}>;
|
|
412
|
-
/** The workspace's text inputs `{NAME: value}` (0.7.0
|
|
433
|
+
/** The workspace's text inputs `{NAME: value}` (0.7.0). Secret inputs are bound secrets, never listed. */
|
|
413
434
|
inputs(workspaceId: string): Promise<Record<string, string>>;
|
|
414
435
|
operations(workspaceId: string, params?: {
|
|
415
436
|
limit?: number;
|
|
@@ -454,17 +475,17 @@ export declare class Shardflux {
|
|
|
454
475
|
readonly egress: EgressPolicyApi;
|
|
455
476
|
/** Organization audit trail (owner/admin; API keys are refused with 403). */
|
|
456
477
|
readonly audit: AuditApi;
|
|
457
|
-
/** Shared volumes: persistent storage attached to workspaces at a mount path
|
|
478
|
+
/** Shared volumes: persistent storage attached to workspaces at a mount path. */
|
|
458
479
|
readonly volumes: VolumesApi;
|
|
459
480
|
constructor(opts: ShardfluxOptions);
|
|
460
481
|
/** The authenticated principal (the API key, its organization and project). */
|
|
461
482
|
me(): Promise<Me>;
|
|
462
483
|
entitlements(organizationId: string): Promise<Entitlements>;
|
|
463
484
|
/**
|
|
464
|
-
* Send feedback straight to the Shardflux
|
|
465
|
-
*
|
|
466
|
-
*
|
|
467
|
-
* the logs. `context.client` defaults to `shardflux-sdk-ts/<SDK_VERSION>`. Returns `{ id, receivedAt, duplicate }`
|
|
485
|
+
* Send feedback straight to the Shardflux team (0.9.0+; POST /v1/feedback), who read every message. Use it while you
|
|
486
|
+
* work, the moment something fails unexpectedly, an error or doc is unclear, an option is missing, or a workaround was
|
|
487
|
+
* needed, and when your user asks for a capability, an option or a smoother workflow; short and specific beats
|
|
488
|
+
* polished, and `context.requestId` / `errorCode` let the team find the logs. `context.client` defaults to `shardflux-sdk-ts/<SDK_VERSION>`. Returns `{ id, receivedAt, duplicate }`
|
|
468
489
|
* (`duplicate`: the same message from this key within 24 hours; no second email). 429 `rate_limited` (with
|
|
469
490
|
* `retryAfterSeconds`) and 422 `validation_failed` are ShardfluxApiErrors; the call is never retried.
|
|
470
491
|
*/
|
package/dist/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { OperationFailedError, OperationTimeoutError, ShardfluxApiError, ShardfluxProtocolError } from "./errors.js";
|
|
1
|
+
import { DurabilityLostError, OperationFailedError, OperationTimeoutError, ShardfluxApiError, ShardfluxProtocolError } from "./errors.js";
|
|
2
2
|
import { HttpClient, SDK_VERSION, SERVER_WAIT_MAX_S, defaultFetch, defaultSleep, pollWithWait, randomId } from "./http.js";
|
|
3
3
|
import { Workspace } from "./workspace.js";
|
|
4
4
|
import { AuditApi } from "./audit.js";
|
|
@@ -9,11 +9,11 @@ import { UsageApi } from "./usage.js";
|
|
|
9
9
|
import { VolumesApi } from "./volumes.js";
|
|
10
10
|
import { versionCheckHook } from "./version-check.js";
|
|
11
11
|
import { AFTER_WAIT, HELD_RESUME, TRACE, runLifecycle, waitOptionsOf } from "./lifecycle.js";
|
|
12
|
-
import { Trace, combineListeners, traced } from "./progress.js";
|
|
12
|
+
import { Trace, combineListeners, durabilityOf, isDurable, traced } from "./progress.js";
|
|
13
13
|
import { CaptureRegistry } from "./capture.js";
|
|
14
14
|
import { sendFeedback } from "./feedback.js";
|
|
15
15
|
/**
|
|
16
|
-
* The workspace a key names
|
|
16
|
+
* The workspace a key names: the live row (deleted_at null) when there is one, since at most one live
|
|
17
17
|
* workspace holds a key; otherwise the newest tombstone (ended sessions leave tombstones with the same key, and a
|
|
18
18
|
* deleted persistent key keeps its tombstone). Null when no row has exactly this key. Rows need not be sorted.
|
|
19
19
|
*/
|
|
@@ -61,7 +61,7 @@ function abortableSleep(sleep, ms, signal) {
|
|
|
61
61
|
}
|
|
62
62
|
export class WorkspacesApi {
|
|
63
63
|
#ctx;
|
|
64
|
-
/** The cell endpoint of the last tool token seen: an open pre-connects to it while the server works
|
|
64
|
+
/** The cell endpoint of the last tool token seen: an open pre-connects to it while the server works. */
|
|
65
65
|
#cellHint = null;
|
|
66
66
|
constructor(ctx) {
|
|
67
67
|
this.#ctx = ctx;
|
|
@@ -96,6 +96,10 @@ export class WorkspacesApi {
|
|
|
96
96
|
body.secrets = params.secrets;
|
|
97
97
|
if (params.inputs !== undefined)
|
|
98
98
|
body.inputs = params.inputs;
|
|
99
|
+
if (params.labels !== undefined)
|
|
100
|
+
body.labels = params.labels;
|
|
101
|
+
if (params.idlePolicy !== undefined)
|
|
102
|
+
body.idle_policy = params.idlePolicy;
|
|
99
103
|
if (params.lifetime !== undefined)
|
|
100
104
|
body.lifetime = params.lifetime;
|
|
101
105
|
if (params.mode !== undefined)
|
|
@@ -108,7 +112,7 @@ export class WorkspacesApi {
|
|
|
108
112
|
const started = Date.now();
|
|
109
113
|
const timeoutMs = waitOpts?.timeoutMs ?? 300_000;
|
|
110
114
|
if (waitOpts && waitOpts.serverWait !== false) {
|
|
111
|
-
// Held open
|
|
115
|
+
// Held open: the server answers once the operation is terminal (200 with the running workspace
|
|
112
116
|
// and a tool token) or the wait elapsed (202); a server without it answers 202 at once and the poll below runs.
|
|
113
117
|
const s = Math.min(SERVER_WAIT_MAX_S, Math.floor(timeoutMs / 1000));
|
|
114
118
|
if (s >= 1) {
|
|
@@ -188,13 +192,13 @@ export class WorkspacesApi {
|
|
|
188
192
|
}
|
|
189
193
|
/**
|
|
190
194
|
* Waits for an operation: each GET /v1/operations/{id} asks the server to hold the response until the state changes
|
|
191
|
-
* (`Prefer: wait`, at most 20 s
|
|
195
|
+
* (`Prefer: wait`, at most 20 s), so completion is seen within one notification of the commit. A server
|
|
192
196
|
* that does not wait is polled with bounded exponential backoff (+-20 % jitter). Resolves when the operation
|
|
193
197
|
* succeeds; throws OperationFailedError when it fails or is canceled, OperationTimeoutError after `timeoutMs` (the
|
|
194
198
|
* operation keeps running and can be awaited again). Both errors carry the wait's `timing`; `onProgress` sees each
|
|
195
|
-
* state change (queued, capacity_pending, running, with the server's reason) as it is observed. A start
|
|
196
|
-
* capacity_pending
|
|
197
|
-
* (`err.retryable` true: nothing was started
|
|
199
|
+
* state change (queued, capacity_pending, running, with the server's reason) as it is observed. A start in
|
|
200
|
+
* capacity_pending that passes its deadline (`deadlineAt` on the phase event) fails with `capacity_unavailable`
|
|
201
|
+
* (`err.retryable` true: nothing was started; send it again); the SDK does not retry it.
|
|
198
202
|
*/
|
|
199
203
|
async waitForOperation(operationId, opts = {}) {
|
|
200
204
|
const inherited = opts[TRACE];
|
|
@@ -246,6 +250,61 @@ export class WorkspacesApi {
|
|
|
246
250
|
interval = Math.min(maxInterval, interval * 2);
|
|
247
251
|
}
|
|
248
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Waits for the durable copy of a succeeded suspend or fork (0.12.0+): resolves with the operation once
|
|
255
|
+
* `result.durable` is true, typically within a second of the suspend. An operation already durable (or one whose result
|
|
256
|
+
* predates the field) resolves at once without a request. Polls GET /v1/operations/{id} (`pollIntervalMs`, default
|
|
257
|
+
* 250 ms, doubling up to `maxPollIntervalMs`, default 1 000 ms). Throws DurabilityLostError when the copy cannot be
|
|
258
|
+
* made (`durability.state` `lost`), OperationFailedError if the operation did not succeed, and OperationTimeoutError
|
|
259
|
+
* (`durable: true`) after `timeoutMs` (default 300 000 ms; the copy continues server side). `signal` aborts the wait.
|
|
260
|
+
*/
|
|
261
|
+
async waitForDurable(operation, opts = {}) {
|
|
262
|
+
const inherited = opts[TRACE];
|
|
263
|
+
const id = typeof operation === 'string' ? operation : operation.id;
|
|
264
|
+
const trace = inherited ?? new Trace('wait', combineListeners(this.#ctx().onProgress, opts.onProgress), { operationId: id });
|
|
265
|
+
const run = async () => {
|
|
266
|
+
const { sleep, http } = this.#ctx();
|
|
267
|
+
const timeoutMs = opts.timeoutMs ?? 300_000;
|
|
268
|
+
const maxInterval = opts.maxPollIntervalMs ?? 1_000;
|
|
269
|
+
let interval = opts.pollIntervalMs ?? 250;
|
|
270
|
+
const started = Date.now();
|
|
271
|
+
const aborted = () => (opts.signal?.reason instanceof Error ? opts.signal.reason : new Error('aborted'));
|
|
272
|
+
let op = typeof operation === 'string' ? null : operation;
|
|
273
|
+
for (;;) {
|
|
274
|
+
if (opts.signal?.aborted)
|
|
275
|
+
throw aborted();
|
|
276
|
+
if (op === null) {
|
|
277
|
+
const { body } = await pollWithWait(http, `/v1/operations/${encodeURIComponent(id)}`, this.#ctx().authorization, 0, opts.signal, trace.onRetry);
|
|
278
|
+
op = body.operation;
|
|
279
|
+
trace.observe(op);
|
|
280
|
+
}
|
|
281
|
+
if (op.state !== 'succeeded') {
|
|
282
|
+
if (TERMINAL.has(op.state))
|
|
283
|
+
throw new OperationFailedError(op);
|
|
284
|
+
// Not finished yet (a fork or suspend passed by id): wait for it first, then for its copy.
|
|
285
|
+
op = await this.waitForOperation(id, { ...opts, timeoutMs: Math.max(1, timeoutMs - (Date.now() - started)), [TRACE]: trace });
|
|
286
|
+
}
|
|
287
|
+
if (isDurable(op) !== false)
|
|
288
|
+
return op;
|
|
289
|
+
const durability = durabilityOf(op);
|
|
290
|
+
if (durability?.state === 'lost')
|
|
291
|
+
throw new DurabilityLostError(op);
|
|
292
|
+
trace.phase('durable', durability?.overdueAt ? 'overdue' : null);
|
|
293
|
+
const waited = Date.now() - started;
|
|
294
|
+
if (waited >= timeoutMs)
|
|
295
|
+
throw new OperationTimeoutError(op, waited, true);
|
|
296
|
+
const jitter = interval * 0.2 * (Math.random() * 2 - 1);
|
|
297
|
+
const delay = Math.max(10, Math.min(interval + jitter, timeoutMs - waited));
|
|
298
|
+
await (opts.signal ? abortableSleep(sleep, delay, opts.signal) : sleep(delay));
|
|
299
|
+
interval = Math.min(maxInterval, interval * 2);
|
|
300
|
+
op = null;
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
if (inherited)
|
|
304
|
+
return run();
|
|
305
|
+
trace.phase('request');
|
|
306
|
+
return traced(trace, run);
|
|
307
|
+
}
|
|
249
308
|
/** One operation (GET /v1/operations/{id}); lifecycle operations stay pollable after a workspace is deleted. */
|
|
250
309
|
async getOperation(operationId, opts = {}) {
|
|
251
310
|
const { operation } = await this.#http.json('GET', `/v1/operations/${encodeURIComponent(operationId)}`, opts.signal ? { signal: opts.signal } : {}, this.#auth);
|
|
@@ -257,12 +316,21 @@ export class WorkspacesApi {
|
|
|
257
316
|
async get(workspaceId, opts = {}) {
|
|
258
317
|
return this.#wrap(await this.#getView(workspaceId), opts);
|
|
259
318
|
}
|
|
319
|
+
/** Replace labels. An empty map clears them. */
|
|
320
|
+
async setLabels(workspaceId, labels) {
|
|
321
|
+
return this.#wrap(await this.#http.json('PUT', `/v1/workspaces/${encodeURIComponent(workspaceId)}/labels`, { json: { labels } }, this.#auth));
|
|
322
|
+
}
|
|
323
|
+
/** null clears the override, restoring the template or platform policy. */
|
|
324
|
+
async setIdlePolicy(workspaceId, idlePolicy) {
|
|
325
|
+
return this.#wrap(await this.#http.json('PUT', `/v1/workspaces/${encodeURIComponent(workspaceId)}/idle-policy`, { json: { idle_policy: idlePolicy } }, this.#auth));
|
|
326
|
+
}
|
|
260
327
|
async list(params = {}) {
|
|
261
328
|
const page = await this.#http.json('GET', '/v1/workspaces', {
|
|
262
329
|
query: {
|
|
263
330
|
state: params.state,
|
|
264
331
|
desired_state: params.desiredState,
|
|
265
332
|
key_prefix: params.keyPrefix,
|
|
333
|
+
labels: params.labels === undefined ? undefined : JSON.stringify(params.labels),
|
|
266
334
|
project_id: params.projectId,
|
|
267
335
|
organization_id: params.organizationId,
|
|
268
336
|
include_deleted: params.includeDeleted,
|
|
@@ -379,7 +447,7 @@ export class WorkspacesApi {
|
|
|
379
447
|
}
|
|
380
448
|
/**
|
|
381
449
|
* One `POST /v1/workspaces/{id}/resume` (0.9.0), held for up to `waitS` seconds when that is at least 1 (`Prefer:
|
|
382
|
-
* wait
|
|
450
|
+
* wait`), asking for the tool token of `agentLabel`/`tools`. `ready` only for a 200 the server says it
|
|
383
451
|
* held (`Preference-Applied`); every other answer is the operation to wait for, so a server without the held resume
|
|
384
452
|
* works unchanged. Refusals (409 operation_in_progress, already_running without the hold, ...) throw as usual.
|
|
385
453
|
* `Workspace.wake()` and `resume({ wait })` use it; most callers want those.
|
|
@@ -406,11 +474,11 @@ export class WorkspacesApi {
|
|
|
406
474
|
const res = await this.#http.jsonWithHeaders('POST', `/v1/workspaces/${encodeURIComponent(workspaceId)}/resume`, init, this.#auth);
|
|
407
475
|
const b = res.body;
|
|
408
476
|
if (!b || typeof b !== 'object' || !b.workspace)
|
|
409
|
-
throw new ShardfluxProtocolError('resume: response has no workspace', res.status);
|
|
477
|
+
throw new ShardfluxProtocolError('resume: response has no workspace', res.status, 'api');
|
|
410
478
|
const pa = res.headers.get('preference-applied');
|
|
411
479
|
const ready = held && res.status === 200 && pa !== null && /\bwait\s*=/i.test(pa);
|
|
412
480
|
if (!ready && !b.operation)
|
|
413
|
-
throw new ShardfluxProtocolError('resume: response has no operation', res.status);
|
|
481
|
+
throw new ShardfluxProtocolError('resume: response has no operation', res.status, 'api');
|
|
414
482
|
const toolToken = ready ? (b.tool_token ?? null) : null;
|
|
415
483
|
this.#noteToken(toolToken);
|
|
416
484
|
return { ready, operation: b.operation ?? null, workspace: b.workspace, toolToken, requestId: res.headers.get('x-request-id') };
|
|
@@ -419,7 +487,7 @@ export class WorkspacesApi {
|
|
|
419
487
|
return this.#op('snapshot', workspaceId, opts.label === undefined ? {} : { label: opts.label }, opts);
|
|
420
488
|
}
|
|
421
489
|
/**
|
|
422
|
-
* Suspends the workspace once it has been idle for `afterSeconds` (0.9.0
|
|
490
|
+
* Suspends the workspace once it has been idle for `afterSeconds` (0.9.0): meant for the end of an
|
|
423
491
|
* agent turn, so the workspace stops using RAM soon after instead of waiting out its idle policy. The idle time
|
|
424
492
|
* counts from the later of the workspace's last work and this request. A command still running, an attached exec or
|
|
425
493
|
* terminal stream, or a keepalive postpones the suspend until `afterSeconds` after it ends; a tool call after the
|
|
@@ -429,7 +497,7 @@ export class WorkspacesApi {
|
|
|
429
497
|
* Resolves with the recorded `suspendRequest`, or, when a suspend is already in progress, with that `operation` and
|
|
430
498
|
* nothing recorded. Tool-call capture writes recorded before the call land first (a later write would count as the
|
|
431
499
|
* next turn). Errors: ShardfluxApiError 409 with reason `not_running`, `operation_in_progress`, `session_lifetime` or
|
|
432
|
-
* `workspace_deleted`; 422 `validation_failed` for `afterSeconds` outside
|
|
500
|
+
* `workspace_deleted`; 422 `validation_failed` for `afterSeconds` outside 0..3600.
|
|
433
501
|
*
|
|
434
502
|
* await cloud.workspaces.suspendWhenIdle(workspace.id, { afterSeconds: 60 });
|
|
435
503
|
*/
|
|
@@ -465,7 +533,7 @@ export class WorkspacesApi {
|
|
|
465
533
|
return this.#op('reset', workspaceId, body, opts);
|
|
466
534
|
}
|
|
467
535
|
/**
|
|
468
|
-
* Saves a layered workspace as the next version of an organization template
|
|
536
|
+
* Saves a layered workspace as the next version of an organization template. A running workspace is
|
|
469
537
|
* captured briefly (`operation`, layer_snapshot); poll `build` with templates.builds.waitForBuild. Owners/admins and
|
|
470
538
|
* API keys with a tool permission only.
|
|
471
539
|
*/
|
|
@@ -488,7 +556,7 @@ export class WorkspacesApi {
|
|
|
488
556
|
}, { settle: true });
|
|
489
557
|
return { operation, workspace: copy };
|
|
490
558
|
}
|
|
491
|
-
/** The workspace's text inputs `{NAME: value}` (0.7.0
|
|
559
|
+
/** The workspace's text inputs `{NAME: value}` (0.7.0). Secret inputs are bound secrets, never listed. */
|
|
492
560
|
async inputs(workspaceId) {
|
|
493
561
|
const body = await this.#http.json('GET', `/v1/workspaces/${encodeURIComponent(workspaceId)}/inputs`, {}, this.#auth);
|
|
494
562
|
return body.inputs;
|
|
@@ -546,7 +614,7 @@ export class Shardflux {
|
|
|
546
614
|
egress;
|
|
547
615
|
/** Organization audit trail (owner/admin; API keys are refused with 403). */
|
|
548
616
|
audit;
|
|
549
|
-
/** Shared volumes: persistent storage attached to workspaces at a mount path
|
|
617
|
+
/** Shared volumes: persistent storage attached to workspaces at a mount path. */
|
|
550
618
|
volumes;
|
|
551
619
|
#ctx;
|
|
552
620
|
constructor(opts) {
|
|
@@ -583,10 +651,10 @@ export class Shardflux {
|
|
|
583
651
|
return this.#ctx.http.json('GET', `/v1/organizations/${encodeURIComponent(organizationId)}/entitlements`, {}, this.#ctx.authorization);
|
|
584
652
|
}
|
|
585
653
|
/**
|
|
586
|
-
* Send feedback straight to the Shardflux
|
|
587
|
-
*
|
|
588
|
-
*
|
|
589
|
-
* the logs. `context.client` defaults to `shardflux-sdk-ts/<SDK_VERSION>`. Returns `{ id, receivedAt, duplicate }`
|
|
654
|
+
* Send feedback straight to the Shardflux team (0.9.0+; POST /v1/feedback), who read every message. Use it while you
|
|
655
|
+
* work, the moment something fails unexpectedly, an error or doc is unclear, an option is missing, or a workaround was
|
|
656
|
+
* needed, and when your user asks for a capability, an option or a smoother workflow; short and specific beats
|
|
657
|
+
* polished, and `context.requestId` / `errorCode` let the team find the logs. `context.client` defaults to `shardflux-sdk-ts/<SDK_VERSION>`. Returns `{ id, receivedAt, duplicate }`
|
|
590
658
|
* (`duplicate`: the same message from this key within 24 hours; no second email). 429 `rate_limited` (with
|
|
591
659
|
* `retryAfterSeconds`) and 422 `validation_failed` are ShardfluxApiErrors; the call is never retried.
|
|
592
660
|
*/
|
package/dist/egress.d.ts
CHANGED
|
@@ -50,7 +50,7 @@ export interface EgressPolicyVersion {
|
|
|
50
50
|
created_at: string;
|
|
51
51
|
}
|
|
52
52
|
export interface EffectiveEgressPolicy {
|
|
53
|
-
/** organization: an organization egress override
|
|
53
|
+
/** organization: an organization egress override wins over every policy (mode deny_all, no version). */
|
|
54
54
|
source: 'organization' | 'workspace' | 'project' | 'platform_default';
|
|
55
55
|
policy_version_id: string | null;
|
|
56
56
|
policy_version: number | null;
|
|
@@ -60,7 +60,7 @@ export interface EffectiveEgressPolicy {
|
|
|
60
60
|
policy_sha256: string;
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
63
|
-
* Active organization egress override
|
|
63
|
+
* Active organization egress override: the organization used its whole outbound transfer allowance,
|
|
64
64
|
* so outbound internet traffic of every workspace is blocked until `lifts_at` (period end) or an upgrade/purchase.
|
|
65
65
|
* Stored policies are kept and apply again when it lifts.
|
|
66
66
|
*/
|
|
@@ -123,7 +123,7 @@ export interface WorkspaceEgressPolicy {
|
|
|
123
123
|
organization_override: OrganizationEgressOverride | null;
|
|
124
124
|
enforcement: EgressEnforcement;
|
|
125
125
|
/**
|
|
126
|
-
* The template version's network ceiling (0.7.0
|
|
126
|
+
* The template version's network ceiling (0.7.0 `defaults.egress`), or null. The host enforces
|
|
127
127
|
* `effective` intersected with it; a workspace policy it would narrow is refused with 422 egress_widening.
|
|
128
128
|
*/
|
|
129
129
|
template_egress: {
|