withruntime 0.8.4 → 0.10.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.
Files changed (132) hide show
  1. package/BLAXEL.md +4 -4
  2. package/COMPATIBILITY.md +145 -0
  3. package/DAYTONA.md +13 -7
  4. package/E2B.md +34 -20
  5. package/README.md +22 -11
  6. package/README.src.md +22 -11
  7. package/VERCEL.md +13 -3
  8. package/compatibility-lock.json +357 -0
  9. package/dist/ai-harness/index.js +2 -2
  10. package/dist/api-defaults.d.ts +17 -0
  11. package/dist/api-defaults.js +22 -0
  12. package/dist/blaxel/errors.js +1 -1
  13. package/dist/blaxel/lookup.d.ts +3 -2
  14. package/dist/blaxel/lookup.js +3 -2
  15. package/dist/blaxel/pagination.d.ts +2 -2
  16. package/dist/blaxel/pagination.js +45 -17
  17. package/dist/blaxel/preview.d.ts +3 -2
  18. package/dist/blaxel/preview.js +16 -14
  19. package/dist/blaxel/process.d.ts +1 -1
  20. package/dist/blaxel/process.js +66 -11
  21. package/dist/blaxel/sandbox.d.ts +2 -0
  22. package/dist/blaxel/sandbox.js +2 -0
  23. package/dist/blaxel/snapshot.js +3 -5
  24. package/dist/cli-words.d.ts +27 -0
  25. package/dist/cli-words.js +135 -0
  26. package/dist/cli.d.ts +2 -0
  27. package/dist/cli.js +351 -85
  28. package/dist/client.d.ts +33 -4
  29. package/dist/client.js +47 -12
  30. package/dist/cloudflare/identity.d.ts +7 -0
  31. package/dist/cloudflare/identity.js +13 -0
  32. package/dist/cloudflare/index.d.ts +303 -0
  33. package/dist/cloudflare/index.js +592 -0
  34. package/dist/cloudflare/worker.d.ts +419 -0
  35. package/dist/cloudflare/worker.js +447 -0
  36. package/dist/codesandbox/guest-service.d.ts +31 -0
  37. package/dist/codesandbox/guest-service.js +490 -0
  38. package/dist/codesandbox/index.d.ts +386 -0
  39. package/dist/codesandbox/index.js +1347 -0
  40. package/dist/compat/core.d.ts +40 -0
  41. package/dist/compat/core.js +238 -0
  42. package/dist/compat/files.d.ts +26 -0
  43. package/dist/compat/files.js +133 -0
  44. package/dist/compat/shell.d.ts +4 -0
  45. package/dist/compat/shell.js +165 -0
  46. package/dist/credentials.d.ts +6 -1
  47. package/dist/credentials.js +107 -38
  48. package/dist/daytona/context.js +2 -1
  49. package/dist/daytona/daytona.js +11 -4
  50. package/dist/daytona/filesystem.d.ts +17 -4
  51. package/dist/daytona/filesystem.js +121 -38
  52. package/dist/daytona/index.d.ts +1 -1
  53. package/dist/daytona/process.d.ts +1 -1
  54. package/dist/daytona/process.js +27 -4
  55. package/dist/daytona/sandbox.d.ts +1 -1
  56. package/dist/daytona/sandbox.js +16 -7
  57. package/dist/e2b/client.js +1 -1
  58. package/dist/e2b/code-interpreter.d.ts +4 -5
  59. package/dist/e2b/code-interpreter.js +54 -56
  60. package/dist/e2b/commands.d.ts +35 -12
  61. package/dist/e2b/commands.js +169 -73
  62. package/dist/e2b/errors.js +1 -1
  63. package/dist/e2b/filesystem.d.ts +8 -6
  64. package/dist/e2b/filesystem.js +127 -28
  65. package/dist/e2b/index.d.ts +1 -0
  66. package/dist/e2b/index.js +1 -0
  67. package/dist/e2b/pty.d.ts +24 -0
  68. package/dist/e2b/pty.js +80 -0
  69. package/dist/e2b/sandbox.d.ts +6 -4
  70. package/dist/e2b/sandbox.js +59 -25
  71. package/dist/errors.d.ts +8 -0
  72. package/dist/errors.js +15 -2
  73. package/dist/file-source.d.ts +16 -0
  74. package/dist/file-source.js +162 -0
  75. package/dist/freestyle/index.d.ts +309 -0
  76. package/dist/freestyle/index.js +343 -0
  77. package/dist/index.d.ts +3 -1
  78. package/dist/login.js +16 -10
  79. package/dist/mcp.js +2 -2
  80. package/dist/modal/index.d.ts +205 -0
  81. package/dist/modal/index.js +486 -0
  82. package/dist/network-cli.js +112 -44
  83. package/dist/openai-agents/index.js +18 -71
  84. package/dist/page.d.ts +1 -1
  85. package/dist/page.js +2 -2
  86. package/dist/products/audit.d.ts +3 -0
  87. package/dist/products/index.d.ts +14 -0
  88. package/dist/products/index.js +12 -0
  89. package/dist/products/interpreter.d.ts +11 -4
  90. package/dist/products/interpreter.js +33 -9
  91. package/dist/products/jobs.js +3 -2
  92. package/dist/products/limits.d.ts +10 -0
  93. package/dist/products/observability.d.ts +44 -0
  94. package/dist/products/observability.js +6 -0
  95. package/dist/products/previews.d.ts +18 -4
  96. package/dist/products/previews.js +14 -5
  97. package/dist/products/tailscale.d.ts +57 -0
  98. package/dist/products/tailscale.js +17 -0
  99. package/dist/products/volumes.d.ts +42 -0
  100. package/dist/products/volumes.js +36 -0
  101. package/dist/products/watch.d.ts +25 -4
  102. package/dist/products/watch.js +132 -18
  103. package/dist/proxy.js +3 -1
  104. package/dist/runloop/index.d.ts +326 -0
  105. package/dist/runloop/index.js +535 -0
  106. package/dist/runloop/pagination.d.ts +46 -0
  107. package/dist/runloop/pagination.js +82 -0
  108. package/dist/runloop/snapshots.d.ts +52 -0
  109. package/dist/runloop/snapshots.js +244 -0
  110. package/dist/sandbox.d.ts +76 -16
  111. package/dist/sandbox.js +696 -135
  112. package/dist/snapshots.d.ts +20 -5
  113. package/dist/snapshots.js +11 -1
  114. package/dist/sprites/index.d.ts +251 -0
  115. package/dist/sprites/index.js +588 -0
  116. package/dist/tar.d.ts +10 -1
  117. package/dist/tar.js +311 -35
  118. package/dist/tools/index.js +2 -1
  119. package/dist/transport.d.ts +23 -8
  120. package/dist/transport.js +247 -127
  121. package/dist/types.d.ts +52 -3
  122. package/dist/vercel/command.d.ts +1 -1
  123. package/dist/vercel/command.js +15 -3
  124. package/dist/vercel/errors.d.ts +2 -2
  125. package/dist/vercel/errors.js +19 -2
  126. package/dist/vercel/filesystem.d.ts +21 -18
  127. package/dist/vercel/filesystem.js +95 -48
  128. package/dist/vercel/sandbox.d.ts +8 -8
  129. package/dist/vercel/sandbox.js +159 -64
  130. package/dist/vercel/snapshot.d.ts +3 -2
  131. package/dist/vercel/snapshot.js +9 -5
  132. package/package.json +46 -3
