withruntime 0.11.2 → 0.12.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 (164) hide show
  1. package/BLAXEL.md +2 -2
  2. package/COMPATIBILITY.md +74 -9
  3. package/DAYTONA.md +33 -5
  4. package/E2B.md +85 -24
  5. package/README.md +9 -8
  6. package/README.src.md +8 -7
  7. package/VERCEL.md +43 -5
  8. package/compatibility-lock.json +58 -49
  9. package/dist/billing-cli.d.ts +1 -1
  10. package/dist/billing-cli.js +77 -1
  11. package/dist/blaxel/errors.d.ts +14 -0
  12. package/dist/blaxel/errors.js +29 -0
  13. package/dist/blaxel/index.d.ts +2 -2
  14. package/dist/blaxel/index.js +4 -4
  15. package/dist/blaxel/interpreter.d.ts +2 -5
  16. package/dist/blaxel/sandbox.d.ts +14 -5
  17. package/dist/blaxel/sandbox.js +63 -7
  18. package/dist/blaxel/snapshot.d.ts +4 -0
  19. package/dist/blaxel/snapshot.js +4 -0
  20. package/dist/cli/logs.d.ts +21 -0
  21. package/dist/cli/logs.js +188 -0
  22. package/dist/cli/logs.test.d.ts +1 -0
  23. package/dist/cli/logs.test.js +272 -0
  24. package/dist/cli-extras.d.ts +3 -3
  25. package/dist/cli-extras.js +17 -12
  26. package/dist/cli-words.d.ts +10 -1
  27. package/dist/cli-words.js +18 -0
  28. package/dist/cli.d.ts +5 -1
  29. package/dist/cli.js +405 -204
  30. package/dist/client.d.ts +2 -4
  31. package/dist/client.js +27 -11
  32. package/dist/cloudflare/errors.d.ts +143 -0
  33. package/dist/cloudflare/errors.js +167 -0
  34. package/dist/cloudflare/index.d.ts +175 -6
  35. package/dist/cloudflare/index.js +416 -26
  36. package/dist/cloudflare/interpreter.d.ts +119 -0
  37. package/dist/cloudflare/interpreter.js +223 -0
  38. package/dist/cloudflare/streams.d.ts +38 -0
  39. package/dist/cloudflare/streams.js +145 -0
  40. package/dist/cloudflare/watch.d.ts +9 -0
  41. package/dist/cloudflare/watch.js +24 -0
  42. package/dist/codesandbox/index.d.ts +216 -28
  43. package/dist/codesandbox/index.js +478 -78
  44. package/dist/compat/core.d.ts +18 -0
  45. package/dist/compat/core.js +29 -0
  46. package/dist/compat/files.d.ts +1 -0
  47. package/dist/compat/files.js +25 -5
  48. package/dist/daytona/computer-use.d.ts +13 -4
  49. package/dist/daytona/computer-use.js +66 -8
  50. package/dist/daytona/daytona.d.ts +6 -0
  51. package/dist/daytona/daytona.js +30 -3
  52. package/dist/daytona/errors.d.ts +11 -0
  53. package/dist/daytona/errors.js +18 -0
  54. package/dist/daytona/filesystem.js +2 -1
  55. package/dist/daytona/image.d.ts +13 -9
  56. package/dist/daytona/image.js +18 -12
  57. package/dist/daytona/index.d.ts +1 -1
  58. package/dist/daytona/sandbox.d.ts +8 -2
  59. package/dist/daytona/sandbox.js +16 -2
  60. package/dist/domains/index.d.ts +9 -0
  61. package/dist/domains/index.js +10 -0
  62. package/dist/e2b/commands.d.ts +1 -1
  63. package/dist/e2b/commands.js +6 -7
  64. package/dist/e2b/errors.d.ts +17 -0
  65. package/dist/e2b/errors.js +40 -1
  66. package/dist/e2b/index.d.ts +2 -2
  67. package/dist/e2b/index.js +1 -1
  68. package/dist/e2b/sandbox.d.ts +27 -21
  69. package/dist/e2b/sandbox.js +34 -8
  70. package/dist/e2b/unsupported.js +1 -1
  71. package/dist/errors.d.ts +10 -1
  72. package/dist/errors.js +16 -4
  73. package/dist/freestyle/index.d.ts +285 -37
  74. package/dist/freestyle/index.js +656 -144
  75. package/dist/identity.d.ts +3 -0
  76. package/dist/identity.js +27 -0
  77. package/dist/images/index.d.ts +9 -0
  78. package/dist/images/index.js +10 -0
  79. package/dist/index.d.ts +1 -0
  80. package/dist/ips/index.d.ts +9 -0
  81. package/dist/ips/index.js +10 -0
  82. package/dist/jobs/index.d.ts +9 -0
  83. package/dist/jobs/index.js +10 -0
  84. package/dist/keys.js +2 -0
  85. package/dist/login.js +2 -0
  86. package/dist/machine.d.ts +12 -3
  87. package/dist/machine.js +20 -3
  88. package/dist/mcp.js +1 -1
  89. package/dist/modal/index.d.ts +339 -61
  90. package/dist/modal/index.js +781 -121
  91. package/dist/network-cli.d.ts +3 -1
  92. package/dist/network-cli.js +78 -36
  93. package/dist/networks/index.d.ts +11 -0
  94. package/dist/networks/index.js +10 -0
  95. package/dist/ports/index.d.ts +9 -0
  96. package/dist/ports/index.js +10 -0
  97. package/dist/product-names.d.ts +52 -0
  98. package/dist/product-names.js +164 -0
  99. package/dist/product-renames.d.ts +103 -0
  100. package/dist/product-renames.js +189 -0
  101. package/dist/products/apps.d.ts +176 -0
  102. package/dist/products/apps.js +165 -0
  103. package/dist/products/billing.d.ts +41 -0
  104. package/dist/products/billing.js +13 -0
  105. package/dist/products/browser.d.ts +10 -0
  106. package/dist/products/browsers.d.ts +71 -0
  107. package/dist/products/browsers.js +102 -0
  108. package/dist/products/builds.d.ts +126 -0
  109. package/dist/products/builds.js +221 -0
  110. package/dist/products/index.d.ts +3 -16
  111. package/dist/products/index.js +3 -14
  112. package/dist/products/jobs.d.ts +17 -0
  113. package/dist/products/jobs.js +27 -10
  114. package/dist/products/logs.d.ts +245 -0
  115. package/dist/products/logs.js +274 -0
  116. package/dist/products/logs.test.d.ts +1 -0
  117. package/dist/products/logs.test.js +343 -0
  118. package/dist/products/network-products.d.ts +38 -7
  119. package/dist/products/network-products.js +31 -18
  120. package/dist/products/observability.d.ts +3 -2
  121. package/dist/products/private-network.js +1 -1
  122. package/dist/products/queues.d.ts +149 -0
  123. package/dist/products/queues.js +213 -0
  124. package/dist/products/referrals.d.ts +21 -7
  125. package/dist/products/referrals.js +3 -3
  126. package/dist/products/secrets.d.ts +7 -3
  127. package/dist/products/secrets.js +39 -18
  128. package/dist/products/signed-requests.d.ts +21 -0
  129. package/dist/products/signed-requests.js +11 -0
  130. package/dist/products/streams.d.ts +73 -0
  131. package/dist/products/streams.js +17 -0
  132. package/dist/products/switching.d.ts +35 -7
  133. package/dist/products/switching.js +4 -3
  134. package/dist/products/volumes.d.ts +3 -0
  135. package/dist/record-cli.d.ts +23 -0
  136. package/dist/record-cli.js +308 -0
  137. package/dist/renamed-products.d.ts +38 -0
  138. package/dist/renamed-products.js +34 -0
  139. package/dist/runloop/index.d.ts +9 -2
  140. package/dist/runloop/index.js +29 -8
  141. package/dist/runloop/sdk.d.ts +131 -0
  142. package/dist/runloop/sdk.js +167 -0
  143. package/dist/sandbox.d.ts +3 -2
  144. package/dist/sandbox.js +14 -9
  145. package/dist/sandboxes/index.d.ts +11 -0
  146. package/dist/sandboxes/index.js +12 -0
  147. package/dist/snapshots/index.d.ts +9 -0
  148. package/dist/snapshots/index.js +11 -0
  149. package/dist/snapshots.d.ts +7 -4
  150. package/dist/sprites/index.d.ts +343 -31
  151. package/dist/sprites/index.js +866 -84
  152. package/dist/ssh.d.ts +1 -1
  153. package/dist/ssh.js +11 -11
  154. package/dist/terminal.d.ts +24 -0
  155. package/dist/terminal.js +32 -0
  156. package/dist/transport.d.ts +15 -1
  157. package/dist/transport.js +56 -3
  158. package/dist/tunnel.js +1 -1
  159. package/dist/types.d.ts +63 -0
  160. package/dist/vercel/sandbox.js +5 -3
  161. package/dist/vercel/snapshot.js +10 -2
  162. package/dist/volumes/index.d.ts +9 -0
  163. package/dist/volumes/index.js +10 -0
  164. package/package.json +48 -2
