withruntime 0.12.0 → 0.14.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 (50) hide show
  1. package/README.md +3 -3
  2. package/README.src.md +2 -2
  3. package/dist/billing-cli.js +8 -4
  4. package/dist/blaxel/sandbox.d.ts +1 -1
  5. package/dist/blaxel/sandbox.js +1 -1
  6. package/dist/cli.d.ts +17 -2
  7. package/dist/cli.js +242 -69
  8. package/dist/credentials.d.ts +3 -0
  9. package/dist/credentials.js +15 -0
  10. package/dist/daytona/daytona.d.ts +1 -1
  11. package/dist/daytona/daytona.js +1 -1
  12. package/dist/e2b/sandbox.d.ts +1 -1
  13. package/dist/errors.d.ts +1 -1
  14. package/dist/errors.js +1 -1
  15. package/dist/index.d.ts +2 -1
  16. package/dist/machine.d.ts +15 -1
  17. package/dist/machine.js +26 -11
  18. package/dist/mcp.js +144 -20
  19. package/dist/products/api-keys.d.ts +73 -0
  20. package/dist/products/api-keys.js +23 -0
  21. package/dist/products/apps.d.ts +12 -31
  22. package/dist/products/apps.js +8 -113
  23. package/dist/products/balancers.d.ts +87 -0
  24. package/dist/products/balancers.js +8 -0
  25. package/dist/products/builds.d.ts +30 -0
  26. package/dist/products/builds.js +6 -0
  27. package/dist/products/index.d.ts +2 -0
  28. package/dist/products/index.js +2 -0
  29. package/dist/products/jobs.d.ts +2 -2
  30. package/dist/products/jobs.js +1 -1
  31. package/dist/products/observability.d.ts +7 -2
  32. package/dist/products/resources.d.ts +54 -0
  33. package/dist/products/resources.js +122 -0
  34. package/dist/products/secrets.d.ts +22 -0
  35. package/dist/products/secrets.js +8 -0
  36. package/dist/products/volumes.d.ts +3 -0
  37. package/dist/sandbox.d.ts +4 -0
  38. package/dist/sandbox.js +27 -2
  39. package/dist/transport.d.ts +4 -1
  40. package/dist/transport.js +25 -4
  41. package/dist/types.d.ts +9 -5
  42. package/dist/vercel/sandbox.d.ts +1 -1
  43. package/dist/vercel/sandbox.js +1 -1
  44. package/dist/wallet.d.ts +4 -1
  45. package/dist/wallet.js +2 -1
  46. package/package.json +2 -14
  47. package/dist/cli/logs.test.d.ts +0 -1
  48. package/dist/cli/logs.test.js +0 -272
  49. package/dist/products/logs.test.d.ts +0 -1
  50. package/dist/products/logs.test.js +0 -343
@@ -1,343 +0,0 @@
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
- });