@@ -0,0 +1,343 @@
1
+ import { all, client, CompatibilityError, create, destroy, execOptions, lookup, only, ready, } from "../compat/core.js";
2
+ import { GuestFiles } from "../compat/files.js";
3
+ export { CompatibilityError };
4
+ function validateMetadata(metadata) {
5
+ for (const key of ["compat.provider", "fs.displayName"])
6
+ if (metadata !== undefined && Object.prototype.hasOwnProperty.call(metadata, key))
7
+ throw new CompatibilityError("Freestyle", `metadata key ${key} reserved by this adapter`);
8
+ }
9
+ const data = (s) => ({
10
+ id: s.id,
11
+ state: s.state,
12
+ slug: s.info.name,
13
+ displayName: s.info.labels["fs.displayName"] ?? null,
14
+ resources: { cpu: s.info.vcpu, memory: s.info.memoryMiB, storage: s.info.diskMiB },
15
+ metadata: Object.fromEntries(Object.entries(s.info.labels).filter(([k]) => !["compat.provider", "fs.displayName"].includes(k))),
16
+ idleTimeoutSeconds: s.info.idlePauseSeconds === 0 ? -1 : s.info.idlePauseSeconds,
17
+ vpcs: [],
18
+ networks: [],
19
+ createdAt: s.info.createdAt,
20
+ updatedAt: s.info.createdAt,
21
+ });
22
+ const snapshotData = (s) => ({
23
+ id: s.id,
24
+ sourceVmId: s.sourceSandboxId,
25
+ slug: s.name,
26
+ displayName: s.labels?.["fs.displayName"] ?? null,
27
+ public: false,
28
+ createdAt: s.createdAt,
29
+ updatedAt: s.createdAt,
30
+ ttlSeconds: s.retentionDays * 86400,
31
+ });
32
+ export class Freestyle {
33
+ vms;
34
+ constructor(options = {}) {
35
+ this.vms = new VmsNamespace(client(options));
36
+ }
37
+ }
38
+ export class VmsNamespace {
39
+ runtime;
40
+ snapshots;
41
+ constructor(runtime) {
42
+ this.runtime = runtime;
43
+ this.snapshots = {
44
+ get: async (id) => snapshotData(await this.resolveSnapshot(id)),
45
+ delete: async (id) => runtime.snapshots.delete((await this.resolveSnapshot(id)).id),
46
+ list: async (options = {}) => {
47
+ only("Freestyle snapshot list", options, ["sourceVmId", "limit", "offset"]);
48
+ if (options.sourceVmId !== undefined && typeof options.sourceVmId !== "string")
49
+ throw new TypeError("sourceVmId must be a string");
50
+ if (options.limit !== undefined &&
51
+ (!Number.isSafeInteger(options.limit) || options.limit <= 0))
52
+ throw new TypeError("limit must be a positive safe integer");
53
+ if (options.offset !== undefined &&
54
+ (!Number.isSafeInteger(options.offset) || options.offset < 0))
55
+ throw new TypeError("offset must be a nonnegative safe integer");
56
+ const offset = options.offset ?? 0;
57
+ const snapshots = [];
58
+ let totalCount = 0;
59
+ // Count the filtered catalog across every native page without retaining
60
+ // snapshots outside the requested page or Page.toArray's default cap.
61
+ const page = await runtime.snapshots.list(options.sourceVmId === undefined ? {} : { sandboxId: options.sourceVmId });
62
+ for await (const snapshot of page) {
63
+ if (options.sourceVmId !== undefined && snapshot.sourceSandboxId !== options.sourceVmId)
64
+ continue;
65
+ if (totalCount >= offset &&
66
+ (options.limit === undefined || snapshots.length < options.limit))
67
+ snapshots.push(snapshotData(snapshot));
68
+ totalCount++;
69
+ }
70
+ return { snapshots, totalCount };
71
+ },
72
+ };
73
+ }
74
+ async resolveSnapshot(id) {
75
+ if (/^[a-f0-9-]{36}$/i.test(id))
76
+ return this.runtime.snapshots.get(id);
77
+ const found = (await this.runtime.snapshots.list({ name: id })).data.find((s) => s.name === id);
78
+ if (!found)
79
+ throw new CompatibilityError("Freestyle", `snapshot ${id}; import the source environment into Runtime first`);
80
+ return found;
81
+ }
82
+ async create(options) {
83
+ only("Freestyle create", options, [
84
+ "snapshotId",
85
+ "slug",
86
+ "displayName",
87
+ "idleTimeoutSeconds",
88
+ "metadata",
89
+ "firewall",
90
+ ]);
91
+ validateMetadata(options.metadata);
92
+ if (!options.firewall || !Array.isArray(options.firewall.rules))
93
+ throw new TypeError("firewall.rules is required");
94
+ only("Freestyle firewall", options.firewall, ["rules"]);
95
+ for (const r of options.firewall.rules) {
96
+ only("Freestyle firewall rule", r, ["action", "source", "destination"]);
97
+ if (r.action !== "allow" ||
98
+ Object.keys(r.source).length ||
99
+ Object.keys(r.destination).length !== 1 ||
100
+ r.destination.public !== true)
101
+ throw new CompatibilityError("Freestyle", "firewall rules other than unrestricted outbound public access");
102
+ }
103
+ const snapshot = options.snapshotId
104
+ ? (await this.resolveSnapshot(options.snapshotId)).id
105
+ : undefined;
106
+ const s = await create(this.runtime, "freestyle", {
107
+ snapshot,
108
+ name: options.slug ?? undefined,
109
+ labels: { ...options.metadata, "fs.displayName": options.displayName ?? "" },
110
+ persistent: true,
111
+ idlePauseSeconds: options.idleTimeoutSeconds === -1 || options.idleTimeoutSeconds == null
112
+ ? 0
113
+ : options.idleTimeoutSeconds,
114
+ network: { internet: options.firewall.rules.length > 0 },
115
+ });
116
+ return {
117
+ vm: new Vm(this.runtime, s.id),
118
+ vmId: s.id,
119
+ data: data(s),
120
+ firewallRules: options.firewall.rules.map((rule) => ({
121
+ ...rule,
122
+ id: `runtime-network-${s.id}`,
123
+ source: { vmId: s.id },
124
+ })),
125
+ tlsRules: [],
126
+ };
127
+ }
128
+ ref(id) {
129
+ return new Vm(this.runtime, id);
130
+ }
131
+ async get(id) {
132
+ return data(await lookup(this.runtime, id));
133
+ }
134
+ async delete(id) {
135
+ await destroy(await lookup(this.runtime, id));
136
+ }
137
+ async list(options = {}) {
138
+ only("Freestyle list", options, ["state", "slug", "metadata", "limit", "offset"]);
139
+ const filters = options.metadata?.split(",").map((x) => {
140
+ const i = x.indexOf(":");
141
+ return [x.slice(0, i), x.slice(i + 1)];
142
+ }) ?? [];
143
+ const values = (await all(this.runtime, "freestyle")).filter((s) => (!options.state || s.state === options.state) &&
144
+ (!options.slug || s.info.name === options.slug) &&
145
+ filters.every(([k, v]) => s.info.labels[k] === v));
146
+ return {
147
+ vms: values
148
+ .slice(options.offset ?? 0, (options.offset ?? 0) + (options.limit ?? 100))
149
+ .map(data),
150
+ totalCount: values.length,
151
+ runningCount: values.filter((s) => s.state === "running").length,
152
+ startingCount: values.filter((s) => s.state === "starting").length,
153
+ pausingCount: values.filter((s) => s.state === "pausing").length,
154
+ pausedCount: values.filter((s) => s.state === "paused").length,
155
+ stoppedCount: values.filter((s) => s.state === "stopped").length,
156
+ };
157
+ }
158
+ }
159
+ export class VmFilesystem {
160
+ get;
161
+ files;
162
+ constructor(get) {
163
+ this.get = get;
164
+ this.files = new GuestFiles(get, "/");
165
+ }
166
+ async readFile(path, options = {}) {
167
+ return new Uint8Array(await new Response(await this.readFileStream(path, options)).arrayBuffer());
168
+ }
169
+ async readTextFile(path, options = {}) {
170
+ return new TextDecoder().decode(await this.readFile(path, options));
171
+ }
172
+ async readFileStream(path, options = {}) {
173
+ only("Freestyle file read", options, ["offset", "length", "signal"]);
174
+ for (const value of [options.offset, options.length])
175
+ if (value !== undefined && (!Number.isSafeInteger(value) || value < 0))
176
+ throw new TypeError("File range must contain nonnegative safe integers");
177
+ options.signal?.throwIfAborted();
178
+ const source = await (await this.get()).files.readStream(this.files.path(path), { signal: options.signal });
179
+ if (options.offset === undefined && options.length === undefined)
180
+ return source;
181
+ const reader = source.getReader();
182
+ let skip = options.offset ?? 0, remaining = options.length ?? Infinity;
183
+ return new ReadableStream({
184
+ async pull(controller) {
185
+ try {
186
+ options.signal?.throwIfAborted();
187
+ if (!remaining) {
188
+ await reader.cancel();
189
+ controller.close();
190
+ return;
191
+ }
192
+ for (;;) {
193
+ const next = await reader.read();
194
+ if (next.done) {
195
+ controller.close();
196
+ return;
197
+ }
198
+ const start = Math.min(skip, next.value.length);
199
+ skip -= start;
200
+ const chunk = next.value.subarray(start, start + remaining);
201
+ if (chunk.length) {
202
+ remaining -= chunk.length;
203
+ controller.enqueue(chunk);
204
+ return;
205
+ }
206
+ }
207
+ }
208
+ catch (error) {
209
+ await reader.cancel(error);
210
+ controller.error(error);
211
+ }
212
+ },
213
+ cancel(reason) {
214
+ return reader.cancel(reason);
215
+ },
216
+ });
217
+ }
218
+ async writeFile(path, content, options = {}) {
219
+ only("Freestyle file write", options, [
220
+ "mode",
221
+ "onProgress",
222
+ "signal",
223
+ "chunkSize",
224
+ "maxAttempts",
225
+ ]);
226
+ if (options.chunkSize !== undefined || options.maxAttempts !== undefined)
227
+ throw new CompatibilityError("Freestyle", "custom upload chunk sizing/retry budgets");
228
+ options.signal?.throwIfAborted();
229
+ const bytes = typeof content === "string"
230
+ ? new TextEncoder().encode(content)
231
+ : content instanceof Uint8Array
232
+ ? content
233
+ : new Uint8Array(await content.arrayBuffer());
234
+ let mode = options.mode;
235
+ if (mode === undefined)
236
+ mode = (await this.files.exists(path))
237
+ ? parseInt((await this.files.stat(path)).mode, 8)
238
+ : 0o600;
239
+ await this.files.writeFile(path, bytes, { mode, signal: options.signal });
240
+ options.onProgress?.({ completedBytes: bytes.length, totalBytes: bytes.length });
241
+ }
242
+ writeTextFile(path, content, options = {}) {
243
+ return this.writeFile(path, content, options);
244
+ }
245
+ mkdir(path) {
246
+ return this.files.mkdir(path, true);
247
+ }
248
+ remove(path) {
249
+ return this.files.remove(path, true);
250
+ }
251
+ exists(path) {
252
+ return this.files.exists(path);
253
+ }
254
+ async readDir(path) {
255
+ return (await this.files.readdir(path)).map((e) => ({ name: e.name, kind: e.type }));
256
+ }
257
+ async stat(path) {
258
+ const e = await this.files.stat(path);
259
+ const s = await this.get();
260
+ const r = await s.exec(["stat", "-c", "%U:%G", "--", this.files.path(path)], { check: true });
261
+ const [owner, group] = r.stdout.trim().split(":");
262
+ return {
263
+ size: e.size,
264
+ isFile: e.type === "file",
265
+ isDirectory: e.type === "directory",
266
+ isSymlink: e.type === "symlink",
267
+ permissions: e.mode,
268
+ owner: owner,
269
+ group: group,
270
+ modified: e.modifiedAt,
271
+ };
272
+ }
273
+ }
274
+ export class Vm {
275
+ runtime;
276
+ id;
277
+ fs;
278
+ constructor(runtime, id) {
279
+ this.runtime = runtime;
280
+ this.id = id;
281
+ this.fs = new VmFilesystem(() => lookup(runtime, id));
282
+ }
283
+ async data() {
284
+ return data(await lookup(this.runtime, this.id));
285
+ }
286
+ async start() {
287
+ return data(await ready(await lookup(this.runtime, this.id)));
288
+ }
289
+ async pause() {
290
+ const s = await lookup(this.runtime, this.id);
291
+ await s.pause();
292
+ return data(s);
293
+ }
294
+ async delete() {
295
+ await destroy(await lookup(this.runtime, this.id));
296
+ }
297
+ async update(options) {
298
+ only("Freestyle update", options, ["slug", "displayName", "idleTimeoutSeconds", "metadata"]);
299
+ validateMetadata(options.metadata);
300
+ const s = await lookup(this.runtime, this.id);
301
+ await s.update({
302
+ name: options.slug,
303
+ idlePauseSeconds: options.idleTimeoutSeconds === -1 ? 0 : options.idleTimeoutSeconds,
304
+ labels: {
305
+ ...s.info.labels,
306
+ ...options.metadata,
307
+ ...(options.displayName === undefined ? {} : { "fs.displayName": options.displayName }),
308
+ },
309
+ });
310
+ return data(s);
311
+ }
312
+ async exec(options) {
313
+ const o = typeof options === "string" ? { command: options } : options;
314
+ only("Freestyle exec", o, ["command", "linuxUser", "timeoutMs", "env", "stdin"]);
315
+ if (o.linuxUser)
316
+ throw new CompatibilityError("Freestyle", "exec as a different Linux user");
317
+ const s = await lookup(this.runtime, this.id);
318
+ const r = await s.exec(o.command, await execOptions(s, {
319
+ onStdout: () => undefined,
320
+ onStderr: () => undefined,
321
+ timeoutMs: o.timeoutMs,
322
+ env: o.env,
323
+ stdin: o.stdin === undefined
324
+ ? undefined
325
+ : Uint8Array.from(atob(o.stdin), (c) => c.charCodeAt(0)),
326
+ }));
327
+ return { stdout: r.stdout, stderr: r.stderr, statusCode: r.timedOut ? null : r.exitCode };
328
+ }
329
+ async snapshot(options = {}) {
330
+ only("Freestyle snapshot", options, ["slug", "displayName", "ttlSeconds"]);
331
+ if (options.ttlSeconds !== undefined &&
332
+ (options.ttlSeconds <= 0 ||
333
+ options.ttlSeconds > 365 * 86400 ||
334
+ options.ttlSeconds % 86400 !== 0))
335
+ throw new CompatibilityError("Freestyle", "snapshot retention outside 1 to 365 whole days");
336
+ const s = await (await lookup(this.runtime, this.id)).snapshot({
337
+ name: options.slug,
338
+ labels: options.displayName ? { "fs.displayName": options.displayName } : undefined,
339
+ retentionDays: options.ttlSeconds === undefined ? undefined : options.ttlSeconds / 86400,
340
+ });
341
+ return { snapshotId: s.id, sourceVmId: s.sourceSandboxId, snapshot: snapshotData(s) };
342
+ }
343
+ }
package/dist/index.d.ts CHANGED
@@ -15,12 +15,14 @@ export { Tunnel, TunnelStream, type PortForward } from "./tunnel.js";
15
15
  export { Page } from "./page.js";
