@finalai/computers 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.

Potentially problematic release.


This version of @finalai/computers might be problematic. Click here for more details.

Files changed (38) hide show
  1. package/README.md +16 -0
  2. package/dist/modules/browser-access.d.mts +18 -0
  3. package/dist/modules/browser-access.mjs +22 -0
  4. package/dist/modules/browser-access.mjs.map +7 -0
  5. package/dist/modules/client.d.mts +5 -0
  6. package/dist/modules/client.mjs +764 -0
  7. package/dist/modules/client.mjs.map +7 -0
  8. package/dist/modules/file-snapshots.d.mts +69 -0
  9. package/dist/modules/file-snapshots.mjs +64 -0
  10. package/dist/modules/file-snapshots.mjs.map +7 -0
  11. package/dist/modules/index.d.mts +11 -0
  12. package/dist/modules/index.mjs +63 -0
  13. package/dist/modules/index.mjs.map +7 -0
  14. package/dist/modules/json.d.mts +5 -0
  15. package/dist/modules/json.mjs +8 -0
  16. package/dist/modules/json.mjs.map +7 -0
  17. package/dist/modules/normalize.d.mts +7 -0
  18. package/dist/modules/normalize.mjs +210 -0
  19. package/dist/modules/normalize.mjs.map +7 -0
  20. package/dist/modules/result.d.mts +13 -0
  21. package/dist/modules/result.mjs +14 -0
  22. package/dist/modules/result.mjs.map +7 -0
  23. package/dist/modules/screen.d.mts +41 -0
  24. package/dist/modules/screen.mjs +37 -0
  25. package/dist/modules/screen.mjs.map +7 -0
  26. package/dist/modules/shell.d.mts +1 -0
  27. package/dist/modules/shell.mjs +5 -0
  28. package/dist/modules/shell.mjs.map +7 -0
  29. package/dist/modules/transport-api.d.mts +2 -0
  30. package/dist/modules/transport-api.mjs +7 -0
  31. package/dist/modules/transport-api.mjs.map +7 -0
  32. package/dist/modules/transport.d.mts +15 -0
  33. package/dist/modules/transport.mjs +67 -0
  34. package/dist/modules/transport.mjs.map +7 -0
  35. package/dist/modules/types.d.mts +467 -0
  36. package/dist/modules/types.mjs +69 -0
  37. package/dist/modules/types.mjs.map +7 -0
  38. package/package.json +52 -0
