@shardflux/sdk 0.13.1 → 0.14.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 +92 -0
- package/README.md +106 -7
- package/dist/account.d.ts +41 -2
- package/dist/account.js +36 -2
- package/dist/cell.d.ts +14 -0
- package/dist/cell.js +2 -0
- package/dist/client.d.ts +86 -2
- package/dist/client.js +148 -0
- package/dist/codex-proof.d.ts +53 -0
- package/dist/codex-proof.js +200 -0
- package/dist/errors.d.ts +1 -1
- package/dist/generated/app-api.d.ts +2137 -412
- package/dist/generated/cell-api.d.ts +6 -0
- package/dist/http.d.ts +1 -1
- package/dist/http.js +1 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.js +3 -1
- 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/tools.d.ts +8 -0
- package/dist/tools.js +20 -0
- package/dist/workspace.d.ts +26 -1
- package/dist/workspace.js +35 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.0 — 2026-10-04 (not yet published)
|
|
4
|
+
|
|
5
|
+
- 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.
|
|
6
|
+
|
|
3
7
|
Every API the README shows is available from the version named here. Breaking changes ship in minor releases and are
|
|
4
8
|
marked **Breaking**.
|
|
5
9
|
|
|
6
10
|
## 0.14.0 (not yet published)
|
|
7
11
|
|
|
12
|
+
### Sign up from an agent
|
|
13
|
+
|
|
14
|
+
Additive (API contracts §44).
|
|
15
|
+
|
|
16
|
+
- `ShardfluxAccount.signup({ email?, displayName?, codexIdToken? })` (POST /v1/auth/signup): a working account and
|
|
17
|
+
its session in one call, with no browser and no email round-trip. Returns `{ account, result }`; `result.access`
|
|
18
|
+
is `verified`, `provisional` (works at once on the Free plan; the trial starts when the email is verified) or
|
|
19
|
+
`verification_required`, and `result.created` is false when a Codex proof signed in to an existing account.
|
|
20
|
+
- `codexIdentityProof(opts?)` (Node): the OpenAI ID token of the Codex CLI signed in on this machine, refreshed by
|
|
21
|
+
Codex itself (`codex app-server`, `account/read {refreshToken: true}`) and read from `$CODEX_HOME/auth.json`. Only
|
|
22
|
+
the ID token is returned. Never throws: `{ ok: false, reason: 'codex_not_found' | 'not_signed_in' |
|
|
23
|
+
'api_key_login' | 'stale', message }`.
|
|
24
|
+
- `account.auth.requestEmailCode()` and `account.auth.verifyEmailCode(code)` (POST /v1/auth/verify-email/code):
|
|
25
|
+
verify with a 6-digit emailed code; nothing is signed out.
|
|
26
|
+
- `account.auth.stepUp({ codexIdToken })`: a step-up with the linked Codex login instead of the password.
|
|
27
|
+
- New types `SignupResult`, `SignupAccess`, `EmailCodeResult`, `CodexIdentityProof`, `CodexProofOptions`,
|
|
28
|
+
`CodexProofUnavailable`.
|
|
29
|
+
|
|
8
30
|
### Workspaces working at once
|
|
9
31
|
|
|
10
32
|
Additive. The plan's workspace number caps workspaces working at the same moment; stored workspaces are bounded by
|
|
@@ -25,6 +47,76 @@ Retained state.
|
|
|
25
47
|
- Regenerated contract types: `ErrorCode` includes `quota_exceeded` for the workspace gateway; allowance
|
|
26
48
|
`enforcement` adds `storage_block`, `cap_state` adds `storage_blocked`.
|
|
27
49
|
|
|
50
|
+
### Inbound ports
|
|
51
|
+
|
|
52
|
+
Additive. A TCP port of a processful workspace can be served at its own private HTTPS URL,
|
|
53
|
+
`https://<port>-<handle>.<domain>`; a request wakes a parked or suspended workspace and is answered once it runs.
|
|
54
|
+
|
|
55
|
+
- `workspace.ports` and `cloud.workspaces.ports(id)` (class `WorkspacePorts`): `expose(port)` →
|
|
56
|
+
`{port, url, createdAt, callback, created}` (`ExposePortResult`; idempotent, `created` false when it already was),
|
|
57
|
+
`list()` → `ExposedPort[]`, `close(port)`, `token(port, {ttlSeconds?})` → `{token, expiresAt, url}` (`PortToken`),
|
|
58
|
+
`link(port, {ttlSeconds?, path?})` → `{url, expiresAt}` (`PortLink`), `createCallbackUrl(port)` →
|
|
59
|
+
`{url, createdAt}` (`PortCallbackUrl`) and `revokeCallbackUrl(port)`. `expose`, `close` and `revokeCallbackUrl` are
|
|
60
|
+
retried on transient failures; minting calls are not.
|
|
61
|
+
- Types `ExposedPort`, `ExposePortResult`, `PortToken`, `PortLink`, `PortCallbackUrl`, `PortTokenOptions`,
|
|
62
|
+
`PortLinkOptions` and the raw API shapes `PortView`, `PortTokenResponse`, `PortLinkResponse`, `PortCallbackResponse`
|
|
63
|
+
(regenerated contract types).
|
|
64
|
+
- `KnownErrorReason` adds `inbound_ports_not_available`, `port_not_exposed` and `port_limit`.
|
|
65
|
+
|
|
66
|
+
### Memory across platform upgrades
|
|
67
|
+
|
|
68
|
+
Additive. A resume keeps the workspace's memory and running processes across host kernel, CPU generation and
|
|
69
|
+
Firecracker upgrades of the platform.
|
|
70
|
+
|
|
71
|
+
- `ColdBootReason` gains `runtime_retired`, and `COLD_BOOT_REASONS` lists every reason this version knows
|
|
72
|
+
(`runtime_changed`, `host_lost`, `runtime_retired`). `runtime_retired`: the runtime the workspace was suspended on
|
|
73
|
+
was retired after an announced window; the resume booted the saved disk (`resumePath` `cold_boot`,
|
|
74
|
+
`memoryRestored` false). A reason this version does not know is still passed through as a string.
|
|
75
|
+
|
|
76
|
+
### Resize a workspace
|
|
77
|
+
|
|
78
|
+
Additive. Change the memory, CPU and disk of any workspace, running or suspended, fixed or elastic, without a restart
|
|
79
|
+
or a fork.
|
|
80
|
+
|
|
81
|
+
- `workspace.resize({ memoryMib, memoryMibHeld, allocationMode, cpuMillis, diskGib })` and
|
|
82
|
+
`cloud.workspaces.resize(id, params)` send `PATCH /v1/workspaces/{id}/caps` with the given caps (snake_case, at
|
|
83
|
+
least one; none throws `TypeError` before any request), an `Idempotency-Key` (`idempotencyKey`, default a fresh key
|
|
84
|
+
per call, so a retried request replays) and `Prefer: wait` (up to 20 s, within `timeoutMs`). They resolve once the
|
|
85
|
+
resize has finished: a resize the server answers with `202` is waited for like any operation (`timeoutMs`, default
|
|
86
|
+
300 000 ms; `signal`; `onProgress`, action `resize`). A resize that meets another lifecycle operation (409
|
|
87
|
+
`operation_in_progress`) waits for it and is sent again with the same `Idempotency-Key`, within `timeoutMs`. A failed
|
|
88
|
+
resize is its error: `ShardfluxApiError` 409 with `operationId` (reason `resize_failed`, ...) when the server held
|
|
89
|
+
the request until then, `OperationFailedError` when the SDK waited for the operation.
|
|
90
|
+
- `ResizeResult`: `workspaceId`, `operationId`, `state`, `caps` (the stored caps every later start uses),
|
|
91
|
+
`operation` (the finished operation after a `202`, else null) and, for each resource the request named,
|
|
92
|
+
`memory` / `cpu` / `disk` (`ResizeMemory`, `ResizeCpu`, `ResizeDisk`): `applies_at` (`now`, `resume` or
|
|
93
|
+
`next_start`), `requested_*`, `target_*`, `previous_*`, `applied_*`, `limit_reason` (`plan`, `template`,
|
|
94
|
+
`host_capacity`, `shrink_stalled`) and `reason` (`suspended`, `stopped`, `boot_vcpus`, `region`, `below_base`,
|
|
95
|
+
`legacy_layout`); memory adds `allocation_mode`, `held_mib` and `converged`. Types `ResizeParams`,
|
|
96
|
+
`ResizeAppliesAt`, `ResizeLimitReason` and `ResizeDeferReason` are exported.
|
|
97
|
+
- The handle refreshes its view after a resize. A file-first workspace is refused locally with
|
|
98
|
+
`NotSupportedForModeError`.
|
|
99
|
+
- A finished operation's `result` (the cell's) is returned in the held answer's shape: each named resource with its
|
|
100
|
+
documented keys, a key left out taken from the operation's input or null, other keys dropped.
|
|
101
|
+
- `KnownErrorReason` adds `resize_not_available` (409 `conflict`), `resize_failed` (409 `conflict`, a failed resize)
|
|
102
|
+
and `shrink_not_supported` (422 `validation_failed`, `details.current_disk_gib`). `LifecycleAction` adds `resize`.
|
|
103
|
+
|
|
104
|
+
### Memory hint for commands
|
|
105
|
+
|
|
106
|
+
Additive. `RunOptions.resourceHint` (`'auto' | 'light' | 'heavy'`, type `ResourceHint`) on `exec.run()`, and
|
|
107
|
+
`resource_hint` on `exec.start()` requests: `heavy` gives an elastic workspace its memory before the command starts,
|
|
108
|
+
`light` starts it at once, `auto` (the default) decides from the command. Sent only when set, so other requests are
|
|
109
|
+
unchanged. The processful `exec` agent tool takes `resource_hint` with the same values.
|
|
110
|
+
|
|
111
|
+
### Elastic by default
|
|
112
|
+
|
|
113
|
+
With elastic-by-default enabled for the organization, an open of a new processful workspace without
|
|
114
|
+
`caps.allocation_mode` is elastic: it holds the memory its commands use and grows up to `memory_mib` (when not given,
|
|
115
|
+
the plan's largest workspace, bounded by the template); billing counts the memory it holds. `allocation_mode: 'fixed'`
|
|
116
|
+
gives a static allocation of `memory_mib`. Reopening an existing workspace with caps that omit `allocation_mode` then
|
|
117
|
+
keeps its stored mode; a fork without caps inherits its source's. The SDK's requests do not change;
|
|
118
|
+
`workspace.memory` and `workspace.allocationMode` report the mode the workspace got.
|
|
119
|
+
|
|
28
120
|
## 0.13.1 (not yet published)
|
|
29
121
|
|
|
30
122
|
Patch, additive: the transport, a documented value, a fix for tool calls across a move and self-healing workspaces.
|
package/README.md
CHANGED
|
@@ -360,10 +360,11 @@ lookups by key.
|
|
|
360
360
|
|
|
361
361
|
### Elastic memory (0.13.0+)
|
|
362
362
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
363
|
+
A workspace can hold only the memory its commands use and grow up to `memory_mib` when they need more; billing counts
|
|
364
|
+
the memory it holds. An elastic workspace idles at its held floor (`memory_mib_held`, default 1024) and shrinks back
|
|
365
|
+
after 30 s without work. Package installs, test runners, compilers, type checkers and bundlers get their memory before
|
|
366
|
+
they start, so they size their heaps and workers from what they will actually get; everything else starts at once and
|
|
367
|
+
the workspace grows while it runs.
|
|
367
368
|
|
|
368
369
|
```ts
|
|
369
370
|
const ws = await cloud.workspaces.open({
|
|
@@ -375,10 +376,32 @@ ws.memory; // { allocation_mode: 'elastic', promised_mib: 8192, held_m
|
|
|
375
376
|
|
|
376
377
|
const r = await ws.cell().exec.run(['npm', 'test']);
|
|
377
378
|
r.memoryGrow; // { outcome: 'delivered', from_mib: 1024, want_mib: 4096, got_mib: 4096, deliver_ms: 175 }
|
|
379
|
+
|
|
380
|
+
await ws.cell().exec.run(['python3', 'train.py'], { resourceHint: 'heavy' }); // 0.14.0+: memory first, then the command
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
`resourceHint` (0.14.0+) is `'auto'` (the default: decided from the command), `'heavy'` or `'light'`; the exec agent
|
|
384
|
+
tool takes `resource_hint`. `allocation_mode: 'fixed'` gives a static allocation of `memory_mib`. Send
|
|
385
|
+
`allocation_mode` with every `caps` you pass to keep the mode you chose; an open without `caps` keeps the stored
|
|
386
|
+
layout, and a fork without `caps` inherits its source's. The exec agent tool adds `memory_grow` to its result when the
|
|
387
|
+
command's start grew the workspace.
|
|
388
|
+
|
|
389
|
+
### Resize a workspace (0.14.0+)
|
|
390
|
+
|
|
391
|
+
Change the memory, CPU and disk of any workspace, running or suspended, fixed or elastic, without a restart:
|
|
392
|
+
|
|
393
|
+
```ts
|
|
394
|
+
const r = await ws.resize({ memoryMib: 6144, diskGib: 20 });
|
|
395
|
+
r.memory; // { applies_at: 'now', previous_mib: 2048, applied_mib: 6144, converged: true, ... }
|
|
396
|
+
r.disk; // { applies_at: 'now', previous_gib: 10, applied_gib: 20, ... }
|
|
397
|
+
|
|
398
|
+
await ws.resize({ allocationMode: 'elastic', memoryMibHeld: 1024 }); // switch modes live
|
|
378
399
|
```
|
|
379
400
|
|
|
380
|
-
|
|
381
|
-
|
|
401
|
+
Memory changes live, and disks grow online. A suspended workspace gets its new size when it resumes, before its first
|
|
402
|
+
call. CPU changes live within the vCPUs the workspace booted with; a larger CPU size applies at the next start. Each
|
|
403
|
+
resource says when it applies (`applies_at`: `now`, `resume` or `next_start`), and the new caps are stored, so every
|
|
404
|
+
later start uses them.
|
|
382
405
|
|
|
383
406
|
### Burst execution (0.13.0+)
|
|
384
407
|
|
|
@@ -398,6 +421,36 @@ workspace's own processes resume where they were. A burst is not signalled or ca
|
|
|
398
421
|
fails (`burst_apply_failed`), retry `exec.run` with the same `sessionId` to finish the apply. The agent tools offer
|
|
399
422
|
bursts when asked: `workspaceTools(ws, { burst: true })`.
|
|
400
423
|
|
|
424
|
+
### Inbound ports (0.14.0+)
|
|
425
|
+
|
|
426
|
+
Serve a port of the workspace at its own HTTPS URL: a preview of the app your agent is building, a webhook receiver,
|
|
427
|
+
an OAuth redirect, a live view. A request wakes a parked or suspended workspace and is answered once it runs, so the
|
|
428
|
+
workspace can sleep between visits. Every port is private: a request carries a port token, a signed link's browser
|
|
429
|
+
session, or arrives on the port's callback URL.
|
|
430
|
+
|
|
431
|
+
```ts
|
|
432
|
+
await ws.cell().exec.start({ argv: ['python3', '-m', 'http.server', '3000', '--bind', '0.0.0.0'] });
|
|
433
|
+
const { url } = await ws.ports.expose(3000); // https://3000-<handle>.shardflux.app
|
|
434
|
+
|
|
435
|
+
const { token } = await ws.ports.token(3000); // { token: 'sfp_…', expiresAt, url }, 1 h by default
|
|
436
|
+
await fetch(url, { headers: { authorization: `Bearer ${token}` } });
|
|
437
|
+
|
|
438
|
+
const link = await ws.ports.link(3000, { path: '/dashboard' }); // open link.url in a browser (24 h by default)
|
|
439
|
+
const hook = await ws.ports.createCallbackUrl(3000); // register `${hook.url}github` as a webhook URL
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
- The server inside the workspace listens on `0.0.0.0` (all interfaces), not only `127.0.0.1`.
|
|
443
|
+
- `expose(port)` is idempotent: `created` is true when this call exposed the port; an exposed port keeps its URL, tokens,
|
|
444
|
+
links and callback URL. `list()` returns `{ port, url, createdAt, callback }` for each exposed port.
|
|
445
|
+
- `token(port, { ttlSeconds })` (60-86400): send it as `Authorization: Bearer <token>`, or as `X-Shardflux-Token`
|
|
446
|
+
when the app reads `Authorization` itself. `link(port, { ttlSeconds, path })` (60-604800 s): anyone with the link can
|
|
447
|
+
open the port until it expires, so share it like a password.
|
|
448
|
+
- `createCallbackUrl(port)`: a URL with a secret in its path for GitHub, Slack, Stripe or an OAuth provider; a request
|
|
449
|
+
to `<url><rest>` reaches `/<rest>` without a token header. Its secret is shown only once; calling it again replaces
|
|
450
|
+
it, `revokeCallbackUrl(port)` revokes it.
|
|
451
|
+
- `close(port)` stops the port's tokens, links and callback URL at once (also when the port is exposed again).
|
|
452
|
+
- By id, without reading the workspace: `cloud.workspaces.ports(id)`.
|
|
453
|
+
|
|
401
454
|
### Timing and progress (0.6.0+)
|
|
402
455
|
|
|
403
456
|
Every open, wake and waited lifecycle call is traced. `workspace.lastTiming` (and `err.timing` when the call fails)
|
|
@@ -849,7 +902,7 @@ await workspace.cell().exec.run(['python3', 'agent.py']); // sees $OPEN
|
|
|
849
902
|
|
|
850
903
|
`ShardfluxAccount` does what a person does in the web app, with a user session instead of an API key: sign up, sign
|
|
851
904
|
in (with MFA), organizations, projects, API keys, members, invitations, billing, audit, data export and account
|
|
852
|
-
deletion.
|
|
905
|
+
deletion. Paying in Stripe Checkout is the step a person does.
|
|
853
906
|
|
|
854
907
|
```ts
|
|
855
908
|
import { Shardflux, ShardfluxAccount } from '@shardflux/sdk';
|
|
@@ -887,6 +940,37 @@ const cloud = new Shardflux({ apiKey: secret });
|
|
|
887
940
|
- `parseEmailToken(input)` returns the `token` of a link's `#token=` fragment or `?token=` query (else the trimmed
|
|
888
941
|
input), and throws for a link without one.
|
|
889
942
|
|
|
943
|
+
### Sign up from an agent (0.14.0+)
|
|
944
|
+
|
|
945
|
+
One call gives a working account, with no browser and no email round-trip. With the Codex CLI signed in with ChatGPT
|
|
946
|
+
on the machine, the account is verified by that login and starts with its trial:
|
|
947
|
+
|
|
948
|
+
```ts
|
|
949
|
+
import { ShardfluxAccount, codexIdentityProof } from '@shardflux/sdk';
|
|
950
|
+
|
|
951
|
+
const proof = await codexIdentityProof(); // asks Codex to refresh its login, returns the ID token
|
|
952
|
+
const { account, result } = proof.ok
|
|
953
|
+
? await ShardfluxAccount.signup({ codexIdToken: proof.idToken, onSessionToken: (s) => save(s.token) })
|
|
954
|
+
: await ShardfluxAccount.signup({ email: 'ada@example.com', onSessionToken: (s) => save(s.token) });
|
|
955
|
+
// result.access: 'verified' | 'provisional' | 'verification_required'; result.created: false when it signed in
|
|
956
|
+
|
|
957
|
+
if (result.access !== 'verified') {
|
|
958
|
+
await account.auth.requestEmailCode(); // a 6-digit code to the inbox (15 minutes)
|
|
959
|
+
await account.auth.verifyEmailCode('123456'); // verified; the session and keys keep working
|
|
960
|
+
}
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
- `codexIdentityProof()` (Node) reads only the ID token from `$CODEX_HOME/auth.json` (default `~/.codex`), after
|
|
964
|
+
`codex app-server` refreshes it; it never throws: `{ ok: false, reason }` is `codex_not_found`, `not_signed_in`,
|
|
965
|
+
`api_key_login` or `stale`. The token proves the ChatGPT account's verified email and is not a credential to
|
|
966
|
+
OpenAI.
|
|
967
|
+
- With a Codex proof, `signup` signs in to the account of that ChatGPT identity or email when there is one, else
|
|
968
|
+
creates it. `result.status` `mfa_required`: `account.auth.completeMfa({ code })`.
|
|
969
|
+
- With an email only, a `provisional` account works at once on the Free plan; verifying the email starts the trial.
|
|
970
|
+
An address that has an account is 409 `conflict` (`details.reason` `email_registered`).
|
|
971
|
+
- `account.auth.stepUp({ codexIdToken })` confirms a sensitive action with the linked Codex login instead of a
|
|
972
|
+
password.
|
|
973
|
+
|
|
890
974
|
Upgrading a plan: a person pays at the Checkout `url`; the code waits for the subscription.
|
|
891
975
|
|
|
892
976
|
```ts
|
|
@@ -966,6 +1050,11 @@ of that base), or `invalid_path` (`details.field` `recipe.immutable[<i>]`). A bu
|
|
|
966
1050
|
`failure.code` `immutable_path_missing` (`failure.details.path` is not a directory in the built filesystem) or
|
|
967
1051
|
`immutable_image_too_large`.
|
|
968
1052
|
|
|
1053
|
+
Inbound ports **(0.14.0+)**: 403 `forbidden` with `reason` `inbound_ports_not_available` (inbound ports are not enabled
|
|
1054
|
+
for the organization), 404 `not_found` with `reason` `port_not_exposed` (a token, link or callback URL of a port that is
|
|
1055
|
+
not exposed: expose it first; `details.port`), 409 `conflict` with `reason` `port_limit` (close a port first;
|
|
1056
|
+
`details.limit`), and 422 `validation_failed` for a port outside 1-65535 or an option out of range.
|
|
1057
|
+
|
|
969
1058
|
## Usage and overage
|
|
970
1059
|
|
|
971
1060
|
`cloud.usage` reads the organization's usage (API keys see organization totals and their own project's workspaces):
|
|
@@ -1110,3 +1199,13 @@ import { isWorkspaceGone, ShardfluxProtocolError } from '@shardflux/sdk';
|
|
|
1110
1199
|
### Repositories with a minimum release age
|
|
1111
1200
|
|
|
1112
1201
|
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.
|
|
1202
|
+
|
|
1203
|
+
### Prepare tool input (0.14.0+)
|
|
1204
|
+
|
|
1205
|
+
`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.
|
|
1206
|
+
|
|
1207
|
+
```ts
|
|
1208
|
+
const tools = workspaceTools(workspace, { prewake: true });
|
|
1209
|
+
// In your model stream's tool-input-start handler:
|
|
1210
|
+
tools.find(tool => tool.name === toolName)?.onInputStart?.();
|
|
1211
|
+
```
|
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;
|
package/dist/cell.d.ts
CHANGED
|
@@ -30,6 +30,12 @@ import type { RequestOptions } from './http.js';
|
|
|
30
30
|
import type { ProgressListener } from './progress.js';
|
|
31
31
|
import type { ToolTokenManager } from './tokens.js';
|
|
32
32
|
type S = components['schemas'];
|
|
33
|
+
/**
|
|
34
|
+
* How much memory a command needs at its start (0.14.0; `resource_hint` of an exec start): `heavy` grows an elastic
|
|
35
|
+
* workspace's memory before the command starts (a build, a test suite, a package install), `light` starts it at once,
|
|
36
|
+
* `auto` (the default) decides from the command. A fixed workspace has its memory already.
|
|
37
|
+
*/
|
|
38
|
+
export type ResourceHint = NonNullable<S['ExecStartRequest']['resource_hint']>;
|
|
33
39
|
export type ExecStartRequest = S['ExecStartRequest'];
|
|
34
40
|
export type ExecSession = S['ExecSession'];
|
|
35
41
|
/**
|
|
@@ -293,6 +299,14 @@ export interface RunOptions {
|
|
|
293
299
|
burstVcpus?: number;
|
|
294
300
|
/** Burst VM memory in MiB (512-65536; default the host's, 8192, or the plan's ceiling when lower). */
|
|
295
301
|
burstMemoryMib?: number;
|
|
302
|
+
/**
|
|
303
|
+
* How much memory the command needs at its start (0.14.0; sent as `resource_hint`, omitted when unset): `'heavy'`
|
|
304
|
+
* grows an elastic workspace's memory before the command starts, so a build, test suite or install sizes its heap and
|
|
305
|
+
* workers from the memory it gets; `'light'` starts it at once and the workspace grows while it runs; `'auto'` (the
|
|
306
|
+
* default) decides from the command (package installers, test runners, compilers, type checkers and bundlers are
|
|
307
|
+
* heavy). A value outside these is ShardfluxApiError 422 `validation_failed` (details.field `resource_hint`).
|
|
308
|
+
*/
|
|
309
|
+
resourceHint?: ResourceHint;
|
|
296
310
|
}
|
|
297
311
|
/** The API error of a burst's recorded failure (`burst.error` of its session). Internal (the agent tools use it too). */
|
|
298
312
|
export declare function burstFailure(e: BurstError): ShardfluxApiError;
|
package/dist/cell.js
CHANGED
|
@@ -444,6 +444,8 @@ export class CellClient {
|
|
|
444
444
|
req.kill_grace_ms = opts.killGraceMs;
|
|
445
445
|
if (opts.secretRefs !== undefined)
|
|
446
446
|
req.secret_refs = opts.secretRefs;
|
|
447
|
+
if (opts.resourceHint !== undefined)
|
|
448
|
+
req.resource_hint = opts.resourceHint;
|
|
447
449
|
if (opts.burst !== undefined)
|
|
448
450
|
req.burst = opts.burst;
|
|
449
451
|
if (opts.burstVcpus !== undefined)
|
package/dist/client.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type { ToolName, ToolToken } from './tokens.js';
|
|
|
16
16
|
import { Workspace } from './workspace.js';
|
|
17
17
|
import { AuditApi } from './audit.js';
|
|
18
18
|
import { EgressPolicyApi } from './egress.js';
|
|
19
|
+
import { WorkspacePorts } from './ports.js';
|
|
19
20
|
import { SecretsApi } from './secrets.js';
|
|
20
21
|
import { TemplatesApi } from './templates.js';
|
|
21
22
|
import type { SaveAsTemplateParams, SaveAsTemplateResponse } from './templates.js';
|
|
@@ -158,8 +159,9 @@ export interface Caps {
|
|
|
158
159
|
memory_mib?: number;
|
|
159
160
|
disk_gib?: number;
|
|
160
161
|
/**
|
|
161
|
-
* `fixed`
|
|
162
|
-
* the
|
|
162
|
+
* `fixed` or `elastic` (0.13.0). Omitted: fixed, or elastic where that is the organization's default (a reopen then
|
|
163
|
+
* keeps the stored mode); the SDK never fills it in. Given caps replace the stored ones; omitted caps keep the stored
|
|
164
|
+
* layout. `workspace.resize()` (0.14.0+) changes it on a running or suspended workspace. Elastic needs the organization's entitlement, else
|
|
163
165
|
* ShardfluxApiError 422 `validation_failed` reason `allocation_mode_not_available` (nothing is created or changed);
|
|
164
166
|
* a file-first workspace gets `not_supported_for_mode`. A promise that does not exceed the held floor by at least
|
|
165
167
|
* 512 MiB is fixed at the promise (the returned `caps.allocation_mode` says which). Takes effect at the next VM start.
|
|
@@ -176,6 +178,64 @@ export interface ForkTarget {
|
|
|
176
178
|
caps?: Caps;
|
|
177
179
|
lifetime?: WorkspaceLifetime;
|
|
178
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* `workspace.resize()` / `workspaces.resize()` (0.14.0): the caps to change. Give at least one; the others keep their
|
|
183
|
+
* stored values. Each value is bounded by the plan's per-workspace maximum and the template's limit, as caps at open.
|
|
184
|
+
*/
|
|
185
|
+
export interface ResizeParams {
|
|
186
|
+
/** Memory in MiB: the size of a fixed workspace, the maximum an elastic one may grow to. */
|
|
187
|
+
memoryMib?: number;
|
|
188
|
+
/** Elastic only: the memory the workspace holds while idle, in MiB (at most the resulting `memoryMib`). */
|
|
189
|
+
memoryMibHeld?: number;
|
|
190
|
+
/** `fixed` or `elastic`: switches the mode, live on a running workspace. */
|
|
191
|
+
allocationMode?: AllocationMode;
|
|
192
|
+
/** CPU in millicores (1000 = one vCPU). */
|
|
193
|
+
cpuMillis?: number;
|
|
194
|
+
/** Disk in GiB; disks grow only. */
|
|
195
|
+
diskGib?: number;
|
|
196
|
+
/** Replays the stored response for a repeated request (default: a fresh key per call, so transport retries replay). */
|
|
197
|
+
idempotencyKey?: string;
|
|
198
|
+
/** Give up waiting for the resize after this long (default 300 000 ms); it continues server side. */
|
|
199
|
+
timeoutMs?: number;
|
|
200
|
+
signal?: AbortSignal;
|
|
201
|
+
/** Progress of the resize (the held request, the operation's states, retries, `done` with the timing). */
|
|
202
|
+
onProgress?: ProgressListener;
|
|
203
|
+
}
|
|
204
|
+
/** The held answer of `PATCH /v1/workspaces/{id}/caps` (and a finished resize operation's `result`). */
|
|
205
|
+
export type ResizeResponse = components['schemas']['ResizeResult'];
|
|
206
|
+
/** The memory part of a resize result (0.14.0). `*_mib` are MiB; `applied_mib` is what is live after the call (null when no VM runs), `held_mib` the elastic floor, `converged` whether a live change reached its target. */
|
|
207
|
+
export type ResizeMemory = NonNullable<ResizeResponse['memory']>;
|
|
208
|
+
/** The CPU part of a resize result (0.14.0), in millicores. */
|
|
209
|
+
export type ResizeCpu = NonNullable<ResizeResponse['cpu']>;
|
|
210
|
+
/** The disk part of a resize result (0.14.0), in GiB. */
|
|
211
|
+
export type ResizeDisk = NonNullable<ResizeResponse['disk']>;
|
|
212
|
+
/** When a resized resource takes effect (0.14.0): `now`, when the suspended workspace resumes, or at its next start. */
|
|
213
|
+
export type ResizeAppliesAt = ResizeMemory['applies_at'];
|
|
214
|
+
/** Why less than requested applied: clamped to the plan or template bound, the host had no room, or the guest kept memory it uses. */
|
|
215
|
+
export type ResizeLimitReason = NonNullable<ResizeMemory['limit_reason']>;
|
|
216
|
+
/**
|
|
217
|
+
* Why a resource applies later than now: `suspended` (at resume), `stopped` (at the next start), `boot_vcpus` (more CPU
|
|
218
|
+
* than the vCPUs the workspace booted with), `region` / `below_base` / `legacy_layout` (a memory size its running VM was
|
|
219
|
+
* not booted for).
|
|
220
|
+
*/
|
|
221
|
+
export type ResizeDeferReason = NonNullable<ResizeMemory['reason']>;
|
|
222
|
+
/**
|
|
223
|
+
* What a resize did (0.14.0), per resource the request named (the others are null): when it applies (`applies_at`),
|
|
224
|
+
* what was asked, the bounded target, the size before, what is live now, and why less or later.
|
|
225
|
+
*/
|
|
226
|
+
export interface ResizeResult {
|
|
227
|
+
workspaceId: string;
|
|
228
|
+
operationId: string | null;
|
|
229
|
+
/** The workspace's state the result describes: `running`, `suspended` or `stopped`. */
|
|
230
|
+
state: string;
|
|
231
|
+
memory: ResizeMemory | null;
|
|
232
|
+
cpu: ResizeCpu | null;
|
|
233
|
+
disk: ResizeDisk | null;
|
|
234
|
+
/** The workspace's stored caps after the change: every later start uses them. */
|
|
235
|
+
caps: ResizeResponse['caps'] | null;
|
|
236
|
+
/** The finished `resize` operation when the API answered before it was done (202), else null. */
|
|
237
|
+
operation: Operation | null;
|
|
238
|
+
}
|
|
179
239
|
export interface WaitOptions {
|
|
180
240
|
/**
|
|
181
241
|
* Give up waiting after this long (default 300 000 ms); the operation continues server side. A queued start
|
|
@@ -338,6 +398,8 @@ export declare class WorkspacesApi {
|
|
|
338
398
|
agentLabel?: string;
|
|
339
399
|
tools?: ToolName[];
|
|
340
400
|
}): Promise<Workspace>;
|
|
401
|
+
/** The exposed ports of a workspace by id (0.14.0; the same as `workspace.ports` without reading the workspace first). */
|
|
402
|
+
ports(workspaceId: string): WorkspacePorts;
|
|
341
403
|
/** Replace labels. An empty map clears them. */
|
|
342
404
|
setLabels(workspaceId: string, labels: Record<string, string>): Promise<Workspace>;
|
|
343
405
|
/** null clears the override, restoring the template or platform policy. */
|
|
@@ -412,6 +474,28 @@ export declare class WorkspacesApi {
|
|
|
412
474
|
* is not undone: it is the workspace's `activeOperation` (resume or open the workspace instead).
|
|
413
475
|
*/
|
|
414
476
|
cancelSuspendWhenIdle(workspaceId: string): Promise<Workspace>;
|
|
477
|
+
/**
|
|
478
|
+
* Resizes a workspace (0.14.0; `PATCH /v1/workspaces/{id}/caps`): any of memory, the held floor, the allocation mode,
|
|
479
|
+
* CPU and disk, of a running, suspended or stopped workspace, fixed or elastic, without a restart or a fork. The new
|
|
480
|
+
* caps are stored, so every later start uses them. Resolves once the resize has finished, with what applied per
|
|
481
|
+
* resource: `now` (live), `resume` (a suspended workspace gets it when it resumes, before its first call) or
|
|
482
|
+
* `next_start` (with the `reason`). The request is held by the server while the resize runs; a resize still running
|
|
483
|
+
* after the hold is waited for like any operation.
|
|
484
|
+
*
|
|
485
|
+
* A resize that meets another lifecycle operation (409 `operation_in_progress`: a suspend, a resume, another resize)
|
|
486
|
+
* waits for it and is sent again, within `timeoutMs`.
|
|
487
|
+
*
|
|
488
|
+
* Errors: ShardfluxApiError 409 `conflict` (reason `resize_not_available`, `workspace_deleted`), 422
|
|
489
|
+
* `validation_failed` (reason `shrink_not_supported` with `details.current_disk_gib`, `requires_elastic`,
|
|
490
|
+
* `exceeds_memory_mib`, `allocation_mode_not_available`, `not_supported_for_mode`). A resize that fails is its
|
|
491
|
+
* error: ShardfluxApiError with `operationId` when the server held the request until then, OperationFailedError when
|
|
492
|
+
* the SDK waited for the operation. OperationTimeoutError after `timeoutMs`. Params without a resource throw
|
|
493
|
+
* TypeError before any request.
|
|
494
|
+
*
|
|
495
|
+
* const r = await cloud.workspaces.resize(id, { memoryMib: 6144 });
|
|
496
|
+
* r.memory?.applies_at; // 'now'
|
|
497
|
+
*/
|
|
498
|
+
resize(workspaceId: string, params: ResizeParams): Promise<ResizeResult>;
|
|
415
499
|
/**
|
|
416
500
|
* Ends a session workspace now: the workspace is deleted exactly like delete() (ended_reason
|
|
417
501
|
* closed) and returns the `delete` operation (input.reason session_closed); the key then opens a NEW workspace.
|