@shardflux/sdk 0.9.0 → 0.10.1
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 +64 -0
- package/README.md +115 -2
- package/dist/account.d.ts +30 -6
- package/dist/account.js +25 -4
- package/dist/cell.d.ts +6 -0
- package/dist/cell.js +9 -2
- package/dist/client.d.ts +44 -0
- package/dist/client.js +28 -0
- package/dist/errors.d.ts +29 -1
- package/dist/errors.js +28 -0
- package/dist/generated/app-api.d.ts +464 -24
- package/dist/generated/cell-api.d.ts +38 -1
- package/dist/http.d.ts +1 -1
- package/dist/http.js +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -1
- package/dist/tools.js +26 -7
- package/dist/usage.d.ts +36 -6
- package/dist/usage.js +19 -4
- package/dist/workspace.d.ts +23 -1
- package/dist/workspace.js +34 -0
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,69 @@
|
|
|
3
3
|
Every API the README shows is available from the version named here. Below 1.0, a minor release may break
|
|
4
4
|
compatibility; breaking changes are marked **Breaking**.
|
|
5
5
|
|
|
6
|
+
## 0.10.1 (not yet published)
|
|
7
|
+
|
|
8
|
+
Public support: package metadata and the README now link to the [shared bug tracker](https://github.com/shardfluxdev/community/issues),
|
|
9
|
+
feature requests, and private support/security reporting.
|
|
10
|
+
|
|
11
|
+
## 0.10.0
|
|
12
|
+
|
|
13
|
+
### A command that could not start rejects exec.run() (ExecStartError)
|
|
14
|
+
|
|
15
|
+
A minor release for one runtime change. Production (2026-09-29): `shard ws exec <key> --cwd app -- ls` exited 1 and
|
|
16
|
+
printed nothing, because the session ended `failed_to_start` and `exec.run()` dropped its reason.
|
|
17
|
+
|
|
18
|
+
- **Breaking:** `cell.exec.run()` rejects with the new `ExecStartError` when the command could not start (a `cwd` that
|
|
19
|
+
is not a directory, a program not on `PATH`, an unknown user). It was resolving with `exitCode: null`, empty output
|
|
20
|
+
and the reason only in `session.error`. `ExecStartError` extends `ShardfluxApiError` as a 409 `conflict` with
|
|
21
|
+
`reason` `exec_failed_to_start` (as a file-first execution that could not start reports it), `details.session_id`,
|
|
22
|
+
`details.error`, `sessionId` and `session`; its message is `The command could not start: <the workspace's reason>`,
|
|
23
|
+
e.g. `working directory "/home/user/app" is not a directory`. Also when a start answered `starting` ends that way.
|
|
24
|
+
- The `exec` agent tool (processful) returns `error: { code, message, reason }` with `exit_code: null` for such a
|
|
25
|
+
command, as the file-first `exec` tool does for a failed execution, instead of an empty result. Tool definitions
|
|
26
|
+
are unchanged.
|
|
27
|
+
- The cell API now refuses a relative `cwd` on exec, execution and PTY starts with 422 `validation_failed`,
|
|
28
|
+
`details.reason` `invalid_cwd`, `details.field` `cwd` (for every SDK version); the message names the absolute path
|
|
29
|
+
it likely means, e.g. `use "/home/user/app"`. `KnownErrorReason` adds `invalid_cwd`; `RunOptions.cwd` and the cell
|
|
30
|
+
types (regenerated) document it.
|
|
31
|
+
|
|
32
|
+
### Opt-in overage with a spend cap
|
|
33
|
+
|
|
34
|
+
Additive: an API without overage sends no `spend_cap` and no `reason`. The usage reads change types only (regenerated
|
|
35
|
+
from the API's OpenAPI); the account client's `setSpendPolicy()` gains the overage fields.
|
|
36
|
+
|
|
37
|
+
- Opt-in overage: `usage.summary()`, `spend()` and `estimate()` report `spend_cap` (new type `SpendCap`: `state`
|
|
38
|
+
`unavailable` | `off` | `paused` | `within_allowance` | `accruing` | `warning` | `reached`, `cap_minor`,
|
|
39
|
+
`effective_cap_minor`, `max_cap_minor`, `charges_minor`, `remaining_minor`, `percent_of_cap`, `currency`,
|
|
40
|
+
`resets_at`, `lines` per allowance with `units_over`, `billed_units`, `rate_minor`, `amount_minor`, and
|
|
41
|
+
`projected_reached_at`). Allowances past `included` while overage is on have `cap_state: 'overage'`; `summary()`
|
|
42
|
+
and `spend()` add `exhausted_reason`, and `spend.usage_charges_minor` and the estimate's charges are real amounts.
|
|
43
|
+
- `usage.spendPolicy()` and `ShardfluxAccount.billing.spendPolicy()` return the overage settings:
|
|
44
|
+
`overage_available`, `overage_enabled`, `overage_state` (`unavailable` | `off` | `on` | `paused`),
|
|
45
|
+
`spend_cap_minor`, `spend_cap_min_minor`, `spend_cap_max_minor`, `rates`, `currency` and `version`.
|
|
46
|
+
- `ShardfluxAccount.billing.setSpendPolicy(orgId, update)` (owners and billing members, with a user session; an API
|
|
47
|
+
key gets 403) takes `overageEnabled` and `spendCapMinor` besides `alertThresholdsPercent`, all optional (at least
|
|
48
|
+
one; an empty update throws before any request), and `ifMatch` (the `version` read, or `'*'`), sent as If-Match. New
|
|
49
|
+
type `SpendPolicyUpdate`. Runtime change: the body carries only the fields given.
|
|
50
|
+
- 402 `allowance_exhausted` carries `details.reason` (`allowance_used`, `overage_paused`, `spend_cap_reached`; also
|
|
51
|
+
`err.reason`) and `details.spend_cap` (`cap_minor`, `effective_cap_minor`, `charges_minor`, `currency`).
|
|
52
|
+
`KnownErrorReason` adds them, the spend-policy refusals (422 `overage_unavailable`, `spend_cap_required`,
|
|
53
|
+
`spend_cap_below_minimum`, `spend_cap_above_plan_price`, `spend_cap_below_charges`) and 409 `version_mismatch`.
|
|
54
|
+
|
|
55
|
+
### Suspend when idle (contracts §20.6)
|
|
56
|
+
|
|
57
|
+
- `workspace.suspendWhenIdle({ afterSeconds, idempotencyKey? })` and `cloud.workspaces.suspendWhenIdle(id, {
|
|
58
|
+
afterSeconds })` (POST /v1/workspaces/{id}/suspend-when-idle): the workspace is suspended once it has been idle for
|
|
59
|
+
`afterSeconds` (30..3600), counted from the later of its last work and the request. Meant for the end of an agent
|
|
60
|
+
turn. A running command, an attached stream or a keepalive postpones it; the next tool call or a resume cancels it.
|
|
61
|
+
Resolves with `{ suspendRequest, operation, workspace }`: `operation` is the suspend already in progress, if any
|
|
62
|
+
(then nothing is recorded).
|
|
63
|
+
- `workspace.cancelSuspendWhenIdle()` and `cloud.workspaces.cancelSuspendWhenIdle(id)` (DELETE, idempotent).
|
|
64
|
+
- `workspace.suspendRequest`: the pending request from the view's `idle.suspend_request`, or null.
|
|
65
|
+
- Tool-call capture writes recorded before `suspendWhenIdle` land first, as for `suspend`, since a later write would
|
|
66
|
+
count as the next turn and cancel the request.
|
|
67
|
+
- Types `SuspendRequest`, `SuspendWhenIdleOptions`, `SuspendWhenIdleResult`, `SuspendWhenIdleResponse`.
|
|
68
|
+
|
|
6
69
|
## 0.9.0 (not yet published; npm `latest` is 0.8.0)
|
|
7
70
|
|
|
8
71
|
Elastic compute (decision 0007): file tools and wake hints for parked workspaces. Additive; older APIs and cell
|
|
@@ -202,6 +265,7 @@ behavior is the automatic version check (below), which makes one background requ
|
|
|
202
265
|
- New exports: `FeedbackCategory`, `FeedbackContext`, `FeedbackReceipt`, `SendFeedbackParams`, `AccountFeedbackParams`,
|
|
203
266
|
`FEEDBACK_CATEGORIES`, `FEEDBACK_MESSAGE_MAX_LENGTH`.
|
|
204
267
|
|
|
268
|
+
|
|
205
269
|
## 0.8.0 (2026-09-28)
|
|
206
270
|
|
|
207
271
|
Types only; nothing changes at run time and the API is unchanged.
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ tool calls into the workspace ([tool-call capture](#tool-call-capture-070)).
|
|
|
10
10
|
> **Early access.** Shardflux is in early access. The API is versioned (`/v1`), but this SDK is
|
|
11
11
|
> below 1.0: a minor release may contain breaking changes (see [Compatibility](#compatibility)).
|
|
12
12
|
|
|
13
|
-
> **Versions.** This README describes 0.
|
|
13
|
+
> **Versions.** This README describes 0.10.1. Anything marked **(0.10.0+)** is not in 0.9.0, **(0.9.0+)** not in 0.8.x, **(0.8.0+)** not in 0.7.x,
|
|
14
14
|
> **(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
15
|
> `npm ls @shardflux/sdk` or the exported `SDK_VERSION`.
|
|
16
16
|
|
|
@@ -19,6 +19,17 @@ tool calls into the workspace ([tool-call capture](#tool-call-capture-070)).
|
|
|
19
19
|
- Typed from the published OpenAPI documents.
|
|
20
20
|
- Retries, idempotency keys, operation polling and tool-token refresh are handled for you.
|
|
21
21
|
|
|
22
|
+
## Support and bug reports
|
|
23
|
+
|
|
24
|
+
[Report a bug](https://github.com/shardfluxdev/community/issues/new?template=bug_report.yml) or
|
|
25
|
+
[request a feature](https://github.com/shardfluxdev/community/issues/new?template=feature_request.yml).
|
|
26
|
+
Include the package and runtime versions, a minimal reproduction, expected and actual behavior, and the request ID
|
|
27
|
+
or error code when available. Reports are public: leave out secrets and confidential data.
|
|
28
|
+
|
|
29
|
+
For usage, account, billing, or private support, email [shardflux@heliosone.fi](mailto:shardflux@heliosone.fi).
|
|
30
|
+
Report vulnerabilities through [private security reporting](https://github.com/shardfluxdev/community/security/advisories/new).
|
|
31
|
+
See the [support guide](https://github.com/shardfluxdev/community/blob/main/SUPPORT.md) for all reporting options.
|
|
32
|
+
|
|
22
33
|
## Install
|
|
23
34
|
|
|
24
35
|
```sh
|
|
@@ -101,6 +112,23 @@ await cell.files.remove('/home/user/data.bin');
|
|
|
101
112
|
twice. Aborting its `signal` also cancels the command in the workspace. File writes are atomic
|
|
102
113
|
and durable (acknowledged after fsync).
|
|
103
114
|
|
|
115
|
+
`cwd` is an absolute path (commands start in `/home/user` without one). The API refuses a relative
|
|
116
|
+
`cwd` with 422 `validation_failed` (`err.reason === 'invalid_cwd'`); the message names the absolute
|
|
117
|
+
path it likely means. A command that could not start (a `cwd` that is not a directory, a program
|
|
118
|
+
that is not on `PATH`) rejects with `ExecStartError` **(0.10.0+)**, whose message is the workspace's
|
|
119
|
+
reason; before 0.10.0 `exec.run()` resolved with `exitCode: null` and no output.
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import { ExecStartError } from '@shardflux/sdk';
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await cell.exec.run(['ls'], { cwd: '/home/user/app' });
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (!(err instanceof ExecStartError)) throw err;
|
|
128
|
+
console.error(err.message); // The command could not start: working directory "/home/user/app" is not a directory
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
104
132
|
### Search, patch and revisions (0.9.0+)
|
|
105
133
|
|
|
106
134
|
```ts
|
|
@@ -194,6 +222,29 @@ try {
|
|
|
194
222
|
}
|
|
195
223
|
```
|
|
196
224
|
|
|
225
|
+
### Suspend when idle (0.10.0+)
|
|
226
|
+
|
|
227
|
+
A running workspace is billed while it is awake, and its idle policy waits a while before suspending it. When your
|
|
228
|
+
agent's turn ends, ask for a suspend once the workspace has been idle for a short time instead:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
const { suspendRequest } = await workspace.suspendWhenIdle({ afterSeconds: 60 }); // 30..3600
|
|
232
|
+
workspace.suspendRequest; // { requested_at, after_seconds, not_before } until it applies or is cancelled
|
|
233
|
+
await workspace.cancelSuspendWhenIdle(); // idempotent
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
- The idle time counts from the later of the workspace's last work and the request; `not_before` is the earliest
|
|
237
|
+
suspend.
|
|
238
|
+
- A command still running, an attached exec or terminal stream, or a keepalive postpones the suspend until
|
|
239
|
+
`afterSeconds` after it ends.
|
|
240
|
+
- The next tool call on the workspace (the next turn) or a resume cancels the request. Repeating replaces it.
|
|
241
|
+
- It applies under every idle policy, `never` included, and never delays a suspend the policy would do sooner.
|
|
242
|
+
- When a suspend is already in progress, the result's `operation` is that suspend and nothing is recorded.
|
|
243
|
+
- Errors: `ShardfluxApiError` 409 with `reason` `not_running`, `operation_in_progress`, `session_lifetime` or
|
|
244
|
+
`workspace_deleted`, and 422 `validation_failed` for `afterSeconds` outside 30..3600. A file-first workspace is never
|
|
245
|
+
suspended: `NotSupportedForModeError` (409 `not_supported_for_mode`).
|
|
246
|
+
- By id: `cloud.workspaces.suspendWhenIdle(id, { afterSeconds })` and `cloud.workspaces.cancelSuspendWhenIdle(id)`.
|
|
247
|
+
|
|
197
248
|
**Suspended workspaces wake on use.** A tool call on a suspended workspace resumes it (or joins the resume or open
|
|
198
249
|
already running), then runs. A call made during a suspend or resume waits for the transition to finish. The call
|
|
199
250
|
never runs twice: the cell executes nothing it refused.
|
|
@@ -589,7 +640,7 @@ never injected).
|
|
|
589
640
|
|
|
590
641
|
**Read-your-writes.** Calls through the same client first wait for capture writes recorded before them (bounded by
|
|
591
642
|
`settleTimeoutMs`, 30 s; they never fail because of capture): `exec` and files calls through `workspace.cell()`,
|
|
592
|
-
`workspaceTools`, `snapshot`, `fork`, `suspend`, `saveAsTemplate` and `close` (on the workspace handle and on
|
|
643
|
+
`workspaceTools`, `snapshot`, `fork`, `suspend`, `suspendWhenIdle` (0.10.0+), `saveAsTemplate` and `close` (on the workspace handle and on
|
|
593
644
|
`cloud.workspaces.*(id)`). A lifecycle call's timing shows the wait as a `capture_flush` phase. `delete` and `reset`
|
|
594
645
|
drop pending writes. A write to a suspended workspace wakes it (`wake: null` opts out).
|
|
595
646
|
|
|
@@ -703,6 +754,26 @@ if (!done.subscription_active) console.log(`checkout ${done.status}`);
|
|
|
703
754
|
// After timeoutMs: CheckoutTimeoutError (err.checkout is the last status); on abort: the signal's reason.
|
|
704
755
|
```
|
|
705
756
|
|
|
757
|
+
Opt-in overage and its spend cap (owners and billing members): read the policy, then change it. Every field is
|
|
758
|
+
optional (give at least one); `ifMatch` (the `version` you read) makes a concurrent change a 409 `version_mismatch`
|
|
759
|
+
instead of overwriting it.
|
|
760
|
+
|
|
761
|
+
```ts
|
|
762
|
+
const policy = await account.billing.spendPolicy(org.id);
|
|
763
|
+
// overage_state: unavailable | off | on | paused; the cap range: spend_cap_min_minor..spend_cap_max_minor (the plan price)
|
|
764
|
+
if (policy.overage_available) {
|
|
765
|
+
await account.billing.setSpendPolicy(org.id, { overageEnabled: true, spendCapMinor: 900, ifMatch: policy.version }); // $9.00
|
|
766
|
+
}
|
|
767
|
+
await account.billing.setSpendPolicy(org.id, { overageEnabled: false }); // always allowed
|
|
768
|
+
await account.billing.setSpendPolicy(org.id, { alertThresholdsPercent: [50, 80, 100] });
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
A refused change is a `ShardfluxApiError` 422 `validation_failed` with `reason` `overage_unavailable`,
|
|
772
|
+
`spend_cap_required`, `spend_cap_below_minimum` (`details.min_minor`), `spend_cap_above_plan_price`
|
|
773
|
+
(`details.max_minor`) or `spend_cap_below_charges` (`details.charges_minor`: the cap cannot go below what overage
|
|
774
|
+
already charged this period). Every owner and billing member gets an email when overage is turned on or off or the cap
|
|
775
|
+
changes.
|
|
776
|
+
|
|
706
777
|
## Errors
|
|
707
778
|
|
|
708
779
|
- `ShardfluxApiError`: the API or the workspace refused the request. Fields: `status`, `code`,
|
|
@@ -724,6 +795,18 @@ if (!done.subscription_active) console.log(`checkout ${done.status}`);
|
|
|
724
795
|
|
|
725
796
|
Treat unknown error codes and reasons as generic errors: show `message`, and use `retryable`.
|
|
726
797
|
|
|
798
|
+
A 402 `allowance_exhausted` (opens, resumes and forks refused while a CPU-hours or RAM GiB-hours allowance is used up)
|
|
799
|
+
carries `reason` **(0.9.0+)**:
|
|
800
|
+
|
|
801
|
+
| `reason` | Meaning | What to do |
|
|
802
|
+
| --- | --- | --- |
|
|
803
|
+
| `allowance_used` | The allowance is used up and overage is off or not on the plan. | Upgrade, or have an owner or billing member turn on overage under Usage & billing; or wait for `details.resets_at`. |
|
|
804
|
+
| `overage_paused` | Overage is on but paused while a plan payment is past due. | An owner or billing member updates the payment method. |
|
|
805
|
+
| `spend_cap_reached` | Overage charges reached the spend cap for this billing period. | Raise the cap (up to the plan price) or upgrade under Usage & billing; or wait for `details.resets_at`. |
|
|
806
|
+
|
|
807
|
+
`details.spend_cap` is `{ cap_minor, effective_cap_minor, charges_minor, currency }` (minor units, `null` when the plan
|
|
808
|
+
has no overage). An older API sends no `reason`. Do not retry these in a loop.
|
|
809
|
+
|
|
727
810
|
Retryable 429/502/503/504 refusals (for example 503 `host_capacity`, when the workspace's host has no room to restore
|
|
728
811
|
it right now, or `wake_failed`) are retried after `Retry-After` for reads, searches and calls that carry an
|
|
729
812
|
Idempotency-Key (writes and patches); other calls surface them with `retryable: true` and `retryAfterSeconds`.
|
|
@@ -733,6 +816,36 @@ A read of a sleeping workspace that its disk cannot answer (409 `workspace_not_r
|
|
|
733
816
|
`host_feature_unavailable` (the workspace's host predates the call, `details.feature`) is neither retried nor
|
|
734
817
|
woken: it lasts until the workspace runs on an upgraded host.
|
|
735
818
|
|
|
819
|
+
## Usage and overage
|
|
820
|
+
|
|
821
|
+
`cloud.usage` reads the organization's usage (API keys see organization totals and their own project's workspaces):
|
|
822
|
+
`summary(orgId)`, `series(orgId, params)`, `workspace(workspaceId, params)`, `estimate(orgId)`, `grants(orgId)`,
|
|
823
|
+
`spend(orgId)` and `spendPolicy(orgId)`.
|
|
824
|
+
|
|
825
|
+
```ts
|
|
826
|
+
const s = await cloud.usage.summary(orgId);
|
|
827
|
+
if (s.allowance_exhausted) console.log('starts are refused:', s.exhausted_reason); // allowance_used | overage_paused | spend_cap_reached
|
|
828
|
+
const cap = s.spend_cap; // opt-in overage this period (0.10.0+)
|
|
829
|
+
const usd = (minor: number) => `$${(minor / 100).toFixed(2)}`; // amounts are minor units of cap.currency
|
|
830
|
+
if (cap.state === 'accruing' || cap.state === 'warning') {
|
|
831
|
+
console.log(`overage ${usd(cap.charges_minor)} of ${usd(cap.effective_cap_minor)}; cap reached ${cap.projected_reached_at ?? 'not this period'}`);
|
|
832
|
+
}
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
Opt-in overage **(0.10.0+)**: while an owner or billing member has turned it on, workspaces keep opening and running
|
|
836
|
+
past the CPU-hours and RAM GiB-hours allowances (those allowances show `cap_state: 'overage'`), and the usage past them
|
|
837
|
+
is charged on the next invoice until the charges reach the spend cap.
|
|
838
|
+
|
|
839
|
+
- `summary()`, `spend()` and `estimate()` carry `spend_cap` (type `SpendCap`): `state` (`unavailable`, `off`,
|
|
840
|
+
`paused`, `within_allowance`, `accruing`, `warning`, `reached`), `cap_minor`, `effective_cap_minor`,
|
|
841
|
+
`charges_minor`, `remaining_minor`, `percent_of_cap`, `currency`, `resets_at`, `lines` (per allowance: `units_over`,
|
|
842
|
+
`billed_units`, `rate_minor`, `amount_minor`) and `projected_reached_at`. `summary()` and `spend()` also carry
|
|
843
|
+
`exhausted_reason`.
|
|
844
|
+
- `spendPolicy()` returns the settings: `overage_available`, `overage_enabled`, `overage_state` (`unavailable`, `off`,
|
|
845
|
+
`on`, `paused`), `spend_cap_minor`, `spend_cap_min_minor`, `spend_cap_max_minor`, `rates` and `currency`.
|
|
846
|
+
- An API key only reads them. Owners and billing members turn overage on or off and change the cap in the console,
|
|
847
|
+
or with a user session: `ShardfluxAccount.billing.setSpendPolicy()` (see below).
|
|
848
|
+
|
|
736
849
|
## Feedback (0.9.0+)
|
|
737
850
|
|
|
738
851
|
`cloud.sendFeedback()` sends a message straight to the Shardflux founder, who reads every one. If you or your coding
|
package/dist/account.d.ts
CHANGED
|
@@ -320,7 +320,7 @@ export declare class CheckoutTimeoutError extends Error {
|
|
|
320
320
|
}
|
|
321
321
|
/**
|
|
322
322
|
* Billing with a user session: the catalog and subscription (as for API keys) plus Checkout, the Stripe portal,
|
|
323
|
-
* invoices
|
|
323
|
+
* invoices, usage alert thresholds and opt-in overage with its spend cap (owner/billing members).
|
|
324
324
|
*/
|
|
325
325
|
export declare class AccountBillingApi extends BillingApi {
|
|
326
326
|
#private;
|
|
@@ -342,12 +342,36 @@ export declare class AccountBillingApi extends BillingApi {
|
|
|
342
342
|
/** A Stripe customer portal link (plan changes, payment methods, cancellation). */
|
|
343
343
|
portal(organizationId: string): Promise<BillingPortalSession>;
|
|
344
344
|
invoices(organizationId: string, params?: PageParams): Promise<BillingInvoicePage>;
|
|
345
|
-
/**
|
|
345
|
+
/**
|
|
346
|
+
* Usage alert thresholds and opt-in overage: `overage_available`, `overage_enabled`, `overage_state` (`unavailable`,
|
|
347
|
+
* `off`, `on`, `paused`), `spend_cap_minor` with `spend_cap_min_minor` and `spend_cap_max_minor` (the plan price),
|
|
348
|
+
* `rates`, `currency` and `version` (send it as `ifMatch`).
|
|
349
|
+
*/
|
|
346
350
|
spendPolicy(organizationId: string): Promise<SpendPolicy>;
|
|
347
|
-
/**
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
+
/**
|
|
352
|
+
* Changes the spend policy (owner/billing members): alert thresholds and opt-in overage. Every field is optional;
|
|
353
|
+
* give at least one. The change takes effect at once, and every owner and billing member gets an email when overage
|
|
354
|
+
* is turned on or off or the cap changes. Refusals: 422 `validation_failed` with `reason` `overage_unavailable`,
|
|
355
|
+
* `spend_cap_required`, `spend_cap_below_minimum` (`details.min_minor`), `spend_cap_above_plan_price`
|
|
356
|
+
* (`details.max_minor`) or `spend_cap_below_charges` (`details.charges_minor`); with `ifMatch`, 409 `conflict`
|
|
357
|
+
* `version_mismatch` (`details.current_version`) when the policy changed since you read it. Nothing changes then.
|
|
358
|
+
*/
|
|
359
|
+
setSpendPolicy(organizationId: string, params: SpendPolicyUpdate): Promise<SpendPolicy>;
|
|
360
|
+
}
|
|
361
|
+
/** A spend-policy change (`AccountBillingApi.setSpendPolicy`): every field is optional; give at least one. */
|
|
362
|
+
export interface SpendPolicyUpdate {
|
|
363
|
+
/** Percent of each allowance that sends a usage email (1..100, at most 5); [] turns alerts off. */
|
|
364
|
+
alertThresholdsPercent?: number[];
|
|
365
|
+
/** Turns opt-in overage on (needs a spend cap, given here or set before) or off (always allowed; what it charged stays on the next invoice). */
|
|
366
|
+
overageEnabled?: boolean;
|
|
367
|
+
/**
|
|
368
|
+
* The spend cap per billing period in minor units of the plan currency (900 = $9.00): at least
|
|
369
|
+
* `spend_cap_min_minor`, at most `spend_cap_max_minor` (the plan price), and not below what overage charged this
|
|
370
|
+
* period.
|
|
371
|
+
*/
|
|
372
|
+
spendCapMinor?: number;
|
|
373
|
+
/** The policy `version` you read (sent as If-Match), or '*' for any: a concurrent change answers 409 `version_mismatch`. */
|
|
374
|
+
ifMatch?: number | '*';
|
|
351
375
|
}
|
|
352
376
|
export declare class AccountExportsApi {
|
|
353
377
|
#private;
|
package/dist/account.js
CHANGED
|
@@ -347,7 +347,7 @@ export class CheckoutTimeoutError extends Error {
|
|
|
347
347
|
}
|
|
348
348
|
/**
|
|
349
349
|
* Billing with a user session: the catalog and subscription (as for API keys) plus Checkout, the Stripe portal,
|
|
350
|
-
* invoices
|
|
350
|
+
* invoices, usage alert thresholds and opt-in overage with its spend cap (owner/billing members).
|
|
351
351
|
*/
|
|
352
352
|
export class AccountBillingApi extends BillingApi {
|
|
353
353
|
#core;
|
|
@@ -394,13 +394,34 @@ export class AccountBillingApi extends BillingApi {
|
|
|
394
394
|
invoices(organizationId, params) {
|
|
395
395
|
return json(this.#core, 'GET', `/v1/organizations/${enc(organizationId)}/billing/invoices`, pageQuery(params));
|
|
396
396
|
}
|
|
397
|
-
/**
|
|
397
|
+
/**
|
|
398
|
+
* Usage alert thresholds and opt-in overage: `overage_available`, `overage_enabled`, `overage_state` (`unavailable`,
|
|
399
|
+
* `off`, `on`, `paused`), `spend_cap_minor` with `spend_cap_min_minor` and `spend_cap_max_minor` (the plan price),
|
|
400
|
+
* `rates`, `currency` and `version` (send it as `ifMatch`).
|
|
401
|
+
*/
|
|
398
402
|
spendPolicy(organizationId) {
|
|
399
403
|
return json(this.#core, 'GET', `/v1/organizations/${enc(organizationId)}/spend-policy`);
|
|
400
404
|
}
|
|
401
|
-
/**
|
|
405
|
+
/**
|
|
406
|
+
* Changes the spend policy (owner/billing members): alert thresholds and opt-in overage. Every field is optional;
|
|
407
|
+
* give at least one. The change takes effect at once, and every owner and billing member gets an email when overage
|
|
408
|
+
* is turned on or off or the cap changes. Refusals: 422 `validation_failed` with `reason` `overage_unavailable`,
|
|
409
|
+
* `spend_cap_required`, `spend_cap_below_minimum` (`details.min_minor`), `spend_cap_above_plan_price`
|
|
410
|
+
* (`details.max_minor`) or `spend_cap_below_charges` (`details.charges_minor`); with `ifMatch`, 409 `conflict`
|
|
411
|
+
* `version_mismatch` (`details.current_version`) when the policy changed since you read it. Nothing changes then.
|
|
412
|
+
*/
|
|
402
413
|
setSpendPolicy(organizationId, params) {
|
|
403
|
-
|
|
414
|
+
const body = {};
|
|
415
|
+
if (params.alertThresholdsPercent !== undefined)
|
|
416
|
+
body.alert_thresholds_percent = params.alertThresholdsPercent;
|
|
417
|
+
if (params.overageEnabled !== undefined)
|
|
418
|
+
body.overage_enabled = params.overageEnabled;
|
|
419
|
+
if (params.spendCapMinor !== undefined)
|
|
420
|
+
body.spend_cap_minor = params.spendCapMinor;
|
|
421
|
+
if (Object.keys(body).length === 0)
|
|
422
|
+
return Promise.reject(new Error('setSpendPolicy: give at least one of alertThresholdsPercent, overageEnabled, spendCapMinor'));
|
|
423
|
+
const headers = params.ifMatch === undefined ? {} : { 'if-match': params.ifMatch === '*' ? '*' : `"${params.ifMatch}"` };
|
|
424
|
+
return json(this.#core, 'PUT', `/v1/organizations/${enc(organizationId)}/spend-policy`, { json: body, headers });
|
|
404
425
|
}
|
|
405
426
|
}
|
|
406
427
|
export class AccountExportsApi {
|
package/dist/cell.d.ts
CHANGED
|
@@ -223,6 +223,10 @@ export interface RunResult {
|
|
|
223
223
|
}
|
|
224
224
|
export interface RunOptions {
|
|
225
225
|
sessionId?: string;
|
|
226
|
+
/**
|
|
227
|
+
* Absolute working directory (default the workspace's, /home/user). The API refuses a relative path with 422
|
|
228
|
+
* `validation_failed`, details.reason `invalid_cwd`; one that is not a directory rejects with ExecStartError.
|
|
229
|
+
*/
|
|
226
230
|
cwd?: string;
|
|
227
231
|
env?: Record<string, string>;
|
|
228
232
|
user?: string;
|
|
@@ -297,6 +301,8 @@ export declare class CellClient {
|
|
|
297
301
|
/**
|
|
298
302
|
* Starts (or re-attaches to) a session and collects its output until it exits, reconnecting
|
|
299
303
|
* with offsets after dropped streams. Never issues a second start for the same session_id.
|
|
304
|
+
* A command that could not start (a cwd that is not a directory, a program not on PATH, an unknown user) rejects
|
|
305
|
+
* with ExecStartError (0.10.0+): nothing ran, so there is no exit code to return.
|
|
300
306
|
* A file-first workspace has no sessions: use `executions.run()` (refused locally when the mode is known).
|
|
301
307
|
*/
|
|
302
308
|
run: (argv: string[], opts?: RunOptions) => Promise<RunResult>;
|
package/dist/cell.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NotSupportedForModeError, ShardfluxApiError, ShardfluxProtocolError } from "./errors.js";
|
|
1
|
+
import { ExecStartError, NotSupportedForModeError, ShardfluxApiError, ShardfluxProtocolError } from "./errors.js";
|
|
2
2
|
import { EXECUTION_ID, ExecutionResult, newExecutionId } from "./executions.js";
|
|
3
3
|
import { HttpClient, defaultSleep, randomId, treeRevisionOf } from "./http.js";
|
|
4
4
|
import { describeFailure, emitTo } from "./progress.js";
|
|
@@ -347,6 +347,8 @@ export class CellClient {
|
|
|
347
347
|
/**
|
|
348
348
|
* Starts (or re-attaches to) a session and collects its output until it exits, reconnecting
|
|
349
349
|
* with offsets after dropped streams. Never issues a second start for the same session_id.
|
|
350
|
+
* A command that could not start (a cwd that is not a directory, a program not on PATH, an unknown user) rejects
|
|
351
|
+
* with ExecStartError (0.10.0+): nothing ran, so there is no exit code to return.
|
|
350
352
|
* A file-first workspace has no sessions: use `executions.run()` (refused locally when the mode is known).
|
|
351
353
|
*/
|
|
352
354
|
run: async (argv, opts = {}) => {
|
|
@@ -369,7 +371,9 @@ export class CellClient {
|
|
|
369
371
|
req.kill_grace_ms = opts.killGraceMs;
|
|
370
372
|
if (opts.secretRefs !== undefined)
|
|
371
373
|
req.secret_refs = opts.secretRefs;
|
|
372
|
-
await this.exec.start(req, opts.signal);
|
|
374
|
+
const started = await this.exec.start(req, opts.signal);
|
|
375
|
+
if (started.state === 'failed_to_start')
|
|
376
|
+
throw new ExecStartError(started);
|
|
373
377
|
try {
|
|
374
378
|
return await this.#collect(sessionId, opts);
|
|
375
379
|
}
|
|
@@ -453,6 +457,9 @@ export class CellClient {
|
|
|
453
457
|
break;
|
|
454
458
|
}
|
|
455
459
|
}
|
|
460
|
+
// A start answered while the session was still starting can end without starting the command.
|
|
461
|
+
if (session.state === 'failed_to_start')
|
|
462
|
+
throw new ExecStartError(session);
|
|
456
463
|
return {
|
|
457
464
|
sessionId,
|
|
458
465
|
exitCode: session.exit_code ?? null,
|
package/dist/client.d.ts
CHANGED
|
@@ -94,6 +94,28 @@ export type CheckoutSession = Ok<operations['postApiV1OrganizationsOrganizationI
|
|
|
94
94
|
export type PortalSession = Ok<operations['postApiV1OrganizationsOrganizationIdBillingPortalSessions']>;
|
|
95
95
|
export type InvoicePage = Ok<operations['getApiV1OrganizationsOrganizationIdBillingInvoices']>;
|
|
96
96
|
export type Invoice = InvoicePage['data'][number];
|
|
97
|
+
/**
|
|
98
|
+
* A pending suspend-when-idle request (0.10.0; contracts §20.6): once the workspace has been idle for `after_seconds`
|
|
99
|
+
* (counted from the later of its last work and `requested_at`), it is suspended; `not_before` = requested_at +
|
|
100
|
+
* after_seconds is the earliest.
|
|
101
|
+
*/
|
|
102
|
+
export type SuspendRequest = components['schemas']['SuspendRequest'];
|
|
103
|
+
/** POST /v1/workspaces/{id}/suspend-when-idle (202). */
|
|
104
|
+
export type SuspendWhenIdleResponse = Ok<operations['postV1WorkspacesWorkspaceIdSuspendWhenIdle']>;
|
|
105
|
+
export interface SuspendWhenIdleOptions {
|
|
106
|
+
/** Seconds the workspace must stay idle before it is suspended: an integer from 30 to 3600 (the API refuses others with 422 validation_failed). */
|
|
107
|
+
afterSeconds: number;
|
|
108
|
+
/** Replays the stored response for a repeated request (default: a fresh key per call, so transport retries replay). */
|
|
109
|
+
idempotencyKey?: string;
|
|
110
|
+
}
|
|
111
|
+
export interface SuspendWhenIdleResult {
|
|
112
|
+
/** The recorded request; null when a suspend was already in progress (then `operation` is that suspend). */
|
|
113
|
+
suspendRequest: SuspendRequest | null;
|
|
114
|
+
/** The suspend already in progress (nothing was recorded), else null: the suspend itself happens later, from the cell's idle loop. */
|
|
115
|
+
operation: Operation | null;
|
|
116
|
+
/** The workspace after the call (on a handle: the handle itself, with its view updated). */
|
|
117
|
+
workspace: Workspace;
|
|
118
|
+
}
|
|
97
119
|
export interface ShardfluxOptions {
|
|
98
120
|
/** Project API key: sfk_<key_id>_<secret>. */
|
|
99
121
|
apiKey: string;
|
|
@@ -324,6 +346,28 @@ export declare class WorkspacesApi {
|
|
|
324
346
|
snapshot(workspaceId: string, opts?: LifecycleOptions & {
|
|
325
347
|
label?: string;
|
|
326
348
|
}): Promise<Operation>;
|
|
349
|
+
/**
|
|
350
|
+
* Suspends the workspace once it has been idle for `afterSeconds` (0.9.0; contracts §20.6): meant for the end of an
|
|
351
|
+
* agent turn, so the workspace stops using RAM soon after instead of waiting out its idle policy. The idle time
|
|
352
|
+
* counts from the later of the workspace's last work and this request. A command still running, an attached exec or
|
|
353
|
+
* terminal stream, or a keepalive postpones the suspend until `afterSeconds` after it ends; a tool call after the
|
|
354
|
+
* request (the next turn) or a resume cancels it. Repeating replaces the pending request. It applies under every idle
|
|
355
|
+
* policy (`never` included) and never delays a suspend the policy would do sooner.
|
|
356
|
+
*
|
|
357
|
+
* Resolves with the recorded `suspendRequest`, or, when a suspend is already in progress, with that `operation` and
|
|
358
|
+
* nothing recorded. Tool-call capture writes recorded before the call land first (a later write would count as the
|
|
359
|
+
* next turn). Errors: ShardfluxApiError 409 with reason `not_running`, `operation_in_progress`, `session_lifetime` or
|
|
360
|
+
* `workspace_deleted`; 422 `validation_failed` for `afterSeconds` outside 30..3600.
|
|
361
|
+
*
|
|
362
|
+
* await cloud.workspaces.suspendWhenIdle(workspace.id, { afterSeconds: 60 });
|
|
363
|
+
*/
|
|
364
|
+
suspendWhenIdle(workspaceId: string, opts: SuspendWhenIdleOptions): Promise<SuspendWhenIdleResult>;
|
|
365
|
+
/**
|
|
366
|
+
* Cancels a pending suspend-when-idle request (0.10.0; DELETE /v1/workspaces/{id}/suspend-when-idle). Idempotent,
|
|
367
|
+
* in any workspace state; resolves with the workspace (`suspendRequest` null). A suspend the request already started
|
|
368
|
+
* is not undone: it is the workspace's `activeOperation` (resume or open the workspace instead).
|
|
369
|
+
*/
|
|
370
|
+
cancelSuspendWhenIdle(workspaceId: string): Promise<Workspace>;
|
|
327
371
|
/**
|
|
328
372
|
* Ends a session workspace now (contracts §19.11): the workspace is deleted exactly like delete() (ended_reason
|
|
329
373
|
* closed) and returns the `delete` operation (input.reason session_closed); the key then opens a NEW workspace.
|
package/dist/client.js
CHANGED
|
@@ -418,6 +418,34 @@ export class WorkspacesApi {
|
|
|
418
418
|
snapshot(workspaceId, opts = {}) {
|
|
419
419
|
return this.#op('snapshot', workspaceId, opts.label === undefined ? {} : { label: opts.label }, opts);
|
|
420
420
|
}
|
|
421
|
+
/**
|
|
422
|
+
* Suspends the workspace once it has been idle for `afterSeconds` (0.9.0; contracts §20.6): meant for the end of an
|
|
423
|
+
* agent turn, so the workspace stops using RAM soon after instead of waiting out its idle policy. The idle time
|
|
424
|
+
* counts from the later of the workspace's last work and this request. A command still running, an attached exec or
|
|
425
|
+
* terminal stream, or a keepalive postpones the suspend until `afterSeconds` after it ends; a tool call after the
|
|
426
|
+
* request (the next turn) or a resume cancels it. Repeating replaces the pending request. It applies under every idle
|
|
427
|
+
* policy (`never` included) and never delays a suspend the policy would do sooner.
|
|
428
|
+
*
|
|
429
|
+
* Resolves with the recorded `suspendRequest`, or, when a suspend is already in progress, with that `operation` and
|
|
430
|
+
* nothing recorded. Tool-call capture writes recorded before the call land first (a later write would count as the
|
|
431
|
+
* next turn). Errors: ShardfluxApiError 409 with reason `not_running`, `operation_in_progress`, `session_lifetime` or
|
|
432
|
+
* `workspace_deleted`; 422 `validation_failed` for `afterSeconds` outside 30..3600.
|
|
433
|
+
*
|
|
434
|
+
* await cloud.workspaces.suspendWhenIdle(workspace.id, { afterSeconds: 60 });
|
|
435
|
+
*/
|
|
436
|
+
async suspendWhenIdle(workspaceId, opts) {
|
|
437
|
+
await this.#ctx().captures.settle(workspaceId);
|
|
438
|
+
const body = await this.#http.json('POST', `/v1/workspaces/${encodeURIComponent(workspaceId)}/suspend-when-idle`, { json: { after_seconds: opts.afterSeconds }, idempotencyKey: opts.idempotencyKey ?? randomId('op-') }, this.#auth);
|
|
439
|
+
return { suspendRequest: body.suspend_request, operation: body.operation, workspace: this.#wrap(body.workspace) };
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Cancels a pending suspend-when-idle request (0.10.0; DELETE /v1/workspaces/{id}/suspend-when-idle). Idempotent,
|
|
443
|
+
* in any workspace state; resolves with the workspace (`suspendRequest` null). A suspend the request already started
|
|
444
|
+
* is not undone: it is the workspace's `activeOperation` (resume or open the workspace instead).
|
|
445
|
+
*/
|
|
446
|
+
async cancelSuspendWhenIdle(workspaceId) {
|
|
447
|
+
return this.#wrap(await this.#http.json('DELETE', `/v1/workspaces/${encodeURIComponent(workspaceId)}/suspend-when-idle`, {}, this.#auth));
|
|
448
|
+
}
|
|
421
449
|
async close(workspaceId, opts = {}) {
|
|
422
450
|
return (await this.closeWithView(workspaceId, opts)).operation;
|
|
423
451
|
}
|
package/dist/errors.d.ts
CHANGED
|
@@ -38,8 +38,21 @@ export type ErrorCode = AppErrorCode | CellErrorCode;
|
|
|
38
38
|
* service_unavailable no_execution_host (retryable, Retry-After). An execution that ended `failed` or `lost` names its
|
|
39
39
|
* cause in `error.details.reason`: lease_expired, host_unreachable, host_restarted, tree_moved, blob_missing,
|
|
40
40
|
* blob_corrupt, exec_failed_to_start, among others.
|
|
41
|
+
* Working directories (0.10.0): 422 validation_failed invalid_cwd (details.field `cwd`): an exec, execution or PTY start
|
|
42
|
+
* named a relative cwd, which is refused rather than resolved (the message names the absolute path it likely means);
|
|
43
|
+
* a processful command that could not start rejects `exec.run()` with ExecStartError (409 conflict
|
|
44
|
+
* exec_failed_to_start).
|
|
45
|
+
* Opt-in overage with a spend cap (0.10.0): 402 allowance_exhausted on opens, resumes and forks carries details.reason
|
|
46
|
+
* allowance_used (a CPU-hours or RAM GiB-hours allowance is used up and overage is off or not on the plan),
|
|
47
|
+
* overage_paused (overage is on but paused while a plan payment is past due) or spend_cap_reached (overage charges
|
|
48
|
+
* reached the spend cap), plus details.spend_cap {cap_minor, effective_cap_minor, charges_minor, currency}; an API
|
|
49
|
+
* older than overage sends no reason. Changing overage is for owners and billing members (the console, or a user
|
|
50
|
+
* session: ShardfluxAccount.billing.setSpendPolicy; an API key gets 403), whose 422 validation_failed reasons are
|
|
51
|
+
* overage_unavailable, spend_cap_required, spend_cap_below_minimum (details.min_minor), spend_cap_above_plan_price
|
|
52
|
+
* (details.max_minor) and spend_cap_below_charges (details.charges_minor); with `ifMatch`, 409 conflict
|
|
53
|
+
* version_mismatch (details.current_version), as egress puts with `ifMatch` answer too.
|
|
41
54
|
*/
|
|
42
|
-
export type KnownErrorReason = 'invalid_recipe' | 'base_not_layered' | 'language_unavailable' | 'language_conflict' | 'invalid_package' | 'too_many_files' | 'platform_owned_path' | 'upload_required' | 'upload_missing' | 'upload_digest_mismatch' | 'upload_too_large' | 'extra_hosts_without_auto' | 'invalid_settings' | 'services_unsupported' | 'input_required' | 'input_unknown' | 'input_invalid' | 'egress_widening' | 'reserved_session_id' | 'env_collision' | 'reserved_template_slug' | 'package_index_unavailable' | 'package_not_found' | 'startup_failed' | 'service_not_ready' | 'secrets_unavailable' | 'workspace_not_running' | 'operation_in_progress' | 'workspace_deleted' | 'secret_not_available' | 'legacy_disk_layout' | 'not_session' | 'session_lifetime' | 'lifetime_mismatch' | 'not_resettable' | 'template_not_layered' | 'draft_exists' | 'draft_stale' | 'build_in_progress' | 'file_list_unavailable' | 'file_list_indexing' | 'guest_feature_unavailable' | 'confirm_destructive_required' | 'reserved_key_prefix' | 'invalid_defaults' | 'update_policy_not_available' | 'invalid_path' | 'too_many_acknowledged_findings' | 'template_dev_mode_role' | 'draft_not_found' | 'version_not_found' | 'path_not_found' | 'revision_mismatch' | 'edit_not_found' | 'edit_ambiguous' | 'edit_not_text' | 'patch_invalid' | 'host_capacity' | 'wake_failed' | 'workspace_fenced' | 'offline_unavailable' | 'offline_budget' | 'offline_changed' | 'host_feature_unavailable' | 'not_supported_for_mode' | 'mode_mismatch' | 'mode_not_available' | 'layout_unsupported' | 'tree_revision_mismatch' | 'outside_tree_root' | 'execution_in_progress' | 'execution_id_reused' | 'operation_id_reused' | 'no_execution_host' | 'lease_expired' | 'host_unreachable' | 'host_restarted' | 'tree_moved' | 'blob_missing' | 'blob_corrupt' | 'exec_failed_to_start';
|
|
55
|
+
export type KnownErrorReason = 'invalid_recipe' | 'base_not_layered' | 'language_unavailable' | 'language_conflict' | 'invalid_package' | 'too_many_files' | 'platform_owned_path' | 'upload_required' | 'upload_missing' | 'upload_digest_mismatch' | 'upload_too_large' | 'extra_hosts_without_auto' | 'invalid_settings' | 'services_unsupported' | 'input_required' | 'input_unknown' | 'input_invalid' | 'egress_widening' | 'reserved_session_id' | 'env_collision' | 'reserved_template_slug' | 'package_index_unavailable' | 'package_not_found' | 'startup_failed' | 'service_not_ready' | 'secrets_unavailable' | 'workspace_not_running' | 'operation_in_progress' | 'workspace_deleted' | 'secret_not_available' | 'legacy_disk_layout' | 'not_session' | 'session_lifetime' | 'lifetime_mismatch' | 'not_resettable' | 'template_not_layered' | 'draft_exists' | 'draft_stale' | 'build_in_progress' | 'file_list_unavailable' | 'file_list_indexing' | 'guest_feature_unavailable' | 'confirm_destructive_required' | 'reserved_key_prefix' | 'invalid_defaults' | 'update_policy_not_available' | 'invalid_path' | 'too_many_acknowledged_findings' | 'template_dev_mode_role' | 'draft_not_found' | 'version_not_found' | 'path_not_found' | 'revision_mismatch' | 'edit_not_found' | 'edit_ambiguous' | 'edit_not_text' | 'patch_invalid' | 'host_capacity' | 'wake_failed' | 'workspace_fenced' | 'offline_unavailable' | 'offline_budget' | 'offline_changed' | 'host_feature_unavailable' | 'not_supported_for_mode' | 'mode_mismatch' | 'mode_not_available' | 'layout_unsupported' | 'tree_revision_mismatch' | 'outside_tree_root' | 'execution_in_progress' | 'execution_id_reused' | 'operation_id_reused' | 'no_execution_host' | 'lease_expired' | 'host_unreachable' | 'host_restarted' | 'tree_moved' | 'blob_missing' | 'blob_corrupt' | 'exec_failed_to_start' | 'invalid_cwd' | 'allowance_used' | 'overage_paused' | 'spend_cap_reached' | 'overage_unavailable' | 'spend_cap_required' | 'spend_cap_below_minimum' | 'spend_cap_above_plan_price' | 'spend_cap_below_charges' | 'version_mismatch';
|
|
43
56
|
/** A known reason, or any other string the server sends (reasons are open-ended). */
|
|
44
57
|
export type ErrorReason = KnownErrorReason | (string & {});
|
|
45
58
|
export interface ErrorBodyLike {
|
|
@@ -104,6 +117,21 @@ export declare class TreeRevisionMismatchError extends ShardfluxApiError {
|
|
|
104
117
|
readonly currentTreeRevision: number | null;
|
|
105
118
|
constructor(status: number, body: ErrorBodyLike, source: 'api' | 'cell', retryAfterSeconds?: number, treeRevision?: number);
|
|
106
119
|
}
|
|
120
|
+
type ExecSession = CellComponents['schemas']['ExecSession'];
|
|
121
|
+
/**
|
|
122
|
+
* A command that could not start (0.10.0+): its working directory is not a directory, its program is not on PATH, or
|
|
123
|
+
* its user does not exist. Nothing ran, so there is no exit code and no output. `exec.run()` rejects with it when the
|
|
124
|
+
* session ends `failed_to_start` (before 0.10.0 it resolved with `exitCode: null` and empty output). It is a 409
|
|
125
|
+
* `conflict` with details.reason `exec_failed_to_start`, as a file-first execution that could not start reports it;
|
|
126
|
+
* the message carries the workspace's own words, e.g. `working directory "/home/user/app" is not a directory`.
|
|
127
|
+
*/
|
|
128
|
+
export declare class ExecStartError extends ShardfluxApiError {
|
|
129
|
+
/** The exec session that could not start. */
|
|
130
|
+
readonly sessionId: string;
|
|
131
|
+
/** The session as the cell reported it (state `failed_to_start`, `error`). */
|
|
132
|
+
readonly session: ExecSession;
|
|
133
|
+
constructor(session: ExecSession);
|
|
134
|
+
}
|
|
107
135
|
/**
|
|
108
136
|
* Builds the error for an error body, typed by its reason: NotSupportedForModeError for `not_supported_for_mode`,
|
|
109
137
|
* TreeRevisionMismatchError for `tree_revision_mismatch`, else ShardfluxApiError. Every SDK request builds its errors
|
package/dist/errors.js
CHANGED
|
@@ -83,6 +83,34 @@ export class TreeRevisionMismatchError extends ShardfluxApiError {
|
|
|
83
83
|
this.currentTreeRevision = typeof cur === 'number' && Number.isSafeInteger(cur) ? cur : (treeRevision ?? null);
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* A command that could not start (0.10.0+): its working directory is not a directory, its program is not on PATH, or
|
|
88
|
+
* its user does not exist. Nothing ran, so there is no exit code and no output. `exec.run()` rejects with it when the
|
|
89
|
+
* session ends `failed_to_start` (before 0.10.0 it resolved with `exitCode: null` and empty output). It is a 409
|
|
90
|
+
* `conflict` with details.reason `exec_failed_to_start`, as a file-first execution that could not start reports it;
|
|
91
|
+
* the message carries the workspace's own words, e.g. `working directory "/home/user/app" is not a directory`.
|
|
92
|
+
*/
|
|
93
|
+
export class ExecStartError extends ShardfluxApiError {
|
|
94
|
+
/** The exec session that could not start. */
|
|
95
|
+
sessionId;
|
|
96
|
+
/** The session as the cell reported it (state `failed_to_start`, `error`). */
|
|
97
|
+
session;
|
|
98
|
+
constructor(session) {
|
|
99
|
+
const why = session.error ?? 'the workspace gave no reason';
|
|
100
|
+
super(409, {
|
|
101
|
+
error: {
|
|
102
|
+
code: 'conflict',
|
|
103
|
+
message: `The command could not start: ${why}`,
|
|
104
|
+
request_id: '',
|
|
105
|
+
retryable: false,
|
|
106
|
+
details: { reason: 'exec_failed_to_start', session_id: session.session_id, ...(session.error ? { error: session.error } : {}) },
|
|
107
|
+
},
|
|
108
|
+
}, 'cell');
|
|
109
|
+
this.name = 'ExecStartError';
|
|
110
|
+
this.sessionId = session.session_id;
|
|
111
|
+
this.session = session;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
86
114
|
/**
|
|
87
115
|
* Builds the error for an error body, typed by its reason: NotSupportedForModeError for `not_supported_for_mode`,
|
|
88
116
|
* TreeRevisionMismatchError for `tree_revision_mismatch`, else ShardfluxApiError. Every SDK request builds its errors
|