16
16
  export type { KeyLimits } from "./products/limits.js";
17
17
  export type { AuditEvent, AuditPage } from "./products/audit.js";
18
+ export type { UsageExportQuery, UsageExportRow, UsageExportPage, } from "./products/observability.js";
18
19
  export type { JobSecret, Secret, SecretRule, SecretUse, SetSecret } from "./products/secrets.js";
19
20
  export { SecretPartlyStoredError } from "./products/secrets.js";
20
21
  export type { McpCatalogEntry, McpGateway, McpServerRequest } from "./products/mcp.js";
21
22
  export type { FileEvent, FileEventType, WatchHandle, WatchNotice, WatchOptions, } from "./products/watch.js";
22
23
  export type { Mount, MountBucket } from "./products/mounts.js";
23
- export type { Volume, VolumeBackup, CreateVolume } from "./products/volumes.js";
24
+ export type { TailscaleJoin, TailscaleJoined, TailscaleNode, TailscaleStatus, } from "./products/tailscale.js";
25
+ export type { Volume, VolumeBackup, CreateVolume, VolumeAttachment } from "./products/volumes.js";
24
26
  export type { CreateJob, Job, JobCompute, JobDefinition, JobLogs, JobRun, JobRunState, JobSchedule, } from "./products/jobs.js";