@@ -0,0 +1,343 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { Transport } from "../transport.js";
3
+ import { InvalidRequestError, NotFoundError, RuntimeError } from "../errors.js";
4
+ import { alerts, logs, traces, } from "./logs.js";
5
+ const line = {
6
+ time: "2026-10-06T12:00:00.000000001Z",
7
+ timeUnixNano: "1791288000000000001",
8
+ severity: 9,
9
+ severityText: "INFO",
10
+ body: "same α",
11
+ labels: { app: "api" },
12
+ attributes: {},
13
+ traceId: null,
14
+ spanId: null,
15
+ };
16
+ const view = { name: "errors", labels: {}, query: '{app="api"}' };
17
+ const rule = { ...view, condition: "above 5", for: "5m", actions: [] };
18
+ const options = { idempotencyKey: "repeat-this-write" };
19
+ function fixture(handler) {
20
+ const requests = [];
21
+ const transport = new Transport({
22
+ apiKey: "test-only",
23
+ baseUrl: "https://fake.test",
24
+ maxRetries: 0,
25
+ fetch: (async (input, init) => {
26
+ const url = new URL(input instanceof Request ? input.url : input);
27
+ requests.push({ url, init: init ?? {} });
28
+ return handler(url, init ?? {});
29
+ }),
30
+ });
31
+ return {
32
+ client: { logs: logs(transport), traces: traces(transport), alerts: alerts(transport) },
33
+ requests,
34
+ };
35
+ }
36
+ const calls = [
37
+ {
38
+ name: "search",
39
+ method: "POST",
40
+ path: "/v1/logs:search",
41
+ body: { query: "{}", cursor: "c", limit: 2 },
42
+ run: (c) => c.logs.search({ query: "{}", cursor: "c", limit: 2 }, options),
43
+ },
44
+ {
45
+ name: "instant query",
46
+ method: "GET",
47
+ path: "/v1/logs/query",
48
+ query: { query: "{}", time: "42", direction: "forward", limit: "2" },
49
+ run: (c) => c.logs.query({ query: "{}", time: "42", direction: "forward", limit: 2 }),
50
+ },
51
+ {
52
+ name: "range query",
53
+ method: "GET",
54
+ path: "/v1/logs/query_range",
55
+ query: { query: "{}", start: "1", end: "2", step: "1s" },
56
+ run: (c) => c.logs.queryRange({ query: "{}", start: "1", end: "2", step: "1s" }),
57
+ },
58
+ {
59
+ name: "labels",
60
+ method: "GET",
61
+ path: "/v1/logs/labels",
62
+ query: { start: "1", end: "2" },
63
+ run: (c) => c.logs.labels({ start: "1", end: "2" }),
64
+ },
65
+ {
66
+ name: "label values",
67
+ method: "GET",
68
+ path: "/v1/logs/labels/a%2Fb/values",
69
+ run: (c) => c.logs.labelValues("a/b"),
70
+ },
71
+ { name: "trace get", method: "GET", path: "/v1/traces/a%2Fb", run: (c) => c.traces.get("a/b") },
72
+ {
73
+ name: "trace search",
74
+ method: "GET",
75
+ path: "/v1/traces:search",
76
+ query: { q: "{duration > 1s}" },
77
+ run: (c) => c.traces.search("{duration > 1s}"),
78
+ },
79
+ { name: "trace tags", method: "GET", path: "/v1/traces/tags", run: (c) => c.traces.tags() },
80
+ {
81
+ name: "log ingest",
82
+ method: "POST",
83
+ path: "/v1/logs",
84
+ body: { resourceLogs: [] },
85
+ run: (c) => c.logs.ingest({ resourceLogs: [] }, options),
86
+ },
87
+ {
88
+ name: "trace ingest",
89
+ method: "POST",
90
+ path: "/v1/traces",
91
+ body: { resourceSpans: [] },
92
+ run: (c) => c.traces.ingest({ resourceSpans: [] }, options),
93
+ },
94
+ {
95
+ name: "test rule",
96
+ method: "POST",
97
+ path: "/v1/alerts/a%2Fb/test",
98
+ body: {},
99
+ run: (c) => c.alerts.test("a/b", options),
100
+ },
101
+ {
102
+ name: "silence rule",
103
+ method: "POST",
104
+ path: "/v1/alerts/a%2Fb/silence",
105
+ body: { until: "2026-10-07T00:00:00Z" },
106
+ run: (c) => c.alerts.silence("a/b", { until: "2026-10-07T00:00:00Z" }, options),
107
+ },
108
+ ];
109
+ for (const product of ["logs", "traces", "alerts"]) {
110
+ const path = `/v1/${product}${product === "alerts" ? "" : "/views"}`;
111
+ const resource = (c) => (product === "alerts" ? c.alerts : c[product].views);
112
+ const input = product === "alerts" ? rule : view;
113
+ calls.push({
114
+ name: `${product} create`,
115
+ method: "POST",
116
+ path,
117
+ body: input,
118
+ run: (c) => resource(c).create(input, options),
119
+ }, {
120
+ name: `${product} get`,
121
+ method: "GET",
122
+ path: `${path}/a%2Fb`,
123
+ run: (c) => resource(c).get("a/b"),
124
+ }, {
125
+ name: `${product} list`,
126
+ method: "GET",
127
+ path,
128
+ query: { limit: "2", cursor: "next" },
129
+ run: (c) => resource(c).list({ limit: 2, cursor: "next" }),
130
+ }, {
131
+ name: `${product} update`,
132
+ method: "PUT",
133
+ path: `${path}/a%2Fb`,
134
+ body: { name: "changed" },
135
+ run: (c) => resource(c).update("a/b", { name: "changed" }, options),
136
+ }, {
137
+ name: `${product} delete`,
138
+ method: "DELETE",
139
+ path: `${path}/a%2Fb`,
140
+ run: (c) => resource(c).delete("a/b", options),
141
+ });
142
+ }
143
+ for (const entry of calls)
144
+ describe(entry.name, () => {
145
+ test("request, encoded path and idempotency", async () => {
146
+ const { client, requests } = fixture(() => entry.method === "DELETE"
147
+ ? new Response(null, { status: 204 })
148
+ : Response.json({ data: [], nextCursor: null, marker: "answer" }));
149
+ const answer = await entry.run(client);
150
+ if (entry.method === "DELETE")
151
+ expect(answer).toBeNull();
152
+ else if (!entry.name.endsWith(" list"))
153
+ expect(answer).toEqual({ data: [], nextCursor: null, marker: "answer" });
154
+ expect(requests).toHaveLength(1);
155
+ const request = requests[0];
156
+ expect(request.url.pathname).toBe(entry.path);
157
+ expect(request.init.method).toBe(entry.method);
158
+ expect(Object.fromEntries(request.url.searchParams)).toEqual(entry.query ?? {});
159
+ if (entry.body !== undefined)
160
+ expect(JSON.parse(request.init.body)).toEqual(entry.body);
161
+ if (entry.method !== "GET")
162
+ expect(new Headers(request.init.headers).get("idempotency-key")).toBe(options.idempotencyKey);
163
+ });
164
+ test("typed server error preserves code and hint", async () => {
165
+ const { client } = fixture(() => Response.json({ error: { code: "not_found", message: "Missing", hint: "List resources first." } }, { status: 404 }));
166
+ try {
167
+ await entry.run(client);
168
+ throw new Error("Accepted failure");
169
+ }
170
+ catch (error) {
171
+ expect(error).toBeInstanceOf(NotFoundError);
172
+ expect(error).toMatchObject({ code: "not_found", hint: "List resources first." });
173
+ }
174
+ });
175
+ });
176
+ test("search paging follows opaque cursors and preserves equal timestamp lines", async () => {
177
+ const { client, requests } = fixture((_url, init) => Response.json({
178
+ lines: [line],
179
+ next: JSON.parse(init.body).cursor ? null : "opaque/+",
180
+ stats: {},
181
+ }));
182
+ const pages = [];
183
+ for await (const page of client.logs.pages({ query: "{}", limit: 1 }))
184
+ pages.push(page);
185
+ expect(pages.flatMap((page) => page.lines)).toEqual([line, line]);
186
+ expect(JSON.parse(requests[1].init.body).cursor).toBe("opaque/+");
187
+ });
188
+ test("search detects a cursor loop", async () => {
189
+ const { client } = fixture(() => Response.json({ lines: [], next: "same" }));
190
+ const iterator = client.logs.pages({ query: "{}", cursor: "same" });
191
+ await iterator.next();
192
+ await expect(iterator.next()).rejects.toMatchObject({ code: "invalid_stream" });
193
+ });
194
+ test("resource lists can follow pages", async () => {
195
+ const { client } = fixture((url) => Response.json({
196
+ data: [{ id: url.searchParams.get("cursor") ?? "first" }],
197
+ nextCursor: url.searchParams.has("cursor") ? null : "second",
198
+ }));
199
+ expect(await (await client.logs.views.list()).toArray()).toEqual([
200
+ { id: "first" },
201
+ { id: "second" },
202
+ ]);
203
+ });
204
+ test("UTF-8 bytes enforce the 1 MiB ingest bound before fetch", () => {
205
+ const { client, requests } = fixture(() => {
206
+ throw new Error("No fetch allowed");
207
+ });
208
+ expect(() => client.logs.ingest({ resourceLogs: [{ body: "α".repeat(530_000) }] })).toThrow(InvalidRequestError);
209
+ expect(() => client.traces.ingest({ resourceSpans: [{ body: "α".repeat(530_000) }] })).toThrow(InvalidRequestError);
210
+ expect(requests).toHaveLength(0);
211
+ });
212
+ const event = (id, value = line, kind = "") => `id: ${id}\r\n${kind ? `event: ${kind}\r\n` : ""}data: ${JSON.stringify(value)}\r\n\r\n`;
213
+ function sse(text, bytes = 7) {
214
+ const encoded = new TextEncoder().encode(text);
215
+ let at = 0;
216
+ return new Response(new ReadableStream({
217
+ pull(controller) {
218
+ if (at === encoded.length)
219
+ controller.close();
220
+ else {
221
+ controller.enqueue(encoded.slice(at, at + bytes));
222
+ at = Math.min(at + bytes, encoded.length);
223
+ }
224
+ },
225
+ }), { headers: { "content-type": "text/event-stream" } });
226
+ }
227
+ test("SSE reconnects by cursor, discards torn events, keeps identical records and decodes split UTF-8", async () => {
228
+ let count = 0;
229
+ const { client, requests } = fixture(() => ++count === 1
230
+ ? sse(`: heartbeat\r\n\r\n${event("one")}id: torn\ndata: {`)
231
+ : sse(event("one") + event("two")));
232
+ const iterator = client.logs.tail("{}", { reconnectMs: 0 });
233
+ expect((await iterator.next()).value).toEqual({ ...line, cursor: "one" });
234
+ expect((await iterator.next()).value).toEqual({ ...line, cursor: "two" });
235
+ expect(requests[1].url.searchParams.get("cursor")).toBe("one");
236
+ expect(new Headers(requests[0].init.headers).get("accept")).toBe("text/event-stream");
237
+ await iterator.return(undefined);
238
+ });
239
+ test("a drop before the first line resumes from the start event's cursor", async () => {
240
+ let count = 0;
241
+ const { client, requests } = fixture(() => ++count === 1
242
+ ? sse(event("began", { cursor: "began" }, "start"))
243
+ : sse(event("began", { cursor: "began" }, "start") + event("first")));
244
+ const iterator = client.logs.tail("{}", { reconnectMs: 0 });
245
+ expect((await iterator.next()).value).toEqual({ ...line, cursor: "first" });
246
+ expect(requests[0].url.searchParams.get("cursor")).toBeNull();
247
+ expect(requests[1].url.searchParams.get("cursor")).toBe("began");
248
+ await iterator.return(undefined);
249
+ });
250
+ test("server retention gap becomes typed from/to and resumes after it", async () => {
251
+ const { client } = fixture(() => sse(event("kept", { cursor: "kept" }, "gap") + event("later")));
252
+ const iterator = client.logs.tail("{}", { cursor: "expired", reconnectMs: 0 });
253
+ expect((await iterator.next()).value).toEqual({ gap: true, from: "expired", to: "kept" });
254
+ expect((await iterator.next()).value).toMatchObject({ cursor: "later" });
255
+ await iterator.return(undefined);
256
+ });
257
+ test("reader error, including an echoed cursor, reconnects without moving the cursor", async () => {
258
+ let calls = 0;
259
+ const { client, requests } = fixture(() => sse(++calls === 1 ? event("old", { message: "reader gone" }, "error") : event("new")));
260
+ const iterator = client.logs.tail("{}", { cursor: "old", reconnectMs: 0 });
261
+ expect((await iterator.next()).value).toMatchObject({ cursor: "new" });
262
+ expect(requests[1].url.searchParams.get("cursor")).toBe("old");
263
+ await iterator.return(undefined);
264
+ });
265
+ for (const [name, wire] of [
266
+ ["JSON", "id: a\ndata: nope\n\n"],
267
+ ["cursor", `data: ${JSON.stringify(line)}\n\n`],
268
+ ["line", event("a", {})],
269
+ ["gap", event("a", { cursor: "wrong" }, "gap")],
270
+ ["oversized event", `id: a\ndata: ${"x".repeat(1_048_577)}\n\n`],
271
+ ])
272
+ test(`invalid ${name} fails once with a code and hint`, async () => {
273
+ const { client, requests } = fixture(() => sse(wire, wire.length));
274
+ await expect(client.logs.tail("{}", { cursor: "before", reconnectMs: 0 }).next()).rejects.toMatchObject({ code: "invalid_stream", hint: expect.any(String) });
275
+ expect(requests).toHaveLength(1);
276
+ });
277
+ test("tail authentication errors do not reconnect", async () => {
278
+ const { client, requests } = fixture(() => Response.json({ error: { code: "unauthorized", hint: "Sign in." } }, { status: 401 }));
279
+ await expect(client.logs.tail("{}").next()).rejects.toMatchObject({
280
+ code: "unauthorized",
281
+ hint: "Sign in.",
282
+ });
283
+ expect(requests).toHaveLength(1);
284
+ });
285
+ test("aborting an idle connection closes its reader", async () => {
286
+ let cancelled = false;
287
+ const { client } = fixture(() => new Response(new ReadableStream({
288
+ cancel() {
289
+ cancelled = true;
290
+ },
291
+ }), { headers: { "content-type": "text/event-stream" } }));
292
+ const abort = new AbortController();
293
+ const iterator = client.logs.tail("{}", { signal: abort.signal });
294
+ const pending = iterator.next();
295
+ await new Promise((resolve) => setTimeout(resolve, 10));
296
+ abort.abort(new Error("test stop"));
297
+ await expect(pending).rejects.toThrow("test stop");
298
+ expect(cancelled).toBe(true);
299
+ });
300
+ test("non-SSE success is refused", async () => {
301
+ const { client } = fixture(() => Response.json({}));
302
+ await expect(client.logs.tail("{}").next()).rejects.toBeInstanceOf(RuntimeError);
303
+ });
304
+ test("an abrupt read failure reconnects, rather than only handling clean EOF", async () => {
305
+ let attempt = 0;
306
+ const { client, requests } = fixture(() => {
307
+ if (++attempt > 1)
308
+ return sse(event("second"));
309
+ let pulled = false;
310
+ return new Response(new ReadableStream({
311
+ pull(controller) {
312
+ if (!pulled) {
313
+ pulled = true;
314
+ controller.enqueue(new TextEncoder().encode(event("first")));
315
+ }
316
+ else
317
+ controller.error(new Error("connection reset"));
318
+ },
319
+ }), { headers: { "content-type": "text/event-stream" } });
320
+ });
321
+ const iterator = client.logs.tail("{}", { reconnectMs: 0 });
322
+ expect((await iterator.next()).value).toMatchObject({ cursor: "first" });
323
+ expect((await iterator.next()).value).toMatchObject({ cursor: "second" });
324
+ expect(requests[1].url.searchParams.get("cursor")).toBe("first");
325
+ await iterator.return(undefined);
326
+ });
327
+ test("a multline SSE event and the agreed from/to gap shape are accepted", async () => {
328
+ const { client } = fixture(() => sse('event: gap\nid: kept\ndata: {"from":"old",\ndata: "to":"kept"}\n\n'));
329
+ const iterator = client.logs.tail("{}", { cursor: "old" });
330
+ expect((await iterator.next()).value).toEqual({ gap: true, from: "old", to: "kept" });
331
+ await iterator.return(undefined);
332
+ });
333
+ test("abort during reconnect delay stops without another request", async () => {
334
+ const abort = new AbortController();
335
+ const { client, requests } = fixture(() => sse(event("first")));
336
+ const iterator = client.logs.tail("{}", { signal: abort.signal, reconnectMs: 1000 });
337
+ await iterator.next();
338
+ const pending = iterator.next();
339
+ await new Promise((resolve) => setTimeout(resolve, 10));
340
+ abort.abort(new Error("stop reconnect"));
341
+ await expect(pending).rejects.toThrow("stop reconnect");
342
+ expect(requests).toHaveLength(1);
343
+ });
@@ -129,9 +129,9 @@ export declare function ports(t: Transport): {
129
129
  deleted: true;
130
130
  }>;
