@shardflux/sdk 0.13.1 → 0.15.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 +134 -0
- package/README.md +233 -13
- package/dist/account.d.ts +41 -2
- package/dist/account.js +36 -2
- package/dist/cell.d.ts +39 -0
- package/dist/cell.js +71 -0
- package/dist/client.d.ts +115 -6
- package/dist/client.js +205 -5
- package/dist/codex-proof.d.ts +53 -0
- package/dist/codex-proof.js +200 -0
- package/dist/computer.d.ts +143 -0
- package/dist/computer.js +146 -0
- package/dist/errors.d.ts +1 -1
- package/dist/generated/app-api.d.ts +2649 -540
- package/dist/generated/cell-api.d.ts +338 -0
- package/dist/http.d.ts +1 -1
- package/dist/http.js +1 -1
- package/dist/index.d.ts +14 -6
- package/dist/index.js +7 -2
- package/dist/ports.d.ts +117 -0
- package/dist/ports.js +62 -0
- package/dist/progress.d.ts +11 -7
- package/dist/progress.js +2 -0
- package/dist/templates.d.ts +12 -0
- package/dist/templates.js +9 -0
- package/dist/tools.d.ts +24 -3
- package/dist/tools.js +131 -22
- package/dist/workspace-ref.d.ts +83 -0
- package/dist/workspace-ref.js +149 -0
- package/dist/workspace.d.ts +48 -1
- package/dist/workspace.js +71 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,8 +3,72 @@
|
|
|
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.15.0 (not yet published)
|
|
7
|
+
|
|
8
|
+
### Workspaces by key
|
|
9
|
+
|
|
10
|
+
Additive (contracts §46). A workspace named by its key, created on its first use, with no lifecycle code:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { workspace } from '@shardflux/sdk';
|
|
14
|
+
const run = await workspace('acme/thread-42', { template: 'default' }).exec('python3 -c "print(40 + 2)"');
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- `cloud.workspace(key, params)` and module-level `workspace(key, params)` return a `WorkspaceRef` without a request.
|
|
18
|
+
Its first call opens the key (one held request); concurrent first calls share it, a failed open is retried by the
|
|
19
|
+
next call, and a workspace deleted under the ref is opened again by the call after the one that met the deletion.
|
|
20
|
+
- `WorkspaceRef`: `exec(command)` (a string runs through `bash -lc`, an argv array without a shell), `files`,
|
|
21
|
+
`executions`, `tools()` (definitions from the key's grants before any VM; the first tool call opens), `open()` (the
|
|
22
|
+
`Workspace`), `cell()`, `hint()` (starts the open), `key`, `created`, `mode`. `create: false` never creates (404
|
|
23
|
+
`not_found` for an unknown key).
|
|
24
|
+
- `new Shardflux()` reads `SHARDFLUX_API_KEY` and `SHARDFLUX_API_URL` when `apiKey` / `baseUrl` are not passed.
|
|
25
|
+
- `workspace.created`: whether the `open()` that returned it created the workspace (the API's new `created` field).
|
|
26
|
+
- `template: 'default'` opens the platform default template (`python-node-browser`) on any client version.
|
|
27
|
+
- `workspaceTools()` takes a `ToolTarget`: a `Workspace` or a `WorkspaceRef`.
|
|
28
|
+
|
|
29
|
+
### Computer use
|
|
30
|
+
|
|
31
|
+
Additive. A desktop in the workspace that agents drive with screenshots, clicks and keystrokes (contracts §45).
|
|
32
|
+
|
|
33
|
+
- `workspace.computer`: `act(actions, { screenshot, settleMs, format, quality })` runs a batch of Claude's computer
|
|
34
|
+
toolset actions in order (the first failure stops it; the rest come back `skipped`), `screenshot()`, `status()`,
|
|
35
|
+
`start({ width, height })`, `stop()`, `stream({ interactive, ttlSeconds })` (a signed link to the viewer; it exposes
|
|
36
|
+
port 61002, or 61003 when interactive) and `stopStream()`.
|
|
37
|
+
- `computerToolset(workspace)` / `COMPUTER_TOOLSET`: answers Claude's `computer_toolset_20260801` calls of a model turn
|
|
38
|
+
with one batch; every `tool_result` echoes `toolset_name: "computer"`.
|
|
39
|
+
- `workspaceTools()` adds, when the token grants `computer`, the `computer` tool (one action per call) and
|
|
40
|
+
`computer_batch` (up to 50 actions in one call, with each action's result, a zoom's image and one screenshot after
|
|
41
|
+
them). Both take `screenshot: false`, `settle_ms`, and `format: "jpeg"` with `quality`.
|
|
42
|
+
- `open({ computerUse })`, `workspace.setComputerUse(true | false | null)`, `workspace.computerUse`,
|
|
43
|
+
`cloud.workspaces.setComputerUse(id, enabled)`. Tool tokens carry `computer` while the switch is on; a computer call
|
|
44
|
+
whose cached token predates the switch is sent again with a fresh token.
|
|
45
|
+
- `cloud.templates.setComputerUse(slug, enabled, { organizationId })` switches it for every workspace of one of your
|
|
46
|
+
templates. `ToolName` includes `computer`; template views carry `computer_use`.
|
|
47
|
+
|
|
48
|
+
## 0.14.0 — 2026-10-04 (not yet published)
|
|
49
|
+
|
|
50
|
+
- 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
|
+
|
|
6
52
|
## 0.14.0 (not yet published)
|
|
7
53
|
|
|
54
|
+
### Sign up from an agent
|
|
55
|
+
|
|
56
|
+
Additive (API contracts §44).
|
|
57
|
+
|
|
58
|
+
- `ShardfluxAccount.signup({ email?, displayName?, codexIdToken? })` (POST /v1/auth/signup): a working account and
|
|
59
|
+
its session in one call, with no browser and no email round-trip. Returns `{ account, result }`; `result.access`
|
|
60
|
+
is `verified`, `provisional` (works at once on the Free plan; the trial starts when the email is verified) or
|
|
61
|
+
`verification_required`, and `result.created` is false when a Codex proof signed in to an existing account.
|
|
62
|
+
- `codexIdentityProof(opts?)` (Node): the OpenAI ID token of the Codex CLI signed in on this machine, refreshed by
|
|
63
|
+
Codex itself (`codex app-server`, `account/read {refreshToken: true}`) and read from `$CODEX_HOME/auth.json`. Only
|
|
64
|
+
the ID token is returned. Never throws: `{ ok: false, reason: 'codex_not_found' | 'not_signed_in' |
|
|
65
|
+
'api_key_login' | 'stale', message }`.
|
|
66
|
+
- `account.auth.requestEmailCode()` and `account.auth.verifyEmailCode(code)` (POST /v1/auth/verify-email/code):
|
|
67
|
+
verify with a 6-digit emailed code; nothing is signed out.
|
|
68
|
+
- `account.auth.stepUp({ codexIdToken })`: a step-up with the linked Codex login instead of the password.
|
|
69
|
+
- New types `SignupResult`, `SignupAccess`, `EmailCodeResult`, `CodexIdentityProof`, `CodexProofOptions`,
|
|
70
|
+
`CodexProofUnavailable`.
|
|
71
|
+
|
|
8
72
|
### Workspaces working at once
|
|
9
73
|
|
|
10
74
|
Additive. The plan's workspace number caps workspaces working at the same moment; stored workspaces are bounded by
|
|
@@ -25,6 +89,76 @@ Retained state.
|
|
|
25
89
|
- Regenerated contract types: `ErrorCode` includes `quota_exceeded` for the workspace gateway; allowance
|
|
26
90
|
`enforcement` adds `storage_block`, `cap_state` adds `storage_blocked`.
|
|
27
91
|
|
|
92
|
+
### Inbound ports
|
|
93
|
+
|
|
94
|
+
Additive. A TCP port of a processful workspace can be served at its own private HTTPS URL,
|
|
95
|
+
`https://<port>-<handle>.<domain>`; a request wakes a parked or suspended workspace and is answered once it runs.
|
|
96
|
+
|
|
97
|
+
- `workspace.ports` and `cloud.workspaces.ports(id)` (class `WorkspacePorts`): `expose(port)` →
|
|
98
|
+
`{port, url, createdAt, callback, created}` (`ExposePortResult`; idempotent, `created` false when it already was),
|
|
99
|
+
`list()` → `ExposedPort[]`, `close(port)`, `token(port, {ttlSeconds?})` → `{token, expiresAt, url}` (`PortToken`),
|
|
100
|
+
`link(port, {ttlSeconds?, path?})` → `{url, expiresAt}` (`PortLink`), `createCallbackUrl(port)` →
|
|
101
|
+
`{url, createdAt}` (`PortCallbackUrl`) and `revokeCallbackUrl(port)`. `expose`, `close` and `revokeCallbackUrl` are
|
|
102
|
+
retried on transient failures; minting calls are not.
|
|
103
|
+
- Types `ExposedPort`, `ExposePortResult`, `PortToken`, `PortLink`, `PortCallbackUrl`, `PortTokenOptions`,
|
|
104
|
+
`PortLinkOptions` and the raw API shapes `PortView`, `PortTokenResponse`, `PortLinkResponse`, `PortCallbackResponse`
|
|
105
|
+
(regenerated contract types).
|
|
106
|
+
- `KnownErrorReason` adds `inbound_ports_not_available`, `port_not_exposed` and `port_limit`.
|
|
107
|
+
|
|
108
|
+
### Memory across platform upgrades
|
|
109
|
+
|
|
110
|
+
Additive. A resume keeps the workspace's memory and running processes across host kernel, CPU generation and
|
|
111
|
+
Firecracker upgrades of the platform.
|
|
112
|
+
|
|
113
|
+
- `ColdBootReason` gains `runtime_retired`, and `COLD_BOOT_REASONS` lists every reason this version knows
|
|
114
|
+
(`runtime_changed`, `host_lost`, `runtime_retired`). `runtime_retired`: the runtime the workspace was suspended on
|
|
115
|
+
was retired after an announced window; the resume booted the saved disk (`resumePath` `cold_boot`,
|
|
116
|
+
`memoryRestored` false). A reason this version does not know is still passed through as a string.
|
|
117
|
+
|
|
118
|
+
### Resize a workspace
|
|
119
|
+
|
|
120
|
+
Additive. Change the memory, CPU and disk of any workspace, running or suspended, fixed or elastic, without a restart
|
|
121
|
+
or a fork.
|
|
122
|
+
|
|
123
|
+
- `workspace.resize({ memoryMib, memoryMibHeld, allocationMode, cpuMillis, diskGib })` and
|
|
124
|
+
`cloud.workspaces.resize(id, params)` send `PATCH /v1/workspaces/{id}/caps` with the given caps (snake_case, at
|
|
125
|
+
least one; none throws `TypeError` before any request), an `Idempotency-Key` (`idempotencyKey`, default a fresh key
|
|
126
|
+
per call, so a retried request replays) and `Prefer: wait` (up to 20 s, within `timeoutMs`). They resolve once the
|
|
127
|
+
resize has finished: a resize the server answers with `202` is waited for like any operation (`timeoutMs`, default
|
|
128
|
+
300 000 ms; `signal`; `onProgress`, action `resize`). A resize that meets another lifecycle operation (409
|
|
129
|
+
`operation_in_progress`) waits for it and is sent again with the same `Idempotency-Key`, within `timeoutMs`. A failed
|
|
130
|
+
resize is its error: `ShardfluxApiError` 409 with `operationId` (reason `resize_failed`, ...) when the server held
|
|
131
|
+
the request until then, `OperationFailedError` when the SDK waited for the operation.
|
|
132
|
+
- `ResizeResult`: `workspaceId`, `operationId`, `state`, `caps` (the stored caps every later start uses),
|
|
133
|
+
`operation` (the finished operation after a `202`, else null) and, for each resource the request named,
|
|
134
|
+
`memory` / `cpu` / `disk` (`ResizeMemory`, `ResizeCpu`, `ResizeDisk`): `applies_at` (`now`, `resume` or
|
|
135
|
+
`next_start`), `requested_*`, `target_*`, `previous_*`, `applied_*`, `limit_reason` (`plan`, `template`,
|
|
136
|
+
`host_capacity`, `shrink_stalled`) and `reason` (`suspended`, `stopped`, `boot_vcpus`, `region`, `below_base`,
|
|
137
|
+
`legacy_layout`); memory adds `allocation_mode`, `held_mib` and `converged`. Types `ResizeParams`,
|
|
138
|
+
`ResizeAppliesAt`, `ResizeLimitReason` and `ResizeDeferReason` are exported.
|
|
139
|
+
- The handle refreshes its view after a resize. A file-first workspace is refused locally with
|
|
140
|
+
`NotSupportedForModeError`.
|
|
141
|
+
- A finished operation's `result` (the cell's) is returned in the held answer's shape: each named resource with its
|
|
142
|
+
documented keys, a key left out taken from the operation's input or null, other keys dropped.
|
|
143
|
+
- `KnownErrorReason` adds `resize_not_available` (409 `conflict`), `resize_failed` (409 `conflict`, a failed resize)
|
|
144
|
+
and `shrink_not_supported` (422 `validation_failed`, `details.current_disk_gib`). `LifecycleAction` adds `resize`.
|
|
145
|
+
|
|
146
|
+
### Memory hint for commands
|
|
147
|
+
|
|
148
|
+
Additive. `RunOptions.resourceHint` (`'auto' | 'light' | 'heavy'`, type `ResourceHint`) on `exec.run()`, and
|
|
149
|
+
`resource_hint` on `exec.start()` requests: `heavy` gives an elastic workspace its memory before the command starts,
|
|
150
|
+
`light` starts it at once, `auto` (the default) decides from the command. Sent only when set, so other requests are
|
|
151
|
+
unchanged. The processful `exec` agent tool takes `resource_hint` with the same values.
|
|
152
|
+
|
|
153
|
+
### Elastic by default
|
|
154
|
+
|
|
155
|
+
With elastic-by-default enabled for the organization, an open of a new processful workspace without
|
|
156
|
+
`caps.allocation_mode` is elastic: it holds the memory its commands use and grows up to `memory_mib` (when not given,
|
|
157
|
+
the plan's largest workspace, bounded by the template); billing counts the memory it holds. `allocation_mode: 'fixed'`
|
|
158
|
+
gives a static allocation of `memory_mib`. Reopening an existing workspace with caps that omit `allocation_mode` then
|
|
159
|
+
keeps its stored mode; a fork without caps inherits its source's. The SDK's requests do not change;
|
|
160
|
+
`workspace.memory` and `workspace.allocationMode` report the mode the workspace got.
|
|
161
|
+
|
|
28
162
|
## 0.13.1 (not yet published)
|
|
29
163
|
|
|
30
164
|
Patch, additive: the transport, a documented value, a fix for tool calls across a move and self-healing workspaces.
|
package/README.md
CHANGED
|
@@ -10,8 +10,9 @@ tool calls into the workspace ([tool-call capture](#tool-call-capture-070)).
|
|
|
10
10
|
> **Compatibility.** The API is versioned (`/v1`). Breaking changes ship only in minor releases and are marked
|
|
11
11
|
> **Breaking** in the changelog (see [Compatibility](#compatibility)).
|
|
12
12
|
|
|
13
|
-
> **Versions.** This README describes 0.
|
|
14
|
-
> **(0.
|
|
13
|
+
> **Versions.** This README describes 0.15.0. Anything marked **(0.15.0+)** is not in 0.14.x, **(0.14.0+)** not in 0.13.x,
|
|
14
|
+
> **(0.13.0+)** not in 0.12.x, **(0.12.0+)** not in 0.11.x, **(0.11.0+)** not in 0.10.x, **(0.10.0+)** not in 0.9.0, **(0.9.0+)** not in 0.8.x,
|
|
15
|
+
> **(0.8.0+)** not in 0.7.x, **(0.7.0+)** not in 0.6.x and **(0.6.0+)** not in 0.5.0; [CHANGELOG.md](./CHANGELOG.md) lists what each version added. Check yours with
|
|
15
16
|
> `npm ls @shardflux/sdk` or the exported `SDK_VERSION`.
|
|
16
17
|
|
|
17
18
|
- ESM only, Node.js 24 or later. Reading a YAML template file uses the optional peer
|
|
@@ -41,6 +42,33 @@ npm install @shardflux/sdk
|
|
|
41
42
|
Create a project API key in the Shardflux console (`sfk_<key id>_<secret>`) and keep it on the
|
|
42
43
|
server. API keys are server credentials: never put one in a browser bundle.
|
|
43
44
|
|
|
45
|
+
```ts
|
|
46
|
+
import { workspace } from '@shardflux/sdk';
|
|
47
|
+
|
|
48
|
+
const ws = workspace('customer-42/main', { template: 'default' }); // reads SHARDFLUX_API_KEY; no request yet
|
|
49
|
+
|
|
50
|
+
// 1. Run a command: the first call creates the workspace. Check that it worked.
|
|
51
|
+
const run = await ws.exec('python3 -c "print(40 + 2)"');
|
|
52
|
+
if (run.exitCode !== 0) throw new Error(`python3 exited ${run.exitCode}: ${run.stderr}`);
|
|
53
|
+
console.log(run.stdout.trim()); // 42
|
|
54
|
+
|
|
55
|
+
// 2. Write a file, then suspend the workspace and wait until the suspend has finished.
|
|
56
|
+
await ws.files.write('/home/user/notes.txt', 'hello from the SDK\n');
|
|
57
|
+
await (await ws.open()).suspend({ wait: true }); // resolves once suspended
|
|
58
|
+
|
|
59
|
+
// 3. The same key again: the workspace wakes, and the file is still there.
|
|
60
|
+
const again = workspace('customer-42/main', { template: 'default' });
|
|
61
|
+
console.log(await again.files.readText('/home/user/notes.txt')); // hello from the SDK
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The key names the workspace **(0.15.0+)**: the first call creates it, later calls (from any process) reuse it, and it
|
|
65
|
+
suspends when idle and wakes on the next call with its files, packages and processes. There is no create call and no
|
|
66
|
+
lifecycle code to write. See [Workspaces by key](#workspaces-by-key-0150).
|
|
67
|
+
|
|
68
|
+
### Open, suspend and resume
|
|
69
|
+
|
|
70
|
+
The same workspace with every step explicit:
|
|
71
|
+
|
|
44
72
|
```ts
|
|
45
73
|
import { Shardflux, formatTiming } from '@shardflux/sdk';
|
|
46
74
|
|
|
@@ -70,6 +98,48 @@ installed packages and running processes are still there. The same program is in
|
|
|
70
98
|
With 0.5.0, wait for the suspend by its operation instead:
|
|
71
99
|
`await cloud.workspaces.waitForOperation((await workspace.suspend()).id)`.
|
|
72
100
|
|
|
101
|
+
## Workspaces by key (0.15.0+)
|
|
102
|
+
|
|
103
|
+
`workspace(key, params)` (or `cloud.workspace(key, params)` on your own client) names a workspace by the identifier your
|
|
104
|
+
application already has: a user, a thread, a repository, a customer. It makes no request. Its first call opens the key:
|
|
105
|
+
the workspace is created from `params.template` on first use and resumed afterwards, in one request that also brings
|
|
106
|
+
back its tool token. Later calls go straight to the workspace.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { workspace } from '@shardflux/sdk';
|
|
110
|
+
|
|
111
|
+
const ws = workspace(`acme/${threadId}`, { template: 'default' });
|
|
112
|
+
|
|
113
|
+
await ws.exec('pip install requests && python3 app.py'); // a string runs through bash -lc
|
|
114
|
+
await ws.exec(['python3', '-c', 'print(42)']); // an argv array runs without a shell
|
|
115
|
+
await ws.files.write('/home/user/notes.txt', 'hello\n');
|
|
116
|
+
console.log(await ws.files.readText('/home/user/notes.txt'));
|
|
117
|
+
|
|
118
|
+
ws.created; // true when this ref's open created the workspace
|
|
119
|
+
const full = await ws.open(); // the Workspace: suspend, fork, ports, computer, capture
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
- `params` are `open()`'s without the key: `template` is required (`'default'` is the platform default template,
|
|
123
|
+
`python-node-browser`), plus `secrets`, `inputs`, `caps`, `labels`, `idlePolicy`, `lifetime`, `computerUse`,
|
|
124
|
+
`agentLabel`, `tools`.
|
|
125
|
+
- Calls made while the first open runs share it. A failed open is not kept: the next call opens again.
|
|
126
|
+
- `create: false` never creates: the first call finds the key's workspace and fails with 404 `not_found` when it has
|
|
127
|
+
none.
|
|
128
|
+
- A workspace deleted under the ref (`isWorkspaceGone(err)`, 409 `workspace_deleted`) fails the call that meets it;
|
|
129
|
+
the next call opens the key again, which creates a new workspace.
|
|
130
|
+
- `ws.hint()` starts the open early, for example when your model starts a tool call.
|
|
131
|
+
|
|
132
|
+
**In an agent.** The tools exist before the workspace does; the model's first tool call creates it:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import { toAnthropicTools, executeToolCall, workspace } from '@shardflux/sdk';
|
|
136
|
+
|
|
137
|
+
const tools = await workspace(`acme/${threadId}`, { template: 'default' }).tools(); // reads the key's grants
|
|
138
|
+
const anthropicTools = toAnthropicTools(tools);
|
|
139
|
+
// for each tool_use block the model returns:
|
|
140
|
+
const output = await executeToolCall(tools, block);
|
|
141
|
+
```
|
|
142
|
+
|
|
73
143
|
## Configuration
|
|
74
144
|
|
|
75
145
|
```ts
|
|
@@ -82,8 +152,9 @@ const cloud = new Shardflux({
|
|
|
82
152
|
});
|
|
83
153
|
```
|
|
84
154
|
|
|
85
|
-
|
|
86
|
-
|
|
155
|
+
`new Shardflux()` with no `apiKey` reads `SHARDFLUX_API_KEY`, and with no `baseUrl` reads `SHARDFLUX_API_URL`
|
|
156
|
+
**(0.15.0+)**; options you pass win. A missing key throws when the client is created. The module-level `workspace()`
|
|
157
|
+
uses such a client, created on its first call.
|
|
87
158
|
|
|
88
159
|
## Commands and files
|
|
89
160
|
|
|
@@ -360,10 +431,11 @@ lookups by key.
|
|
|
360
431
|
|
|
361
432
|
### Elastic memory (0.13.0+)
|
|
362
433
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
434
|
+
A workspace can hold only the memory its commands use and grow up to `memory_mib` when they need more; billing counts
|
|
435
|
+
the memory it holds. An elastic workspace idles at its held floor (`memory_mib_held`, default 1024) and shrinks back
|
|
436
|
+
after 30 s without work. Package installs, test runners, compilers, type checkers and bundlers get their memory before
|
|
437
|
+
they start, so they size their heaps and workers from what they will actually get; everything else starts at once and
|
|
438
|
+
the workspace grows while it runs.
|
|
367
439
|
|
|
368
440
|
```ts
|
|
369
441
|
const ws = await cloud.workspaces.open({
|
|
@@ -375,10 +447,32 @@ ws.memory; // { allocation_mode: 'elastic', promised_mib: 8192, held_m
|
|
|
375
447
|
|
|
376
448
|
const r = await ws.cell().exec.run(['npm', 'test']);
|
|
377
449
|
r.memoryGrow; // { outcome: 'delivered', from_mib: 1024, want_mib: 4096, got_mib: 4096, deliver_ms: 175 }
|
|
450
|
+
|
|
451
|
+
await ws.cell().exec.run(['python3', 'train.py'], { resourceHint: 'heavy' }); // 0.14.0+: memory first, then the command
|
|
378
452
|
```
|
|
379
453
|
|
|
380
|
-
|
|
381
|
-
|
|
454
|
+
`resourceHint` (0.14.0+) is `'auto'` (the default: decided from the command), `'heavy'` or `'light'`; the exec agent
|
|
455
|
+
tool takes `resource_hint`. `allocation_mode: 'fixed'` gives a static allocation of `memory_mib`. Send
|
|
456
|
+
`allocation_mode` with every `caps` you pass to keep the mode you chose; an open without `caps` keeps the stored
|
|
457
|
+
layout, and a fork without `caps` inherits its source's. The exec agent tool adds `memory_grow` to its result when the
|
|
458
|
+
command's start grew the workspace.
|
|
459
|
+
|
|
460
|
+
### Resize a workspace (0.14.0+)
|
|
461
|
+
|
|
462
|
+
Change the memory, CPU and disk of any workspace, running or suspended, fixed or elastic, without a restart:
|
|
463
|
+
|
|
464
|
+
```ts
|
|
465
|
+
const r = await ws.resize({ memoryMib: 6144, diskGib: 20 });
|
|
466
|
+
r.memory; // { applies_at: 'now', previous_mib: 2048, applied_mib: 6144, converged: true, ... }
|
|
467
|
+
r.disk; // { applies_at: 'now', previous_gib: 10, applied_gib: 20, ... }
|
|
468
|
+
|
|
469
|
+
await ws.resize({ allocationMode: 'elastic', memoryMibHeld: 1024 }); // switch modes live
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
Memory changes live, and disks grow online. A suspended workspace gets its new size when it resumes, before its first
|
|
473
|
+
call. CPU changes live within the vCPUs the workspace booted with; a larger CPU size applies at the next start. Each
|
|
474
|
+
resource says when it applies (`applies_at`: `now`, `resume` or `next_start`), and the new caps are stored, so every
|
|
475
|
+
later start uses them.
|
|
382
476
|
|
|
383
477
|
### Burst execution (0.13.0+)
|
|
384
478
|
|
|
@@ -398,6 +492,83 @@ workspace's own processes resume where they were. A burst is not signalled or ca
|
|
|
398
492
|
fails (`burst_apply_failed`), retry `exec.run` with the same `sessionId` to finish the apply. The agent tools offer
|
|
399
493
|
bursts when asked: `workspaceTools(ws, { burst: true })`.
|
|
400
494
|
|
|
495
|
+
### Inbound ports (0.14.0+)
|
|
496
|
+
|
|
497
|
+
Serve a port of the workspace at its own HTTPS URL: a preview of the app your agent is building, a webhook receiver,
|
|
498
|
+
an OAuth redirect, a live view. A request wakes a parked or suspended workspace and is answered once it runs, so the
|
|
499
|
+
workspace can sleep between visits. Every port is private: a request carries a port token, a signed link's browser
|
|
500
|
+
session, or arrives on the port's callback URL.
|
|
501
|
+
|
|
502
|
+
```ts
|
|
503
|
+
await ws.cell().exec.start({ argv: ['python3', '-m', 'http.server', '3000', '--bind', '0.0.0.0'] });
|
|
504
|
+
const { url } = await ws.ports.expose(3000); // https://3000-<handle>.shardflux.app
|
|
505
|
+
|
|
506
|
+
const { token } = await ws.ports.token(3000); // { token: 'sfp_…', expiresAt, url }, 1 h by default
|
|
507
|
+
await fetch(url, { headers: { authorization: `Bearer ${token}` } });
|
|
508
|
+
|
|
509
|
+
const link = await ws.ports.link(3000, { path: '/dashboard' }); // open link.url in a browser (24 h by default)
|
|
510
|
+
const hook = await ws.ports.createCallbackUrl(3000); // register `${hook.url}github` as a webhook URL
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
- The server inside the workspace listens on `0.0.0.0` (all interfaces), not only `127.0.0.1`.
|
|
514
|
+
- `expose(port)` is idempotent: `created` is true when this call exposed the port; an exposed port keeps its URL, tokens,
|
|
515
|
+
links and callback URL. `list()` returns `{ port, url, createdAt, callback }` for each exposed port.
|
|
516
|
+
- `token(port, { ttlSeconds })` (60-86400): send it as `Authorization: Bearer <token>`, or as `X-Shardflux-Token`
|
|
517
|
+
when the app reads `Authorization` itself. `link(port, { ttlSeconds, path })` (60-604800 s): anyone with the link can
|
|
518
|
+
open the port until it expires, so share it like a password.
|
|
519
|
+
- `createCallbackUrl(port)`: a URL with a secret in its path for GitHub, Slack, Stripe or an OAuth provider; a request
|
|
520
|
+
to `<url><rest>` reaches `/<rest>` without a token header. Its secret is shown only once; calling it again replaces
|
|
521
|
+
it, `revokeCallbackUrl(port)` revokes it.
|
|
522
|
+
- `close(port)` stops the port's tokens, links and callback URL at once (also when the port is exposed again).
|
|
523
|
+
- By id, without reading the workspace: `cloud.workspaces.ports(id)`.
|
|
524
|
+
|
|
525
|
+
### Computer use (0.15.0+)
|
|
526
|
+
|
|
527
|
+
A desktop in the workspace for agents that operate GUI software: a screen, a mouse and a keyboard. Switch it on for a
|
|
528
|
+
workspace (or for every workspace of your template); the platform starts the desktop on the first call that needs it.
|
|
529
|
+
|
|
530
|
+
```ts
|
|
531
|
+
const ws = await cloud.workspaces.open({ key: 'agent/42', template: 'python-node-browser', computerUse: true });
|
|
532
|
+
|
|
533
|
+
const shot = await ws.computer.screenshot(); // { format: 'png', width: 1280, height: 800, data }
|
|
534
|
+
await ws.computer.act([
|
|
535
|
+
{ action: 'left_click', coordinate: [640, 400] },
|
|
536
|
+
{ action: 'type', text: 'hello' },
|
|
537
|
+
{ action: 'key', text: 'Return' },
|
|
538
|
+
], { screenshot: true }); // one round trip for the whole batch and a look
|
|
539
|
+
|
|
540
|
+
const { url } = await ws.computer.stream(); // a private link to watch the screen live
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
With Claude, answer the computer toolset directly:
|
|
544
|
+
|
|
545
|
+
```ts
|
|
546
|
+
import { computerToolset } from '@shardflux/sdk';
|
|
547
|
+
|
|
548
|
+
const computer = computerToolset(ws);
|
|
549
|
+
const msg = await anthropic.messages.create({ model: 'claude-opus-5-5', max_tokens: 16000, tools: [computer.definition], messages });
|
|
550
|
+
messages.push({ role: 'assistant', content: msg.content });
|
|
551
|
+
messages.push({ role: 'user', content: await computer.run(msg.content) });
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
- The actions are Claude's computer toolset members with their parameters: `screenshot`, `zoom`, `left_click`,
|
|
555
|
+
`right_click`, `middle_click`, `double_click`, `triple_click`, `left_click_drag`, `mouse_move`, `left_mouse_down`,
|
|
556
|
+
`left_mouse_up`, `cursor_position`, `scroll`, `type`, `key`, `hold_key`, `wait`. A batch runs in order and stops at
|
|
557
|
+
the first failure; later actions come back `skipped`.
|
|
558
|
+
- `computer.run(content)` sends every computer call of a model turn as one batch and returns one `tool_result` per call
|
|
559
|
+
(each with `toolset_name: "computer"`), with a screenshot on the last one when the turn did not end with a look.
|
|
560
|
+
- `workspaceTools()` includes, for any model while the workspace's computer use is on, a `computer` tool (one action
|
|
561
|
+
per call, answered with a screenshot) and `computer_batch` (several actions in one call, with each action's result
|
|
562
|
+
and one screenshot after them). Both take `screenshot: false` to skip the image, `settle_ms`, and `format: "jpeg"`
|
|
563
|
+
with `quality` for smaller images.
|
|
564
|
+
- `act(actions, { screenshot, settleMs, format, quality })` takes the same options.
|
|
565
|
+
- Commands the agent runs see the desktop: `exec` of `chromium https://example.com &` opens the browser on it.
|
|
566
|
+
- `stream({ interactive })` returns a signed link (view only by default; `interactive: true` lets the viewer use the
|
|
567
|
+
mouse and keyboard); `stopStream()` ends every view. Streaming uses inbound ports.
|
|
568
|
+
- `ws.setComputerUse(true | false | null)` sets the workspace's own switch (null follows the template);
|
|
569
|
+
`ws.computerUse` reads `{ enabled, workspace, template, available }`. `status()`, `start({ width, height })` and
|
|
570
|
+
`stop()` manage the desktop directly.
|
|
571
|
+
|
|
401
572
|
### Timing and progress (0.6.0+)
|
|
402
573
|
|
|
403
574
|
Every open, wake and waited lifecycle call is traced. `workspace.lastTiming` (and `err.timing` when the call fails)
|
|
@@ -657,7 +828,9 @@ it: add paths, never remove them. A build reports the list its version declares
|
|
|
657
828
|
## Agent tools
|
|
658
829
|
|
|
659
830
|
`workspaceTools(workspace)` returns tools with a name, a description, a JSON Schema for the
|
|
660
|
-
parameters and an `execute` function.
|
|
831
|
+
parameters and an `execute` function. It takes a `Workspace` or **(0.15.0+)** a `WorkspaceRef`, whose
|
|
832
|
+
`await ref.tools(opts)` builds them from the API key's grants before the workspace exists (see
|
|
833
|
+
[Workspaces by key](#workspaces-by-key-0150)). Export them for your model provider and dispatch its tool
|
|
661
834
|
calls:
|
|
662
835
|
|
|
663
836
|
```ts
|
|
@@ -682,7 +855,8 @@ Your agent loop and model calls stay in your application; the workspace is the c
|
|
|
682
855
|
act on.
|
|
683
856
|
|
|
684
857
|
The tools are `exec`, `read_file`, `write_file`, `list_files`, `search_files` and `edit_file` (0.9.0+), the process,
|
|
685
|
-
terminal, git and browser tools,
|
|
858
|
+
terminal, git and browser tools, and `computer` (0.15.0+, while the workspace's computer use is on), filtered by the
|
|
859
|
+
tools your key grants. `edit_file` replaces exact text; when the
|
|
686
860
|
model passes no `expected_revision` it reads the file's revision first, so a change made in between fails the edit
|
|
687
861
|
instead of being overwritten. Each call first sends `workspace.hint()` without waiting for it (`hint: false` turns
|
|
688
862
|
that off, e.g. when you send the hint yourself as the model starts a tool call), except `read_file`, `list_files` and
|
|
@@ -849,7 +1023,7 @@ await workspace.cell().exec.run(['python3', 'agent.py']); // sees $OPEN
|
|
|
849
1023
|
|
|
850
1024
|
`ShardfluxAccount` does what a person does in the web app, with a user session instead of an API key: sign up, sign
|
|
851
1025
|
in (with MFA), organizations, projects, API keys, members, invitations, billing, audit, data export and account
|
|
852
|
-
deletion.
|
|
1026
|
+
deletion. Paying in Stripe Checkout is the step a person does.
|
|
853
1027
|
|
|
854
1028
|
```ts
|
|
855
1029
|
import { Shardflux, ShardfluxAccount } from '@shardflux/sdk';
|
|
@@ -887,6 +1061,37 @@ const cloud = new Shardflux({ apiKey: secret });
|
|
|
887
1061
|
- `parseEmailToken(input)` returns the `token` of a link's `#token=` fragment or `?token=` query (else the trimmed
|
|
888
1062
|
input), and throws for a link without one.
|
|
889
1063
|
|
|
1064
|
+
### Sign up from an agent (0.14.0+)
|
|
1065
|
+
|
|
1066
|
+
One call gives a working account, with no browser and no email round-trip. With the Codex CLI signed in with ChatGPT
|
|
1067
|
+
on the machine, the account is verified by that login and starts with its trial:
|
|
1068
|
+
|
|
1069
|
+
```ts
|
|
1070
|
+
import { ShardfluxAccount, codexIdentityProof } from '@shardflux/sdk';
|
|
1071
|
+
|
|
1072
|
+
const proof = await codexIdentityProof(); // asks Codex to refresh its login, returns the ID token
|
|
1073
|
+
const { account, result } = proof.ok
|
|
1074
|
+
? await ShardfluxAccount.signup({ codexIdToken: proof.idToken, onSessionToken: (s) => save(s.token) })
|
|
1075
|
+
: await ShardfluxAccount.signup({ email: 'ada@example.com', onSessionToken: (s) => save(s.token) });
|
|
1076
|
+
// result.access: 'verified' | 'provisional' | 'verification_required'; result.created: false when it signed in
|
|
1077
|
+
|
|
1078
|
+
if (result.access !== 'verified') {
|
|
1079
|
+
await account.auth.requestEmailCode(); // a 6-digit code to the inbox (15 minutes)
|
|
1080
|
+
await account.auth.verifyEmailCode('123456'); // verified; the session and keys keep working
|
|
1081
|
+
}
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
- `codexIdentityProof()` (Node) reads only the ID token from `$CODEX_HOME/auth.json` (default `~/.codex`), after
|
|
1085
|
+
`codex app-server` refreshes it; it never throws: `{ ok: false, reason }` is `codex_not_found`, `not_signed_in`,
|
|
1086
|
+
`api_key_login` or `stale`. The token proves the ChatGPT account's verified email and is not a credential to
|
|
1087
|
+
OpenAI.
|
|
1088
|
+
- With a Codex proof, `signup` signs in to the account of that ChatGPT identity or email when there is one, else
|
|
1089
|
+
creates it. `result.status` `mfa_required`: `account.auth.completeMfa({ code })`.
|
|
1090
|
+
- With an email only, a `provisional` account works at once on the Free plan; verifying the email starts the trial.
|
|
1091
|
+
An address that has an account is 409 `conflict` (`details.reason` `email_registered`).
|
|
1092
|
+
- `account.auth.stepUp({ codexIdToken })` confirms a sensitive action with the linked Codex login instead of a
|
|
1093
|
+
password.
|
|
1094
|
+
|
|
890
1095
|
Upgrading a plan: a person pays at the Checkout `url`; the code waits for the subscription.
|
|
891
1096
|
|
|
892
1097
|
```ts
|
|
@@ -966,6 +1171,11 @@ of that base), or `invalid_path` (`details.field` `recipe.immutable[<i>]`). A bu
|
|
|
966
1171
|
`failure.code` `immutable_path_missing` (`failure.details.path` is not a directory in the built filesystem) or
|
|
967
1172
|
`immutable_image_too_large`.
|
|
968
1173
|
|
|
1174
|
+
Inbound ports **(0.14.0+)**: 403 `forbidden` with `reason` `inbound_ports_not_available` (inbound ports are not enabled
|
|
1175
|
+
for the organization), 404 `not_found` with `reason` `port_not_exposed` (a token, link or callback URL of a port that is
|
|
1176
|
+
not exposed: expose it first; `details.port`), 409 `conflict` with `reason` `port_limit` (close a port first;
|
|
1177
|
+
`details.limit`), and 422 `validation_failed` for a port outside 1-65535 or an option out of range.
|
|
1178
|
+
|
|
969
1179
|
## Usage and overage
|
|
970
1180
|
|
|
971
1181
|
`cloud.usage` reads the organization's usage (API keys see organization totals and their own project's workspaces):
|
|
@@ -1110,3 +1320,13 @@ import { isWorkspaceGone, ShardfluxProtocolError } from '@shardflux/sdk';
|
|
|
1110
1320
|
### Repositories with a minimum release age
|
|
1111
1321
|
|
|
1112
1322
|
Keep your repository's age policy. Pin an exact version that has aged enough; do not exempt the entire `@shardflux/*` scope. The first SDK, 0.5.0, was published September 26, 2026 at 21:13:52 UTC and reaches seven days on October 3 at that time. New versions, including 0.12.0, need their own seven days after publication. Before then no registry setting on our side can make them eligible. `npm view @shardflux/sdk time --json` shows publication times. A targeted exception for an inspected exact release is a repository-owner decision, not an installation requirement we bypass. Older versions do not contain this release's fixes.
|
|
1323
|
+
|
|
1324
|
+
### Prepare tool input (0.14.0+)
|
|
1325
|
+
|
|
1326
|
+
`workspaceTools(workspace, { prewake: true })` adds an optional `onInputStart()` callback to tools that need the VM. Call the matching tool's callback when the model starts streaming its input, then execute the tool normally once its arguments are complete. The callback returns immediately and prepares the workspace in the background. Without `prewake`, tools have no input-start callback. File reads served from disk and file-first tools have no callback.
|
|
1327
|
+
|
|
1328
|
+
```ts
|
|
1329
|
+
const tools = workspaceTools(workspace, { prewake: true });
|
|
1330
|
+
// In your model stream's tool-input-start handler:
|
|
1331
|
+
tools.find(tool => tool.name === toolName)?.onInputStart?.();
|
|
1332
|
+
```
|
package/dist/account.d.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* The account plane with a user session (0.9.0): what a person does in the web app, from code.
|
|
3
3
|
*
|
|
4
4
|
* // Before a session exists (no token needed)
|
|
5
|
+
* const proof = await codexIdentityProof(); // 0.14.0+: this machine's Codex login
|
|
6
|
+
* const { account } = await ShardfluxAccount.signup(proof.ok ? { codexIdToken: proof.idToken } : { email });
|
|
5
7
|
* await ShardfluxAccount.register({ email, password, displayName });
|
|
6
8
|
* await ShardfluxAccount.verifyEmail(linkFromTheEmail);
|
|
7
9
|
* const { account, result } = await ShardfluxAccount.login({ email, password, onSessionToken: (s) => save(s.token) });
|
|
@@ -44,6 +46,14 @@ type Ok<Op> = Op extends {
|
|
|
44
46
|
[K in keyof R]: K extends 200 | 201 | 202 ? JsonOf<R[K]> : never;
|
|
45
47
|
}[keyof R] : never;
|
|
46
48
|
export type RegisterResult = Ok<operations['postV1AuthRegister']>;
|
|
49
|
+
/**
|
|
50
|
+
* Agent signup (0.14.0+): `status`, `created`, `access` (`verified` | `provisional` | `verification_required`), the
|
|
51
|
+
* user, and the new `session_token`.
|
|
52
|
+
*/
|
|
53
|
+
export type SignupResult = Ok<operations['postV1AuthSignup']>;
|
|
54
|
+
export type SignupAccess = SignupResult['access'];
|
|
55
|
+
/** POST /v1/auth/verify-email/code: `{ status: 'accepted' }` (code sent) or `{ status: 'verified' }`. */
|
|
56
|
+
export type EmailCodeResult = Ok<operations['postV1AuthVerifyEmailCode']>;
|
|
47
57
|
export type VerifyEmailResult = Ok<operations['postV1AuthVerifyEmail']>;
|
|
48
58
|
export type PasswordResetRequestResult = Ok<operations['postV1AuthPasswordResetRequest']>;
|
|
49
59
|
export type PasswordResetConfirmResult = Ok<operations['postV1AuthPasswordResetConfirm']>;
|
|
@@ -178,11 +188,15 @@ export declare class AccountAuthApi {
|
|
|
178
188
|
sessions(): Promise<AuthSessionPage>;
|
|
179
189
|
revokeSession(sessionId: string): Promise<void>;
|
|
180
190
|
/**
|
|
181
|
-
* Confirms the password
|
|
191
|
+
* Confirms the password, or a Codex identity proof of the Codex login linked to the account (0.14.0+:
|
|
192
|
+
* `codexIdToken` from codexIdentityProof()), plus a code when MFA is on, for the calls that need a recent check (403
|
|
182
193
|
* `step_up_required`). Rotates the session token.
|
|
183
194
|
*/
|
|
184
|
-
stepUp(params: {
|
|
195
|
+
stepUp(params: ({
|
|
185
196
|
password: string;
|
|
197
|
+
} | {
|
|
198
|
+
codexIdToken: string;
|
|
199
|
+
}) & {
|
|
186
200
|
code?: string;
|
|
187
201
|
recoveryCode?: string;
|
|
188
202
|
}): Promise<StepUpResult>;
|
|
@@ -195,6 +209,13 @@ export declare class AccountAuthApi {
|
|
|
195
209
|
changeEmail(newEmail: string): Promise<EmailChangeResult>;
|
|
196
210
|
/** Sends the verification email again. */
|
|
197
211
|
resendVerification(): Promise<ResendVerificationResult>;
|
|
212
|
+
/**
|
|
213
|
+
* Emails a 6-digit verification code to the account address (0.14.0+; valid 15 minutes, once). Confirm it with
|
|
214
|
+
* verifyEmailCode: nothing is signed out, and an account from `signup` gets its trial.
|
|
215
|
+
*/
|
|
216
|
+
requestEmailCode(): Promise<EmailCodeResult>;
|
|
217
|
+
/** Verifies the email with the 6-digit code (0.14.0+). A wrong code is 400 `token_invalid`; 5 wrong attempts end it. */
|
|
218
|
+
verifyEmailCode(code: string): Promise<EmailCodeResult>;
|
|
198
219
|
}
|
|
199
220
|
export declare class OrganizationExportsApi {
|
|
200
221
|
#private;
|
|
@@ -458,6 +479,24 @@ export declare class ShardfluxAccount {
|
|
|
458
479
|
sendFeedback(params: AccountFeedbackParams): Promise<FeedbackReceipt>;
|
|
459
480
|
/** Raw access to any /v1 endpoint with the session's authentication and the SDK's error handling. */
|
|
460
481
|
request<T>(method: string, path: string, init?: RequestOptions): Promise<T>;
|
|
482
|
+
/**
|
|
483
|
+
* Signs up from an agent with no browser or email round-trip (0.14.0+); `account` holds the new session.
|
|
484
|
+
*
|
|
485
|
+
* - `codexIdToken` (from codexIdentityProof()): the email is verified by the Codex login's ChatGPT account. Signs in
|
|
486
|
+
* to the account of that identity or email when there is one (`result.created` false), else creates it.
|
|
487
|
+
* `result.status` `mfa_required`: complete with `account.auth.completeMfa({ code })`.
|
|
488
|
+
* - `email` only: creates an unverified account. `result.access` `provisional`: it works now on the default plan,
|
|
489
|
+
* and the trial comes with `auth.requestEmailCode()` + `auth.verifyEmailCode(code)`; `verification_required`:
|
|
490
|
+
* verify first. An address that has an account is 409 `conflict` (`details.reason` `email_registered`).
|
|
491
|
+
*/
|
|
492
|
+
static signup(params: PreSession<{
|
|
493
|
+
email?: string;
|
|
494
|
+
displayName?: string;
|
|
495
|
+
codexIdToken?: string;
|
|
496
|
+
}>): Promise<{
|
|
497
|
+
account: ShardfluxAccount;
|
|
498
|
+
result: SignupResult;
|
|
499
|
+
}>;
|
|
461
500
|
/** Creates an account; a verification link is emailed (`{ status: 'accepted' }` also when the email is taken). */
|
|
462
501
|
static register(params: PreSession<{
|
|
463
502
|
email: string;
|
package/dist/account.js
CHANGED
|
@@ -149,11 +149,12 @@ export class AccountAuthApi {
|
|
|
149
149
|
return json(this.#core, 'DELETE', `/v1/auth/sessions/${enc(sessionId)}`);
|
|
150
150
|
}
|
|
151
151
|
/**
|
|
152
|
-
* Confirms the password
|
|
152
|
+
* Confirms the password, or a Codex identity proof of the Codex login linked to the account (0.14.0+:
|
|
153
|
+
* `codexIdToken` from codexIdentityProof()), plus a code when MFA is on, for the calls that need a recent check (403
|
|
153
154
|
* `step_up_required`). Rotates the session token.
|
|
154
155
|
*/
|
|
155
156
|
async stepUp(params) {
|
|
156
|
-
const body = { password: params.password };
|
|
157
|
+
const body = 'password' in params ? { password: params.password } : { codex_id_token: params.codexIdToken };
|
|
157
158
|
if (params.code !== undefined)
|
|
158
159
|
body.code = params.code;
|
|
159
160
|
if (params.recoveryCode !== undefined)
|
|
@@ -173,6 +174,17 @@ export class AccountAuthApi {
|
|
|
173
174
|
resendVerification() {
|
|
174
175
|
return json(this.#core, 'POST', '/v1/auth/verify-email/resend');
|
|
175
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Emails a 6-digit verification code to the account address (0.14.0+; valid 15 minutes, once). Confirm it with
|
|
179
|
+
* verifyEmailCode: nothing is signed out, and an account from `signup` gets its trial.
|
|
180
|
+
*/
|
|
181
|
+
requestEmailCode() {
|
|
182
|
+
return json(this.#core, 'POST', '/v1/auth/verify-email/code', { json: {} });
|
|
183
|
+
}
|
|
184
|
+
/** Verifies the email with the 6-digit code (0.14.0+). A wrong code is 400 `token_invalid`; 5 wrong attempts end it. */
|
|
185
|
+
verifyEmailCode(code) {
|
|
186
|
+
return json(this.#core, 'POST', '/v1/auth/verify-email/code', { json: { code: code.replace(/\s+/g, '') } });
|
|
187
|
+
}
|
|
176
188
|
}
|
|
177
189
|
export class OrganizationExportsApi {
|
|
178
190
|
#core;
|
|
@@ -603,6 +615,28 @@ export class ShardfluxAccount {
|
|
|
603
615
|
const account = ShardfluxAccount.#client(opts);
|
|
604
616
|
return account.#ctx.http.json('POST', path, body === undefined ? {} : { json: body });
|
|
605
617
|
}
|
|
618
|
+
/**
|
|
619
|
+
* Signs up from an agent with no browser or email round-trip (0.14.0+); `account` holds the new session.
|
|
620
|
+
*
|
|
621
|
+
* - `codexIdToken` (from codexIdentityProof()): the email is verified by the Codex login's ChatGPT account. Signs in
|
|
622
|
+
* to the account of that identity or email when there is one (`result.created` false), else creates it.
|
|
623
|
+
* `result.status` `mfa_required`: complete with `account.auth.completeMfa({ code })`.
|
|
624
|
+
* - `email` only: creates an unverified account. `result.access` `provisional`: it works now on the default plan,
|
|
625
|
+
* and the trial comes with `auth.requestEmailCode()` + `auth.verifyEmailCode(code)`; `verification_required`:
|
|
626
|
+
* verify first. An address that has an account is 409 `conflict` (`details.reason` `email_registered`).
|
|
627
|
+
*/
|
|
628
|
+
static async signup(params) {
|
|
629
|
+
const { email, displayName, codexIdToken, ...opts } = params;
|
|
630
|
+
const account = ShardfluxAccount.#client(opts);
|
|
631
|
+
const body = {
|
|
632
|
+
...(email === undefined ? {} : { email }),
|
|
633
|
+
...(displayName === undefined ? {} : { display_name: displayName }),
|
|
634
|
+
...(codexIdToken === undefined ? {} : { codex_id_token: codexIdToken }),
|
|
635
|
+
};
|
|
636
|
+
const result = await account.#ctx.http.json('POST', '/v1/auth/signup', { json: body });
|
|
637
|
+
await account.#adopt(result);
|
|
638
|
+
return { account, result };
|
|
639
|
+
}
|
|
606
640
|
/** Creates an account; a verification link is emailed (`{ status: 'accepted' }` also when the email is taken). */
|
|
607
641
|
static register(params) {
|
|
608
642
|
const { email, password, displayName, ...opts } = params;
|