@@ -0,0 +1,764 @@
1
+ import * as z from "zod";
2
+ import { computerBrowserAccessRequestSchema, computerBrowserAccessSchema } from "./browser-access.mjs";
3
+ import { computerSnapshotRequestSchema, computerSnapshotResponseSchema } from "./file-snapshots.mjs";
4
+ import { normalizeComputerBase, normalizeComputerImage, normalizeComputerInfo } from "./normalize.mjs";
5
+ import { err, ok } from "./result.mjs";
6
+ import { jsonObjectSchema } from "./json.mjs";
7
+ import { computerScreenSessionSchema } from "./screen.mjs";
8
+ import { createTransport } from "./transport.mjs";
9
+ import {
10
+ ComputerState,
11
+ ComputerTokenScope
12
+ } from "./types.mjs";
13
+ const computerFromResponse = (data) => {
14
+ const response = jsonObjectSchema.safeParse(data);
15
+ if (!response.success) return null;
16
+ return normalizeComputerInfo(response.data.computer ?? response.data.vm ?? null);
17
+ };
18
+ const toBase64 = (data) => (data instanceof Uint8Array ? Buffer.from(data) : Buffer.from(data, "utf8")).toString("base64");
19
+ const processStateSchema = z.union([z.literal("running"), z.literal("exited")]);
20
+ const computerProcessSchema = z.object({
21
+ id: z.string(),
22
+ bootId: z.string(),
23
+ command: z.string(),
24
+ leaderPid: z.number().nullable().default(null),
25
+ state: processStateSchema,
26
+ keepsAwake: z.boolean().default(false),
27
+ createdAtMs: z.number().default(0),
28
+ updatedAtMs: z.number().default(0),
29
+ endedAtMs: z.number().nullable().default(null),
30
+ cpuUsec: z.number().default(0),
31
+ memoryBytes: z.number().default(0),
32
+ memoryPeakBytes: z.number().default(0),
33
+ ioReadBytes: z.number().default(0),
34
+ ioWriteBytes: z.number().default(0),
35
+ processCount: z.number().default(0),
36
+ descendantCount: z.number().optional()
37
+ });
38
+ const processesResponseSchema = z.object({ processes: z.array(computerProcessSchema) });
39
+ const managedProcessSchema = z.object({
40
+ id: z.string(),
41
+ bootId: z.string(),
42
+ pid: z.number().nullable(),
43
+ state: processStateSchema
44
+ });
45
+ const execResponseSchema = managedProcessSchema.extend({
46
+ pid: z.number(),
47
+ state: z.literal("running")
48
+ });
49
+ const outputChunkSchema = z.object({
50
+ dataB64: z.string(),
51
+ offset: z.number(),
52
+ nextOffset: z.number(),
53
+ eof: z.boolean()
54
+ });
55
+ const processOutputResponseSchema = z.object({
56
+ stdout: outputChunkSchema,
57
+ stderr: outputChunkSchema,
58
+ state: processStateSchema,
59
+ result: z.object({ exitCode: z.number().nullable(), signal: z.string().nullable() }).nullable()
60
+ });
61
+ const runResponseSchema = processOutputResponseSchema.extend({
62
+ id: z.string(),
63
+ bootId: z.string(),
64
+ pid: z.number(),
65
+ timedOut: z.boolean(),
66
+ elapsedMs: z.number()
67
+ });
68
+ const okResponseSchema = z.object({ ok: z.literal(true) });
69
+ const fileReadResponseSchema = z.object({
70
+ dataB64: z.string(),
71
+ eof: z.boolean(),
72
+ offset: z.number().optional()
73
+ });
74
+ const FILE_TRANSFER_CHUNK_BYTES = 1024 * 1024;
75
+ const fileWriteResponseSchema = z.object({ bytesWritten: z.number() });
76
+ const decodeResult = (result, schema, errorMessage) => {
77
+ if (!result.ok) return result;
78
+ const parsed = schema.safeParse(result.data);
79
+ return parsed.success ? ok(parsed.data) : err(errorMessage);
80
+ };
81
+ const resourcePath = () => "/computers";
82
+ const tokenResponseSchema = z.object({ id: z.string(), token: z.string() });
83
+ const PREVIEW_TOKEN_LIFETIME_MS = 60 * 6e4;
84
+ const mintToken = async (transport, opts) => {
85
+ const body = { name: opts.name };
86
+ if (opts.scopes != null) body.scopes = opts.scopes;
87
+ if (opts.expiresAt != null) body.expiresAt = opts.expiresAt.toISOString();
88
+ if (opts.computerId != null) body.computerId = opts.computerId;
89
+ return decodeResult(
90
+ await transport.request({ method: "POST", path: "/tokens", jsonBody: body }),
91
+ tokenResponseSchema,
92
+ "unexpected token response shape"
93
+ );
94
+ };
95
+ const createComputerHandle = (transport, initialInfo) => {
96
+ let info = initialInfo;
97
+ const computerPath = `${resourcePath()}/${encodeURIComponent(info.id)}`;
98
+ const updateFromComputerResponse = (result) => {
99
+ if (!result.ok) {
100
+ return result;
101
+ }
102
+ const computer = computerFromResponse(result.data);
103
+ if (computer == null) {
104
+ return err("unexpected computer response shape");
105
+ }
106
+ info = computer;
107
+ return ok(computer);
108
+ };
109
+ const action = async (name) => updateFromComputerResponse(
110
+ await transport.request({ method: "POST", path: `${computerPath}/actions/${name}` })
111
+ );
112
+ const processHandle = (process) => {
113
+ const path = `${computerPath}/processes/${encodeURIComponent(process.id)}`;
114
+ let stdoutOffset = 0;
115
+ let stderrOffset = 0;
116
+ const readOutput = async (stream, opts = {}) => {
117
+ const offset = opts.offset ?? (stream === "stdout" ? stdoutOffset : stderrOffset);
118
+ const result = await transport.request({
119
+ method: "GET",
120
+ path: `${path}/output`,
121
+ query: {
122
+ bootId: process.bootId,
123
+ stdoutOffset: String(stream === "stdout" ? offset : 0),
124
+ stderrOffset: String(stream === "stderr" ? offset : 0),
125
+ waitMs: String(opts.waitMs ?? 0)
126
+ }
127
+ });
128
+ const parsed = decodeResult(
129
+ result,
130
+ processOutputResponseSchema,
131
+ "unexpected process output response shape"
132
+ );
133
+ if (!parsed.ok) return parsed;
134
+ const chunk = parsed.data[stream];
135
+ if (opts.offset == null) {
136
+ if (stream === "stdout") stdoutOffset = chunk.nextOffset;
137
+ else stderrOffset = chunk.nextOffset;
138
+ }
139
+ const state = parsed.data.state;
140
+ handle.state = state;
141
+ const dataBytes = Buffer.from(chunk.dataB64, "base64");
142
+ return ok({
143
+ data: dataBytes.toString("utf8"),
144
+ dataBytes,
145
+ offset: chunk.offset,
146
+ nextOffset: chunk.nextOffset,
147
+ eof: chunk.eof,
148
+ state
149
+ });
150
+ };
151
+ const readBothOutputs = async (opts) => {
152
+ const parsed = decodeResult(
153
+ await transport.request({
154
+ method: "GET",
155
+ path: `${path}/output`,
156
+ query: {
157
+ bootId: process.bootId,
158
+ stdoutOffset: String(opts.stdoutOffset),
159
+ stderrOffset: String(opts.stderrOffset),
160
+ waitMs: String(opts.waitMs ?? 0)
161
+ }
162
+ }),
163
+ processOutputResponseSchema,
164
+ "unexpected process output response shape"
165
+ );
166
+ if (!parsed.ok) return parsed;
167
+ const state = parsed.data.state;
168
+ handle.state = state;
169
+ const chunkFor = (stream) => {
170
+ const chunk = parsed.data[stream];
171
+ const dataBytes = Buffer.from(chunk.dataB64, "base64");
172
+ return {
173
+ data: dataBytes.toString("utf8"),
174
+ dataBytes,
175
+ offset: chunk.offset,
176
+ nextOffset: chunk.nextOffset,
177
+ eof: chunk.eof,
178
+ state
179
+ };
180
+ };
181
+ return ok({
182
+ stdout: chunkFor("stdout"),
183
+ stderr: chunkFor("stderr"),
184
+ state,
185
+ result: parsed.data.result
186
+ });
187
+ };
188
+ const input = async (body) => decodeResult(
189
+ await transport.request({
190
+ method: "POST",
191
+ path: `${path}/input`,
192
+ jsonBody: { bootId: process.bootId, ...body }
193
+ }),
194
+ okResponseSchema,
195
+ "unexpected process input response shape"
196
+ );
197
+ const handle = {
198
+ id: process.id,
199
+ bootId: process.bootId,
200
+ pid: process.pid,
201
+ state: process.state,
202
+ readOutput: readBothOutputs,
203
+ stdout: { read: (opts) => readOutput("stdout", opts) },
204
+ stderr: { read: (opts) => readOutput("stderr", opts) },
205
+ wait: async (opts = {}) => {
206
+ const deadline = opts.timeoutMs == null ? Number.POSITIVE_INFINITY : Date.now() + opts.timeoutMs;
207
+ let waitStdoutOffset = stdoutOffset;
208
+ let waitStderrOffset = stderrOffset;
209
+ while (true) {
210
+ const remaining = deadline - Date.now();
211
+ if (remaining <= 0) return err(`wait timed out after ${opts.timeoutMs}ms`, 504);
212
+ const result = await transport.request({
213
+ method: "GET",
214
+ path: `${path}/output`,
215
+ query: {
216
+ bootId: process.bootId,
217
+ stdoutOffset: String(waitStdoutOffset),
218
+ stderrOffset: String(waitStderrOffset),
219
+ waitMs: String(Math.min(3e4, remaining))
220
+ }
221
+ });
222
+ const parsed = decodeResult(
223
+ result,
224
+ processOutputResponseSchema,
225
+ "unexpected process wait response shape"
226
+ );
227
+ if (!parsed.ok) return parsed;
228
+ waitStdoutOffset = parsed.data.stdout.nextOffset;
229
+ waitStderrOffset = parsed.data.stderr.nextOffset;
230
+ if (parsed.data.state === "exited") {
231
+ handle.state = "exited";
232
+ return ok(parsed.data.result ?? { exitCode: null, signal: null });
233
+ }
234
+ }
235
+ },
236
+ write: (data) => input({ dataB64: toBase64(data) }),
237
+ eof: () => input({ eof: true }),
238
+ resize: (cols, rows) => input({ cols, rows }),
239
+ signal: (signal) => input({ signal }),
240
+ terminate: async () => decodeResult(
241
+ await transport.request({
242
+ method: "POST",
243
+ path: `${path}/terminate`,
244
+ jsonBody: { bootId: process.bootId }
245
+ }),
246
+ okResponseSchema,
247
+ "unexpected process terminate response shape"
248
+ )
249
+ };
250
+ return handle;
251
+ };
252
+ const readFileBuffer = async (path, options) => {
253
+ const chunks = [];
254
+ let offset = 0;
255
+ for (; ; ) {
256
+ const query = {
257
+ path,
258
+ offset: String(offset),
259
+ length: String(FILE_TRANSFER_CHUNK_BYTES)
260
+ };
261
+ if (options?.autoResume != null) query.autoResume = String(options.autoResume);
262
+ const result = await transport.request({
263
+ method: "GET",
264
+ path: `${computerPath}/files`,
265
+ query
266
+ });
267
+ if (!result.ok) {
268
+ return result;
269
+ }
270
+ const parsed = fileReadResponseSchema.safeParse(result.data);
271
+ if (!parsed.success) {
272
+ return err("unexpected file read response shape");
273
+ }
274
+ if (offset > 0 && parsed.data.offset !== offset) {
275
+ return err("Server does not support ranged file reads; partial contents refused");
276
+ }
277
+ const chunk = Buffer.from(parsed.data.dataB64, "base64");
278
+ chunks.push(chunk);
279
+ if (parsed.data.eof) {
280
+ return ok(Buffer.concat(chunks));
281
+ }
282
+ if (chunk.length === 0) {
283
+ return err("File read made no progress before the end of the file");
284
+ }
285
+ offset += chunk.length;
286
+ }
287
+ };
288
+ const exec = async (command, opts) => {
289
+ const result = await transport.request({
290
+ method: "POST",
291
+ path: `${computerPath}/exec`,
292
+ jsonBody: {
293
+ command,
294
+ env: opts.env,
295
+ cwd: opts.cwd,
296
+ user: opts.user,
297
+ pty: opts.pty,
298
+ cols: opts.cols,
299
+ rows: opts.rows,
300
+ timeoutMs: opts.timeoutMs,
301
+ autoResume: opts.autoResume
302
+ }
303
+ });
304
+ if (!result.ok) return result;
305
+ const parsed = execResponseSchema.safeParse(result.data);
306
+ return parsed.success ? ok(processHandle(parsed.data)) : err("unexpected exec response shape");
307
+ };
308
+ const run = async (command, opts) => {
309
+ const parsed = decodeResult(
310
+ await transport.request({
311
+ method: "POST",
312
+ path: `${computerPath}/run`,
313
+ jsonBody: {
314
+ command,
315
+ env: opts.env,
316
+ cwd: opts.cwd,
317
+ user: opts.user,
318
+ timeoutMs: opts.timeoutMs,
319
+ autoResume: opts.autoResume,
320
+ waitMs: opts.waitMs,
321
+ readyTimeoutMs: opts.readyTimeoutMs
322
+ }
323
+ }),
324
+ runResponseSchema,
325
+ "unexpected run response shape"
326
+ );
327
+ if (!parsed.ok) return parsed;
328
+ const { id, bootId, pid, state, result, timedOut, elapsedMs } = parsed.data;
329
+ const chunkFor = (stream) => {
330
+ const chunk = parsed.data[stream];
331
+ const dataBytes = Buffer.from(chunk.dataB64, "base64");
332
+ return {
333
+ data: dataBytes.toString("utf8"),
334
+ dataBytes,
335
+ offset: chunk.offset,
336
+ nextOffset: chunk.nextOffset,
337
+ eof: chunk.eof,
338
+ state
339
+ };
340
+ };
341
+ return ok({
342
+ process: processHandle({ id, bootId, pid, state }),
343
+ output: { stdout: chunkFor("stdout"), stderr: chunkFor("stderr"), state, result },
344
+ timedOut,
345
+ elapsedMs
346
+ });
347
+ };
348
+ return {
349
+ screen: {
350
+ status: async () => {
351
+ const result = await transport.request({ method: "GET", path: `${computerPath}/screen` });
352
+ if (!result.ok) return result;
353
+ const parsed = z.object({ available: z.boolean() }).safeParse(result.data);
354
+ return parsed.success ? ok(parsed.data) : err("unexpected screen status response");
355
+ },
356
+ open: async (options) => {
357
+ const result = await transport.request({
358
+ method: "POST",
359
+ path: `${computerPath}/screen`,
360
+ jsonBody: options ?? {}
361
+ });
362
+ if (!result.ok) return result;
363
+ const parsed = computerScreenSessionSchema.safeParse(result.data);
364
+ return parsed.success ? ok(parsed.data) : err("unexpected screen session response");
365
+ }
366
+ },
367
+ computerUse: {
368
+ status: async () => {
369
+ const result = await transport.request({
370
+ method: "GET",
371
+ path: `${computerPath}/computer-use`
372
+ });
373
+ if (!result.ok) return result;
374
+ const parsed = z.object({ available: z.boolean(), installed: z.boolean().optional() }).safeParse(result.data);
375
+ return parsed.success ? ok({
376
+ available: parsed.data.available,
377
+ installed: parsed.data.installed ?? parsed.data.available
378
+ }) : err("unexpected computer-use capability response shape");
379
+ },
380
+ available: async () => {
381
+ const result = await transport.request({
382
+ method: "GET",
383
+ path: `${computerPath}/computer-use`
384
+ });
385
+ if (!result.ok) return result;
386
+ const parsed = z.object({ available: z.boolean() }).safeParse(result.data);
387
+ return parsed.success ? ok(parsed.data.available) : err("unexpected computer-use capability response shape");
388
+ },
389
+ open: async (options = {}) => {
390
+ const result = await transport.request({
391
+ method: "POST",
392
+ path: `${computerPath}/computer-use`,
393
+ jsonBody: {
394
+ cwd: options.cwd,
395
+ readyTimeoutMs: options.readyTimeoutMs
396
+ }
397
+ });
398
+ if (!result.ok) return result;
399
+ const parsed = execResponseSchema.safeParse(result.data);
400
+ return parsed.success ? ok(processHandle(parsed.data)) : err("unexpected computer-use process response shape");
401
+ }
402
+ },
403
+ id: info.id,
404
+ name: info.name,
405
+ getInfo: () => info,
406
+ refresh: async () => updateFromComputerResponse(await transport.request({ method: "GET", path: computerPath })),
407
+ refreshActivity: () => action("refresh-activity"),
408
+ exec: (command, opts = {}) => exec(command, opts),
409
+ run: (command, opts = {}) => run(command, opts),
410
+ processes: {
411
+ list: async () => {
412
+ const result = await transport.request({
413
+ method: "GET",
414
+ path: `${computerPath}/processes`
415
+ });
416
+ if (!result.ok) {
417
+ return result;
418
+ }
419
+ const processes = processesResponseSchema.safeParse(result.data);
420
+ if (!processes.success) {
421
+ return err("unexpected processes response shape");
422
+ }
423
+ return ok(processes.data.processes);
424
+ },
425
+ get: async (id, bootId) => {
426
+ const result = await transport.request({
427
+ method: "GET",
428
+ path: `${computerPath}/processes/${encodeURIComponent(id)}`,
429
+ query: { bootId }
430
+ });
431
+ if (!result.ok) return result;
432
+ const process = z.object({ process: computerProcessSchema }).safeParse(result.data);
433
+ if (!process.success) return err("unexpected process response shape");
434
+ return ok(
435
+ processHandle({
436
+ id: process.data.process.id,
437
+ bootId: process.data.process.bootId,
438
+ pid: process.data.process.leaderPid,
439
+ state: process.data.process.state
440
+ })
441
+ );
442
+ }
443
+ },
444
+ browserAccess: {
445
+ open: async (options) => {
446
+ const input = computerBrowserAccessRequestSchema.safeParse(options);
447
+ if (!input.success) return err("invalid browser access request");
448
+ return decodeResult(
449
+ await transport.request({
450
+ method: "POST",
451
+ path: `${computerPath}/browser-access`,
452
+ jsonBody: input.data
453
+ }),
454
+ computerBrowserAccessSchema,
455
+ "unexpected browser access response"
456
+ );
457
+ },
458
+ close: async (tokenId) => decodeResult(
459
+ await transport.request({
460
+ method: "DELETE",
461
+ path: `${computerPath}/browser-access/${encodeURIComponent(tokenId)}`
462
+ }),
463
+ okResponseSchema,
464
+ "unexpected browser access close response"
465
+ )
466
+ },
467
+ files: {
468
+ snapshot: async (request) => {
469
+ const parsed = computerSnapshotRequestSchema.safeParse(request);
470
+ if (!parsed.success) return err("invalid file snapshot request");
471
+ return decodeResult(
472
+ await transport.request({
473
+ method: "POST",
474
+ path: `${computerPath}/file-snapshots/read`,
475
+ jsonBody: parsed.data
476
+ }),
477
+ computerSnapshotResponseSchema,
478
+ "unexpected file snapshot response"
479
+ );
480
+ },
481
+ read: async (path, options) => {
482
+ const result = await readFileBuffer(path, options);
483
+ return result.ok ? ok(result.data.toString("utf8")) : result;
484
+ },
485
+ readBytes: async (path, options) => {
486
+ const result = await readFileBuffer(path, options);
487
+ return result.ok ? ok(new Uint8Array(result.data)) : result;
488
+ },
489
+ write: async (path, data) => {
490
+ const bytes = data instanceof Uint8Array ? Buffer.from(data.buffer, data.byteOffset, data.byteLength) : Buffer.from(data, "utf8");
491
+ let bytesWritten = 0;
492
+ for (let offset = 0; offset === 0 || offset < bytes.length; offset += FILE_TRANSFER_CHUNK_BYTES) {
493
+ const body = {
494
+ path,
495
+ dataB64: bytes.subarray(offset, offset + FILE_TRANSFER_CHUNK_BYTES).toString("base64")
496
+ };
497
+ if (offset > 0) body.append = true;
498
+ const result = await transport.request({
499
+ method: "PUT",
500
+ path: `${computerPath}/files`,
501
+ jsonBody: body
502
+ });
503
+ if (!result.ok) {
504
+ return result;
505
+ }
506
+ const parsed = fileWriteResponseSchema.safeParse(result.data);
507
+ if (!parsed.success) {
508
+ return err("unexpected file write response shape");
509
+ }
510
+ bytesWritten += parsed.data.bytesWritten;
511
+ }
512
+ return ok({ bytesWritten });
513
+ }
514
+ },
515
+ getHost: (port) => {
516
+ if (info.hostname == null) {
517
+ return null;
518
+ }
519
+ return port === 443 ? info.hostname : `${info.hostname}:${port}`;
520
+ },
521
+ createPreviewToken: (opts = {}) => mintToken(transport, {
522
+ name: opts.name ?? `preview ${info.name}`,
523
+ computerId: info.id,
524
+ scopes: [ComputerTokenScope.Http],
525
+ expiresAt: opts.expiresAt ?? new Date(Date.now() + PREVIEW_TOKEN_LIFETIME_MS)
526
+ }),
527
+ pause: () => action("pause"),
528
+ resume: () => action("resume"),
529
+ start: () => action("start"),
530
+ stop: () => action("stop"),
531
+ restart: () => action("restart"),
532
+ kill: () => action("delete"),
533
+ settings: async (patch) => updateFromComputerResponse(
534
+ await transport.request({ method: "PATCH", path: computerPath, jsonBody: patch })
535
+ )
536
+ };
537
+ };
538
+ const createComputerClientWithTransport = (transport) => {
539
+ const baseResourcePath = resourcePath();
540
+ const getComputer = async (idOrName) => {
541
+ const result = await transport.request({
542
+ method: "GET",
543
+ path: `${baseResourcePath}/${encodeURIComponent(idOrName)}`
544
+ });
545
+ if (!result.ok) {
546
+ return result;
547
+ }
548
+ const computer = computerFromResponse(result.data);
549
+ if (computer == null) {
550
+ return err("unexpected computer response shape");
551
+ }
552
+ return ok(computer);
553
+ };
554
+ const createComputer = async (opts) => {
555
+ const { idempotencyKey, ...body } = opts;
556
+ const result = await transport.request({
557
+ method: "POST",
558
+ path: baseResourcePath,
559
+ jsonBody: body,
560
+ headers: idempotencyKey == null ? void 0 : { "idempotency-key": idempotencyKey }
561
+ });
562
+ if (!result.ok) {
563
+ return result;
564
+ }
565
+ const computer = computerFromResponse(result.data);
566
+ if (computer == null) {
567
+ return err("unexpected computer response shape");
568
+ }
569
+ return ok(createComputerHandle(transport, computer));
570
+ };
571
+ const connectComputer = async (target) => {
572
+ const idOrName = "id" in target ? target.id : target.name;
573
+ const fetched = await getComputer(idOrName);
574
+ if (!fetched.ok) {
575
+ return fetched;
576
+ }
577
+ let computer = fetched.data;
578
+ if (target.resumeIfNeeded !== false && (computer.state === ComputerState.Paused || computer.state === ComputerState.Stopped || computer.state === ComputerState.Shelved)) {
579
+ const resumed = await transport.request({
580
+ method: "POST",
581
+ path: `${baseResourcePath}/${encodeURIComponent(computer.id)}/actions/resume`
582
+ });
583
+ if (!resumed.ok) {
584
+ return resumed;
585
+ }
586
+ const resumedComputer = computerFromResponse(resumed.data);
587
+ if (resumedComputer != null) {
588
+ computer = resumedComputer;
589
+ }
590
+ }
591
+ return ok(createComputerHandle(transport, computer));
592
+ };
593
+ const listComputers = async () => {
594
+ const result = await transport.request({ method: "GET", path: baseResourcePath });
595
+ if (!result.ok) {
596
+ return result;
597
+ }
598
+ const response = jsonObjectSchema.safeParse(result.data);
599
+ if (!response.success) {
600
+ return err("unexpected computer list response shape");
601
+ }
602
+ const rawComputers = response.data.computers ?? response.data.vms;
603
+ if (!Array.isArray(rawComputers)) {
604
+ return err("unexpected computer list response shape");
605
+ }
606
+ const computers = [];
607
+ for (const raw of rawComputers) {
608
+ const computer = normalizeComputerInfo(raw);
609
+ if (computer != null) {
610
+ computers.push(computer);
611
+ }
612
+ }
613
+ return ok(computers);
614
+ };
615
+ const lifecycleAction = async (idOrName, actionName, options = {}) => {
616
+ const result = await transport.request({
617
+ method: "POST",
618
+ path: `${baseResourcePath}/${encodeURIComponent(idOrName)}/actions/${actionName}`,
619
+ signal: options.signal
620
+ });
621
+ if (!result.ok) {
622
+ return result;
623
+ }
624
+ const computer = computerFromResponse(result.data);
625
+ if (computer == null) {
626
+ return err("unexpected computer response shape");
627
+ }
628
+ return ok(computer);
629
+ };
630
+ const deleteComputer = async (idOrName) => lifecycleAction(idOrName, "delete");
631
+ const pauseComputer = async (idOrName) => lifecycleAction(idOrName, "pause");
632
+ const resumeComputer = async (idOrName) => lifecycleAction(idOrName, "resume");
633
+ const stopComputer = async (idOrName, options) => lifecycleAction(idOrName, "stop", options);
634
+ const stopComputerRetained = async (idOrName) => lifecycleAction(idOrName, "stop-retained");
635
+ const refreshComputerActivity = async (idOrName) => lifecycleAction(idOrName, "refresh-activity");
636
+ const withEphemeralComputer = async (opts, fn) => {
637
+ const created = await createComputer({ ...opts, ephemeral: true });
638
+ if (!created.ok) {
639
+ throw new Error(`withEphemeralComputer: create failed: ${created.error}`);
640
+ }
641
+ try {
642
+ return await fn(created.data);
643
+ } finally {
644
+ await created.data.kill();
645
+ }
646
+ };
647
+ const imageFromResponse = (data) => {
648
+ const response = jsonObjectSchema.safeParse(data);
649
+ if (!response.success) return null;
650
+ return normalizeComputerImage(response.data.image ?? response.data.version ?? null);
651
+ };
652
+ const registerImage = async (opts) => {
653
+ const { idempotencyKey, ...body } = opts;
654
+ const result = await transport.request({
655
+ method: "POST",
656
+ path: "/images",
657
+ jsonBody: body,
658
+ headers: idempotencyKey == null ? void 0 : { "idempotency-key": idempotencyKey }
659
+ });
660
+ if (!result.ok) return result;
661
+ const image = imageFromResponse(result.data);
662
+ return image == null ? err("unexpected image response shape") : ok(image);
663
+ };
664
+ const getImage = async (id) => {
665
+ const result = await transport.request({
666
+ method: "GET",
667
+ path: `/images/${encodeURIComponent(id)}`
668
+ });
669
+ if (!result.ok) return result;
670
+ const image = imageFromResponse(result.data);
671
+ return image == null ? err("unexpected image response shape") : ok(image);
672
+ };
673
+ const listImages = async (opts = {}) => {
674
+ const query = {};
675
+ if (opts.lineage != null) query.lineage = opts.lineage;
676
+ if (opts.state != null) query.state = opts.state;
677
+ const result = await transport.request({
678
+ method: "GET",
679
+ path: "/images",
680
+ query
681
+ });
682
+ if (!result.ok) return result;
683
+ const response = jsonObjectSchema.safeParse(result.data);
684
+ const rawImages = response.success ? response.data.images ?? response.data.versions : void 0;
685
+ if (!Array.isArray(rawImages)) {
686
+ return err("unexpected image list response shape");
687
+ }
688
+ const images = [];
689
+ for (const raw of rawImages) {
690
+ const image = normalizeComputerImage(raw);
691
+ if (image != null) images.push(image);
692
+ }
693
+ return ok(images);
694
+ };
695
+ const baseFromResponse = (data) => {
696
+ const response = jsonObjectSchema.safeParse(data);
697
+ return response.success ? normalizeComputerBase(response.data.base ?? null) : null;
698
+ };
699
+ const captureComputer = async (idOrName, options) => {
700
+ const result = await transport.request({
701
+ method: "POST",
702
+ path: `${baseResourcePath}/${encodeURIComponent(idOrName)}/capture`,
703
+ jsonBody: options
704
+ });
705
+ if (!result.ok) return result;
706
+ const base = baseFromResponse(result.data);
707
+ return base == null ? err("unexpected base response shape") : ok(base);
708
+ };
709
+ const getBase = async (id) => {
710
+ const result = await transport.request({
711
+ method: "GET",
712
+ path: `/bases/${encodeURIComponent(id)}`
713
+ });
714
+ if (!result.ok) return result;
715
+ const base = baseFromResponse(result.data);
716
+ return base == null ? err("unexpected base response shape") : ok(base);
717
+ };
718
+ const setBaseRetention = async (id, retained) => {
719
+ const result = await transport.request({
720
+ method: "PATCH",
721
+ path: `/bases/${encodeURIComponent(id)}`,
722
+ jsonBody: { retained }
723
+ });
724
+ if (!result.ok) return result;
725
+ const base = baseFromResponse(result.data);
726
+ return base == null ? err("unexpected base response shape") : ok(base);
727
+ };
728
+ const revokeToken = async (id) => {
729
+ const result = decodeResult(
730
+ await transport.request({ method: "DELETE", path: `/tokens/${encodeURIComponent(id)}` }),
731
+ okResponseSchema,
732
+ "unexpected token revoke response shape"
733
+ );
734
+ return result.ok ? ok(void 0) : result;
735
+ };
736
+ return {
737
+ createComputer,
738
+ connectComputer,
739
+ registerImage,
740
+ getImage,
741
+ listImages,
742
+ captureComputer,
743
+ getBase,
744
+ setBaseRetention,
745
+ createToken: (opts) => mintToken(transport, opts),
746
+ revokeToken,
747
+ pauseComputer,
748
+ resumeComputer,
749
+ stopComputer,
750
+ stopComputerRetained,
751
+ refreshComputerActivity,
752
+ listComputers,
753
+ getComputer,
754
+ deleteComputer,
755
+ withEphemeralComputer
756
+ };
757
+ };
758
+ const createComputerClient = (args) => createComputerClientWithTransport(createTransport(args));
759
+ export {
760
+ createComputerClient,
761
+ createComputerClientWithTransport,
762
+ createComputerHandle
763
+ };
764
+ //# sourceMappingURL=client.mjs.map