131
131
  };
132
- /** `runtime.addresses`: a dedicated outbound address every sandbox of the
132
+ /** `runtime.ips`: a dedicated outbound address every sandbox of the
133
133
  * account sends from, for allow-lists. */
134
- export declare function addresses(t: Transport): {
134
+ export declare function ips(t: Transport): {
135
135
  reserve: (input?: {
136
136
  family?: 4 | 6;
137
137
  }, options?: RequestOptions) => Promise<EgressAddress>;
@@ -142,8 +142,8 @@ export declare function addresses(t: Transport): {
142
142
  deleted: true;
143
143
  }>;
144
144
  };
145
- /** `runtime.tunnel`: a WireGuard tunnel from your own network into your sandboxes. */
146
- export declare function tunnel(t: Transport): {
145
+ /** `runtime.networks.wireguard`: a WireGuard tunnel from your own network into your sandboxes. */
146
+ export declare function wireguard(t: Transport): {
147
147
  get: (options?: RequestOptions) => Promise<Tunnel>;
148
148
  /** Idempotent: returns the tunnel you have. */
149
149
  create: (input?: {
@@ -186,15 +186,39 @@ export type SetUpstreamProxy = {
186
186
  * every connection when absent. 1 to 16. */
187
187
  hosts?: string[];
188
188
  };
189
- /** `runtime.network`: the network settings of the whole account.
189
+ /** `runtime.networks`: the networks of the whole account: the WireGuard
190
+ * tunnel in, your sandboxes reaching each other, and the proxy out.
190
191
  *
191
- * await runtime.network.upstreamProxy.set({ url: "http://proxy.example.com:3128" });
192
+ * await runtime.networks.upstreamProxy.set({ url: "http://proxy.example.com:3128" });
192
193
  *
193
194
  * `upstreamProxy`: after Runtime's own rules allow a connection, the host
194
195
  * reaches it with CONNECT through your proxy, so your proxy's controls and
195
196
  * logs apply. A proxy that refuses or cannot be reached fails the connection
196
197
  * (upstream-proxy-failed); nothing goes around it. Needs a kept top-up. */
197
- export declare function network(t: Transport): {
198
+ export declare function networks(t: Transport): {
199
+ /** The WireGuard tunnel from your own network into your sandboxes. */
200
+ wireguard: {
201
+ get: (options?: RequestOptions) => Promise<Tunnel>;
202
+ /** Idempotent: returns the tunnel you have. */
203
+ create: (input?: {
204
+ subnet?: string;
205
+ }, options?: RequestOptions) => Promise<Tunnel>;
206
+ delete: (options?: RequestOptions) => Promise<{
207
+ id: string;
208
+ deleted: true;
209
+ }>;
210
+ /** Leave publicKey out and Runtime generates the pair; the private key is
211
+ * then in `config`, shown once. */
212
+ addPeer: (input: {
213
+ name: string;
214
+ publicKey?: string;
215
+ routes?: string[];
216
+ }, options?: RequestOptions) => Promise<TunnelPeerCreated>;
217
+ rotatePeer: (peerId: string, input?: {
218
+ publicKey?: string;
219
+ }, options?: RequestOptions) => Promise<TunnelPeerCreated>;
220
+ removePeer: (peerId: string, options?: RequestOptions) => Promise<Tunnel>;
221
+ };
198
222
  /** Your sandboxes reaching each other by name (`private-network.ts`). */
199
223
  private: {
200
224
  get: (options?: RequestOptions) => Promise<import("./private-network.js").PrivateNetwork>;
@@ -202,6 +226,13 @@ export declare function network(t: Transport): {
202
226
  enabled: boolean;
203
227
  }, options?: RequestOptions) => Promise<import("./private-network.js").PrivateNetwork>;
204
228
  };
229
+ /** Your sandboxes' requests signed as Runtime's agents (`signed-requests.ts`). */
230
+ signedRequests: {
231
+ get: (options?: RequestOptions) => Promise<import("./signed-requests.js").SignedRequests>;
232
+ set: (input: {
233
+ enabled: boolean;
234
+ }, options?: RequestOptions) => Promise<import("./signed-requests.js").SignedRequests>;
235
+ };
205
236
  upstreamProxy: {
206
237
  /** Send outbound connections through your proxy, replacing any set before. */
207
238
  set: (input: SetUpstreamProxy, options?: RequestOptions) => Promise<UpstreamProxy & {
@@ -1,4 +1,5 @@
1
1
  import { privateNetwork } from "./private-network.js";
2
+ import { signedRequests } from "./signed-requests.js";
2
3
  const enc = encodeURIComponent;
3
4
  /** `runtime.domains`: serve a sandbox's port at your own hostname, with HTTPS. */
4
5
  export function domains(t) {
@@ -43,57 +44,69 @@ export function ports(t) {
43
44
  }),
44
45
  };
45
46
  }
46
- /** `runtime.addresses`: a dedicated outbound address every sandbox of the
47
+ /** `runtime.ips`: a dedicated outbound address every sandbox of the
47
48
  * account sends from, for allow-lists. */
48
- export function addresses(t) {
49
+ export function ips(t) {
49
50
  return {
50
- reserve: (input = {}, options) => t.json({ method: "POST", path: "/v1/addresses", body: input, ...options }),
51
- list: async (options) => (await t.json({ method: "GET", path: "/v1/addresses", ...options }))
52
- .data,
51
+ reserve: (input = {}, options) => t.json({ method: "POST", path: "/v1/ips", body: input, ...options }),
52
+ list: async (options) => (await t.json({ method: "GET", path: "/v1/ips", ...options })).data,
53
53
  release: (addressId, options) => t.json({
54
54
  method: "DELETE",
55
- path: `/v1/addresses/${enc(addressId)}`,
55
+ path: `/v1/ips/${enc(addressId)}`,
56
56
  ...options,
57
57
  }),
58
58
  };
59
59
  }
60
- /** `runtime.tunnel`: a WireGuard tunnel from your own network into your sandboxes. */
61
- export function tunnel(t) {
60
+ /** `runtime.networks.wireguard`: a WireGuard tunnel from your own network into your sandboxes. */
61
+ export function wireguard(t) {
62
62
  return {
63
- get: (options) => t.json({ method: "GET", path: "/v1/tunnel", ...options }),
63
+ get: (options) => t.json({ method: "GET", path: "/v1/networks/wireguard", ...options }),
64
64
  /** Idempotent: returns the tunnel you have. */
65
- create: (input = {}, options) => t.json({ method: "POST", path: "/v1/tunnel", body: input, ...options }),
66
- delete: (options) => t.json({ method: "DELETE", path: "/v1/tunnel", ...options }),
65
+ create: (input = {}, options) => t.json({ method: "POST", path: "/v1/networks/wireguard", body: input, ...options }),
66
+ delete: (options) => t.json({
67
+ method: "DELETE",
68
+ path: "/v1/networks/wireguard",
69
+ ...options,
70
+ }),
67
71
  /** Leave publicKey out and Runtime generates the pair; the private key is
68
72
  * then in `config`, shown once. */
69
73
  addPeer: (input, options) => t.json({
70
74
  method: "POST",
71
- path: "/v1/tunnel/peers",
75
+ path: "/v1/networks/wireguard/peers",
72
76
  body: input,
73
77
  ...options,
74
78
  }),
75
79
  rotatePeer: (peerId, input = {}, options) => t.json({
76
80
  method: "POST",
77
- path: `/v1/tunnel/peers/${enc(peerId)}:rotate`,
81
+ path: `/v1/networks/wireguard/peers/${enc(peerId)}:rotate`,
78
82
  body: input,
79
83
  ...options,
80
84
  }),
81
- removePeer: (peerId, options) => t.json({ method: "DELETE", path: `/v1/tunnel/peers/${enc(peerId)}`, ...options }),
85
+ removePeer: (peerId, options) => t.json({
86
+ method: "DELETE",
87
+ path: `/v1/networks/wireguard/peers/${enc(peerId)}`,
88
+ ...options,
89
+ }),
82
90
  };
83
91
  }
84
- /** `runtime.network`: the network settings of the whole account.
92
+ /** `runtime.networks`: the networks of the whole account: the WireGuard
93
+ * tunnel in, your sandboxes reaching each other, and the proxy out.
85
94
  *
86
- * await runtime.network.upstreamProxy.set({ url: "http://proxy.example.com:3128" });
95
+ * await runtime.networks.upstreamProxy.set({ url: "http://proxy.example.com:3128" });
87
96
  *
88
97
  * `upstreamProxy`: after Runtime's own rules allow a connection, the host
89
98
  * reaches it with CONNECT through your proxy, so your proxy's controls and
90
99
  * logs apply. A proxy that refuses or cannot be reached fails the connection
91
100
  * (upstream-proxy-failed); nothing goes around it. Needs a kept top-up. */
92
- export function network(t) {
93
- const path = "/v1/network/upstream-proxy";
101
+ export function networks(t) {
102
+ const path = "/v1/networks/upstream-proxy";
94
103
  return {
104
+ /** The WireGuard tunnel from your own network into your sandboxes. */
105
+ wireguard: wireguard(t),
95
106
  /** Your sandboxes reaching each other by name (`private-network.ts`). */
96
107
  private: privateNetwork(t),
108
+ /** Your sandboxes' requests signed as Runtime's agents (`signed-requests.ts`). */
109
+ signedRequests: signedRequests(t),
97
110
  upstreamProxy: {
98
111
  /** Send outbound connections through your proxy, replacing any set before. */
99
112
  set: (input, options) => t.json({
@@ -35,7 +35,7 @@ export type SandboxMetrics = {
35
35
  latest: MetricPoint | null;
36
36
  points: MetricPoint[];
37
37
  };
38
- export type WebhookEventType = "sandbox.created" | "sandbox.running" | "sandbox.paused" | "sandbox.woken" | "sandbox.stopped" | "sandbox.start_failed" | "sandbox.wake_failed" | "sandbox.host_unreachable" | "sandbox.host_reachable" | "sandbox.recovered" | "sandbox.recovery_failed" | "snapshot.ready" | "snapshot.failed" | "snapshot.deleted" | "volume.ready" | "volume.failed" | "volume.deleted";
38
+ export type WebhookEventType = "sandbox.created" | "sandbox.running" | "sandbox.paused" | "sandbox.woken" | "sandbox.stopped" | "sandbox.start_failed" | "sandbox.wake_failed" | "sandbox.host_unreachable" | "sandbox.host_reachable" | "sandbox.recovered" | "sandbox.recovery_failed" | "sandbox.files.changed" | "sandbox.out_of_memory" | "sandbox.secret_not_applied" | "snapshot.ready" | "snapshot.failed" | "snapshot.deleted" | "volume.ready" | "volume.failed" | "volume.deleted" | "egress_secret.created" | "egress_secret.updated" | "egress_secret.deleted" | "upstream_proxy.set" | "upstream_proxy.removed";
39
39
  /** An event as /v1/events lists it and a webhook delivers it. */
40
40
  export type RuntimeEvent = {
41
41
  id: string;
@@ -113,7 +113,8 @@ export type UsageExportRow = {
113
113
  name: string | null;
114
114
  kind: string;
115
115
  meter: string;
116
- fundedBy: "credit" | "trial";
116
+ /** credit, the trial's hours, or the month's included usage. */
117
+ fundedBy: "credit" | "trial" | "allowance";
117
118
  startsAt: string;
118
119
  endsAt: string;
119
120
  settledAt: string;
@@ -1,5 +1,5 @@
1
1
  export function privateNetwork(t) {
2
- const path = "/v1/network/private";
2
+ const path = "/v1/networks/private";
3
3
  return {
4
4
  /** On or off, and whether this account may turn it on. */
5
5
  get: (options) => t.json({ method: "GET", path, ...options }),