25
27
  export type { CheckedDomain, DnsRecord, Domain, EgressAddress, TcpPort, Tunnel as PrivateTunnel, TunnelPeer, TunnelPeerCreated, SetUpstreamProxy, UpstreamProxy, } from "./products/network-products.js";
26
28
  export { verifyWebhook, WebhookVerificationError, type MetricPoint, type MetricRange, type OtelExport, type RuntimeEvent, type SandboxMetrics, type Webhook, type WebhookDelivery, type WebhookEventType, } from "./products/observability.js";
package/dist/login.js CHANGED
@@ -4,7 +4,7 @@ import { hostname } from "node:os";
4
4
  import { promisify } from "node:util";
5
5
  import { me, named } from "./cli-name.js";
6
6
  import { Runtime } from "./client.js";
7
- import { connectionOrigins, connectionStore } from "./credentials.js";
7
+ import { checkBrowserOrigins, connectionOrigins, connectionStore } from "./credentials.js";
8
8
  import { describeRoute, envFetch } from "./proxy.js";
9
9
  class ConnectionError extends Error {
10
10
  status;
@@ -100,7 +100,16 @@ function connection(env, options) {
100
100
  : response.status === 429
101
101
  ? "Too many connection attempts. Try again later."
102
102
  : "Runtime could not complete this connection. Try again.");
103
- return (await response.json());
103
+ try {
104
+ return (await response.json());
105
+ }
106
+ catch (error) {
107
+ // A complete malformed answer remains terminal. A failed body read can
108
+ // hide an issued token, so keep its private key and retry the same request.
109
+ if (error instanceof SyntaxError)
110
+ throw error;
111
+ throw new ConnectionError(503, "Runtime's connection response was interrupted. Retry the connection.");
112
+ }
104
113
  }
