@boatdev/sdk 1.2.0 → 1.4.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/.openapi-generator/FILES +2 -0
- package/dist/apis/BoatApi.d.ts +2 -2
- package/dist/apis/BoatApi.js +2 -2
- package/dist/boat-helpers.d.ts +17 -1
- package/dist/boat-helpers.js +61 -0
- package/dist/esm/apis/BoatApi.d.ts +2 -2
- package/dist/esm/apis/BoatApi.js +2 -2
- package/dist/esm/boat-helpers.d.ts +17 -1
- package/dist/esm/boat-helpers.js +60 -0
- package/dist/esm/models/CommandRequest.d.ts +6 -0
- package/dist/esm/models/CommandRequest.js +2 -0
- package/dist/esm/models/CommandStreamFrame.d.ts +85 -0
- package/dist/esm/models/CommandStreamFrame.js +67 -0
- package/dist/esm/models/CreateSandboxRequest.d.ts +6 -0
- package/dist/esm/models/CreateSandboxRequest.js +2 -0
- package/dist/esm/models/ForkRequest.d.ts +6 -0
- package/dist/esm/models/ForkRequest.js +2 -0
- package/dist/esm/models/ResumeRequest.d.ts +6 -0
- package/dist/esm/models/ResumeRequest.js +2 -0
- package/dist/esm/models/Sandbox.d.ts +26 -0
- package/dist/esm/models/Sandbox.js +13 -0
- package/dist/esm/models/WebhookEventData.d.ts +10 -4
- package/dist/esm/models/WebhookEventData.js +4 -6
- package/dist/esm/models/WebhookEventType.d.ts +2 -0
- package/dist/esm/models/WebhookEventType.js +3 -1
- package/dist/esm/models/index.d.ts +1 -0
- package/dist/esm/models/index.js +1 -0
- package/dist/models/CommandRequest.d.ts +6 -0
- package/dist/models/CommandRequest.js +2 -0
- package/dist/models/CommandStreamFrame.d.ts +85 -0
- package/dist/models/CommandStreamFrame.js +75 -0
- package/dist/models/CreateSandboxRequest.d.ts +6 -0
- package/dist/models/CreateSandboxRequest.js +2 -0
- package/dist/models/ForkRequest.d.ts +6 -0
- package/dist/models/ForkRequest.js +2 -0
- package/dist/models/ResumeRequest.d.ts +6 -0
- package/dist/models/ResumeRequest.js +2 -0
- package/dist/models/Sandbox.d.ts +26 -0
- package/dist/models/Sandbox.js +14 -1
- package/dist/models/WebhookEventData.d.ts +10 -4
- package/dist/models/WebhookEventData.js +4 -6
- package/dist/models/WebhookEventType.d.ts +2 -0
- package/dist/models/WebhookEventType.js +3 -1
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.js +1 -0
- package/docs/BoatApi.md +6 -3
- package/docs/CommandRequest.md +2 -0
- package/docs/CommandStreamFrame.md +47 -0
- package/docs/CreateSandboxRequest.md +2 -0
- package/docs/ForkRequest.md +2 -0
- package/docs/ResumeRequest.md +2 -0
- package/docs/Sandbox.md +6 -0
- package/docs/WebhookEventData.md +2 -0
- package/package.json +1 -1
- package/src/apis/BoatApi.ts +7 -2
- package/src/boat-helpers.ts +75 -1
- package/src/models/CommandRequest.ts +8 -0
- package/src/models/CommandStreamFrame.ts +136 -0
- package/src/models/CreateSandboxRequest.ts +8 -0
- package/src/models/ForkRequest.ts +8 -0
- package/src/models/ResumeRequest.ts +8 -0
- package/src/models/Sandbox.ts +33 -0
- package/src/models/WebhookEventData.ts +14 -8
- package/src/models/WebhookEventType.ts +3 -1
- package/src/models/index.ts +1 -0
package/docs/BoatApi.md
CHANGED
|
@@ -368,7 +368,7 @@ example().catch(console.error);
|
|
|
368
368
|
|
|
369
369
|
Execute a command in a sandbox
|
|
370
370
|
|
|
371
|
-
Runs the command synchronously by default (timeout configurable via `timeoutSeconds`, 600s cap). With `detached: true` the command starts in the background and a process id is returned immediately; poll `/sandboxes/{sandboxId}/commands/{processId}` for status and logs. Returns 400 invalid_timeout when `timeoutSeconds` is not an integer in 1-600.
|
|
371
|
+
Runs the command synchronously by default (timeout configurable via `timeoutSeconds`, 600s cap). With `detached: true` the command starts in the background and a process id is returned immediately; poll `/sandboxes/{sandboxId}/commands/{processId}` for status and logs. With `stream: true` the output arrives as the command writes it, as newline-delimited JSON (`application/x-ndjson`): `{\"type\":\"started\"}`, then `{\"type\":\"stdout\"|\"stderr\",\"data\":\"...\"}` per chunk, and last `{\"type\":\"exit\",...}` (the synchronous result without stdout/stderr) or `{\"type\":\"error\",\"error\":\"...\",\"message\":\"...\"}`. Returns 400 invalid_timeout when `timeoutSeconds` is not an integer in 1-600. A command sent while the sandbox is still starting (right after a resume) waits up to 60s for it to be ready instead of failing; 409 boat_starting (retryable) is returned only if it is still not ready by then. A sandbox that is not starting (`error`, `stopped`) returns 409 sandbox_not_ready with `retryable: false` and its `state` at once: resume or recover it first. Command execution is never retried automatically: a 502 boat_direct_failed means the command may already be running on the sandbox.
|
|
372
372
|
|
|
373
373
|
### Example
|
|
374
374
|
|
|
@@ -425,13 +425,13 @@ example().catch(console.error);
|
|
|
425
425
|
### HTTP request headers
|
|
426
426
|
|
|
427
427
|
- **Content-Type**: `application/json`
|
|
428
|
-
- **Accept**: `application/json`
|
|
428
|
+
- **Accept**: `application/json`, `application/x-ndjson`
|
|
429
429
|
|
|
430
430
|
|
|
431
431
|
### HTTP response details
|
|
432
432
|
| Status code | Description | Response headers |
|
|
433
433
|
|-------------|-------------|------------------|
|
|
434
|
-
| **200** | Command result (synchronous)
|
|
434
|
+
| **200** | Command result (synchronous), process start confirmation (detached), or the output stream (`stream: true`). | - |
|
|
435
435
|
| **400** | Invalid request body or parameters. | - |
|
|
436
436
|
| **401** | Missing or invalid bearer token. | - |
|
|
437
437
|
| **404** | Resource not found. | - |
|
|
@@ -672,6 +672,7 @@ example().catch(console.error);
|
|
|
672
672
|
| **402** | Account cannot currently create or operate sandboxes. The error body may include a dashboard billing URL, but billing actions are not part of the v1 API. | - |
|
|
673
673
|
| **409** | Request conflicts with current account or sandbox state. | - |
|
|
674
674
|
| **429** | Machine start or concurrent-sandbox limit reached. Create, fork and resume each count as one machine start against your plan\'s start limits (see the Billing guide; `rate_limited`, naming the window you hit). A sandbox that would exceed your plan\'s concurrent-sandbox cap is refused with `limit_reached`, or `member_limit_reached` when an organization owner has capped you below the plan. | - |
|
|
675
|
+
| **503** | No capacity. `no_ready_machine`: a `failFast` create, fork or resume found no ready machine of that type; nothing was started, billed, or counted, and a resumed sandbox stays stopped. `out_of_capacity`: creates and forks are paused until the provisioning queue drains. | - |
|
|
675
676
|
|
|
676
677
|
[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md)
|
|
677
678
|
|
|
@@ -1897,6 +1898,7 @@ example().catch(console.error);
|
|
|
1897
1898
|
| **404** | Resource not found. | - |
|
|
1898
1899
|
| **409** | Request conflicts with current account or sandbox state. | - |
|
|
1899
1900
|
| **429** | Machine start or concurrent-sandbox limit reached. Create, fork and resume each count as one machine start against your plan\'s start limits (see the Billing guide; `rate_limited`, naming the window you hit). A sandbox that would exceed your plan\'s concurrent-sandbox cap is refused with `limit_reached`, or `member_limit_reached` when an organization owner has capped you below the plan. | - |
|
|
1901
|
+
| **503** | No capacity. `no_ready_machine`: a `failFast` create, fork or resume found no ready machine of that type; nothing was started, billed, or counted, and a resumed sandbox stays stopped. `out_of_capacity`: creates and forks are paused until the provisioning queue drains. | - |
|
|
1900
1902
|
|
|
1901
1903
|
[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md)
|
|
1902
1904
|
|
|
@@ -3596,6 +3598,7 @@ example().catch(console.error);
|
|
|
3596
3598
|
| **404** | Resource not found. | - |
|
|
3597
3599
|
| **409** | Request conflicts with current account or sandbox state. | - |
|
|
3598
3600
|
| **429** | Machine start or concurrent-sandbox limit reached. Create, fork and resume each count as one machine start against your plan\'s start limits (see the Billing guide; `rate_limited`, naming the window you hit). A sandbox that would exceed your plan\'s concurrent-sandbox cap is refused with `limit_reached`, or `member_limit_reached` when an organization owner has capped you below the plan. | - |
|
|
3601
|
+
| **503** | No capacity. `no_ready_machine`: a `failFast` create, fork or resume found no ready machine of that type; nothing was started, billed, or counted, and a resumed sandbox stays stopped. `out_of_capacity`: creates and forks are paused until the provisioning queue drains. | - |
|
|
3599
3602
|
|
|
3600
3603
|
[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md)
|
|
3601
3604
|
|
package/docs/CommandRequest.md
CHANGED
|
@@ -10,6 +10,7 @@ Name | Type
|
|
|
10
10
|
`cwd` | string
|
|
11
11
|
`timeoutSeconds` | number
|
|
12
12
|
`detached` | boolean
|
|
13
|
+
`stream` | boolean
|
|
13
14
|
|
|
14
15
|
## Example
|
|
15
16
|
|
|
@@ -22,6 +23,7 @@ const example = {
|
|
|
22
23
|
"cwd": null,
|
|
23
24
|
"timeoutSeconds": null,
|
|
24
25
|
"detached": null,
|
|
26
|
+
"stream": null,
|
|
25
27
|
} satisfies CommandRequest
|
|
26
28
|
|
|
27
29
|
console.log(example)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
|
|
2
|
+
# CommandStreamFrame
|
|
3
|
+
|
|
4
|
+
One line of a streamed command. `started` first, then `stdout`/`stderr` chunks, then exactly one `exit` (the synchronous result, without stdout/stderr) or `error`.
|
|
5
|
+
|
|
6
|
+
## Properties
|
|
7
|
+
|
|
8
|
+
Name | Type
|
|
9
|
+
------------ | -------------
|
|
10
|
+
`type` | string
|
|
11
|
+
`data` | string
|
|
12
|
+
`exitCode` | number
|
|
13
|
+
`success` | boolean
|
|
14
|
+
`timedOut` | boolean
|
|
15
|
+
`error` | string
|
|
16
|
+
`message` | string
|
|
17
|
+
`retryable` | boolean
|
|
18
|
+
|
|
19
|
+
## Example
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import type { CommandStreamFrame } from '@boatdev/sdk'
|
|
23
|
+
|
|
24
|
+
// TODO: Update the object below with actual values
|
|
25
|
+
const example = {
|
|
26
|
+
"type": null,
|
|
27
|
+
"data": null,
|
|
28
|
+
"exitCode": null,
|
|
29
|
+
"success": null,
|
|
30
|
+
"timedOut": null,
|
|
31
|
+
"error": null,
|
|
32
|
+
"message": null,
|
|
33
|
+
"retryable": null,
|
|
34
|
+
} satisfies CommandStreamFrame
|
|
35
|
+
|
|
36
|
+
console.log(example)
|
|
37
|
+
|
|
38
|
+
// Convert the instance to a JSON string
|
|
39
|
+
const exampleJSON: string = JSON.stringify(example)
|
|
40
|
+
console.log(exampleJSON)
|
|
41
|
+
|
|
42
|
+
// Parse the JSON string back to an object
|
|
43
|
+
const exampleParsed = JSON.parse(exampleJSON) as CommandStreamFrame
|
|
44
|
+
console.log(exampleParsed)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md)
|
|
@@ -13,6 +13,7 @@ Name | Type
|
|
|
13
13
|
`environment` | string
|
|
14
14
|
`noEnv` | boolean
|
|
15
15
|
`snapshots` | boolean
|
|
16
|
+
`failFast` | boolean
|
|
16
17
|
`setupScript` | string
|
|
17
18
|
`org` | string
|
|
18
19
|
`teamId` | string
|
|
@@ -31,6 +32,7 @@ const example = {
|
|
|
31
32
|
"environment": null,
|
|
32
33
|
"noEnv": null,
|
|
33
34
|
"snapshots": null,
|
|
35
|
+
"failFast": null,
|
|
34
36
|
"setupScript": null,
|
|
35
37
|
"org": null,
|
|
36
38
|
"teamId": null,
|
package/docs/ForkRequest.md
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
Name | Type
|
|
8
8
|
------------ | -------------
|
|
9
|
+
`failFast` | boolean
|
|
9
10
|
`env` | { [key: string]: string; }
|
|
10
11
|
`environment` | string
|
|
11
12
|
`noEnv` | boolean
|
|
@@ -19,6 +20,7 @@ import type { ForkRequest } from '@boatdev/sdk'
|
|
|
19
20
|
|
|
20
21
|
// TODO: Update the object below with actual values
|
|
21
22
|
const example = {
|
|
23
|
+
"failFast": null,
|
|
22
24
|
"env": null,
|
|
23
25
|
"environment": null,
|
|
24
26
|
"noEnv": null,
|
package/docs/ResumeRequest.md
CHANGED
|
@@ -7,6 +7,7 @@ Options for resuming a stopped sandbox.
|
|
|
7
7
|
|
|
8
8
|
Name | Type
|
|
9
9
|
------------ | -------------
|
|
10
|
+
`failFast` | boolean
|
|
10
11
|
`type` | string
|
|
11
12
|
`env` | { [key: string]: string; }
|
|
12
13
|
`environment` | string
|
|
@@ -20,6 +21,7 @@ import type { ResumeRequest } from '@boatdev/sdk'
|
|
|
20
21
|
|
|
21
22
|
// TODO: Update the object below with actual values
|
|
22
23
|
const example = {
|
|
24
|
+
"failFast": null,
|
|
23
25
|
"type": null,
|
|
24
26
|
"env": null,
|
|
25
27
|
"environment": null,
|
package/docs/Sandbox.md
CHANGED
|
@@ -10,6 +10,9 @@ Name | Type
|
|
|
10
10
|
`name` | string
|
|
11
11
|
`state` | string
|
|
12
12
|
`error` | string
|
|
13
|
+
`health` | string
|
|
14
|
+
`healthReason` | string
|
|
15
|
+
`degradedSince` | Date
|
|
13
16
|
`type` | string
|
|
14
17
|
`vcpu` | number
|
|
15
18
|
`memoryGB` | number
|
|
@@ -47,6 +50,9 @@ const example = {
|
|
|
47
50
|
"name": null,
|
|
48
51
|
"state": null,
|
|
49
52
|
"error": null,
|
|
53
|
+
"health": null,
|
|
54
|
+
"healthReason": null,
|
|
55
|
+
"degradedSince": null,
|
|
50
56
|
"type": null,
|
|
51
57
|
"vcpu": null,
|
|
52
58
|
"memoryGB": null,
|
package/docs/WebhookEventData.md
CHANGED
|
@@ -9,6 +9,7 @@ Name | Type
|
|
|
9
9
|
`sandbox` | [WebhookEventDataSandbox](WebhookEventDataSandbox.md)
|
|
10
10
|
`previousState` | string
|
|
11
11
|
`state` | string
|
|
12
|
+
`reason` | string
|
|
12
13
|
|
|
13
14
|
## Example
|
|
14
15
|
|
|
@@ -20,6 +21,7 @@ const example = {
|
|
|
20
21
|
"sandbox": null,
|
|
21
22
|
"previousState": null,
|
|
22
23
|
"state": null,
|
|
24
|
+
"reason": null,
|
|
23
25
|
} satisfies WebhookEventData
|
|
24
26
|
|
|
25
27
|
console.log(example)
|
package/package.json
CHANGED
package/src/apis/BoatApi.ts
CHANGED
|
@@ -63,6 +63,11 @@ import {
|
|
|
63
63
|
CommandStatusResponseFromJSON,
|
|
64
64
|
CommandStatusResponseToJSON,
|
|
65
65
|
} from '../models/CommandStatusResponse';
|
|
66
|
+
import {
|
|
67
|
+
type CommandStreamFrame,
|
|
68
|
+
CommandStreamFrameFromJSON,
|
|
69
|
+
CommandStreamFrameToJSON,
|
|
70
|
+
} from '../models/CommandStreamFrame';
|
|
66
71
|
import {
|
|
67
72
|
type ConversationsResponse,
|
|
68
73
|
ConversationsResponseFromJSON,
|
|
@@ -979,7 +984,7 @@ export class BoatApi extends runtime.BaseAPI {
|
|
|
979
984
|
}
|
|
980
985
|
|
|
981
986
|
/**
|
|
982
|
-
* Runs the command synchronously by default (timeout configurable via `timeoutSeconds`, 600s cap). With `detached: true` the command starts in the background and a process id is returned immediately; poll `/sandboxes/{sandboxId}/commands/{processId}` for status and logs. Returns 400 invalid_timeout when `timeoutSeconds` is not an integer in 1-600.
|
|
987
|
+
* Runs the command synchronously by default (timeout configurable via `timeoutSeconds`, 600s cap). With `detached: true` the command starts in the background and a process id is returned immediately; poll `/sandboxes/{sandboxId}/commands/{processId}` for status and logs. With `stream: true` the output arrives as the command writes it, as newline-delimited JSON (`application/x-ndjson`): `{\"type\":\"started\"}`, then `{\"type\":\"stdout\"|\"stderr\",\"data\":\"...\"}` per chunk, and last `{\"type\":\"exit\",...}` (the synchronous result without stdout/stderr) or `{\"type\":\"error\",\"error\":\"...\",\"message\":\"...\"}`. Returns 400 invalid_timeout when `timeoutSeconds` is not an integer in 1-600. A command sent while the sandbox is still starting (right after a resume) waits up to 60s for it to be ready instead of failing; 409 boat_starting (retryable) is returned only if it is still not ready by then. A sandbox that is not starting (`error`, `stopped`) returns 409 sandbox_not_ready with `retryable: false` and its `state` at once: resume or recover it first. Command execution is never retried automatically: a 502 boat_direct_failed means the command may already be running on the sandbox.
|
|
983
988
|
* Execute a command in a sandbox
|
|
984
989
|
*/
|
|
985
990
|
async commandRaw(requestParameters: CommandOperationRequest | runtime.FlatBody<CommandOperationRequest, 'commandRequest'>, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Command200Response>> {
|
|
@@ -990,7 +995,7 @@ export class BoatApi extends runtime.BaseAPI {
|
|
|
990
995
|
}
|
|
991
996
|
|
|
992
997
|
/**
|
|
993
|
-
* Runs the command synchronously by default (timeout configurable via `timeoutSeconds`, 600s cap). With `detached: true` the command starts in the background and a process id is returned immediately; poll `/sandboxes/{sandboxId}/commands/{processId}` for status and logs. Returns 400 invalid_timeout when `timeoutSeconds` is not an integer in 1-600.
|
|
998
|
+
* Runs the command synchronously by default (timeout configurable via `timeoutSeconds`, 600s cap). With `detached: true` the command starts in the background and a process id is returned immediately; poll `/sandboxes/{sandboxId}/commands/{processId}` for status and logs. With `stream: true` the output arrives as the command writes it, as newline-delimited JSON (`application/x-ndjson`): `{\"type\":\"started\"}`, then `{\"type\":\"stdout\"|\"stderr\",\"data\":\"...\"}` per chunk, and last `{\"type\":\"exit\",...}` (the synchronous result without stdout/stderr) or `{\"type\":\"error\",\"error\":\"...\",\"message\":\"...\"}`. Returns 400 invalid_timeout when `timeoutSeconds` is not an integer in 1-600. A command sent while the sandbox is still starting (right after a resume) waits up to 60s for it to be ready instead of failing; 409 boat_starting (retryable) is returned only if it is still not ready by then. A sandbox that is not starting (`error`, `stopped`) returns 409 sandbox_not_ready with `retryable: false` and its `state` at once: resume or recover it first. Command execution is never retried automatically: a 502 boat_direct_failed means the command may already be running on the sandbox.
|
|
994
999
|
* Execute a command in a sandbox
|
|
995
1000
|
*/
|
|
996
1001
|
async command(requestParameters: CommandOperationRequest | runtime.FlatBody<CommandOperationRequest, 'commandRequest'>, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Command200Response> {
|
package/src/boat-helpers.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import { BoatApi } from './apis/BoatApi';
|
|
3
|
-
import type { Sandbox, SandboxEvent, DesktopResponse, PromptRequest, PromptRun } from './models';
|
|
3
|
+
import type { Sandbox, SandboxEvent, DesktopResponse, PromptRequest, PromptRun, CommandStreamFrame } from './models';
|
|
4
4
|
|
|
5
5
|
export type WaitOptions = {
|
|
6
6
|
timeoutMs?: number;
|
|
@@ -185,3 +185,77 @@ export async function writeText(api: BoatApi, sandboxId: string, path: string, c
|
|
|
185
185
|
export async function execCommand(api: BoatApi, sandboxId: string, command: string, cwd?: string, timeoutSeconds = 30) {
|
|
186
186
|
return api.command({ sandboxId, commandRequest: { command, cwd, timeoutSeconds } });
|
|
187
187
|
}
|
|
188
|
+
|
|
189
|
+
/** One line of a streamed command, as sent (an `exit` frame also carries e.g. `signal`, `oomKilled`). */
|
|
190
|
+
export type CommandFrame = CommandStreamFrame & Record<string, unknown>;
|
|
191
|
+
|
|
192
|
+
export type StreamCommandOptions = {
|
|
193
|
+
cwd?: string;
|
|
194
|
+
timeoutSeconds?: number;
|
|
195
|
+
signal?: AbortSignal;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// A refusal that provably never ran the command (the sandbox is still restoring
|
|
199
|
+
// its disk, `retryable: true` before `started`) is retried for this long.
|
|
200
|
+
const STREAM_RETRY_MS = 60_000;
|
|
201
|
+
|
|
202
|
+
async function* ndjsonFrames(response: Response): AsyncGenerator<CommandFrame> {
|
|
203
|
+
if (!response.body) throw new Error('The command stream has no body');
|
|
204
|
+
const reader = response.body.getReader();
|
|
205
|
+
const decoder = new TextDecoder();
|
|
206
|
+
let buffered = '';
|
|
207
|
+
let done = false;
|
|
208
|
+
try {
|
|
209
|
+
while (!done) {
|
|
210
|
+
const chunk = await reader.read();
|
|
211
|
+
done = chunk.done;
|
|
212
|
+
buffered += done ? decoder.decode() : decoder.decode(chunk.value, { stream: true });
|
|
213
|
+
let newline = buffered.indexOf('\n');
|
|
214
|
+
while (newline !== -1) {
|
|
215
|
+
const line = buffered.slice(0, newline).trim();
|
|
216
|
+
buffered = buffered.slice(newline + 1);
|
|
217
|
+
if (line) yield JSON.parse(line);
|
|
218
|
+
newline = buffered.indexOf('\n');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (buffered.trim()) yield JSON.parse(buffered);
|
|
222
|
+
} finally {
|
|
223
|
+
// The caller stopped early: drop the connection instead of reading the rest.
|
|
224
|
+
if (!done) await reader.cancel().catch(() => undefined);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Run a command and yield its output as the command writes it: `started`, then
|
|
230
|
+
* `stdout`/`stderr` chunks (`frame.data`), then exactly one `exit` (`exitCode`,
|
|
231
|
+
* `timedOut`, ...) or `error` (`error`, `message`).
|
|
232
|
+
*
|
|
233
|
+
* Safe to call right after `resume()`, without waiting: the API holds a command
|
|
234
|
+
* sent to a starting sandbox for up to 60 s instead of refusing it.
|
|
235
|
+
*/
|
|
236
|
+
export async function* streamCommand(
|
|
237
|
+
api: BoatApi,
|
|
238
|
+
sandboxId: string,
|
|
239
|
+
command: string,
|
|
240
|
+
options?: StreamCommandOptions,
|
|
241
|
+
): AsyncGenerator<CommandFrame> {
|
|
242
|
+
const deadline = Date.now() + STREAM_RETRY_MS;
|
|
243
|
+
for (;;) {
|
|
244
|
+
const response = await api.commandRaw(
|
|
245
|
+
{ sandboxId, commandRequest: { command, cwd: options?.cwd, timeoutSeconds: options?.timeoutSeconds, stream: true } },
|
|
246
|
+
{ signal: options?.signal },
|
|
247
|
+
);
|
|
248
|
+
let started = false;
|
|
249
|
+
let retry = false;
|
|
250
|
+
for await (const frame of ndjsonFrames(response.raw)) {
|
|
251
|
+
started = started || frame.type === 'started';
|
|
252
|
+
if (!started && frame.type === 'error' && frame.retryable === true && Date.now() < deadline) {
|
|
253
|
+
retry = true;
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
yield frame;
|
|
257
|
+
}
|
|
258
|
+
if (!retry) return;
|
|
259
|
+
await delay(1_000);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -43,6 +43,12 @@ export interface CommandRequest {
|
|
|
43
43
|
* @memberof CommandRequest
|
|
44
44
|
*/
|
|
45
45
|
detached?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Stream the output as the command writes it: the response is newline-delimited JSON (application/x-ndjson), one CommandStreamFrame per line. Ignored with detached.
|
|
48
|
+
* @type {boolean}
|
|
49
|
+
* @memberof CommandRequest
|
|
50
|
+
*/
|
|
51
|
+
stream?: boolean;
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
/**
|
|
@@ -67,6 +73,7 @@ export function CommandRequestFromJSONTyped(json: any, ignoreDiscriminator: bool
|
|
|
67
73
|
'cwd': json['cwd'] == null ? undefined : json['cwd'],
|
|
68
74
|
'timeoutSeconds': json['timeoutSeconds'] == null ? undefined : json['timeoutSeconds'],
|
|
69
75
|
'detached': json['detached'] == null ? undefined : json['detached'],
|
|
76
|
+
'stream': json['stream'] == null ? undefined : json['stream'],
|
|
70
77
|
};
|
|
71
78
|
}
|
|
72
79
|
|
|
@@ -85,6 +92,7 @@ export function CommandRequestToJSONTyped(value?: CommandRequest | null, ignoreD
|
|
|
85
92
|
'cwd': value['cwd'],
|
|
86
93
|
'timeoutSeconds': value['timeoutSeconds'],
|
|
87
94
|
'detached': value['detached'],
|
|
95
|
+
'stream': value['stream'],
|
|
88
96
|
};
|
|
89
97
|
}
|
|
90
98
|
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
/**
|
|
4
|
+
* Boat Public API v1
|
|
5
|
+
* Public JSON API for creating, operating, prompting, observing, and exposing sandboxes from backend services, CI jobs, hosted workers, and Boat automation products. The v1 reference intentionally documents the developer integration surface only. Dashboard billing actions are not part of v1.
|
|
6
|
+
*
|
|
7
|
+
* The version of the OpenAPI document: 1.0.0
|
|
8
|
+
*
|
|
9
|
+
*
|
|
10
|
+
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
|
11
|
+
* https://openapi-generator.tech
|
|
12
|
+
* Do not edit the class manually.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { mapValues } from '../runtime';
|
|
16
|
+
/**
|
|
17
|
+
* One line of a streamed command. `started` first, then `stdout`/`stderr` chunks, then exactly one `exit` (the synchronous result, without stdout/stderr) or `error`.
|
|
18
|
+
* @export
|
|
19
|
+
* @interface CommandStreamFrame
|
|
20
|
+
*/
|
|
21
|
+
export interface CommandStreamFrame {
|
|
22
|
+
/**
|
|
23
|
+
*
|
|
24
|
+
* @type {CommandStreamFrameTypeEnum}
|
|
25
|
+
* @memberof CommandStreamFrame
|
|
26
|
+
*/
|
|
27
|
+
type: CommandStreamFrameTypeEnum;
|
|
28
|
+
/**
|
|
29
|
+
* stdout/stderr: the chunk of output, UTF-8.
|
|
30
|
+
* @type {string}
|
|
31
|
+
* @memberof CommandStreamFrame
|
|
32
|
+
*/
|
|
33
|
+
data?: string;
|
|
34
|
+
/**
|
|
35
|
+
*
|
|
36
|
+
* @type {number}
|
|
37
|
+
* @memberof CommandStreamFrame
|
|
38
|
+
*/
|
|
39
|
+
exitCode?: number | null;
|
|
40
|
+
/**
|
|
41
|
+
*
|
|
42
|
+
* @type {boolean}
|
|
43
|
+
* @memberof CommandStreamFrame
|
|
44
|
+
*/
|
|
45
|
+
success?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
*
|
|
48
|
+
* @type {boolean}
|
|
49
|
+
* @memberof CommandStreamFrame
|
|
50
|
+
*/
|
|
51
|
+
timedOut?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
*
|
|
54
|
+
* @type {string}
|
|
55
|
+
* @memberof CommandStreamFrame
|
|
56
|
+
*/
|
|
57
|
+
error?: string;
|
|
58
|
+
/**
|
|
59
|
+
*
|
|
60
|
+
* @type {string}
|
|
61
|
+
* @memberof CommandStreamFrame
|
|
62
|
+
*/
|
|
63
|
+
message?: string;
|
|
64
|
+
/**
|
|
65
|
+
* error: true when the command provably never ran (the sandbox is still restoring its disk), so sending it again is safe.
|
|
66
|
+
* @type {boolean}
|
|
67
|
+
* @memberof CommandStreamFrame
|
|
68
|
+
*/
|
|
69
|
+
retryable?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @export
|
|
75
|
+
*/
|
|
76
|
+
export const CommandStreamFrameTypeEnum = {
|
|
77
|
+
Started: 'started',
|
|
78
|
+
Stdout: 'stdout',
|
|
79
|
+
Stderr: 'stderr',
|
|
80
|
+
Exit: 'exit',
|
|
81
|
+
Error: 'error'
|
|
82
|
+
} as const;
|
|
83
|
+
export type CommandStreamFrameTypeEnum = typeof CommandStreamFrameTypeEnum[keyof typeof CommandStreamFrameTypeEnum];
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check if a given object implements the CommandStreamFrame interface.
|
|
88
|
+
*/
|
|
89
|
+
export function instanceOfCommandStreamFrame(value: object): value is CommandStreamFrame {
|
|
90
|
+
if (!('type' in value) || value['type'] === undefined) return false;
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function CommandStreamFrameFromJSON(json: any): CommandStreamFrame {
|
|
95
|
+
return CommandStreamFrameFromJSONTyped(json, false);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function CommandStreamFrameFromJSONTyped(json: any, ignoreDiscriminator: boolean): CommandStreamFrame {
|
|
99
|
+
if (json == null) {
|
|
100
|
+
return json;
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
|
|
104
|
+
'type': json['type'],
|
|
105
|
+
'data': json['data'] == null ? undefined : json['data'],
|
|
106
|
+
'exitCode': json['exitCode'] == null ? undefined : json['exitCode'],
|
|
107
|
+
'success': json['success'] == null ? undefined : json['success'],
|
|
108
|
+
'timedOut': json['timedOut'] == null ? undefined : json['timedOut'],
|
|
109
|
+
'error': json['error'] == null ? undefined : json['error'],
|
|
110
|
+
'message': json['message'] == null ? undefined : json['message'],
|
|
111
|
+
'retryable': json['retryable'] == null ? undefined : json['retryable'],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function CommandStreamFrameToJSON(json: any): CommandStreamFrame {
|
|
116
|
+
return CommandStreamFrameToJSONTyped(json, false);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function CommandStreamFrameToJSONTyped(value?: CommandStreamFrame | null, ignoreDiscriminator: boolean = false): any {
|
|
120
|
+
if (value == null) {
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
|
|
126
|
+
'type': value['type'],
|
|
127
|
+
'data': value['data'],
|
|
128
|
+
'exitCode': value['exitCode'],
|
|
129
|
+
'success': value['success'],
|
|
130
|
+
'timedOut': value['timedOut'],
|
|
131
|
+
'error': value['error'],
|
|
132
|
+
'message': value['message'],
|
|
133
|
+
'retryable': value['retryable'],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
@@ -55,6 +55,12 @@ export interface CreateSandboxRequest {
|
|
|
55
55
|
* @memberof CreateSandboxRequest
|
|
56
56
|
*/
|
|
57
57
|
snapshots?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Pass `true` to get a sandbox only if a machine of this type is ready right now. The call answers within about 1.5 s: either the usual `202` with the sandbox, or `503` `no_ready_machine` with nothing created, billed, or counted against your start limits, so you can retry or fall back at once. Without it, a create waits for a machine to be built when none is ready. Works with `from`; fork and resume take it too.
|
|
60
|
+
* @type {boolean}
|
|
61
|
+
* @memberof CreateSandboxRequest
|
|
62
|
+
*/
|
|
63
|
+
failFast?: boolean;
|
|
58
64
|
/**
|
|
59
65
|
* Shell script that runs on the sandbox after it is ready. Ready means "ready to accept the user", not "setup done": the script starts in the background once provisioning completes and never blocks the sandbox becoming usable. It runs as the sandbox user via `bash`, with the sandbox's environment applied, and its output goes to a log file on the sandbox. Observe the outcome as `setupStatus` (pending/running/done/failed) and `setupError` on the sandbox. Rejected with a 400 `invalid_setup_script` error when it is not a string or exceeds 64KB.
|
|
60
66
|
* @type {string}
|
|
@@ -116,6 +122,7 @@ export function CreateSandboxRequestFromJSONTyped(json: any, ignoreDiscriminator
|
|
|
116
122
|
'environment': json['environment'] == null ? undefined : json['environment'],
|
|
117
123
|
'noEnv': json['noEnv'] == null ? undefined : json['noEnv'],
|
|
118
124
|
'snapshots': json['snapshots'] == null ? undefined : json['snapshots'],
|
|
125
|
+
'failFast': json['failFast'] == null ? undefined : json['failFast'],
|
|
119
126
|
'setupScript': json['setupScript'] == null ? undefined : json['setupScript'],
|
|
120
127
|
'org': json['org'] == null ? undefined : json['org'],
|
|
121
128
|
'teamId': json['teamId'] == null ? undefined : json['teamId'],
|
|
@@ -140,6 +147,7 @@ export function CreateSandboxRequestToJSONTyped(value?: CreateSandboxRequest | n
|
|
|
140
147
|
'environment': value['environment'],
|
|
141
148
|
'noEnv': value['noEnv'],
|
|
142
149
|
'snapshots': value['snapshots'],
|
|
150
|
+
'failFast': value['failFast'],
|
|
143
151
|
'setupScript': value['setupScript'],
|
|
144
152
|
'org': value['org'],
|
|
145
153
|
'teamId': value['teamId'],
|
|
@@ -19,6 +19,12 @@ import { mapValues } from '../runtime';
|
|
|
19
19
|
* @interface ForkRequest
|
|
20
20
|
*/
|
|
21
21
|
export interface ForkRequest {
|
|
22
|
+
/**
|
|
23
|
+
* Pass `true` to fork only if a machine is ready right now. Within about 1.5 s you get the usual `202`, or `503` `no_ready_machine` with no fork created and nothing counted against your start limits.
|
|
24
|
+
* @type {boolean}
|
|
25
|
+
* @memberof ForkRequest
|
|
26
|
+
*/
|
|
27
|
+
failFast?: boolean;
|
|
22
28
|
/**
|
|
23
29
|
* Replaces the env the fork would otherwise inherit from the source sandbox. Same validation rules as `CreateSandboxRequest.env`.
|
|
24
30
|
* @type {{ [key: string]: string; }}
|
|
@@ -80,6 +86,7 @@ export function ForkRequestFromJSONTyped(json: any, ignoreDiscriminator: boolean
|
|
|
80
86
|
}
|
|
81
87
|
return {
|
|
82
88
|
|
|
89
|
+
'failFast': json['failFast'] == null ? undefined : json['failFast'],
|
|
83
90
|
'env': json['env'] == null ? undefined : json['env'],
|
|
84
91
|
'environment': json['environment'] == null ? undefined : json['environment'],
|
|
85
92
|
'noEnv': json['noEnv'] == null ? undefined : json['noEnv'],
|
|
@@ -99,6 +106,7 @@ export function ForkRequestToJSONTyped(value?: ForkRequest | null, ignoreDiscrim
|
|
|
99
106
|
|
|
100
107
|
return {
|
|
101
108
|
|
|
109
|
+
'failFast': value['failFast'],
|
|
102
110
|
'env': value['env'],
|
|
103
111
|
'environment': value['environment'],
|
|
104
112
|
'noEnv': value['noEnv'],
|
|
@@ -19,6 +19,12 @@ import { mapValues } from '../runtime';
|
|
|
19
19
|
* @interface ResumeRequest
|
|
20
20
|
*/
|
|
21
21
|
export interface ResumeRequest {
|
|
22
|
+
/**
|
|
23
|
+
* Pass `true` to resume only if a machine is ready right now. Within about 1.5 s you get the usual `202`, or `503` `no_ready_machine` with the sandbox left stopped and nothing counted against your start limits.
|
|
24
|
+
* @type {boolean}
|
|
25
|
+
* @memberof ResumeRequest
|
|
26
|
+
*/
|
|
27
|
+
failFast?: boolean;
|
|
22
28
|
/**
|
|
23
29
|
* Resume onto a different machine size. Omit to keep the sandbox's current type. A resume already restores onto a fresh machine, so changing size costs nothing extra. Shrinking is rejected with `type_too_small` when the sandbox's data would not fit the smaller disk.
|
|
24
30
|
* @type {ResumeRequestTypeEnum}
|
|
@@ -80,6 +86,7 @@ export function ResumeRequestFromJSONTyped(json: any, ignoreDiscriminator: boole
|
|
|
80
86
|
}
|
|
81
87
|
return {
|
|
82
88
|
|
|
89
|
+
'failFast': json['failFast'] == null ? undefined : json['failFast'],
|
|
83
90
|
'type': json['type'] == null ? undefined : json['type'],
|
|
84
91
|
'env': json['env'] == null ? undefined : json['env'],
|
|
85
92
|
'environment': json['environment'] == null ? undefined : json['environment'],
|
|
@@ -99,6 +106,7 @@ export function ResumeRequestToJSONTyped(value?: ResumeRequest | null, ignoreDis
|
|
|
99
106
|
|
|
100
107
|
return {
|
|
101
108
|
|
|
109
|
+
'failFast': value['failFast'],
|
|
102
110
|
'type': value['type'],
|
|
103
111
|
'env': value['env'],
|
|
104
112
|
'environment': value['environment'],
|
package/src/models/Sandbox.ts
CHANGED
|
@@ -51,6 +51,24 @@ export interface Sandbox {
|
|
|
51
51
|
* @memberof Sandbox
|
|
52
52
|
*/
|
|
53
53
|
error?: string | null;
|
|
54
|
+
/**
|
|
55
|
+
* `degraded` when the machine is alive but Boat cannot fully reach it (for example a firewall rule inside the sandbox blocks the tunnel). The sandbox keeps running.
|
|
56
|
+
* @type {SandboxHealthEnum}
|
|
57
|
+
* @memberof Sandbox
|
|
58
|
+
*/
|
|
59
|
+
health?: SandboxHealthEnum;
|
|
60
|
+
/**
|
|
61
|
+
* Why the sandbox is degraded, or null.
|
|
62
|
+
* @type {string}
|
|
63
|
+
* @memberof Sandbox
|
|
64
|
+
*/
|
|
65
|
+
healthReason?: string | null;
|
|
66
|
+
/**
|
|
67
|
+
* When the sandbox became degraded, or null.
|
|
68
|
+
* @type {Date}
|
|
69
|
+
* @memberof Sandbox
|
|
70
|
+
*/
|
|
71
|
+
degradedSince?: Date | null;
|
|
54
72
|
/**
|
|
55
73
|
* Current machine size: what the sandbox was created with, or the size it was last resumed or forked onto.
|
|
56
74
|
* @type {SandboxTypeEnum}
|
|
@@ -222,6 +240,15 @@ export const SandboxStateEnum = {
|
|
|
222
240
|
} as const;
|
|
223
241
|
export type SandboxStateEnum = typeof SandboxStateEnum[keyof typeof SandboxStateEnum];
|
|
224
242
|
|
|
243
|
+
/**
|
|
244
|
+
* @export
|
|
245
|
+
*/
|
|
246
|
+
export const SandboxHealthEnum = {
|
|
247
|
+
Ok: 'ok',
|
|
248
|
+
Degraded: 'degraded'
|
|
249
|
+
} as const;
|
|
250
|
+
export type SandboxHealthEnum = typeof SandboxHealthEnum[keyof typeof SandboxHealthEnum];
|
|
251
|
+
|
|
225
252
|
/**
|
|
226
253
|
* @export
|
|
227
254
|
*/
|
|
@@ -291,6 +318,9 @@ export function SandboxFromJSONTyped(json: any, ignoreDiscriminator: boolean): S
|
|
|
291
318
|
'name': json['name'],
|
|
292
319
|
'state': json['state'],
|
|
293
320
|
'error': json['error'] == null ? undefined : json['error'],
|
|
321
|
+
'health': json['health'] == null ? undefined : json['health'],
|
|
322
|
+
'healthReason': json['healthReason'] == null ? undefined : json['healthReason'],
|
|
323
|
+
'degradedSince': json['degradedSince'] == null ? undefined : (new Date(json['degradedSince'])),
|
|
294
324
|
'type': json['type'] == null ? undefined : json['type'],
|
|
295
325
|
'vcpu': json['vcpu'] == null ? undefined : json['vcpu'],
|
|
296
326
|
'memoryGB': json['memoryGB'] == null ? undefined : json['memoryGB'],
|
|
@@ -334,6 +364,9 @@ export function SandboxToJSONTyped(value?: Sandbox | null, ignoreDiscriminator:
|
|
|
334
364
|
'name': value['name'],
|
|
335
365
|
'state': value['state'],
|
|
336
366
|
'error': value['error'],
|
|
367
|
+
'health': value['health'],
|
|
368
|
+
'healthReason': value['healthReason'],
|
|
369
|
+
'degradedSince': value['degradedSince'] == null ? value['degradedSince'] : value['degradedSince'].toISOString(),
|
|
337
370
|
'type': value['type'],
|
|
338
371
|
'vcpu': value['vcpu'],
|
|
339
372
|
'memoryGB': value['memoryGB'],
|