@shardflux/sdk 0.15.0 → 0.16.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 +42 -22
- package/README.md +99 -1
- package/dist/account.d.ts +2 -0
- package/dist/account.js +6 -0
- package/dist/cell.d.ts +2 -0
- package/dist/cell.js +2 -0
- package/dist/client.d.ts +25 -1
- package/dist/client.js +44 -3
- package/dist/computer.d.ts +11 -1
- package/dist/computer.js +89 -6
- package/dist/errors.d.ts +5 -1
- package/dist/errors.js +9 -0
- package/dist/executions.d.ts +2 -6
- package/dist/executions.js +9 -0
- package/dist/exit-code.d.ts +7 -0
- package/dist/exit-code.js +12 -0
- package/dist/generated/app-api.d.ts +565 -13
- package/dist/generated/cell-api.d.ts +2 -0
- package/dist/http.d.ts +7 -1
- package/dist/http.js +34 -13
- package/dist/index.d.ts +8 -5
- package/dist/index.js +4 -2
- package/dist/ports.d.ts +7 -0
- package/dist/ports.js +1 -1
- package/dist/progress.d.ts +2 -2
- package/dist/progress.js +1 -1
- package/dist/testing/index.d.ts +62 -0
- package/dist/testing/index.js +585 -0
- package/dist/testing/seed.d.ts +433 -0
- package/dist/testing/seed.js +449 -0
- package/dist/tools.d.ts +5 -0
- package/dist/tools.js +4 -2
- package/dist/tunnel-assets/linux-amd64.gz +0 -0
- package/dist/tunnel-assets/linux-arm64.gz +0 -0
- package/dist/tunnel-assets.d.ts +10 -0
- package/dist/tunnel-assets.js +11 -0
- package/dist/tunnel-packet.d.ts +3 -0
- package/dist/tunnel-packet.js +43 -0
- package/dist/tunnel-pty.d.ts +86 -0
- package/dist/tunnel-pty.js +243 -0
- package/dist/tunnels.d.ts +47 -0
- package/dist/tunnels.js +454 -0
- package/dist/workspace-ref.d.ts +4 -0
- package/dist/workspace-ref.js +24 -0
- package/dist/workspace.d.ts +18 -1
- package/dist/workspace.js +76 -3
- package/package.json +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,31 @@
|
|
|
3
3
|
Every API the README shows is available from the version named here. Breaking changes ship in minor releases and are
|
|
4
4
|
marked **Breaking**.
|
|
5
5
|
|
|
6
|
-
## 0.
|
|
6
|
+
## 0.16.0 (not yet published)
|
|
7
|
+
|
|
8
|
+
- Add `@shardflux/sdk/testing`: `createFakeShardflux` supplies an isolated in-memory transport beneath real SDK handles, refs and errors, with scripted commands, a virtual clock, fault controls and captured response shapes. A shared scenario manifest runs against the fake and live API.
|
|
9
|
+
|
|
10
|
+
- Add opt-in persistent workspace idle deletion retention, workspace/project setters and projected deletion times.
|
|
11
|
+
|
|
12
|
+
- Accelerate reverse tunnels with a bounded, acknowledged binary WebSocket stream, 64 KiB frames and batched credits. Select `auto`, `pty` or `exec` transport; reconnect preserves byte offsets. Opt-in binary PTY output accepts larger blocks for file transfers.
|
|
13
|
+
|
|
14
|
+
- Add opt-in resize controls to grow named resources only and return the held server answer without polling a pending operation.
|
|
15
|
+
- Expose typed stored workspace caps and preserve nullable grants. Fixed memory without an explicit size uses the template default.
|
|
16
|
+
|
|
17
|
+
- `WorkspaceRef.turn(fn, { afterSeconds: 0 })` and `Workspace.turn()` hint without waiting and request idle suspension when the body settles, including errors.
|
|
18
|
+
- Computer `act()` avoids an extra screenshot after `screenshot`/`zoom`. Viewer links are reused by kind with more than 60 seconds left; `fresh: true` remints, and `stopStream()` clears them.
|
|
19
|
+
- `computerToolset(ws, { beforeAction })` and `workspaceTools(ws, { computer: { beforeAction } })` check each action immediately before execution; refusal answers that action with an error and skips the rest.
|
|
20
|
+
- The default pooled transport honors environment HTTP(S) proxies and NO_PROXY. `SHARDFLUX_BASE_URL` aliases `SHARDFLUX_API_URL`, which takes precedence.
|
|
21
|
+
- Exec and execution results expose `exitCodePosix`. Missing cell file paths with `path_not_found` raise `FileNotFoundError`, distinct from workspace deletion and protocol errors.
|
|
22
|
+
- Embed live desktop viewers and port previews in a web app with an explicit HTTPS origin allowlist and partitioned browser sessions.
|
|
23
|
+
|
|
24
|
+
- Add `workspace.tunnels.reverse({ remotePort, target, bindAddress?, signal? })`: a private workspace TCP listener forwarded to this machine, with binary multiplexing, bounded backpressure, half-close, transport reconnection, suspend/resume, statistics and deterministic close.
|
|
25
|
+
|
|
26
|
+
- Add `ws.upgrade({ at })`, `upgradeAvailable` and `upgradePending`. An opt-in cold start preserves the workspace disk; `next_resume` schedules it. Lifecycle cold boot reasons include `upgraded`.
|
|
27
|
+
|
|
28
|
+
### Integration testing
|
|
29
|
+
|
|
30
|
+
## 0.15.0 — 2026-10-04
|
|
7
31
|
|
|
8
32
|
### Workspaces by key
|
|
9
33
|
|
|
@@ -45,12 +69,10 @@ Additive. A desktop in the workspace that agents drive with screenshots, clicks
|
|
|
45
69
|
- `cloud.templates.setComputerUse(slug, enabled, { organizationId })` switches it for every workspace of one of your
|
|
46
70
|
templates. `ToolName` includes `computer`; template views carry `computer_use`.
|
|
47
71
|
|
|
48
|
-
## 0.14.0 — 2026-10-04
|
|
72
|
+
## 0.14.0 — 2026-10-04
|
|
49
73
|
|
|
50
74
|
- Opt-in `workspaceTools(workspace, { prewake: true })` input-start hooks prepare VM tools while their input streams. Default tools, offline reads and file-first workspaces install no hook.
|
|
51
75
|
|
|
52
|
-
## 0.14.0 (not yet published)
|
|
53
|
-
|
|
54
76
|
### Sign up from an agent
|
|
55
77
|
|
|
56
78
|
Additive (API contracts §44).
|
|
@@ -159,7 +181,7 @@ gives a static allocation of `memory_mib`. Reopening an existing workspace with
|
|
|
159
181
|
keeps its stored mode; a fork without caps inherits its source's. The SDK's requests do not change;
|
|
160
182
|
`workspace.memory` and `workspace.allocationMode` report the mode the workspace got.
|
|
161
183
|
|
|
162
|
-
## 0.13.1
|
|
184
|
+
## 0.13.1 — 2026-10-03
|
|
163
185
|
|
|
164
186
|
Patch, additive: the transport, a documented value, a fix for tool calls across a move and self-healing workspaces.
|
|
165
187
|
`@shardflux/cli` 0.8.1, `@shardflux/mcp` 0.7.1 and the `shardflux` bundle 0.10.1 follow (`^0.13.1`).
|
|
@@ -216,7 +238,7 @@ failing with `409 conflict` (`workspace_not_running`).
|
|
|
216
238
|
runs; new token)`.
|
|
217
239
|
- The CLI, the MCP server and the `shardflux` bundle get it through their `^0.13.0` dependency.
|
|
218
240
|
|
|
219
|
-
## 0.13.0
|
|
241
|
+
## 0.13.0 — 2026-10-02
|
|
220
242
|
|
|
221
243
|
### Elastic memory
|
|
222
244
|
|
|
@@ -326,14 +348,12 @@ template's newest published version, picked up at its next cold boot or resume.
|
|
|
326
348
|
- The cancel that `exec.run` sends when its `signal` aborts passes `killGraceMs` capped at 60000, so a larger
|
|
327
349
|
`killGraceMs` still cancels the command.
|
|
328
350
|
|
|
329
|
-
## 0.12.0
|
|
351
|
+
## 0.12.0 — 2026-09-30
|
|
330
352
|
|
|
331
353
|
Completed exec output streams are drained before releasing their HTTP connections, with a bounded cleanup if a peer does not close.
|
|
332
354
|
|
|
333
355
|
Pooled HTTP/1.1 on Node 26 with pinned undici 8.10.2; custom fetch stays unchanged. Labels on open/list and setLabels, typed idle(), keepalive() and setIdlePolicy(). Protocol errors carry source; isWorkspaceGone recognizes only an explicit API workspace_deleted refusal. Exec sessions accept stdin_open and offset-addressed exec.input. Failed workspaces recover through resume/auto-wake with the same ID. Completed deletions free keys for new IDs.
|
|
334
356
|
|
|
335
|
-
Requires the QM integration backend release for labels, failed recovery, key reuse and pipe stdin. No production deployment has occurred from this branch.
|
|
336
|
-
|
|
337
357
|
### Instant suspend: durable storage in the result
|
|
338
358
|
|
|
339
359
|
Additive. A suspend returns as soon as the workspace is sealed on its host; the copy lands in durable storage right
|
|
@@ -358,7 +378,7 @@ after. This release reads that from the result and can wait for it.
|
|
|
358
378
|
Requires the instant-suspend cell release for `durable: false` results; against other servers every succeeded suspend
|
|
359
379
|
is already durable and `durable: true` resolves at once.
|
|
360
380
|
|
|
361
|
-
## 0.11.1
|
|
381
|
+
## 0.11.1 — 2026-09-30
|
|
362
382
|
|
|
363
383
|
Wording: messages and JSDoc say what to do, without internals (no behaviour change).
|
|
364
384
|
|
|
@@ -372,7 +392,7 @@ Wording: messages and JSDoc say what to do, without internals (no behaviour chan
|
|
|
372
392
|
`SHARDFLUX_HTTP_KEEPALIVE=1`.
|
|
373
393
|
- The `formatTiming()` example is a production resume (413 ms).
|
|
374
394
|
|
|
375
|
-
## 0.11.0
|
|
395
|
+
## 0.11.0 — 2026-09-29
|
|
376
396
|
|
|
377
397
|
### A resume that restarted processes says so (cold boot)
|
|
378
398
|
|
|
@@ -395,17 +415,17 @@ the resume's result through; this release reads it.
|
|
|
395
415
|
- The new `ServerTiming` fields are optional in the type (always set by the SDK), so timings built by hand still
|
|
396
416
|
type-check. No return type or behaviour changes: a wake that cold-booted still resolves `true`.
|
|
397
417
|
|
|
398
|
-
## 0.10.2
|
|
418
|
+
## 0.10.2 — 2026-09-29
|
|
399
419
|
|
|
400
420
|
Fix: the SDK reports 0.10.2. 0.10.1 was published reporting 0.10.0, its previous version; the build now
|
|
401
421
|
fails when `SDK_VERSION` differs from package.json.
|
|
402
422
|
|
|
403
|
-
## 0.10.1
|
|
423
|
+
## 0.10.1 — 2026-09-29
|
|
404
424
|
|
|
405
425
|
Public support: package metadata and the README now link to the [shared bug tracker](https://github.com/shardfluxdev/community/issues),
|
|
406
426
|
feature requests, and private support/security reporting.
|
|
407
427
|
|
|
408
|
-
## 0.10.0
|
|
428
|
+
## 0.10.0 — 2026-09-29
|
|
409
429
|
|
|
410
430
|
### A command that could not start rejects exec.run() (ExecStartError)
|
|
411
431
|
|
|
@@ -453,7 +473,7 @@ from the API's OpenAPI); the account client's `setSpendPolicy()` gains the overa
|
|
|
453
473
|
|
|
454
474
|
- `workspace.suspendWhenIdle({ afterSeconds, idempotencyKey? })` and `cloud.workspaces.suspendWhenIdle(id, {
|
|
455
475
|
afterSeconds })` (POST /v1/workspaces/{id}/suspend-when-idle): the workspace is suspended once it has been idle for
|
|
456
|
-
`afterSeconds` (30..3600), counted from the later of its last work and the request. Meant for the end of an agent
|
|
476
|
+
`afterSeconds` (0..3600 since the 2026-09-30 API release, commit `2b2f5e658`; originally 30..3600), counted from the later of its last work and the request. Meant for the end of an agent
|
|
457
477
|
turn. A running command, an attached stream or a keepalive postpones it; the next tool call or a resume cancels it.
|
|
458
478
|
Resolves with `{ suspendRequest, operation, workspace }`: `operation` is the suspend already in progress, if any
|
|
459
479
|
(then nothing is recorded).
|
|
@@ -463,7 +483,7 @@ from the API's OpenAPI); the account client's `setSpendPolicy()` gains the overa
|
|
|
463
483
|
count as the next turn and cancel the request.
|
|
464
484
|
- Types `SuspendRequest`, `SuspendWhenIdleOptions`, `SuspendWhenIdleResult`, `SuspendWhenIdleResponse`.
|
|
465
485
|
|
|
466
|
-
## 0.9.0
|
|
486
|
+
## 0.9.0 — 2026-09-29
|
|
467
487
|
|
|
468
488
|
Elastic compute (decision 0007): file tools and wake hints for parked workspaces. Additive; older APIs and cell
|
|
469
489
|
gateways keep working (the new calls answer 404 there).
|
|
@@ -659,7 +679,7 @@ behavior is the automatic version check (below), which makes one background requ
|
|
|
659
679
|
- New exports: `FeedbackCategory`, `FeedbackContext`, `FeedbackReceipt`, `SendFeedbackParams`, `AccountFeedbackParams`,
|
|
660
680
|
`FEEDBACK_CATEGORIES`, `FEEDBACK_MESSAGE_MAX_LENGTH`.
|
|
661
681
|
|
|
662
|
-
## 0.8.0
|
|
682
|
+
## 0.8.0 — 2026-09-28
|
|
663
683
|
|
|
664
684
|
Types only; nothing changes at run time and the API is unchanged.
|
|
665
685
|
|
|
@@ -679,7 +699,7 @@ Types only; nothing changes at run time and the API is unchanged.
|
|
|
679
699
|
- Checked at compile time against `@anthropic-ai/sdk` 0.128.0 and `openai` 7.23.0 (devDependencies only; the SDK still
|
|
680
700
|
has no runtime dependencies).
|
|
681
701
|
|
|
682
|
-
## 0.7.0
|
|
702
|
+
## 0.7.0 — 2026-09-28
|
|
683
703
|
|
|
684
704
|
Needs an API with the template editor; every new field is additive and older fields are unchanged.
|
|
685
705
|
|
|
@@ -767,7 +787,7 @@ docs/decisions/0006-tool-call-capture.md (shared with the Python SDK 0.3.0).
|
|
|
767
787
|
- **Breaking (types only):** `LifecyclePhase` gains `capture_flush`; an exhaustive `switch` over phases without a
|
|
768
788
|
`default` case needs the new member.
|
|
769
789
|
|
|
770
|
-
## 0.6.2
|
|
790
|
+
## 0.6.2 — 2026-09-28
|
|
771
791
|
|
|
772
792
|
### Starts that wait for capacity end
|
|
773
793
|
|
|
@@ -782,7 +802,7 @@ boot (and bill) long after every wait had given up.
|
|
|
782
802
|
- `onProgress`: a `capacity_pending` `phase` event carries `deadlineAt` when the API reports it.
|
|
783
803
|
- Docs: waits no longer say a pending start continues indefinitely.
|
|
784
804
|
|
|
785
|
-
## 0.6.1
|
|
805
|
+
## 0.6.1 — 2026-09-28
|
|
786
806
|
|
|
787
807
|
- `formatTiming()` joined two phases that followed each other with `∥` (ran together) when their 0.1 ms times summed
|
|
788
808
|
with a floating-point error (1000.2 + 300.1 > 1300.3); it now prints `→`.
|
|
@@ -792,7 +812,7 @@ boot (and bill) long after every wait had given up.
|
|
|
792
812
|
belonged to no phase (a 1 s wait could show `queued 0 ms`).
|
|
793
813
|
- README: the timing example is the formatter's real output (durations under a second print in ms).
|
|
794
814
|
|
|
795
|
-
## 0.6.0
|
|
815
|
+
## 0.6.0 — 2026-09-28
|
|
796
816
|
|
|
797
817
|
### Lifecycle calls: requested or finished
|
|
798
818
|
|
|
@@ -843,7 +863,7 @@ boot (and bill) long after every wait had given up.
|
|
|
843
863
|
create, capture state, test instances, publish, discard).
|
|
844
864
|
- `ShardfluxApiError.reason` (`details.reason`), with `KnownErrorReason`.
|
|
845
865
|
|
|
846
|
-
## 0.5.0
|
|
866
|
+
## 0.5.0 — 2026-09-26
|
|
847
867
|
|
|
848
868
|
First public release: `Shardflux` client, `workspaces.open/get/list/listAll`, lifecycle calls returning operations,
|
|
849
869
|
`waitForOperation()`, the cell client (`exec`, `files`, `pty`, `processes`, `git`, `browser`), `workspaceTools()` with
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @shardflux/sdk
|
|
2
2
|
|
|
3
|
-
TypeScript SDK for [Shardflux](https://shardflux.dev):
|
|
3
|
+
TypeScript SDK for [Shardflux](https://shardflux.dev): serverless VMs for AI agents.
|
|
4
4
|
|
|
5
5
|
Open a persistent workspace by key, run commands and move files in it, suspend it when idle, resume
|
|
6
6
|
it later with its disk and memory intact, and fork it. Hand your agent framework-neutral workspace
|
|
@@ -20,6 +20,34 @@ tool calls into the workspace ([tool-call capture](#tool-call-capture-070)).
|
|
|
20
20
|
- Typed from the published OpenAPI documents.
|
|
21
21
|
- Retries, idempotency keys, operation polling and tool-token refresh are handled for you.
|
|
22
22
|
|
|
23
|
+
## Test your integration (0.16.0+)
|
|
24
|
+
|
|
25
|
+
Use the same client calls in fast, isolated tests with `@shardflux/sdk/testing`:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { createFakeShardflux } from '@shardflux/sdk/testing';
|
|
29
|
+
|
|
30
|
+
const cloud = createFakeShardflux({
|
|
31
|
+
onExec: (request) => ({ stdout: `ran ${request.argv.join(' ')}\n`, exitCode: 0 }),
|
|
32
|
+
});
|
|
33
|
+
const ref = cloud.workspace('acme/test', { template: 'python-node-browser' });
|
|
34
|
+
const result = await ref.exec(['python3', '-V'], { sessionId: 'version-check' });
|
|
35
|
+
await ref.files.write('/home/user/result.txt', result.stdout);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The fake returns real SDK workspace handles, refs and errors. Workspace lifecycle calls, labels, paginated lists,
|
|
39
|
+
files, command output, desktop actions and port lists use an isolated in-memory store. Command results come from
|
|
40
|
+
`onExec`; returning `null` leaves a command running for `cloud.testing.completeCommand(id, sessionId, result)`.
|
|
41
|
+
Unscripted commands and unimplemented routes raise `FakeUnsupportedError`.
|
|
42
|
+
|
|
43
|
+
Control state with `cloud.testing.setFaults(id, { busy: true, failed: true, legacyLayout: true })`,
|
|
44
|
+
`computerUnavailable`, `deleted`, `resizeRefusal` (`shrink_not_supported` or `resize_not_available`), and
|
|
45
|
+
`memoryLimitMib` (a plan clamp). Set `cloud.testing.quota` to `retained_state` or `concurrent_workspaces`.
|
|
46
|
+
`cloud.testing.advance(milliseconds)` drives idle deadlines; `latencyMs` and `sleep` inject request latency.
|
|
47
|
+
`cloud.testing.inspect(id)` returns a copy of the raw workspace view. Each fake has its own state.
|
|
48
|
+
Import the testing subpath when building tests; the main SDK entry keeps its existing runtime dependencies.
|
|
49
|
+
|
|
50
|
+
|
|
23
51
|
## Support and bug reports
|
|
24
52
|
|
|
25
53
|
[Report a bug](https://github.com/shardfluxdev/community/issues/new?template=bug_report.yml) or
|
|
@@ -31,6 +59,29 @@ For usage, account, billing, or private support, email [shardflux@heliosone.fi](
|
|
|
31
59
|
Report vulnerabilities through [private security reporting](https://github.com/shardfluxdev/community/security/advisories/new).
|
|
32
60
|
See the [support guide](https://github.com/shardfluxdev/community/blob/main/SUPPORT.md) for all reporting options.
|
|
33
61
|
|
|
62
|
+
## Reverse tunnels (0.16.0+)
|
|
63
|
+
|
|
64
|
+
Forward a workspace TCP port to a service on the machine running your SDK:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
const tunnel = await workspace.tunnels.reverse({ remotePort: 8081, target: 'localhost:8081' });
|
|
68
|
+
try {
|
|
69
|
+
// Commands in the workspace can call http://127.0.0.1:8081.
|
|
70
|
+
console.log(tunnel.stats);
|
|
71
|
+
} finally {
|
|
72
|
+
await tunnel.close();
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The guest listener binds to `127.0.0.1` by default; `bindAddress: '0.0.0.0'` selects all guest interfaces.
|
|
77
|
+
The target is dialed from this machine. TCP binary data, concurrent connections and half-close are preserved;
|
|
78
|
+
network reconnection resumes the tunnel while the handle is open. `closed` reports completion or failure,
|
|
79
|
+
`sessionId` identifies the forwarder, and `stats` counts connections, bytes in each direction and reconnects.
|
|
80
|
+
An open tunnel keeps the workspace awake; close it when callbacks are finished. For persistent workspaces, explicit suspend/resume preserves
|
|
81
|
+
an open tunnel. The caller needs `exec` and `files` access.
|
|
82
|
+
|
|
83
|
+
The tunnel selects a streaming transport automatically when `pty` permission is granted. Choose `transport: 'pty'` or `transport: 'exec'` in the SDK, or `--transport pty|exec` in the CLI, to select it explicitly. The handle reports `transport`.
|
|
84
|
+
|
|
34
85
|
## Install
|
|
35
86
|
|
|
36
87
|
```sh
|
|
@@ -1330,3 +1381,50 @@ const tools = workspaceTools(workspace, { prewake: true });
|
|
|
1330
1381
|
// In your model stream's tool-input-start handler:
|
|
1331
1382
|
tools.find(tool => tool.name === toolName)?.onInputStart?.();
|
|
1332
1383
|
```
|
|
1384
|
+
|
|
1385
|
+
Resize controls (0.16.0+): `workspace.resize({ memoryMib: 6144, diskGib: 2, atLeast: true, wait: false })` grows
|
|
1386
|
+
only resources that need more capacity and returns the held server answer with its operation. `workspace.caps` exposes
|
|
1387
|
+
typed stored caps for later starts. A pending operation carries per-resource results once the resize has decided
|
|
1388
|
+
them. Fixed memory without `memory_mib` uses the template default, bounded by the plan and template.
|
|
1389
|
+
|
|
1390
|
+
## New in 0.16.0
|
|
1391
|
+
|
|
1392
|
+
`ref.turn(async (ref) => { ... })` (also `workspace.turn`) sends a wake hint without waiting, returns the body's result, and requests `suspendWhenIdle({ afterSeconds: 0 })` when it settles, including on error. Pass `{ afterSeconds: 60 }` to keep the workspace warm for a billed minute. Use persistent workspaces for resumable turns.
|
|
1393
|
+
|
|
1394
|
+
Computer batches ending in `screenshot` or `zoom` return that action's image without an extra screenshot. `computer.stream()` reuses a link of the same kind with more than 60 seconds left; `{ fresh: true }` mints one, and `stopStream()` clears the cache. Gate each action with `computerToolset(ws, { beforeAction })` or `workspaceTools(ws, { computer: { beforeAction } })`: return `false`, a refusal string, or throw to answer that action with an error and skip the remaining actions. Hooks may be async.
|
|
1395
|
+
|
|
1396
|
+
Exec results expose `exitCodePosix`: timeout 124, canceled 130, nonnegative exit code, 128 + signal, otherwise 1. `FileNotFoundError` is a `ShardfluxApiError` for cell `path_not_found` replies. The default transport honors `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` and `NODE_USE_ENV_PROXY`. `SHARDFLUX_BASE_URL` aliases `SHARDFLUX_API_URL`; `SHARDFLUX_API_URL` wins.
|
|
1397
|
+
|
|
1398
|
+
## Embed a live viewer (0.16.0+)
|
|
1399
|
+
|
|
1400
|
+
Watch the desktop or a port preview inside your web app. Choose 1–10 distinct HTTPS origins, then use the returned URL as the iframe source.
|
|
1401
|
+
|
|
1402
|
+
```ts
|
|
1403
|
+
const { url } = await ws.computer.stream({ embed: { origins: ['https://app.example.com'] } });
|
|
1404
|
+
const preview = await ws.ports.link(3000, { embed: { origins: ['https://app.example.com'] } });
|
|
1405
|
+
```
|
|
1406
|
+
|
|
1407
|
+
```html
|
|
1408
|
+
<iframe src="RETURNED_LINK_URL" title="Watch the agent" style="width:100%;height:600px;border:0"></iframe>
|
|
1409
|
+
```
|
|
1410
|
+
|
|
1411
|
+
The browser session is partitioned by the top-level site. Each origin is exactly a scheme and host with an optional port, such as `https://app.example.com:8443`. The viewer permits framing by those origins; interactive viewing uses the same allowlist.
|
|
1412
|
+
|
|
1413
|
+
## Idle deletion retention (0.16.0+)
|
|
1414
|
+
|
|
1415
|
+
Keep one persistent workspace per conversation and delete it automatically after 14 idle days:
|
|
1416
|
+
|
|
1417
|
+
```ts
|
|
1418
|
+
const ws = await cloud.workspaces.open({ key: 'chat/42', template: 'python-node-browser', retention: { delete_after_idle_days: 14 } });
|
|
1419
|
+
await ws.setRetention({ delete_after_idle_days: 30 });
|
|
1420
|
+
await cloud.projects.setRetention((await cloud.me()).api_key!.project_id, { delete_after_idle_days: 14, labels: { app: 'chat' } });
|
|
1421
|
+
await ws.setRetention(null); // follow the project default
|
|
1422
|
+
```
|
|
1423
|
+
|
|
1424
|
+
`cloud.workspace(key, { template, retention })` accepts the same policy. `ws.retention` shows its effective days
|
|
1425
|
+
and projected `delete_at`. All project selector labels must match exactly; a workspace's own policy wins.
|
|
1426
|
+
Calls and ongoing work postpone deletion. Without a policy, your workspace stays until you delete it.
|
|
1427
|
+
|
|
1428
|
+
## Workspace upgrade (0.16.0+)
|
|
1429
|
+
|
|
1430
|
+
`await ws.upgrade({ at: "now", wait: true })` keeps files, installed package files and home while cold starting on the supported current layout and base. `at: "next_resume"` schedules it for the next resume. Memory and running processes end at the cold start. Read `ws.upgradeAvailable` and `ws.upgradePending` for the target.
|
package/dist/account.d.ts
CHANGED
|
@@ -277,6 +277,8 @@ export declare class AccountProjectsApi {
|
|
|
277
277
|
name: string;
|
|
278
278
|
slug?: string;
|
|
279
279
|
}): Promise<Project>;
|
|
280
|
+
getRetention(projectId: string): Promise<import('./client.js').ProjectRetentionPolicy | null>;
|
|
281
|
+
setRetention(projectId: string, policy: import('./client.js').ProjectRetentionPolicy | null): Promise<import('./client.js').ProjectRetentionPolicy | null>;
|
|
280
282
|
get(projectId: string): Promise<Project>;
|
|
281
283
|
}
|
|
282
284
|
/** Every tool permission an API key can carry (pass it as `toolPermissions` for a key with every tool). */
|
package/dist/account.js
CHANGED
|
@@ -280,6 +280,12 @@ export class AccountProjectsApi {
|
|
|
280
280
|
create(organizationId, params) {
|
|
281
281
|
return json(this.#core, 'POST', `/v1/organizations/${enc(organizationId)}/projects`, { json: { name: params.name, ...(params.slug === undefined ? {} : { slug: params.slug }) } });
|
|
282
282
|
}
|
|
283
|
+
getRetention(projectId) {
|
|
284
|
+
return json(this.#core, 'GET', `/v1/projects/${enc(projectId)}/retention`);
|
|
285
|
+
}
|
|
286
|
+
setRetention(projectId, policy) {
|
|
287
|
+
return json(this.#core, 'PUT', `/v1/projects/${enc(projectId)}/retention`, { json: policy });
|
|
288
|
+
}
|
|
283
289
|
get(projectId) {
|
|
284
290
|
return json(this.#core, 'GET', `/v1/projects/${enc(projectId)}`);
|
|
285
291
|
}
|
package/dist/cell.d.ts
CHANGED
|
@@ -248,6 +248,8 @@ export declare const CAPTURE_BARRIER: unique symbol;
|
|
|
248
248
|
export interface RunResult {
|
|
249
249
|
sessionId: string;
|
|
250
250
|
exitCode: number | null;
|
|
251
|
+
/** POSIX status: timeout 124, canceled 130, exit code, 128 + signal, otherwise 1. */
|
|
252
|
+
exitCodePosix: number;
|
|
251
253
|
termSignal: number | null;
|
|
252
254
|
timedOut: boolean;
|
|
253
255
|
canceled: boolean;
|
package/dist/cell.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ExecStartError, NotSupportedForModeError, ShardfluxApiError, ShardfluxProtocolError, apiError, isErrorBody, isWorkingQuotaRefusal } from "./errors.js";
|
|
2
|
+
import { exitCodePosix } from "./exit-code.js";
|
|
2
3
|
import { EXECUTION_ID, ExecutionResult, newExecutionId } from "./executions.js";
|
|
3
4
|
import { HttpClient, defaultSleep, randomId, treeRevisionOf } from "./http.js";
|
|
4
5
|
import { describeFailure, emitTo } from "./progress.js";
|
|
@@ -590,6 +591,7 @@ export class CellClient {
|
|
|
590
591
|
return {
|
|
591
592
|
sessionId,
|
|
592
593
|
exitCode: session.exit_code ?? null,
|
|
594
|
+
exitCodePosix: exitCodePosix({ exitCode: session.exit_code ?? null, termSignal: session.term_signal ?? null, timedOut: session.timed_out ?? false, canceled: session.canceled ?? false }),
|
|
593
595
|
termSignal: session.term_signal ?? null,
|
|
594
596
|
timedOut: session.timed_out ?? false,
|
|
595
597
|
canceled: session.canceled ?? false,
|
package/dist/client.d.ts
CHANGED
|
@@ -159,7 +159,7 @@ export type AllocationMode = 'fixed' | 'elastic';
|
|
|
159
159
|
export type WorkspaceMemory = WorkspaceView['memory'];
|
|
160
160
|
export interface Caps {
|
|
161
161
|
cpu_millis?: number;
|
|
162
|
-
/** Memory in MiB;
|
|
162
|
+
/** Memory in MiB; elastic promise, or fixed size (the template default when omitted). */
|
|
163
163
|
memory_mib?: number;
|
|
164
164
|
disk_gib?: number;
|
|
165
165
|
/**
|
|
@@ -197,6 +197,10 @@ export interface ResizeParams {
|
|
|
197
197
|
cpuMillis?: number;
|
|
198
198
|
/** Disk in GiB; disks grow only. */
|
|
199
199
|
diskGib?: number;
|
|
200
|
+
/** Only grow each named numeric resource; a smaller or equal value leaves it unchanged. */
|
|
201
|
+
atLeast?: boolean;
|
|
202
|
+
/** Wait for completion (default true); false returns the held server answer without operation polling. */
|
|
203
|
+
wait?: boolean;
|
|
200
204
|
/** Replays the stored response for a repeated request (default: a fresh key per call, so transport retries replay). */
|
|
201
205
|
idempotencyKey?: string;
|
|
202
206
|
/** Give up waiting for the resize after this long (default 300 000 ms); it continues server side. */
|
|
@@ -260,7 +264,21 @@ export interface WaitOptions {
|
|
|
260
264
|
onProgress?: ProgressListener;
|
|
261
265
|
}
|
|
262
266
|
export type IdlePolicy = 'adaptive' | 'never' | `fixed:${number}`;
|
|
267
|
+
export interface RetentionPolicy {
|
|
268
|
+
delete_after_idle_days: number;
|
|
269
|
+
}
|
|
270
|
+
export interface ProjectRetentionPolicy extends RetentionPolicy {
|
|
271
|
+
labels?: Record<string, string>;
|
|
272
|
+
}
|
|
273
|
+
export declare class ProjectsRetentionApi {
|
|
274
|
+
#private;
|
|
275
|
+
constructor(ctx: () => ClientContext);
|
|
276
|
+
getRetention(projectId: string): Promise<ProjectRetentionPolicy | null>;
|
|
277
|
+
setRetention(projectId: string, policy: ProjectRetentionPolicy | null): Promise<ProjectRetentionPolicy | null>;
|
|
278
|
+
}
|
|
263
279
|
export interface OpenParams {
|
|
280
|
+
/** Opt-in idle deletion in days (1..3650), persistent workspaces only. Omitted leaves it unchanged. */
|
|
281
|
+
retention?: RetentionPolicy;
|
|
264
282
|
/** Searchable metadata; supplied labels replace the existing map. */
|
|
265
283
|
labels?: Record<string, string>;
|
|
266
284
|
idlePolicy?: IdlePolicy;
|
|
@@ -415,6 +433,7 @@ export declare class WorkspacesApi {
|
|
|
415
433
|
/** null clears the override, restoring the template or platform policy. */
|
|
416
434
|
/** Computer use (0.15.0+): the workspace's own switch; null follows the template. Returns the switch. */
|
|
417
435
|
setComputerUse(workspaceId: string, enabled: boolean | null): Promise<ComputerUse>;
|
|
436
|
+
setRetention(workspaceId: string, policy: RetentionPolicy | null): Promise<Workspace>;
|
|
418
437
|
setIdlePolicy(workspaceId: string, idlePolicy: IdlePolicy | null): Promise<Workspace>;
|
|
419
438
|
list(params?: ListParams): Promise<Page<Workspace>>;
|
|
420
439
|
/** Iterates every page. */
|
|
@@ -528,6 +547,10 @@ export declare class WorkspacesApi {
|
|
|
528
547
|
* boots blank on the next resume. Returns the `reset` operation; its result names the recovery checkpoint (restorable
|
|
529
548
|
* for 7 days). Errors: 409 legacy_disk_layout, not_resettable, operation_in_progress.
|
|
530
549
|
*/
|
|
550
|
+
/** Opt-in cold start preserving the disk; next_resume schedules it without stopping the VM. */
|
|
551
|
+
upgrade(workspaceId: string, opts?: LifecycleOptions & {
|
|
552
|
+
at?: 'now' | 'next_resume';
|
|
553
|
+
}): Promise<Operation>;
|
|
531
554
|
reset(workspaceId: string, opts: WaitedLifecycleOptions): Promise<FinishedOperation>;
|
|
532
555
|
reset(workspaceId: string, opts?: LifecycleOptions): Promise<Operation>;
|
|
533
556
|
/**
|
|
@@ -583,6 +606,7 @@ export declare function fetchBillingCatalog(opts?: {
|
|
|
583
606
|
}): Promise<BillingCatalog>;
|
|
584
607
|
export declare class Shardflux {
|
|
585
608
|
#private;
|
|
609
|
+
readonly projects: ProjectsRetentionApi;
|
|
586
610
|
readonly workspaces: WorkspacesApi;
|
|
587
611
|
readonly billing: BillingApi;
|
|
588
612
|
/** Usage, allowances, estimates, grants/leases and spend (Phase 9). */
|
package/dist/client.js
CHANGED
|
@@ -14,6 +14,18 @@ import { AFTER_WAIT, HELD_RESUME, TRACE, runLifecycle, waitOptionsOf } from "./l
|
|
|
14
14
|
import { Trace, combineListeners, durabilityOf, isDurable, traced } from "./progress.js";
|
|
15
15
|
import { CaptureRegistry } from "./capture.js";
|
|
16
16
|
import { sendFeedback } from "./feedback.js";
|
|
17
|
+
export class ProjectsRetentionApi {
|
|
18
|
+
#ctx;
|
|
19
|
+
constructor(ctx) { this.#ctx = ctx; }
|
|
20
|
+
getRetention(projectId) {
|
|
21
|
+
const c = this.#ctx();
|
|
22
|
+
return c.http.json('GET', `/v1/projects/${encodeURIComponent(projectId)}/retention`, {}, c.authorization);
|
|
23
|
+
}
|
|
24
|
+
setRetention(projectId, policy) {
|
|
25
|
+
const c = this.#ctx();
|
|
26
|
+
return c.http.json('PUT', `/v1/projects/${encodeURIComponent(projectId)}/retention`, { json: policy }, c.authorization);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
17
29
|
/**
|
|
18
30
|
* The workspace a key names: the live row (deleted_at null) when there is one, since at most one live
|
|
19
31
|
* workspace holds a key; otherwise the newest tombstone (ended sessions leave tombstones with the same key, and a
|
|
@@ -63,6 +75,8 @@ function resizeBody(p) {
|
|
|
63
75
|
body.disk_gib = p.diskGib;
|
|
64
76
|
if (Object.keys(body).length === 0)
|
|
65
77
|
throw new TypeError('resize() needs at least one of memoryMib, memoryMibHeld, allocationMode, cpuMillis, diskGib');
|
|
78
|
+
if (p.atLeast !== undefined)
|
|
79
|
+
body.at_least = p.atLeast;
|
|
66
80
|
return body;
|
|
67
81
|
}
|
|
68
82
|
const RESIZE_KEYS = {
|
|
@@ -164,6 +178,8 @@ export class WorkspacesApi {
|
|
|
164
178
|
body.inputs = params.inputs;
|
|
165
179
|
if (params.labels !== undefined)
|
|
166
180
|
body.labels = params.labels;
|
|
181
|
+
if (params.retention !== undefined)
|
|
182
|
+
body.retention = params.retention;
|
|
167
183
|
if (params.idlePolicy !== undefined)
|
|
168
184
|
body.idle_policy = params.idlePolicy;
|
|
169
185
|
if (params.computerUse !== undefined)
|
|
@@ -399,6 +415,9 @@ export class WorkspacesApi {
|
|
|
399
415
|
async setComputerUse(workspaceId, enabled) {
|
|
400
416
|
return this.#http.json('PUT', `/v1/workspaces/${encodeURIComponent(workspaceId)}/computer-use`, { json: { enabled } }, this.#auth);
|
|
401
417
|
}
|
|
418
|
+
async setRetention(workspaceId, policy) {
|
|
419
|
+
return this.#wrap(await this.#http.json('PUT', `/v1/workspaces/${encodeURIComponent(workspaceId)}/retention`, { json: policy }, this.#auth));
|
|
420
|
+
}
|
|
402
421
|
async setIdlePolicy(workspaceId, idlePolicy) {
|
|
403
422
|
return this.#wrap(await this.#http.json('PUT', `/v1/workspaces/${encodeURIComponent(workspaceId)}/idle-policy`, { json: { idle_policy: idlePolicy } }, this.#auth));
|
|
404
423
|
}
|
|
@@ -474,7 +493,7 @@ export class WorkspacesApi {
|
|
|
474
493
|
if (kind === 'delete' || kind === 'reset')
|
|
475
494
|
this.#ctx().captures.discard(workspaceId, kind);
|
|
476
495
|
return operation;
|
|
477
|
-
}, opts, { settle: kind === 'suspend' || kind === 'snapshot' });
|
|
496
|
+
}, opts, { settle: kind === 'suspend' || kind === 'snapshot' || kind === 'upgrade' });
|
|
478
497
|
}
|
|
479
498
|
delete(workspaceId, opts = {}) {
|
|
480
499
|
return this.#op('delete', workspaceId, undefined, opts);
|
|
@@ -640,7 +659,7 @@ export class WorkspacesApi {
|
|
|
640
659
|
// Another lifecycle operation (a suspend, a resume, another resize) holds the workspace: the refused request
|
|
641
660
|
// changed nothing, so wait for that operation and send it again (bounded, within timeoutMs).
|
|
642
661
|
const active = e instanceof ShardfluxApiError && e.code === 'conflict' && e.reason === 'operation_in_progress' ? (e.operationId ?? e.details?.['active_operation_id']) : undefined;
|
|
643
|
-
if (typeof active !== 'string' || attempt >= 3 || Date.now() - started >= timeoutMs)
|
|
662
|
+
if (params.wait === false || typeof active !== 'string' || attempt >= 3 || Date.now() - started >= timeoutMs)
|
|
644
663
|
throw e;
|
|
645
664
|
trace.retry({ request: `PATCH ${path}`, attempt: attempt + 1, cause: `conflict operation_in_progress (waits for ${active})`, delayMs: 0 });
|
|
646
665
|
try {
|
|
@@ -658,6 +677,15 @@ export class WorkspacesApi {
|
|
|
658
677
|
if (!operation || typeof operation !== 'object')
|
|
659
678
|
throw new ShardfluxProtocolError('resize: 202 response has no operation', res.status, 'api');
|
|
660
679
|
trace.observe(operation);
|
|
680
|
+
if (params.wait === false) {
|
|
681
|
+
if (operation.state === 'failed' || operation.state === 'canceled')
|
|
682
|
+
throw new OperationFailedError(operation);
|
|
683
|
+
const workspace = res.body.workspace;
|
|
684
|
+
const result = resizeResultBodyOf(operation);
|
|
685
|
+
return { workspaceId, operationId: operation.id, state: workspace?.observed_state ?? operation.state,
|
|
686
|
+
memory: result.memory ?? null, cpu: result.cpu ?? null, disk: result.disk ?? null,
|
|
687
|
+
caps: workspace?.caps ?? null, operation };
|
|
688
|
+
}
|
|
661
689
|
let final = operation;
|
|
662
690
|
if (operation.state !== 'succeeded') {
|
|
663
691
|
if (TERMINAL.has(operation.state))
|
|
@@ -685,6 +713,17 @@ export class WorkspacesApi {
|
|
|
685
713
|
}, opts, { settle: true });
|
|
686
714
|
return { operation, workspace: workspace };
|
|
687
715
|
}
|
|
716
|
+
/**
|
|
717
|
+
* Resets a layered workspace to its template: every change in the workspace layer is wiped; key,
|
|
718
|
+
* id, template version, caps, secret bindings and volume attachments stay. Running: restarted on a blank layer
|
|
719
|
+
* (processes are gone; old tool tokens get 409 stale_epoch and the SDK refreshes them). Suspended: stays suspended and
|
|
720
|
+
* boots blank on the next resume. Returns the `reset` operation; its result names the recovery checkpoint (restorable
|
|
721
|
+
* for 7 days). Errors: 409 legacy_disk_layout, not_resettable, operation_in_progress.
|
|
722
|
+
*/
|
|
723
|
+
/** Opt-in cold start preserving the disk; next_resume schedules it without stopping the VM. */
|
|
724
|
+
upgrade(workspaceId, opts = {}) {
|
|
725
|
+
return this.#op('upgrade', workspaceId, { at: opts.at ?? 'now' }, opts);
|
|
726
|
+
}
|
|
688
727
|
reset(workspaceId, opts = {}) {
|
|
689
728
|
const body = { confirm_destructive: true };
|
|
690
729
|
return this.#op('reset', workspaceId, body, opts);
|
|
@@ -787,6 +826,7 @@ export async function fetchBillingCatalog(opts = {}) {
|
|
|
787
826
|
return http.json('GET', '/v1/billing/catalog');
|
|
788
827
|
}
|
|
789
828
|
export class Shardflux {
|
|
829
|
+
projects;
|
|
790
830
|
workspaces;
|
|
791
831
|
billing;
|
|
792
832
|
/** Usage, allowances, estimates, grants/leases and spend (Phase 9). */
|
|
@@ -813,6 +853,7 @@ export class Shardflux {
|
|
|
813
853
|
const f = opts.fetch ?? defaultFetch();
|
|
814
854
|
const userAgent = opts.userAgent ?? `shardflux-sdk-ts/${SDK_VERSION}`;
|
|
815
855
|
const sleep = opts.sleep ?? defaultSleep;
|
|
856
|
+
this.projects = new ProjectsRetentionApi(() => this.#ctx);
|
|
816
857
|
this.workspaces = new WorkspacesApi(() => this.#ctx);
|
|
817
858
|
this.billing = new BillingApi(() => this.#ctx);
|
|
818
859
|
this.usage = new UsageApi(() => this.#ctx);
|
|
@@ -821,7 +862,7 @@ export class Shardflux {
|
|
|
821
862
|
this.egress = new EgressPolicyApi(() => this.#ctx);
|
|
822
863
|
this.audit = new AuditApi(() => this.#ctx);
|
|
823
864
|
this.volumes = new VolumesApi(() => this.#ctx);
|
|
824
|
-
const baseUrl = opts.baseUrl ?? envVar('SHARDFLUX_API_URL') ?? 'https://api.shardflux.dev';
|
|
865
|
+
const baseUrl = opts.baseUrl ?? envVar('SHARDFLUX_API_URL') ?? envVar('SHARDFLUX_BASE_URL') ?? 'https://api.shardflux.dev';
|
|
825
866
|
this.#ctx = {
|
|
826
867
|
http: new HttpClient({ baseUrl, fetch: f, userAgent, timeoutMs: opts.timeoutMs ?? 30_000, maxRetries: opts.maxRetries ?? 2, source: 'api', sleep, onSuccess: versionCheckHook(opts.versionCheck, baseUrl, f, userAgent) }),
|
|
827
868
|
authorization: `Bearer ${apiKey}`,
|
package/dist/computer.d.ts
CHANGED
|
@@ -29,6 +29,10 @@ export interface ComputerActOptions {
|
|
|
29
29
|
signal?: AbortSignal;
|
|
30
30
|
}
|
|
31
31
|
export interface ComputerStreamOptions {
|
|
32
|
+
/** Mint a new link even when a cached link has more than 60 seconds left. */
|
|
33
|
+
fresh?: boolean;
|
|
34
|
+
/** Frame the viewer in these HTTPS origins with a partitioned browser session (§48). */
|
|
35
|
+
embed?: import('./ports.js').PortEmbedOptions;
|
|
32
36
|
/** Let the viewer use the mouse and keyboard (default false: view only, enforced in the workspace). */
|
|
33
37
|
interactive?: boolean;
|
|
34
38
|
/** How long the link works (60..604800 s; default 86400). */
|
|
@@ -111,6 +115,8 @@ export interface ComputerToolResult {
|
|
|
111
115
|
is_error?: true;
|
|
112
116
|
}
|
|
113
117
|
export interface ComputerToolsetOptions {
|
|
118
|
+
/** Called just before each action. Return false/a refusal string, or throw, to stop the turn. */
|
|
119
|
+
beforeAction?: BeforeComputerAction;
|
|
114
120
|
/** Screenshot format (default png). */
|
|
115
121
|
format?: 'png' | 'jpeg';
|
|
116
122
|
quality?: number;
|
|
@@ -134,10 +140,14 @@ export interface ComputerToolsetOptions {
|
|
|
134
140
|
*/
|
|
135
141
|
export declare function computerToolset(workspace: {
|
|
136
142
|
computer: WorkspaceComputer;
|
|
137
|
-
}): {
|
|
143
|
+
}, defaults?: ComputerToolsetOptions): {
|
|
138
144
|
definition: {
|
|
139
145
|
readonly type: "computer_toolset_20260801";
|
|
140
146
|
};
|
|
141
147
|
run(content: readonly unknown[], opts?: ComputerToolsetOptions): Promise<ComputerToolResult[]>;
|
|
142
148
|
};
|
|
149
|
+
export type BeforeComputerAction = (action: Readonly<ComputerAction>) => void | boolean | string | Promise<void | boolean | string>;
|
|
150
|
+
type ComputerBatchResult = Partial<ComputerActionsResult> & Pick<ComputerActionsResult, 'results'>;
|
|
151
|
+
/** No hook: one batch as before. With a hook, check immediately before each individual action runs. */
|
|
152
|
+
export declare function runComputerActions(act: (actions: ComputerAction[], opts: ComputerActOptions) => Promise<ComputerActionsResult>, actions: ComputerAction[], opts: ComputerActOptions, beforeAction?: BeforeComputerAction): Promise<ComputerBatchResult>;
|
|
143
153
|
export {};
|