@remotehost/sdk 0.1.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.md +21 -0
- package/README.md +89 -0
- package/THIRD-PARTY-NOTICES.md +51 -0
- package/dist/client.d.ts +29 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +121 -0
- package/dist/commands.d.ts +15 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +19 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +77 -0
- package/dist/files.d.ts +36 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +61 -0
- package/dist/generated/schema.d.ts +4217 -0
- package/dist/generated/schema.d.ts.map +1 -0
- package/dist/generated/schema.js +5 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/internal.d.ts +18 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +21 -0
- package/dist/metrics.d.ts +15 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +24 -0
- package/dist/previews.d.ts +27 -0
- package/dist/previews.d.ts.map +1 -0
- package/dist/previews.js +35 -0
- package/dist/request-options.d.ts +7 -0
- package/dist/request-options.d.ts.map +1 -0
- package/dist/request-options.js +27 -0
- package/dist/sandboxes.d.ts +115 -0
- package/dist/sandboxes.d.ts.map +1 -0
- package/dist/sandboxes.js +234 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/package.json +62 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { SandboxCommands } from "./commands.js";
|
|
2
|
+
import { RemoteHostAPIError, RemoteHostConfigurationError, RemoteHostError, RemoteHostTimeoutError, } from "./errors.js";
|
|
3
|
+
import { SandboxFiles } from "./files.js";
|
|
4
|
+
import { unwrap } from "./internal.js";
|
|
5
|
+
import { SandboxMetricsResource } from "./metrics.js";
|
|
6
|
+
import { SandboxPreviews } from "./previews.js";
|
|
7
|
+
import { abortableDelay, requestSignal } from "./request-options.js";
|
|
8
|
+
export class Sandboxes {
|
|
9
|
+
api;
|
|
10
|
+
defaultOrgId;
|
|
11
|
+
constructor(api, defaultOrgId) {
|
|
12
|
+
this.api = api;
|
|
13
|
+
this.defaultOrgId = defaultOrgId;
|
|
14
|
+
}
|
|
15
|
+
/** List non-deleted sandboxes visible in an organization. */
|
|
16
|
+
async list(options = {}) {
|
|
17
|
+
const { orgId, projectId, endUserId } = options;
|
|
18
|
+
const data = await unwrap(this.api.GET("/orgs/{orgId}/sandboxes", {
|
|
19
|
+
params: {
|
|
20
|
+
path: { orgId: this.requireOrgId(orgId) },
|
|
21
|
+
query: { projectId, endUserId },
|
|
22
|
+
},
|
|
23
|
+
signal: requestSignal(options),
|
|
24
|
+
}));
|
|
25
|
+
return data.sandboxes.map((sandbox) => new Sandbox(this.api, sandbox));
|
|
26
|
+
}
|
|
27
|
+
/** Create a sandbox, waiting for it to become ready by default. */
|
|
28
|
+
async create(options) {
|
|
29
|
+
const { orgId, pollIntervalMs, signal: _signal, timeoutMs: _timeoutMs, waitForReady = true, waitTimeoutMs, ...body } = options;
|
|
30
|
+
const data = await unwrap(this.api.POST("/orgs/{orgId}/sandboxes", {
|
|
31
|
+
params: { path: { orgId: this.requireOrgId(orgId) } },
|
|
32
|
+
body,
|
|
33
|
+
signal: requestSignal(options),
|
|
34
|
+
}));
|
|
35
|
+
const sandbox = new Sandbox(this.api, data.sandbox);
|
|
36
|
+
return waitForReady
|
|
37
|
+
? sandbox.waitUntilReady({
|
|
38
|
+
pollIntervalMs,
|
|
39
|
+
signal: options.signal,
|
|
40
|
+
timeoutMs: waitTimeoutMs,
|
|
41
|
+
})
|
|
42
|
+
: sandbox;
|
|
43
|
+
}
|
|
44
|
+
/** Retrieve one sandbox by id. */
|
|
45
|
+
async retrieve(sandboxId, options = {}) {
|
|
46
|
+
const data = await unwrap(this.api.GET("/sandboxes/{sandboxId}", {
|
|
47
|
+
params: { path: { sandboxId } },
|
|
48
|
+
signal: requestSignal(options),
|
|
49
|
+
}));
|
|
50
|
+
return new Sandbox(this.api, data.sandbox);
|
|
51
|
+
}
|
|
52
|
+
requireOrgId(orgId) {
|
|
53
|
+
const resolved = orgId ?? this.defaultOrgId;
|
|
54
|
+
if (!resolved) {
|
|
55
|
+
throw new RemoteHostConfigurationError("An orgId is required. Pass it to the client or this request.");
|
|
56
|
+
}
|
|
57
|
+
return resolved;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export class Sandbox {
|
|
61
|
+
api;
|
|
62
|
+
#data;
|
|
63
|
+
commands;
|
|
64
|
+
files;
|
|
65
|
+
metrics;
|
|
66
|
+
previews;
|
|
67
|
+
constructor(api, data) {
|
|
68
|
+
this.api = api;
|
|
69
|
+
this.#data = data;
|
|
70
|
+
this.commands = new SandboxCommands(api, data.id);
|
|
71
|
+
this.files = new SandboxFiles(api, data.id);
|
|
72
|
+
this.metrics = new SandboxMetricsResource(api, data.id);
|
|
73
|
+
this.previews = new SandboxPreviews(api, data.id);
|
|
74
|
+
}
|
|
75
|
+
/** Latest raw API representation. Call refresh() to update it. */
|
|
76
|
+
get data() {
|
|
77
|
+
return this.#data;
|
|
78
|
+
}
|
|
79
|
+
get id() {
|
|
80
|
+
return this.#data.id;
|
|
81
|
+
}
|
|
82
|
+
get orgId() {
|
|
83
|
+
return this.#data.org_id;
|
|
84
|
+
}
|
|
85
|
+
get projectId() {
|
|
86
|
+
return this.#data.project_id;
|
|
87
|
+
}
|
|
88
|
+
get name() {
|
|
89
|
+
return this.#data.name;
|
|
90
|
+
}
|
|
91
|
+
get status() {
|
|
92
|
+
return this.#data.status;
|
|
93
|
+
}
|
|
94
|
+
/** Release compute while preserving the disk of a persistent sandbox. */
|
|
95
|
+
async sleep(options = {}) {
|
|
96
|
+
const data = await unwrap(this.api.POST("/sandboxes/{sandboxId}/sleep", {
|
|
97
|
+
params: { path: { sandboxId: this.id } },
|
|
98
|
+
signal: requestSignal(options),
|
|
99
|
+
}));
|
|
100
|
+
return this.update(data.sandbox);
|
|
101
|
+
}
|
|
102
|
+
/** @deprecated Use sleep(). */
|
|
103
|
+
async stop(options = {}) {
|
|
104
|
+
const data = await unwrap(this.api.POST("/sandboxes/{sandboxId}/stop", {
|
|
105
|
+
params: { path: { sandboxId: this.id } },
|
|
106
|
+
signal: requestSignal(options),
|
|
107
|
+
}));
|
|
108
|
+
return this.update(data.sandbox);
|
|
109
|
+
}
|
|
110
|
+
/** Wake a sleeping persistent sandbox. */
|
|
111
|
+
async wake(options = {}) {
|
|
112
|
+
const data = await unwrap(this.api.POST("/sandboxes/{sandboxId}/wake", {
|
|
113
|
+
params: { path: { sandboxId: this.id } },
|
|
114
|
+
signal: requestSignal(options),
|
|
115
|
+
}));
|
|
116
|
+
return this.update(data.sandbox);
|
|
117
|
+
}
|
|
118
|
+
/** @deprecated Use wake(). */
|
|
119
|
+
async resume(options = {}) {
|
|
120
|
+
const data = await unwrap(this.api.POST("/sandboxes/{sandboxId}/resume", {
|
|
121
|
+
params: { path: { sandboxId: this.id } },
|
|
122
|
+
signal: requestSignal(options),
|
|
123
|
+
}));
|
|
124
|
+
return this.update(data.sandbox);
|
|
125
|
+
}
|
|
126
|
+
/** Renew the sandbox lease. */
|
|
127
|
+
async renew(options = {}) {
|
|
128
|
+
const data = await unwrap(this.api.POST("/sandboxes/{sandboxId}/renew", {
|
|
129
|
+
params: { path: { sandboxId: this.id } },
|
|
130
|
+
signal: requestSignal(options),
|
|
131
|
+
}));
|
|
132
|
+
return this.update(data.sandbox);
|
|
133
|
+
}
|
|
134
|
+
/** Permanently destroy the sandbox. */
|
|
135
|
+
async destroy(options = {}) {
|
|
136
|
+
const data = await unwrap(this.api.POST("/sandboxes/{sandboxId}/destroy", {
|
|
137
|
+
params: { path: { sandboxId: this.id } },
|
|
138
|
+
signal: requestSignal(options),
|
|
139
|
+
}));
|
|
140
|
+
return this.update(data.sandbox);
|
|
141
|
+
}
|
|
142
|
+
/** Change the requested resource allocation. */
|
|
143
|
+
async resize(body, options = {}) {
|
|
144
|
+
const data = await unwrap(this.api.POST("/sandboxes/{sandboxId}/resize", {
|
|
145
|
+
params: { path: { sandboxId: this.id } },
|
|
146
|
+
body,
|
|
147
|
+
signal: requestSignal(options),
|
|
148
|
+
}));
|
|
149
|
+
this.update(data.sandbox);
|
|
150
|
+
return {
|
|
151
|
+
applied: data.applied,
|
|
152
|
+
liveResizeError: data.liveResizeError,
|
|
153
|
+
pendingAllocation: data.pendingAllocation,
|
|
154
|
+
sandbox: this,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/** Refresh this resource in place from the API. */
|
|
158
|
+
async refresh(options = {}) {
|
|
159
|
+
const data = await unwrap(this.api.GET("/sandboxes/{sandboxId}", {
|
|
160
|
+
params: { path: { sandboxId: this.id } },
|
|
161
|
+
signal: requestSignal(options),
|
|
162
|
+
}));
|
|
163
|
+
return this.update(data.sandbox);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Poll until the sandbox can accept commands, or throw on failure or timeout.
|
|
167
|
+
*
|
|
168
|
+
* "running" is the control plane's view; the agent inside the guest can take
|
|
169
|
+
* a few more seconds to answer. Readiness is therefore confirmed with a
|
|
170
|
+
* directory listing through the agent, so the first command after this
|
|
171
|
+
* resolves does not fail with a 502.
|
|
172
|
+
*/
|
|
173
|
+
async waitUntilReady(options = {}) {
|
|
174
|
+
const timeoutMs = options.timeoutMs ?? 5 * 60 * 1000;
|
|
175
|
+
const pollIntervalMs = options.pollIntervalMs ?? 1_000;
|
|
176
|
+
const startedAt = Date.now();
|
|
177
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
178
|
+
throw new TypeError("timeoutMs must be a positive number.");
|
|
179
|
+
}
|
|
180
|
+
if (!Number.isFinite(pollIntervalMs) || pollIntervalMs <= 0) {
|
|
181
|
+
throw new TypeError("pollIntervalMs must be a positive number.");
|
|
182
|
+
}
|
|
183
|
+
while (true) {
|
|
184
|
+
if (this.status === "ready" || this.status === "running") {
|
|
185
|
+
const remainingMs = timeoutMs - (Date.now() - startedAt);
|
|
186
|
+
if (await this.agentAnswers({ signal: options.signal, timeoutMs: remainingMs })) {
|
|
187
|
+
return this;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else if (this.status === "error" ||
|
|
191
|
+
this.status === "deleted" ||
|
|
192
|
+
this.status === "stopping" ||
|
|
193
|
+
this.status === "stopped") {
|
|
194
|
+
// A sandbox that is asleep, or on its way to sleep, will not become
|
|
195
|
+
// ready on its own; waiting would only run out the clock. Wake it first.
|
|
196
|
+
throw new RemoteHostError(`Sandbox ${this.id} entered status "${this.status}" while waiting for readiness.`);
|
|
197
|
+
}
|
|
198
|
+
const remainingMs = timeoutMs - (Date.now() - startedAt);
|
|
199
|
+
if (remainingMs <= 0) {
|
|
200
|
+
throw new RemoteHostTimeoutError(`Sandbox ${this.id} was not ready within ${timeoutMs}ms.`);
|
|
201
|
+
}
|
|
202
|
+
await abortableDelay(Math.min(pollIntervalMs, remainingMs), options.signal);
|
|
203
|
+
await this.refresh({ signal: options.signal, timeoutMs: remainingMs });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
toJSON() {
|
|
207
|
+
return { ...this.#data };
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* True once the agent inside the sandbox answers a directory listing. A
|
|
211
|
+
* 400, 500, or 502 means the guest is still coming up, or has just been
|
|
212
|
+
* paused, and is worth another poll; any other failure is real.
|
|
213
|
+
*/
|
|
214
|
+
async agentAnswers(options) {
|
|
215
|
+
if (options.timeoutMs !== undefined && options.timeoutMs <= 0) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
await this.files.list("/code", options);
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
if (error instanceof RemoteHostAPIError &&
|
|
224
|
+
(error.status === 400 || error.status === 500 || error.status === 502)) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
update(data) {
|
|
231
|
+
this.#data = data;
|
|
232
|
+
return this;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,eAAO,MAAM,OAAO,UAAU,CAAC"}
|
package/dist/version.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@remotehost/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official TypeScript SDK for RemoteHost",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"remotehost",
|
|
7
|
+
"sandbox",
|
|
8
|
+
"developer-tools",
|
|
9
|
+
"coding-agent"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://docs.remotehost.ai/docs/sdk",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"sideEffects": false,
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE.md",
|
|
18
|
+
"THIRD-PARTY-NOTICES.md"
|
|
19
|
+
],
|
|
20
|
+
"main": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./openapi": {
|
|
28
|
+
"types": "./dist/generated/schema.d.ts",
|
|
29
|
+
"import": "./dist/generated/schema.js"
|
|
30
|
+
},
|
|
31
|
+
"./package.json": "./package.json"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=20"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"openapi-fetch": "^0.17.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"openapi-typescript": "^7.13.0",
|
|
44
|
+
"tsx": "^4.19.2"
|
|
45
|
+
},
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/remotehostai/sdk.git"
|
|
50
|
+
},
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/remotehostai/sdk/issues"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "pnpm run generate && tsc -p tsconfig.json",
|
|
56
|
+
"generate": "openapi-typescript ../../apps/api/generated/openapi.json -o src/generated/schema.ts",
|
|
57
|
+
"lint": "pnpm run typecheck",
|
|
58
|
+
"smoke:staging": "node scripts/staging-smoke.mjs",
|
|
59
|
+
"test": "node scripts/test.mjs",
|
|
60
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.test.json"
|
|
61
|
+
}
|
|
62
|
+
}
|