105
114
  return { origins, store, request, fetcher };
106
115
  }
@@ -118,7 +127,7 @@ export async function authenticationCommand(args, env, options = {}) {
118
127
  // A pasted key belongs to the website's key list: forget it here, and
119
128
  // revoke it there if it should stop working everywhere.
120
129
  if (saved.connectionId === PASTED) {
121
- await store.remove();
130
+ await store.remove(saved.key);
122
131
  return { disconnected: true };
123
132
  }
124
133
  try {
@@ -128,7 +137,7 @@ export async function authenticationCommand(args, env, options = {}) {
128
137
  if (!(error instanceof ConnectionError && error.status === 401))
129
138
  throw error;
130
139
  }
131
- await store.remove();
140
+ await store.remove(saved.key);
132
141
  return { disconnected: true };
133
142
  }
134
143
  if (command !== "login")
@@ -160,11 +169,7 @@ export async function authenticationCommand(args, env, options = {}) {
160
169
  agentName.length > 80 ||
161
170
  [...agentName].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127))
162
171
  throw new Error("Give the agent a name of up to 80 characters.");
163
- // Never obtain a production key and then direct it at an unrelated API.
164
- const local = (origin) => ["localhost", "127.0.0.1", "[::1]"].includes(new URL(origin).hostname);
165
- if (!((origins.auth === "https://withruntime.com" && origins.api === "https://api.withruntime.com") ||
166
- (local(origins.auth) && local(origins.api))))
167
- throw new Error("Browser login supports Runtime's production endpoints or local test endpoints. Check RUNTIME_AUTH_URL and RUNTIME_API_URL.");
172
+ checkBrowserOrigins(origins);
168
173
  if (withKey) {
169
174
  // The fallback to a browser approval: a key from the website, read from
170
175
  // standard input so it never sits in a command line or shell history.
@@ -200,7 +205,7 @@ export async function authenticationCommand(args, env, options = {}) {
200
205
  catch (error) {
201
206
  if (!(error instanceof ConnectionError && error.status === 401))
202
207
  throw error;
203
- await store.remove();
208
+ await store.remove(saved.key);
204
209
  }
205
210
  }
206
211
  const pending = await beginDeviceLogin(env, agentName, options);
@@ -245,6 +250,7 @@ export async function authenticationCommand(args, env, options = {}) {
245
250
  * the agent, which shows it to the person it works for. */
246
251
  export async function beginDeviceLogin(env, agentName, options = {}) {
247
252
  const { origins, store, request } = connection(env, options);
253
+ checkBrowserOrigins(origins);
248
254
  // A request this machine already started and nobody has answered yet: pick
249
255
  // it up, so a command stopped while it waited (an agent's tool timing out)
250
256
  // shows the same link and code again rather than a new one to approve.
package/dist/mcp.js CHANGED
@@ -3,7 +3,7 @@ import { resolveCredential } from "./credentials.js";
3
3
  import { RuntimeError } from "./errors.js";
4
4
  import { beginDeviceLogin } from "./login.js";
5
5
  import { envFetch } from "./proxy.js";
6
- import { apiOrigin, VERSION } from "./transport.js";
6
+ import { apiOrigin, defaultBaseUrl, VERSION } from "./transport.js";
7
7
  const CONNECT_INSTRUCTIONS = "Runtime is not connected on this machine yet. Call runtime_connect: it gives you a link and a code for the person you work for to approve in their browser (no key to copy). Once they have, call runtime_connect again; Runtime's tools then appear (sandboxes, commands, files and the rest).";
8
8
  const connectTool = {
9
9
  name: "runtime_connect",
@@ -32,7 +32,7 @@ export async function serveMcp(env = process.env, options = {}) {
32
32
  if (!(error instanceof RuntimeError && error.code === "missing_api_key"))
33
33
  throw error;
34
34
  }
35
- const origin = apiOrigin(env.RUNTIME_API_URL ?? "https://api.withruntime.com");
35
+ const origin = apiOrigin(defaultBaseUrl(env));
36
36
  const endpoint = new URL("/mcp", origin).href;
37
37
  let protocolVersion;
38
38
  let session;
@@ -0,0 +1,205 @@
1
+ import type { Runtime } from "../client.js";
2
+ import type { Process, Sandbox as NativeSandbox } from "../sandbox.js";
3
+ import { CompatibilityError, type ClientOptions } from "../compat/core.js";
4
+ export { CompatibilityError };
5
+ export declare class NotFoundError extends Error {
6
+ }
7
+ export declare class TimeoutError extends Error {
8
+ }
9
+ export declare class SandboxTimeoutError extends TimeoutError {
10
+ }
11
+ export declare class App {
12
+ readonly appId: string;
13
+ readonly name?: string | undefined;
14
+ readonly environmentName?: string | undefined;
15
+ constructor(appId: string, name?: string | undefined, environmentName?: string | undefined);
16
+ }
17
+ export type SandboxCreateParams = {
18
+ name?: string;
19
+ cpu?: number;
20
+ cpuLimit?: number;
21
+ memoryMiB?: number;
22
+ memoryLimitMiB?: number;
23
+ timeoutMs?: number;
24
+ workdir?: string;
25
+ command?: string[];
26
+ env?: Record<string, string>;
27
+ encryptedPorts?: number[];
28
+ blockNetwork?: boolean;
29
+ outboundDomainAllowlist?: string[];
30
+ outboundCidrAllowlist?: string[];
31
+ tags?: Record<string, string>;
32
+ regions?: string[];
33
+ gpu?: string;
34
+ pty?: boolean;
35
+ secrets?: Secret[];
36
+ experimentalEnableSnapshot?: boolean;
37
+ };
38
+ export type SandboxExecParams = {
39
+ mode?: "text" | "binary";
40
+ stdout?: "pipe" | "ignore";
41
+ stderr?: "pipe" | "ignore";
42
+ workdir?: string;
43
+ timeoutMs?: number;
44
+ env?: Record<string, string>;
45
+ pty?: boolean;
46
+ secrets?: Secret[];
47
+ };
48
+ export declare class Secret {
49
+ readonly values: Record<string, string>;
50
+ constructor(values: Record<string, string>);
51
+ }
52
+ export declare class ModalClient {
53
+ readonly runtime: Runtime;
54
+ readonly apps: {
55
+ fromName: (name: string, params?: {
56
+ createIfMissing?: boolean;
57
+ environment?: string;
58
+ }) => Promise<App>;
59
+ };
60
+ readonly images: {
61
+ fromRegistry: (tag: string, secret?: Secret) => Image;
62
+ fromId: (id: string) => Promise<Image>;
63
+ fromName: (name: string) => Promise<Image>;
64
+ delete: (id: string) => Promise<import("../products/images.js").Image>;
65
+ };
66
+ readonly sandboxes: SandboxService;
67
+ readonly secrets: {
68
+ fromObject: (values: Record<string, string>) => Secret;
69
+ };
70
+ constructor(options?: ClientOptions & {
71
+ tokenId?: string;
72
+ tokenSecret?: string;
73
+ environment?: string;
74
+ });
75
+ close(): void;
76
+ }
77
+ export declare class Image {
78
+ private readonly runtime;
79
+ private readonly tag?;
80
+ private readonly commands;
81
+ private readonly env;
82
+ private readonly cache;
83
+ private built?;
84
+ constructor(runtime: Runtime, tag?: string | undefined, id?: string, commands?: string[], env?: Record<string, string>, cache?: boolean);
85
+ get imageId(): string;
86
+ dockerfileCommands(commands: string[], params?: {
87
+ env?: Record<string, string>;
88
+ forceBuild?: boolean;
89
+ }): Image;
90
+ build(_app?: App): Promise<Image>;
91
+ }
92
+ export declare class SandboxService {
93
+ private readonly runtime;
94
+ constructor(runtime: Runtime);
95
+ create(app: App, image: Image, params?: SandboxCreateParams): Promise<Sandbox>;
96
+ fromId(id: string): Promise<Sandbox>;
97
+ fromName(appName: string, name: string): Promise<Sandbox>;
98
+ list(params?: {
99
+ appId?: string;
100
+ tags?: Record<string, string>;
101
+ }): AsyncGenerator<Sandbox, void, unknown>;
102
+ }
103
+ export declare class ModalReadStream<R extends string | Uint8Array = string> extends ReadableStream<R> {
104
+ constructor(process: Process, channel: "stdout" | "stderr", mode: "text" | "binary", ignore?: boolean);
105
+ readBytes(): Promise<Uint8Array<ArrayBuffer>>;
106
+ readText(): Promise<string>;
107
+ }
108
+ export declare class ModalWriteStream<R extends string | Uint8Array = string> extends WritableStream<R> {
109
+ private readonly process;
110
+ constructor(process: Process);
111
+ writeText(text: string): Promise<void>;
112
+ writeBytes(bytes: Uint8Array): Promise<void>;
113
+ }
114
+ export declare class ContainerProcess<R extends string | Uint8Array = string> {
115
+ readonly native: Process;
116
+ readonly stdin: ModalWriteStream<R>;
117
+ readonly stdout: ModalReadStream<R>;
118
+ readonly stderr: ModalReadStream<R>;
119
+ constructor(native: Process, mode: "text" | "binary", params?: SandboxExecParams);
120
+ wait(): Promise<number>;
121
+ closeStdin(): Promise<void>;
122
+ _poll(): Promise<number | null>;
123
+ }
124
+ export declare class Sandbox {
125
+ readonly native: NativeSandbox;
126
+ private readonly primary;
127
+ readonly filesystem: SandboxFilesystem;
128
+ readonly stdin: ModalWriteStream<string>;
129
+ readonly stdout: ModalReadStream<string>;
130
+ readonly stderr: ModalReadStream<string>;
131
+ private detached;
132
+ private lifecycle?;
133
+ monitorEntrypoint(): void;
134
+ constructor(native: NativeSandbox, primary: ContainerProcess);
135
+ get sandboxId(): string;
136
+ exec(command: string[], params?: SandboxExecParams & {
137
+ mode?: "text";
138
+ }): Promise<ContainerProcess<string>>;
139
+ exec(command: string[], params: SandboxExecParams & {
140
+ mode: "binary";
141
+ }): Promise<ContainerProcess<Uint8Array>>;
142
+ setTags(tags: Record<string, string>): Promise<void>;
143
+ getTags(): Promise<Record<string, string>>;
144
+ terminate(params?: {
145
+ wait?: boolean;
146
+ }): Promise<number | undefined>;
147
+ detach(): void;
148
+ wait(): Promise<number>;
149
+ poll(): Promise<number | null>;
150
+ tunnels(): Promise<{
151
+ [k: string]: {
152
+ host: string;
153
+ port: number;
154
+ url: string;
155
+ };
156
+ }>;
157
+ updateNetworkPolicy(params: {
158
+ outboundCidrAllowlist: string[];
159
+ outboundDomainAllowlist: string[];
160
+ }): Promise<void>;
161
+ experimentalSnapshot(): Promise<{
162
+ sandboxSnapshotId: string;
163
+ }>;
164
+ }
165
+ export declare class SandboxFilesystem {
166
+ private readonly native;
167
+ private readonly files;
168
+ constructor(native: NativeSandbox);
169
+ readBytes(path: string): Promise<Uint8Array<ArrayBufferLike>>;
170
+ readText(path: string): Promise<string>;
171
+ writeBytes(content: Uint8Array | ArrayBuffer, path: string): Promise<void>;
172
+ writeText(content: string, path: string): Promise<void>;
173
+ makeDirectory(path: string, options?: {
174
+ createParents?: boolean;
175
+ }): Promise<void>;
176
+ remove(path: string, options?: {
177
+ recursive?: boolean;
178
+ }): Promise<void>;
179
+ copyFromLocal(localPath: string, remotePath: string): Promise<void>;
180
+ copyToLocal(remotePath: string, localPath: string): Promise<void>;
181
+ listFiles(path: string): Promise<{
182
+ name: string;
183
+ path: string;
184
+ type: "file" | "directory" | "symlink" | "other";
185
+ size: number;
186
+ mode: number;
187
+ permissions: string;
188
+ owner: string;
189
+ group: string;
190
+ modifiedTime: number;
191
+ symlinkTarget: string | null;
192
+ }[]>;
193
+ stat(path: string): Promise<{
194
+ name: string;
195
+ path: string;
196
+ type: "file" | "directory" | "symlink" | "other";
197
+ size: number;
198
+ mode: number;
199
+ permissions: string;
200
+ owner: string;
201
+ group: string;
202
+ modifiedTime: number;
203
+ symlinkTarget: string | null;
204
+ }>;
205
+ }