@solarisdk/core 0.3.0 → 0.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/LICENSE +17 -13
- package/README.md +5 -1
- package/dist/chunked-download.d.ts +0 -1
- package/dist/chunked-download.js +0 -11
- package/dist/desktop.d.ts +0 -1
- package/dist/desktop.js +0 -52
- package/dist/errors.d.ts +10 -1
- package/dist/errors.js +16 -39
- package/dist/handle.d.ts +14 -2
- package/dist/handle.js +91 -234
- package/dist/http.d.ts +2 -1
- package/dist/http.js +1 -49
- package/dist/ids.d.ts +6 -0
- package/dist/ids.js +3 -0
- package/dist/image.d.ts +0 -1
- package/dist/image.js +0 -40
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -5
- package/dist/sandbox.d.ts +0 -1
- package/dist/sandbox.js +0 -6
- package/dist/template-client.d.ts +0 -1
- package/dist/template-client.js +3 -27
- package/dist/transport.d.ts +9 -2
- package/dist/transport.js +64 -102
- package/dist/types.d.ts +19 -7
- package/dist/types.js +0 -7
- package/dist/volume-client.d.ts +0 -1
- package/dist/volume-client.js +0 -17
- package/dist/ws.d.ts +0 -1
- package/dist/ws.js +0 -16
- package/package.json +5 -4
- package/dist/chunked-download.d.ts.map +0 -1
- package/dist/chunked-download.js.map +0 -1
- package/dist/desktop.d.ts.map +0 -1
- package/dist/desktop.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/handle.d.ts.map +0 -1
- package/dist/handle.js.map +0 -1
- package/dist/http.d.ts.map +0 -1
- package/dist/http.js.map +0 -1
- package/dist/image.d.ts.map +0 -1
- package/dist/image.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/sandbox.d.ts.map +0 -1
- package/dist/sandbox.js.map +0 -1
- package/dist/template-client.d.ts.map +0 -1
- package/dist/template-client.js.map +0 -1
- package/dist/transport.d.ts.map +0 -1
- package/dist/transport.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/volume-client.d.ts.map +0 -1
- package/dist/volume-client.js.map +0 -1
- package/dist/ws.d.ts.map +0 -1
- package/dist/ws.js.map +0 -1
package/LICENSE
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
Version 2.0, January 2004
|
|
3
|
-
http://www.apache.org/licenses/
|
|
1
|
+
MIT License
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
Copyright (c) 2026 Solari
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -16,6 +16,10 @@ npm install @solarisdk/sdk # unified client + `solari` CLI
|
|
|
16
16
|
|
|
17
17
|
`@solarisdk/core` is a dependency of all three and is installed automatically.
|
|
18
18
|
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
Node 18+ (or a browser with `WebSocket` and `fetch`). The type declarations need TypeScript 5.0 or newer; `await using` needs TypeScript 5.2+ and a runtime with `Symbol.asyncDispose` (Node 24, or a polyfill).
|
|
22
|
+
|
|
19
23
|
## License
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
MIT
|
package/dist/chunked-download.js
CHANGED
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
import { SolariError } from "./errors.js";
|
|
2
2
|
import { base64ToBytes } from "./transport.js";
|
|
3
|
-
/** Default ceiling for one download: the whole file is held in memory. */
|
|
4
3
|
export const DEFAULT_MAX_DOWNLOAD_BYTES = 2 * 1024 * 1024 * 1024;
|
|
5
4
|
const MAX_CHUNKS = 20_000;
|
|
6
|
-
/**
|
|
7
|
-
* Download a whole in-guest file. `fs.download` returns at most one chunk (1 MiB by default) per call and says
|
|
8
|
-
* where the next one starts, so a single call silently truncates anything bigger. This follows `nextOffset` until
|
|
9
|
-
* `eof`. An agent that does not report `eof` sent the whole file in one reply, as before.
|
|
10
|
-
*
|
|
11
|
-
* Guards: the result is capped at `maxBytes` (default 2 GiB) and at a fixed chunk count, so an agent that never
|
|
12
|
-
* says `eof` cannot loop forever or exhaust memory; a `nextOffset` that is not exactly where the chunk ended would
|
|
13
|
-
* silently skip or repeat bytes, so it is an error; a file whose reported size changes mid-read is an error.
|
|
14
|
-
*/
|
|
15
5
|
export async function downloadAll(call, path, opts = {}) {
|
|
16
6
|
const maxBytes = opts.maxBytes ?? DEFAULT_MAX_DOWNLOAD_BYTES;
|
|
17
7
|
const fail = (why) => new SolariError(`fs.download ${JSON.stringify(path)}: ${why}`);
|
|
@@ -52,4 +42,3 @@ export async function downloadAll(call, path, opts = {}) {
|
|
|
52
42
|
}
|
|
53
43
|
return out;
|
|
54
44
|
}
|
|
55
|
-
//# sourceMappingURL=chunked-download.js.map
|
package/dist/desktop.d.ts
CHANGED
package/dist/desktop.js
CHANGED
|
@@ -1,22 +1,5 @@
|
|
|
1
|
-
// `Desktop` — a handle to one live GUI session. Extends the shared
|
|
2
|
-
// {@link SessionHandle} (commands, pty, runCode, files, metrics, snapshot,
|
|
3
|
-
// revert, pause/resume, setTimeout, env, volumes, kill) and adds the
|
|
4
|
-
// computer-use GUI surface (CONTRACTS §4 + CONTRACTS-V2 §4): screenshot,
|
|
5
|
-
// mouse.*, keyboard.*, display.*, clipboard.*, open, stream.*, record.*.
|
|
6
|
-
//
|
|
7
|
-
// Back-compat: the v1 `DesktopClient` constructs `new Desktop(session, config)`
|
|
8
|
-
// where `config.onPause(sessionId)` calls `POST /desktops/:id/pause`. That path
|
|
9
|
-
// is preserved; the older convenience methods (`exec`, `execStream`, `health`,
|
|
10
|
-
// `process`, `ports`, `pkg`) are kept too.
|
|
11
1
|
import { SessionHandle } from "./handle.js";
|
|
12
2
|
import { base64ToBytes } from "./transport.js";
|
|
13
|
-
/**
|
|
14
|
-
* Map a named {@link MouseButton} to the X11/xdotool button code the guest
|
|
15
|
-
* agent expects (`mouseParams.button` is an `int`): left=1, middle=2, right=3.
|
|
16
|
-
* `undefined` is passed through so the guest applies its own default (left).
|
|
17
|
-
* Without this the wire would carry a raw string and the guest's JSON decode
|
|
18
|
-
* fails with `cannot unmarshal string into ... button of type int`.
|
|
19
|
-
*/
|
|
20
3
|
function buttonToCode(button) {
|
|
21
4
|
if (button === undefined)
|
|
22
5
|
return undefined;
|
|
@@ -29,22 +12,13 @@ function buttonToCode(button) {
|
|
|
29
12
|
return 3;
|
|
30
13
|
}
|
|
31
14
|
}
|
|
32
|
-
/** A live desktop session. Construct via {@link DesktopClient}. */
|
|
33
15
|
export class Desktop extends SessionHandle {
|
|
34
16
|
streamUrl;
|
|
35
|
-
/**
|
|
36
|
-
* Presigned playback URL for the session recording — set when the session was
|
|
37
|
-
* created with `record: true`, `undefined` otherwise. The guest uploads the
|
|
38
|
-
* mp4 on `record.stop()`, so the URL only resolves once a recording has been
|
|
39
|
-
* started and stopped.
|
|
40
|
-
*/
|
|
41
17
|
recordingUrl;
|
|
42
|
-
/** The session id (alias of {@link SessionHandle.id}; back-compat). */
|
|
43
18
|
get sessionId() {
|
|
44
19
|
return this.id;
|
|
45
20
|
}
|
|
46
21
|
constructor(session, config = {}) {
|
|
47
|
-
// Fold the legacy onPause shim into the unified hooks.pause.
|
|
48
22
|
const hooks = { ...(config.hooks ?? {}) };
|
|
49
23
|
if (config.onPause && !hooks.pause) {
|
|
50
24
|
const onPause = config.onPause;
|
|
@@ -57,16 +31,12 @@ export class Desktop extends SessionHandle {
|
|
|
57
31
|
this.streamUrl = session.streamUrl;
|
|
58
32
|
this.recordingUrl = session.recordingUrl;
|
|
59
33
|
}
|
|
60
|
-
// GUI RPCs go over the shared control channel via the protected `rpc` hook.
|
|
61
34
|
guiCall(method, params) {
|
|
62
35
|
return this.rpc(method, params);
|
|
63
36
|
}
|
|
64
|
-
// --- readiness ------------------------------------------------------------
|
|
65
|
-
/** `health` — readiness probe (display + VNC + agent). */
|
|
66
37
|
health() {
|
|
67
38
|
return this.guiCall("health", {});
|
|
68
39
|
}
|
|
69
|
-
// --- exec (v1 convenience; prefer `commands.run`) -------------------------
|
|
70
40
|
exec(cmd, opts = {}) {
|
|
71
41
|
return this.guiCall("exec", {
|
|
72
42
|
cmd,
|
|
@@ -82,7 +52,6 @@ export class Desktop extends SessionHandle {
|
|
|
82
52
|
onChunk({ stream: c.stream, text: new TextDecoder().decode(bytes), bytes });
|
|
83
53
|
});
|
|
84
54
|
}
|
|
85
|
-
// --- fs (v1 alias kept; `files` is the canonical surface) -----------------
|
|
86
55
|
fs = {
|
|
87
56
|
read: (path) => this.files.read(path),
|
|
88
57
|
readText: (path) => this.files.readText(path),
|
|
@@ -92,7 +61,6 @@ export class Desktop extends SessionHandle {
|
|
|
92
61
|
remove: (path, recursive = false) => this.files.remove(path, recursive),
|
|
93
62
|
mkdir: (path) => this.files.mkdir(path),
|
|
94
63
|
};
|
|
95
|
-
// --- mouse ----------------------------------------------------------------
|
|
96
64
|
mouse = {
|
|
97
65
|
move: (x, y, opts = {}) => this.mouseCall(x, y, "move", undefined, opts.humanize),
|
|
98
66
|
click: (x, y, opts = {}) => this.mouseCall(x, y, "click", opts.button, opts.humanize),
|
|
@@ -115,11 +83,9 @@ export class Desktop extends SessionHandle {
|
|
|
115
83
|
humanize,
|
|
116
84
|
}).then(() => undefined);
|
|
117
85
|
}
|
|
118
|
-
// --- keyboard -------------------------------------------------------------
|
|
119
86
|
keyboard = {
|
|
120
87
|
type: (text) => this.keyCall({ text, action: "press" }),
|
|
121
88
|
press: (keys) => this.keyCall({ keys: Array.isArray(keys) ? keys : [keys], action: "press" }),
|
|
122
|
-
/** Press a chord (alias of `press` for an array, e.g. `hotkey("ctrl","c")`). */
|
|
123
89
|
hotkey: (...keys) => this.keyCall({ keys, action: "press" }),
|
|
124
90
|
down: (keys) => this.keyCall({ keys: Array.isArray(keys) ? keys : [keys], action: "down" }),
|
|
125
91
|
up: (keys) => this.keyCall({ keys: Array.isArray(keys) ? keys : [keys], action: "up" }),
|
|
@@ -127,7 +93,6 @@ export class Desktop extends SessionHandle {
|
|
|
127
93
|
keyCall(params) {
|
|
128
94
|
return this.guiCall("input.key", params).then(() => undefined);
|
|
129
95
|
}
|
|
130
|
-
// --- screenshot -----------------------------------------------------------
|
|
131
96
|
async screenshot(opts = {}) {
|
|
132
97
|
const r = await this.guiCall("screenshot", {
|
|
133
98
|
format: opts.format ?? "png",
|
|
@@ -135,15 +100,11 @@ export class Desktop extends SessionHandle {
|
|
|
135
100
|
});
|
|
136
101
|
return base64ToBytes(r.base64);
|
|
137
102
|
}
|
|
138
|
-
// --- display --------------------------------------------------------------
|
|
139
103
|
display = {
|
|
140
104
|
set: (w, h) => this.guiCall("display.set", { w, h }).then(() => undefined),
|
|
141
|
-
/** Current display size. */
|
|
142
105
|
size: () => this.guiCall("display.size", {}),
|
|
143
|
-
/** Current cursor position. */
|
|
144
106
|
cursor: () => this.guiCall("display.cursor", {}),
|
|
145
107
|
};
|
|
146
|
-
// --- clipboard ------------------------------------------------------------
|
|
147
108
|
clipboard = {
|
|
148
109
|
get: async () => {
|
|
149
110
|
const r = await this.guiCall("clipboard.get", {});
|
|
@@ -151,26 +112,17 @@ export class Desktop extends SessionHandle {
|
|
|
151
112
|
},
|
|
152
113
|
set: (text) => this.guiCall("clipboard.set", { text }).then(() => undefined),
|
|
153
114
|
};
|
|
154
|
-
// --- open an app ----------------------------------------------------------
|
|
155
|
-
/** Launch a GUI app by name (`app.open`); returns its pid. */
|
|
156
115
|
async open(name, args) {
|
|
157
116
|
const r = await this.guiCall("app.open", { name, args });
|
|
158
117
|
return r.pid;
|
|
159
118
|
}
|
|
160
|
-
// --- live stream ----------------------------------------------------------
|
|
161
119
|
stream = {
|
|
162
|
-
/** Start/obtain the live VNC stream; returns the embeddable URL + token. */
|
|
163
120
|
start: async () => {
|
|
164
|
-
// The stream URL is minted at create time; expose it (+ any token) here so
|
|
165
|
-
// callers have a single `stream.start()` entry point.
|
|
166
121
|
return { streamUrl: this.streamUrl };
|
|
167
122
|
},
|
|
168
|
-
/** Stop the live stream (best-effort; the channel stays up). */
|
|
169
123
|
stop: async () => {
|
|
170
|
-
/* the RFB stream is a separate socket the caller owns; nothing to tear down here */
|
|
171
124
|
},
|
|
172
125
|
};
|
|
173
|
-
// --- process (v1 convenience) ---------------------------------------------
|
|
174
126
|
process = {
|
|
175
127
|
list: async () => {
|
|
176
128
|
const r = await this.guiCall("process.list", {});
|
|
@@ -187,21 +139,17 @@ export class Desktop extends SessionHandle {
|
|
|
187
139
|
},
|
|
188
140
|
signal: (pid, signal) => this.guiCall("process.signal", { pid, signal }).then(() => undefined),
|
|
189
141
|
};
|
|
190
|
-
// --- ports (v1 convenience) -----------------------------------------------
|
|
191
142
|
ports = {
|
|
192
143
|
list: async () => {
|
|
193
144
|
const r = await this.guiCall("ports.list", {});
|
|
194
145
|
return r.ports;
|
|
195
146
|
},
|
|
196
147
|
};
|
|
197
|
-
// --- packages -------------------------------------------------------------
|
|
198
148
|
pkg = {
|
|
199
149
|
install: (manager, packages) => this.guiCall("pkg.install", { manager, packages }),
|
|
200
150
|
};
|
|
201
|
-
// --- recording ------------------------------------------------------------
|
|
202
151
|
record = {
|
|
203
152
|
start: (opts = {}) => this.guiCall("record.start", opts),
|
|
204
153
|
stop: () => this.guiCall("record.stop", {}),
|
|
205
154
|
};
|
|
206
155
|
}
|
|
207
|
-
//# sourceMappingURL=desktop.js.map
|
package/dist/errors.d.ts
CHANGED
|
@@ -8,8 +8,18 @@ export declare class GatewayError extends SolariError {
|
|
|
8
8
|
readonly status: number;
|
|
9
9
|
readonly code?: string;
|
|
10
10
|
readonly body?: GatewayErrorBody;
|
|
11
|
+
/** The gateway's hint that retrying may help (`body.retryable`), on every gateway error. */
|
|
12
|
+
readonly retryable?: boolean;
|
|
11
13
|
constructor(status: number, message: string, body?: GatewayErrorBody);
|
|
12
14
|
}
|
|
15
|
+
/** `404` — the sandbox, desktop, snapshot, template or volume does not exist (or is not yours). */
|
|
16
|
+
export declare class NotFoundError extends GatewayError {
|
|
17
|
+
constructor(message?: string, body?: GatewayErrorBody);
|
|
18
|
+
}
|
|
19
|
+
/** `400` — the request was refused as invalid (a bad field, a limit exceeded); `body.code` / `message` say which. */
|
|
20
|
+
export declare class ValidationError extends GatewayError {
|
|
21
|
+
constructor(message?: string, body?: GatewayErrorBody);
|
|
22
|
+
}
|
|
13
23
|
/** `401` — the API key was missing, malformed, or rejected. */
|
|
14
24
|
export declare class AuthError extends GatewayError {
|
|
15
25
|
constructor(message?: string, body?: GatewayErrorBody);
|
|
@@ -71,4 +81,3 @@ export declare class ConnectionError extends SolariError {
|
|
|
71
81
|
* Falls back to a generic {@link GatewayError} for unrecognized statuses.
|
|
72
82
|
*/
|
|
73
83
|
export declare function mapGatewayError(status: number, body: GatewayErrorBody | undefined): GatewayError;
|
|
74
|
-
//# sourceMappingURL=errors.d.ts.map
|
package/dist/errors.js
CHANGED
|
@@ -1,76 +1,59 @@
|
|
|
1
|
-
// Typed errors. Gateway HTTP status codes (§1) and control-channel RPC
|
|
2
|
-
// failures (§4) are mapped onto these so callers can branch on `instanceof`.
|
|
3
|
-
/** Base class for every error thrown by the SDK. */
|
|
4
1
|
export class SolariError extends Error {
|
|
5
2
|
constructor(message) {
|
|
6
3
|
super(message);
|
|
7
4
|
this.name = new.target.name;
|
|
8
5
|
}
|
|
9
6
|
}
|
|
10
|
-
/** Any non-2xx response from the gateway that isn't otherwise specialized. */
|
|
11
7
|
export class GatewayError extends SolariError {
|
|
12
8
|
status;
|
|
13
9
|
code;
|
|
14
10
|
body;
|
|
11
|
+
retryable;
|
|
15
12
|
constructor(status, message, body) {
|
|
16
13
|
super(message);
|
|
17
14
|
this.status = status;
|
|
18
15
|
this.code = body?.code;
|
|
19
16
|
this.body = body;
|
|
17
|
+
this.retryable = body?.retryable;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class NotFoundError extends GatewayError {
|
|
21
|
+
constructor(message = "Not found", body) {
|
|
22
|
+
super(404, message, body);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export class ValidationError extends GatewayError {
|
|
26
|
+
constructor(message = "Invalid request", body) {
|
|
27
|
+
super(400, message, body);
|
|
20
28
|
}
|
|
21
29
|
}
|
|
22
|
-
/** `401` — the API key was missing, malformed, or rejected. */
|
|
23
30
|
export class AuthError extends GatewayError {
|
|
24
31
|
constructor(message = "Invalid or missing API key", body) {
|
|
25
32
|
super(401, message, body);
|
|
26
33
|
}
|
|
27
34
|
}
|
|
28
|
-
/**
|
|
29
|
-
* The plan doesn't allow this: `402` (`FeatureRequiresPlan`, `InsufficientCredit`) or `403 {code:"NotEntitled"}` (the plan
|
|
30
|
-
* does not include sandboxes/desktops). `status` is the status the gateway really sent.
|
|
31
|
-
*/
|
|
32
35
|
export class PlanError extends GatewayError {
|
|
33
36
|
constructor(message = "This feature requires a different plan", body, status = 402) {
|
|
34
37
|
super(status, message, body);
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
|
-
/**
|
|
38
|
-
* `429 {code:"ConcurrencyLimitExceeded"}` — the org is at its cap of live sessions. Other 429s (for example
|
|
39
|
-
* `TooManyExecutions`, a per-sandbox limit that can be retried) are plain {@link GatewayError}s with the gateway's `code`.
|
|
40
|
-
*/
|
|
41
40
|
export class ConcurrencyLimitError extends GatewayError {
|
|
42
41
|
constructor(message = "Concurrency limit exceeded", body) {
|
|
43
42
|
super(429, message, body);
|
|
44
43
|
}
|
|
45
44
|
}
|
|
46
|
-
/**
|
|
47
|
-
* `503 {code:"NoCapacity"|"StartTimeout"}` (or a `503` with no code) — no desktop host currently available to serve the
|
|
48
|
-
* request. Other 503s (`SandboxUnavailable`, `StoreUnavailable`, `AdmissionUnavailable`, `DependencyUnavailable`) are plain
|
|
49
|
-
* {@link GatewayError}s: the sandbox, store or admission service is unavailable, which is not a capacity problem.
|
|
50
|
-
*/
|
|
51
45
|
export class NoCapacityError extends GatewayError {
|
|
52
46
|
constructor(message = "No desktop host available", body) {
|
|
53
47
|
super(503, message, body);
|
|
54
48
|
}
|
|
55
49
|
}
|
|
56
|
-
/**
|
|
57
|
-
* `409 {code:"NotStopped"}` or `409 {code:"StartInProgress"}` from
|
|
58
|
-
* `POST /sandboxes/:id/start`. `NotStopped` means the sandbox is in another
|
|
59
|
-
* state (use `resume()` for a paused one); `StartInProgress` means a start is
|
|
60
|
-
* already under way, so poll until the sandbox is running. Not retryable as-is.
|
|
61
|
-
* Host refusals (`StoppedDiskMissing`, `StoppedDiskUnsafe`, `GoldenMismatch`, `DiskPlatformMismatch`,
|
|
62
|
-
* `DiskShaMismatch`, `DiskExceedsSpec`, `DiskOwnerMismatch`) and `HostCapabilityMissing` (host pool not armed
|
|
63
|
-
* for start; `body.missingCapabilities`) stay plain {@link GatewayError}s with the host's `code`.
|
|
64
|
-
*/
|
|
65
50
|
export class SandboxNotStoppedError extends GatewayError {
|
|
66
|
-
/** The sandbox's current state as reported by the gateway, when present. */
|
|
67
51
|
state;
|
|
68
52
|
constructor(message = "Sandbox is not stopped", body) {
|
|
69
53
|
super(409, message, body);
|
|
70
54
|
this.state = body?.state;
|
|
71
55
|
}
|
|
72
56
|
}
|
|
73
|
-
/** A computer-use RPC returned `{ok:false}`. */
|
|
74
57
|
export class ActionError extends SolariError {
|
|
75
58
|
method;
|
|
76
59
|
code;
|
|
@@ -80,7 +63,6 @@ export class ActionError extends SolariError {
|
|
|
80
63
|
this.code = code;
|
|
81
64
|
}
|
|
82
65
|
}
|
|
83
|
-
/** An RPC did not receive a reply within the per-call timeout. */
|
|
84
66
|
export class TimeoutError extends SolariError {
|
|
85
67
|
method;
|
|
86
68
|
timeoutMs;
|
|
@@ -90,28 +72,24 @@ export class TimeoutError extends SolariError {
|
|
|
90
72
|
this.timeoutMs = timeoutMs;
|
|
91
73
|
}
|
|
92
74
|
}
|
|
93
|
-
/** The control WebSocket is not open (never connected, or closed). */
|
|
94
75
|
export class ConnectionError extends SolariError {
|
|
95
76
|
constructor(message = "Control channel is not connected") {
|
|
96
77
|
super(message);
|
|
97
78
|
}
|
|
98
79
|
}
|
|
99
|
-
/**
|
|
100
|
-
* Map a gateway HTTP response onto the appropriate typed error.
|
|
101
|
-
* Falls back to a generic {@link GatewayError} for unrecognized statuses.
|
|
102
|
-
*/
|
|
103
80
|
export function mapGatewayError(status, body) {
|
|
104
81
|
const msg = body?.message ?? body?.error ?? body?.code;
|
|
105
|
-
// `code` is the discriminator (sdk/PROTOCOL.md section 4); the status alone is not enough because one status carries
|
|
106
|
-
// several meanings. A response with no code keeps the type its status has always had.
|
|
107
82
|
const code = body?.code;
|
|
108
83
|
switch (status) {
|
|
84
|
+
case 400:
|
|
85
|
+
return new ValidationError(msg, body);
|
|
86
|
+
case 404:
|
|
87
|
+
return new NotFoundError(msg, body);
|
|
109
88
|
case 401:
|
|
110
89
|
return new AuthError(msg, body);
|
|
111
90
|
case 402:
|
|
112
91
|
return new PlanError(msg, body);
|
|
113
92
|
case 403:
|
|
114
|
-
// The gateway's only JSON 403 is NotEntitled (the plan does not include sandboxes/desktops).
|
|
115
93
|
if (code === "NotEntitled")
|
|
116
94
|
return new PlanError(msg, body, 403);
|
|
117
95
|
return new GatewayError(403, msg ?? "Forbidden", body);
|
|
@@ -132,4 +110,3 @@ export function mapGatewayError(status, body) {
|
|
|
132
110
|
return new GatewayError(status, msg ?? `Gateway request failed with status ${status}`, body);
|
|
133
111
|
}
|
|
134
112
|
}
|
|
135
|
-
//# sourceMappingURL=errors.js.map
|
package/dist/handle.d.ts
CHANGED
|
@@ -54,6 +54,8 @@ export interface SessionHooks {
|
|
|
54
54
|
export interface SessionConfig extends ControlChannelConfig {
|
|
55
55
|
hooks?: SessionHooks;
|
|
56
56
|
}
|
|
57
|
+
/** The longest `timeoutMs` the gateway accepts for a command (600 000 ms), and the default for `commands.run`. */
|
|
58
|
+
export declare const DEFAULT_COMMAND_TIMEOUT_MS = 600000;
|
|
57
59
|
/** Cap on PTY output held before the first onData() subscriber (bytes, total). */
|
|
58
60
|
export declare const PTY_EARLY_BUFFER_MAX: number;
|
|
59
61
|
export declare class SessionHandle {
|
|
@@ -66,6 +68,8 @@ export declare class SessionHandle {
|
|
|
66
68
|
/** Set by {@link kill} so {@link close} doesn't warn about a machine we
|
|
67
69
|
* have just released. */
|
|
68
70
|
private killed;
|
|
71
|
+
/** Key of this handle in the exit-reminder list (see `unreleased`). */
|
|
72
|
+
private readonly releaseToken;
|
|
69
73
|
constructor(id: string, controlUrl: string, expiresAt: string, config?: SessionConfig);
|
|
70
74
|
/** Whether the control channel is open. */
|
|
71
75
|
get connected(): boolean;
|
|
@@ -86,8 +90,14 @@ export declare class SessionHandle {
|
|
|
86
90
|
* idle timeout, or until you call {@link kill}.
|
|
87
91
|
*
|
|
88
92
|
* If you meant "I'm done with this machine", call {@link kill}.
|
|
93
|
+
*
|
|
94
|
+
* Pass `{ quiet: true }` when the machine is released some other way (for
|
|
95
|
+
* example a wrapper that pauses or kills it by id right after closing its own
|
|
96
|
+
* channel) and the "still running" nudge would be wrong.
|
|
89
97
|
*/
|
|
90
|
-
close(
|
|
98
|
+
close(opts?: {
|
|
99
|
+
quiet?: boolean;
|
|
100
|
+
}): void;
|
|
91
101
|
readonly commands: {
|
|
92
102
|
/**
|
|
93
103
|
* Run a command to completion. `onStdout`/`onStderr` receive output as it
|
|
@@ -251,6 +261,9 @@ export declare class SessionHandle {
|
|
|
251
261
|
* Resolve a public preview URL for an in-guest port (e.g. a dev server on
|
|
252
262
|
* `:3000`). Returns the routable `https://` URL and, when the gateway signs
|
|
253
263
|
* one, an access `token`.
|
|
264
|
+
*
|
|
265
|
+
* The URL can carry that token as `?slr_token=…`: it is a credential for the port, so don't print or log the whole
|
|
266
|
+
* URL and avoid opening it in a shared browser profile, where it lands in history.
|
|
254
267
|
*/
|
|
255
268
|
previewUrl(port: number): Promise<{
|
|
256
269
|
url: string;
|
|
@@ -262,4 +275,3 @@ export declare class SessionHandle {
|
|
|
262
275
|
/** Local helper type for `createCodeContext` to avoid a hard import cycle. */
|
|
263
276
|
type CodeLanguageArg = "python" | "javascript" | "typescript" | "bash" | "r";
|
|
264
277
|
export {};
|
|
265
|
-
//# sourceMappingURL=handle.d.ts.map
|