@miosa/sdk 1.2.4 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -3
- package/dist/index.d.ts +323 -175
- package/dist/index.js +441 -386
- package/dist/index.js.map +1 -1
- package/package.json +1 -2
- package/src/client.ts +0 -293
- package/src/errors.ts +0 -136
- package/src/http.test.ts +0 -374
- package/src/http.ts +0 -390
- package/src/index.ts +0 -564
- package/src/resources/admin.ts +0 -359
- package/src/resources/analytics.ts +0 -60
- package/src/resources/api-keys.ts +0 -135
- package/src/resources/audit-log.ts +0 -64
- package/src/resources/benchmarks.ts +0 -104
- package/src/resources/builder-sessions.ts +0 -75
- package/src/resources/channels.ts +0 -143
- package/src/resources/checkpoints.ts +0 -225
- package/src/resources/command-center.ts +0 -73
- package/src/resources/community.ts +0 -103
- package/src/resources/completions.ts +0 -104
- package/src/resources/computer-auto-stop.ts +0 -43
- package/src/resources/computer-env.ts +0 -76
- package/src/resources/computer-logs.ts +0 -50
- package/src/resources/computer-osa.ts +0 -76
- package/src/resources/computer-ports.ts +0 -91
- package/src/resources/computer-terminal.ts +0 -61
- package/src/resources/computer-volumes.ts +0 -64
- package/src/resources/computer.ts +0 -551
- package/src/resources/computers.ts +0 -75
- package/src/resources/credits.ts +0 -43
- package/src/resources/cron-jobs.ts +0 -191
- package/src/resources/custom_domains.ts +0 -123
- package/src/resources/dashboard.ts +0 -49
- package/src/resources/databases.ts +0 -218
- package/src/resources/deployments.test.ts +0 -194
- package/src/resources/deployments.ts +0 -1060
- package/src/resources/desktop.ts +0 -134
- package/src/resources/docker-deploy.test.ts +0 -102
- package/src/resources/docker-deploy.ts +0 -183
- package/src/resources/egress.test.ts +0 -318
- package/src/resources/egressAudit.ts +0 -245
- package/src/resources/egressNetwork.ts +0 -450
- package/src/resources/egressSecrets.ts +0 -577
- package/src/resources/email.ts +0 -212
- package/src/resources/embeddings.ts +0 -36
- package/src/resources/events.ts +0 -296
- package/src/resources/exec.ts +0 -319
- package/src/resources/external-keys.ts +0 -79
- package/src/resources/files.test.ts +0 -339
- package/src/resources/files.ts +0 -220
- package/src/resources/flat-custom-domains.ts +0 -127
- package/src/resources/functions.ts +0 -178
- package/src/resources/governance.test.ts +0 -355
- package/src/resources/governance.ts +0 -528
- package/src/resources/health-checks.ts +0 -165
- package/src/resources/integrations.ts +0 -183
- package/src/resources/mcp.ts +0 -70
- package/src/resources/models.ts +0 -45
- package/src/resources/network_policy.ts +0 -73
- package/src/resources/open-computers/agents.ts +0 -91
- package/src/resources/open-computers/apps.ts +0 -102
- package/src/resources/open-computers/clusters.ts +0 -88
- package/src/resources/open-computers/desktop.ts +0 -34
- package/src/resources/open-computers/files.ts +0 -97
- package/src/resources/open-computers/hosts.ts +0 -85
- package/src/resources/open-computers/index.ts +0 -98
- package/src/resources/open-computers/jobs.ts +0 -75
- package/src/resources/open-computers/open_computers.test.ts +0 -288
- package/src/resources/open-computers/secrets.ts +0 -115
- package/src/resources/open-computers/terminal.ts +0 -33
- package/src/resources/open-computers/tunnels.ts +0 -87
- package/src/resources/open-computers/types.ts +0 -343
- package/src/resources/open-computers/workspaces.ts +0 -135
- package/src/resources/org-invites.ts +0 -189
- package/src/resources/phase1.test.ts +0 -187
- package/src/resources/project-auth.ts +0 -142
- package/src/resources/project-integrations.ts +0 -133
- package/src/resources/provider-defaults.ts +0 -89
- package/src/resources/quotas.ts +0 -77
- package/src/resources/regions.ts +0 -94
- package/src/resources/sandbox-processes.ts +0 -112
- package/src/resources/sandbox-shares.ts +0 -83
- package/src/resources/sandbox-templates.ts +0 -195
- package/src/resources/sandboxes.live.test.ts +0 -92
- package/src/resources/sandboxes.test.ts +0 -653
- package/src/resources/sandboxes.ts +0 -1433
- package/src/resources/settings.ts +0 -143
- package/src/resources/snapshots-standalone.ts +0 -51
- package/src/resources/storage.ts +0 -221
- package/src/resources/tenant-events.ts +0 -32
- package/src/resources/tenant.ts +0 -148
- package/src/resources/usage.ts +0 -85
- package/src/resources/volumes.ts +0 -117
- package/src/resources/webhooks.ts +0 -224
- package/src/resources/workspace-invites.ts +0 -188
- package/src/resources/workspace-members.test.ts +0 -121
- package/src/resources/workspace-members.ts +0 -143
- package/src/resources/workspaces.ts +0 -285
- package/src/types.ts +0 -463
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { HttpClient } from "../../http.js";
|
|
2
|
-
import type { FsEntry, FsListResponse, FsStat, HostId } from "./types.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Files resource — direct file-system access on a remote OpenComputers host.
|
|
6
|
-
*
|
|
7
|
-
* Operations are proxied through the OSA daemon running on the host.
|
|
8
|
-
*
|
|
9
|
-
* ```ts
|
|
10
|
-
* const entries = await client.openComputers.files.list(hostId, "/home/user");
|
|
11
|
-
* const bytes = await client.openComputers.files.download(hostId, "/home/user/report.pdf");
|
|
12
|
-
* await client.openComputers.files.upload(hostId, "/tmp/data.json", new TextEncoder().encode("{}"));
|
|
13
|
-
* ```
|
|
14
|
-
*/
|
|
15
|
-
export class OcFiles {
|
|
16
|
-
private readonly http: HttpClient;
|
|
17
|
-
|
|
18
|
-
constructor(http: HttpClient) {
|
|
19
|
-
this.http = http;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
private base(hostId: string): string {
|
|
23
|
-
return `/opencomputers/hosts/${hostId}/fs`;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* List directory entries at `path`.
|
|
28
|
-
*/
|
|
29
|
-
async list(hostId: HostId | string, path: string): Promise<FsListResponse> {
|
|
30
|
-
return this.http.get<FsListResponse>(this.base(hostId), { path });
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Stat a path (does not follow symlinks).
|
|
35
|
-
*/
|
|
36
|
-
async stat(hostId: HostId | string, path: string): Promise<FsStat> {
|
|
37
|
-
return this.http.get<FsStat>(`${this.base(hostId)}/stat`, { path });
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Download a file from the host. Returns raw bytes.
|
|
42
|
-
*/
|
|
43
|
-
async download(hostId: HostId | string, path: string): Promise<Uint8Array> {
|
|
44
|
-
const url = `${this.base(hostId)}/download?path=${encodeURIComponent(path)}`;
|
|
45
|
-
return this.http.getBinary(url);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Upload content to `remotePath` on the host.
|
|
50
|
-
*
|
|
51
|
-
* @param content - `Blob`, `Uint8Array`, or UTF-8 string.
|
|
52
|
-
* @param remotePath - Absolute destination path on the host.
|
|
53
|
-
* @param filename - Optional filename hint; defaults to `remotePath` basename.
|
|
54
|
-
*/
|
|
55
|
-
async upload(
|
|
56
|
-
hostId: HostId | string,
|
|
57
|
-
remotePath: string,
|
|
58
|
-
content: Blob | Uint8Array | string,
|
|
59
|
-
filename?: string,
|
|
60
|
-
): Promise<FsEntry> {
|
|
61
|
-
const fd = new FormData();
|
|
62
|
-
const name = filename ?? remotePath.split("/").pop() ?? "file";
|
|
63
|
-
|
|
64
|
-
if (typeof content === "string") {
|
|
65
|
-
fd.append("file", new Blob([content]), name);
|
|
66
|
-
} else if (content instanceof Uint8Array) {
|
|
67
|
-
fd.append("file", new Blob([content.buffer as ArrayBuffer]), name);
|
|
68
|
-
} else {
|
|
69
|
-
fd.append("file", content, name);
|
|
70
|
-
}
|
|
71
|
-
fd.append("path", remotePath);
|
|
72
|
-
|
|
73
|
-
return this.http.postFormData<FsEntry>(`${this.base(hostId)}/upload`, fd);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Delete a file or directory.
|
|
78
|
-
*
|
|
79
|
-
* @param recursive - When `true`, delete non-empty directories recursively.
|
|
80
|
-
*/
|
|
81
|
-
async delete(
|
|
82
|
-
hostId: HostId | string,
|
|
83
|
-
path: string,
|
|
84
|
-
recursive = false,
|
|
85
|
-
): Promise<void> {
|
|
86
|
-
return this.http.delete<void>(
|
|
87
|
-
`${this.base(hostId)}?path=${encodeURIComponent(path)}&recursive=${recursive}`,
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Create a directory (and any missing parents).
|
|
93
|
-
*/
|
|
94
|
-
async mkdir(hostId: HostId | string, path: string): Promise<void> {
|
|
95
|
-
return this.http.post<void>(`${this.base(hostId)}/mkdir`, { path });
|
|
96
|
-
}
|
|
97
|
-
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { HttpClient } from "../../http.js";
|
|
2
|
-
import type {
|
|
3
|
-
HostCreateParams,
|
|
4
|
-
HostData,
|
|
5
|
-
HostEvent,
|
|
6
|
-
HostId,
|
|
7
|
-
HostListResponse,
|
|
8
|
-
HostUpdateParams,
|
|
9
|
-
} from "./types.js";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Hosts resource — manage BYOC (Bring Your Own Computer) hosts registered
|
|
13
|
-
* under your tenant.
|
|
14
|
-
*
|
|
15
|
-
* ```ts
|
|
16
|
-
* const host = await client.openComputers.hosts.create({ name: "my-mac" });
|
|
17
|
-
* console.log(host.host_key); // store this — shown only once
|
|
18
|
-
* ```
|
|
19
|
-
*/
|
|
20
|
-
export class Hosts {
|
|
21
|
-
private readonly http: HttpClient;
|
|
22
|
-
|
|
23
|
-
constructor(http: HttpClient) {
|
|
24
|
-
this.http = http;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
private base(): string {
|
|
28
|
-
return "/opencomputers/hosts";
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* List all hosts registered for the tenant.
|
|
33
|
-
*/
|
|
34
|
-
async list(): Promise<HostListResponse> {
|
|
35
|
-
return this.http.get<HostListResponse>(this.base());
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Register a new host. The returned `host_key` is shown **once** — store it
|
|
40
|
-
* immediately and install it on the target machine.
|
|
41
|
-
*/
|
|
42
|
-
async create(params: HostCreateParams): Promise<HostData> {
|
|
43
|
-
return this.http.post<HostData>(this.base(), params);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Fetch a single host by ID.
|
|
48
|
-
*/
|
|
49
|
-
async get(id: HostId | string): Promise<HostData> {
|
|
50
|
-
return this.http.get<HostData>(`${this.base()}/${id}`);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Update host metadata (name / labels).
|
|
55
|
-
*/
|
|
56
|
-
async update(
|
|
57
|
-
id: HostId | string,
|
|
58
|
-
params: HostUpdateParams,
|
|
59
|
-
): Promise<HostData> {
|
|
60
|
-
return this.http.patch<HostData>(`${this.base()}/${id}`, params);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Revoke a host. Permanently removes the registration; the host key is
|
|
65
|
-
* invalidated and the host will disconnect on next heartbeat.
|
|
66
|
-
*/
|
|
67
|
-
async revoke(id: HostId | string): Promise<void> {
|
|
68
|
-
return this.http.delete<void>(`${this.base()}/${id}`);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Stream host state-change events (connect / disconnect / error) as an
|
|
73
|
-
* `AsyncIterable`.
|
|
74
|
-
*
|
|
75
|
-
* ```ts
|
|
76
|
-
* for await (const event of client.openComputers.hosts.events()) {
|
|
77
|
-
* console.log(event.type, event.host_id);
|
|
78
|
-
* if (shouldStop) break;
|
|
79
|
-
* }
|
|
80
|
-
* ```
|
|
81
|
-
*/
|
|
82
|
-
events(): AsyncIterableIterator<HostEvent> {
|
|
83
|
-
return this.http.stream<HostEvent>("/opencomputers/hosts/events");
|
|
84
|
-
}
|
|
85
|
-
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { HttpClient } from "../../http.js";
|
|
2
|
-
import { Agents } from "./agents.js";
|
|
3
|
-
import { Apps } from "./apps.js";
|
|
4
|
-
import { Clusters } from "./clusters.js";
|
|
5
|
-
import { Desktop } from "./desktop.js";
|
|
6
|
-
import { OcFiles } from "./files.js";
|
|
7
|
-
import { Hosts } from "./hosts.js";
|
|
8
|
-
import { Jobs } from "./jobs.js";
|
|
9
|
-
import { Secrets } from "./secrets.js";
|
|
10
|
-
import { Terminal } from "./terminal.js";
|
|
11
|
-
import { Tunnels } from "./tunnels.js";
|
|
12
|
-
import { OcWorkspaces } from "./workspaces.js";
|
|
13
|
-
|
|
14
|
-
export * from "./types.js";
|
|
15
|
-
export { Agents } from "./agents.js";
|
|
16
|
-
export { Apps } from "./apps.js";
|
|
17
|
-
export { Clusters } from "./clusters.js";
|
|
18
|
-
export { Desktop } from "./desktop.js";
|
|
19
|
-
export { OcFiles } from "./files.js";
|
|
20
|
-
export { Hosts } from "./hosts.js";
|
|
21
|
-
export { Jobs } from "./jobs.js";
|
|
22
|
-
export { Secrets } from "./secrets.js";
|
|
23
|
-
export { Terminal } from "./terminal.js";
|
|
24
|
-
export { Tunnels } from "./tunnels.js";
|
|
25
|
-
export { OcWorkspaces } from "./workspaces.js";
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* OpenComputers namespace — BYOC (Bring Your Own Computer) host management.
|
|
29
|
-
*
|
|
30
|
-
* Register your own machines (Mac, Linux, Windows) with MIOSA and use them
|
|
31
|
-
* just like cloud computers: run jobs, manage files, open terminals, expose
|
|
32
|
-
* ports, dispatch AI agents, and run LLM inference clusters.
|
|
33
|
-
*
|
|
34
|
-
* Access via `client.openComputers`:
|
|
35
|
-
*
|
|
36
|
-
* ```ts
|
|
37
|
-
* const miosa = new Miosa({ apiKey: "msk_u_..." });
|
|
38
|
-
*
|
|
39
|
-
* // Register a host — save host_key immediately, shown only once
|
|
40
|
-
* const host = await miosa.openComputers.hosts.create({ name: "my-mac" });
|
|
41
|
-
*
|
|
42
|
-
* // Run a command
|
|
43
|
-
* const job = await miosa.openComputers.jobs.run(host.id, { command: "npm test" });
|
|
44
|
-
*
|
|
45
|
-
* // Stream output
|
|
46
|
-
* for await (const event of miosa.openComputers.jobs.stream(host.id, job.id)) {
|
|
47
|
-
* process.stdout.write(String(event.data ?? ""));
|
|
48
|
-
* if (event.type === "done") break;
|
|
49
|
-
* }
|
|
50
|
-
*
|
|
51
|
-
* // Expose a port
|
|
52
|
-
* const tunnel = await miosa.openComputers.tunnels.create(host.id, { target_port: 3000 });
|
|
53
|
-
* console.log(tunnel.public_url);
|
|
54
|
-
*
|
|
55
|
-
* // Dispatch an AI agent
|
|
56
|
-
* const session = await miosa.openComputers.agents.dispatch(host.id, {
|
|
57
|
-
* task: "Run tests and fix failing ones",
|
|
58
|
-
* });
|
|
59
|
-
* ```
|
|
60
|
-
*/
|
|
61
|
-
export class OpenComputers {
|
|
62
|
-
/** Host registration and lifecycle. */
|
|
63
|
-
readonly hosts: Hosts;
|
|
64
|
-
/** Command execution on remote hosts. */
|
|
65
|
-
readonly jobs: Jobs;
|
|
66
|
-
/** File system access on remote hosts. */
|
|
67
|
-
readonly files: OcFiles;
|
|
68
|
-
/** Interactive terminal session tickets. */
|
|
69
|
-
readonly terminal: Terminal;
|
|
70
|
-
/** Desktop streaming session tickets. */
|
|
71
|
-
readonly desktop: Desktop;
|
|
72
|
-
/** HTTP tunnel management (expose host ports publicly). */
|
|
73
|
-
readonly tunnels: Tunnels;
|
|
74
|
-
/** AI agent dispatch and session management. */
|
|
75
|
-
readonly agents: Agents;
|
|
76
|
-
/** Multi-host LLM inference clusters. */
|
|
77
|
-
readonly clusters: Clusters;
|
|
78
|
-
/** App library — one-click installs on hosts. */
|
|
79
|
-
readonly apps: Apps;
|
|
80
|
-
/** Git-backed workspace environments. */
|
|
81
|
-
readonly workspaces: OcWorkspaces;
|
|
82
|
-
/** Encrypted per-host / per-tenant secrets. */
|
|
83
|
-
readonly secrets: Secrets;
|
|
84
|
-
|
|
85
|
-
constructor(http: HttpClient) {
|
|
86
|
-
this.hosts = new Hosts(http);
|
|
87
|
-
this.jobs = new Jobs(http);
|
|
88
|
-
this.files = new OcFiles(http);
|
|
89
|
-
this.terminal = new Terminal(http);
|
|
90
|
-
this.desktop = new Desktop(http);
|
|
91
|
-
this.tunnels = new Tunnels(http);
|
|
92
|
-
this.agents = new Agents(http);
|
|
93
|
-
this.clusters = new Clusters(http);
|
|
94
|
-
this.apps = new Apps(http);
|
|
95
|
-
this.workspaces = new OcWorkspaces(http);
|
|
96
|
-
this.secrets = new Secrets(http);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { HttpClient } from "../../http.js";
|
|
2
|
-
import type {
|
|
3
|
-
HostId,
|
|
4
|
-
JobData,
|
|
5
|
-
JobEvent,
|
|
6
|
-
JobId,
|
|
7
|
-
JobListResponse,
|
|
8
|
-
JobRunParams,
|
|
9
|
-
} from "./types.js";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Jobs resource — run commands on a remote OpenComputers host and stream output.
|
|
13
|
-
*
|
|
14
|
-
* ```ts
|
|
15
|
-
* const job = await client.openComputers.jobs.run(hostId, { command: "npm test" });
|
|
16
|
-
* for await (const event of client.openComputers.jobs.stream(hostId, job.id)) {
|
|
17
|
-
* process.stdout.write(String(event.data ?? ""));
|
|
18
|
-
* if (event.type === "done") break;
|
|
19
|
-
* }
|
|
20
|
-
* ```
|
|
21
|
-
*/
|
|
22
|
-
export class Jobs {
|
|
23
|
-
private readonly http: HttpClient;
|
|
24
|
-
|
|
25
|
-
constructor(http: HttpClient) {
|
|
26
|
-
this.http = http;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
private base(hostId: string): string {
|
|
30
|
-
return `/opencomputers/hosts/${hostId}`;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Dispatch a command to run on the remote host.
|
|
35
|
-
*/
|
|
36
|
-
async run(hostId: HostId | string, params: JobRunParams): Promise<JobData> {
|
|
37
|
-
return this.http.post<JobData>(`${this.base(hostId)}/exec`, params);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* List all jobs for a host.
|
|
42
|
-
*/
|
|
43
|
-
async list(hostId: HostId | string): Promise<JobListResponse> {
|
|
44
|
-
return this.http.get<JobListResponse>(`${this.base(hostId)}/exec`);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Fetch the current state of a job.
|
|
49
|
-
*/
|
|
50
|
-
async get(hostId: HostId | string, jobId: JobId | string): Promise<JobData> {
|
|
51
|
-
return this.http.get<JobData>(`${this.base(hostId)}/exec/${jobId}`);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Stream live output from a running job.
|
|
56
|
-
*
|
|
57
|
-
* Yields `JobEvent` objects with `type` of `stdout`, `stderr`, `exit`, or
|
|
58
|
-
* `done`. Break the loop when you receive `done` or `exit`.
|
|
59
|
-
*/
|
|
60
|
-
stream(
|
|
61
|
-
hostId: HostId | string,
|
|
62
|
-
jobId: JobId | string,
|
|
63
|
-
): AsyncIterableIterator<JobEvent> {
|
|
64
|
-
return this.http.stream<JobEvent>(
|
|
65
|
-
`${this.base(hostId)}/exec/${jobId}/stream`,
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Cancel a running or queued job.
|
|
71
|
-
*/
|
|
72
|
-
async cancel(hostId: HostId | string, jobId: JobId | string): Promise<void> {
|
|
73
|
-
return this.http.delete<void>(`${this.base(hostId)}/exec/${jobId}`);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { HttpClient } from "../../http.js";
|
|
3
|
-
import { OpenComputers } from "./index.js";
|
|
4
|
-
|
|
5
|
-
// ── Shared mock setup ────────────────────────────────────────────────────────
|
|
6
|
-
|
|
7
|
-
const mockGet = vi.fn();
|
|
8
|
-
const mockPost = vi.fn();
|
|
9
|
-
const mockPatch = vi.fn();
|
|
10
|
-
const mockDelete = vi.fn();
|
|
11
|
-
const mockStream = vi.fn();
|
|
12
|
-
|
|
13
|
-
function makeHttp(): HttpClient {
|
|
14
|
-
const http = {} as HttpClient;
|
|
15
|
-
http.get = mockGet;
|
|
16
|
-
http.post = mockPost;
|
|
17
|
-
http.patch = mockPatch;
|
|
18
|
-
http.delete = mockDelete;
|
|
19
|
-
http.stream = mockStream;
|
|
20
|
-
http.getBinary = vi.fn();
|
|
21
|
-
http.postFormData = vi.fn();
|
|
22
|
-
return http;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
beforeEach(() => {
|
|
26
|
-
vi.clearAllMocks();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
afterEach(() => {
|
|
30
|
-
vi.restoreAllMocks();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
// ── Hosts ────────────────────────────────────────────────────────────────────
|
|
34
|
-
|
|
35
|
-
describe("OpenComputers.hosts", () => {
|
|
36
|
-
const hostData = {
|
|
37
|
-
id: "host_abc",
|
|
38
|
-
name: "my-mac",
|
|
39
|
-
region: null,
|
|
40
|
-
status: "online",
|
|
41
|
-
tenant_id: "t_1",
|
|
42
|
-
labels: {},
|
|
43
|
-
created_at: "2026-01-01T00:00:00Z",
|
|
44
|
-
updated_at: "2026-01-01T00:00:00Z",
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
it("list() calls GET /opencomputers/hosts", async () => {
|
|
48
|
-
mockGet.mockResolvedValue({
|
|
49
|
-
data: [hostData],
|
|
50
|
-
meta: { total: 1, page: 1, per_page: 20 },
|
|
51
|
-
});
|
|
52
|
-
const oc = new OpenComputers(makeHttp());
|
|
53
|
-
const result = await oc.hosts.list();
|
|
54
|
-
expect(mockGet).toHaveBeenCalledWith("/opencomputers/hosts");
|
|
55
|
-
expect(result.data).toHaveLength(1);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("create() calls POST /opencomputers/hosts with host_key in response", async () => {
|
|
59
|
-
const created = { ...hostData, host_key: "hk_secret" };
|
|
60
|
-
mockPost.mockResolvedValue(created);
|
|
61
|
-
const oc = new OpenComputers(makeHttp());
|
|
62
|
-
const result = await oc.hosts.create({ name: "my-mac" });
|
|
63
|
-
expect(mockPost).toHaveBeenCalledWith("/opencomputers/hosts", {
|
|
64
|
-
name: "my-mac",
|
|
65
|
-
});
|
|
66
|
-
expect(result.host_key).toBe("hk_secret");
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("get() calls GET /opencomputers/hosts/:id", async () => {
|
|
70
|
-
mockGet.mockResolvedValue(hostData);
|
|
71
|
-
const oc = new OpenComputers(makeHttp());
|
|
72
|
-
await oc.hosts.get("host_abc");
|
|
73
|
-
expect(mockGet).toHaveBeenCalledWith("/opencomputers/hosts/host_abc");
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it("update() calls PATCH /opencomputers/hosts/:id", async () => {
|
|
77
|
-
mockPatch.mockResolvedValue({ ...hostData, name: "renamed" });
|
|
78
|
-
const oc = new OpenComputers(makeHttp());
|
|
79
|
-
await oc.hosts.update("host_abc", { name: "renamed" });
|
|
80
|
-
expect(mockPatch).toHaveBeenCalledWith("/opencomputers/hosts/host_abc", {
|
|
81
|
-
name: "renamed",
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it("revoke() calls DELETE /opencomputers/hosts/:id", async () => {
|
|
86
|
-
mockDelete.mockResolvedValue(undefined);
|
|
87
|
-
const oc = new OpenComputers(makeHttp());
|
|
88
|
-
await oc.hosts.revoke("host_abc");
|
|
89
|
-
expect(mockDelete).toHaveBeenCalledWith("/opencomputers/hosts/host_abc");
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it("events() returns an AsyncIterable from stream()", () => {
|
|
93
|
-
async function* fakeStream() {
|
|
94
|
-
yield {
|
|
95
|
-
type: "host_connected",
|
|
96
|
-
host_id: "host_abc",
|
|
97
|
-
data: null,
|
|
98
|
-
timestamp: "t",
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
mockStream.mockReturnValue(fakeStream());
|
|
102
|
-
const oc = new OpenComputers(makeHttp());
|
|
103
|
-
const iter = oc.hosts.events();
|
|
104
|
-
expect(mockStream).toHaveBeenCalledWith("/opencomputers/hosts/events");
|
|
105
|
-
expect(typeof iter[Symbol.asyncIterator]).toBe("function");
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// ── Jobs ─────────────────────────────────────────────────────────────────────
|
|
110
|
-
|
|
111
|
-
describe("OpenComputers.jobs", () => {
|
|
112
|
-
const jobData = {
|
|
113
|
-
id: "job_1",
|
|
114
|
-
host_id: "host_abc",
|
|
115
|
-
status: "completed",
|
|
116
|
-
command: "npm test",
|
|
117
|
-
args: [],
|
|
118
|
-
env: [],
|
|
119
|
-
cwd: null,
|
|
120
|
-
exit_code: 0,
|
|
121
|
-
stdout: "ok",
|
|
122
|
-
stderr: "",
|
|
123
|
-
created_at: "2026-01-01T00:00:00Z",
|
|
124
|
-
updated_at: "2026-01-01T00:00:00Z",
|
|
125
|
-
completed_at: "2026-01-01T00:00:01Z",
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
it("run() calls POST /opencomputers/hosts/:id/exec", async () => {
|
|
129
|
-
mockPost.mockResolvedValue(jobData);
|
|
130
|
-
const oc = new OpenComputers(makeHttp());
|
|
131
|
-
const result = await oc.jobs.run("host_abc", { command: "npm test" });
|
|
132
|
-
expect(mockPost).toHaveBeenCalledWith(
|
|
133
|
-
"/opencomputers/hosts/host_abc/exec",
|
|
134
|
-
{ command: "npm test" },
|
|
135
|
-
);
|
|
136
|
-
expect(result.id).toBe("job_1");
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it("list() calls GET /opencomputers/hosts/:id/exec", async () => {
|
|
140
|
-
mockGet.mockResolvedValue({
|
|
141
|
-
data: [jobData],
|
|
142
|
-
meta: { total: 1, page: 1, per_page: 20 },
|
|
143
|
-
});
|
|
144
|
-
const oc = new OpenComputers(makeHttp());
|
|
145
|
-
await oc.jobs.list("host_abc");
|
|
146
|
-
expect(mockGet).toHaveBeenCalledWith("/opencomputers/hosts/host_abc/exec");
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
it("get() calls GET /opencomputers/hosts/:id/exec/:job_id", async () => {
|
|
150
|
-
mockGet.mockResolvedValue(jobData);
|
|
151
|
-
const oc = new OpenComputers(makeHttp());
|
|
152
|
-
await oc.jobs.get("host_abc", "job_1");
|
|
153
|
-
expect(mockGet).toHaveBeenCalledWith(
|
|
154
|
-
"/opencomputers/hosts/host_abc/exec/job_1",
|
|
155
|
-
);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it("stream() returns an AsyncIterable", () => {
|
|
159
|
-
async function* fakeStream() {
|
|
160
|
-
yield { type: "stdout", job_id: "job_1", data: "ok\n", timestamp: "t" };
|
|
161
|
-
yield { type: "done", job_id: "job_1", data: null, timestamp: "t" };
|
|
162
|
-
}
|
|
163
|
-
mockStream.mockReturnValue(fakeStream());
|
|
164
|
-
const oc = new OpenComputers(makeHttp());
|
|
165
|
-
const iter = oc.jobs.stream("host_abc", "job_1");
|
|
166
|
-
expect(mockStream).toHaveBeenCalledWith(
|
|
167
|
-
"/opencomputers/hosts/host_abc/exec/job_1/stream",
|
|
168
|
-
);
|
|
169
|
-
expect(typeof iter[Symbol.asyncIterator]).toBe("function");
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it("cancel() calls DELETE /opencomputers/hosts/:id/exec/:job_id", async () => {
|
|
173
|
-
mockDelete.mockResolvedValue(undefined);
|
|
174
|
-
const oc = new OpenComputers(makeHttp());
|
|
175
|
-
await oc.jobs.cancel("host_abc", "job_1");
|
|
176
|
-
expect(mockDelete).toHaveBeenCalledWith(
|
|
177
|
-
"/opencomputers/hosts/host_abc/exec/job_1",
|
|
178
|
-
);
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
// ── Error paths ───────────────────────────────────────────────────────────────
|
|
183
|
-
|
|
184
|
-
describe("OpenComputers error paths", () => {
|
|
185
|
-
it("propagates 401 from hosts.list()", async () => {
|
|
186
|
-
const { AuthError } = await import("../../errors.js");
|
|
187
|
-
mockGet.mockRejectedValue(new AuthError("Unauthorized", "UNAUTHORIZED"));
|
|
188
|
-
const oc = new OpenComputers(makeHttp());
|
|
189
|
-
await expect(oc.hosts.list()).rejects.toBeInstanceOf(AuthError);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it("propagates 404 from hosts.get()", async () => {
|
|
193
|
-
const { NotFoundError } = await import("../../errors.js");
|
|
194
|
-
mockGet.mockRejectedValue(new NotFoundError("Host not found", "NOT_FOUND"));
|
|
195
|
-
const oc = new OpenComputers(makeHttp());
|
|
196
|
-
await expect(oc.hosts.get("nonexistent")).rejects.toBeInstanceOf(
|
|
197
|
-
NotFoundError,
|
|
198
|
-
);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it("propagates 402 from jobs.run() when credits exhausted", async () => {
|
|
202
|
-
const { InsufficientCreditsError } = await import("../../errors.js");
|
|
203
|
-
mockPost.mockRejectedValue(
|
|
204
|
-
new InsufficientCreditsError(
|
|
205
|
-
"Insufficient credits",
|
|
206
|
-
"INSUFFICIENT_CREDITS",
|
|
207
|
-
),
|
|
208
|
-
);
|
|
209
|
-
const oc = new OpenComputers(makeHttp());
|
|
210
|
-
await expect(
|
|
211
|
-
oc.jobs.run("host_abc", { command: "npm test" }),
|
|
212
|
-
).rejects.toBeInstanceOf(InsufficientCreditsError);
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
// ── Tunnels ───────────────────────────────────────────────────────────────────
|
|
217
|
-
|
|
218
|
-
describe("OpenComputers.tunnels", () => {
|
|
219
|
-
const tunnelData = {
|
|
220
|
-
id: "tun_1",
|
|
221
|
-
host_id: "host_abc",
|
|
222
|
-
slug: "abc123",
|
|
223
|
-
target_port: 3000,
|
|
224
|
-
auth_mode: "public",
|
|
225
|
-
public_url: "https://api.miosa.ai/t/abc123",
|
|
226
|
-
enabled: true,
|
|
227
|
-
created_at: "2026-01-01T00:00:00Z",
|
|
228
|
-
updated_at: "2026-01-01T00:00:00Z",
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
it("create() calls POST /opencomputers/hosts/:id/tunnels", async () => {
|
|
232
|
-
mockPost.mockResolvedValue(tunnelData);
|
|
233
|
-
const oc = new OpenComputers(makeHttp());
|
|
234
|
-
const result = await oc.tunnels.create("host_abc", { target_port: 3000 });
|
|
235
|
-
expect(mockPost).toHaveBeenCalledWith(
|
|
236
|
-
"/opencomputers/hosts/host_abc/tunnels",
|
|
237
|
-
{ target_port: 3000 },
|
|
238
|
-
);
|
|
239
|
-
expect(result.public_url).toContain("abc123");
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it("delete() calls DELETE /opencomputers/hosts/:id/tunnels/:tid", async () => {
|
|
243
|
-
mockDelete.mockResolvedValue(undefined);
|
|
244
|
-
const oc = new OpenComputers(makeHttp());
|
|
245
|
-
await oc.tunnels.delete("host_abc", "tun_1");
|
|
246
|
-
expect(mockDelete).toHaveBeenCalledWith(
|
|
247
|
-
"/opencomputers/hosts/host_abc/tunnels/tun_1",
|
|
248
|
-
);
|
|
249
|
-
});
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
// ── Agents ────────────────────────────────────────────────────────────────────
|
|
253
|
-
|
|
254
|
-
describe("OpenComputers.agents", () => {
|
|
255
|
-
const sessionData = {
|
|
256
|
-
id: "sess_1",
|
|
257
|
-
host_id: "host_abc",
|
|
258
|
-
task: "run tests",
|
|
259
|
-
model_id: null,
|
|
260
|
-
status: "pending",
|
|
261
|
-
max_turns: 20,
|
|
262
|
-
turns_used: 0,
|
|
263
|
-
created_at: "2026-01-01T00:00:00Z",
|
|
264
|
-
updated_at: "2026-01-01T00:00:00Z",
|
|
265
|
-
completed_at: null,
|
|
266
|
-
error: null,
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
it("dispatch() calls POST /opencomputers/hosts/:id/agent/dispatch", async () => {
|
|
270
|
-
mockPost.mockResolvedValue(sessionData);
|
|
271
|
-
const oc = new OpenComputers(makeHttp());
|
|
272
|
-
const result = await oc.agents.dispatch("host_abc", { task: "run tests" });
|
|
273
|
-
expect(mockPost).toHaveBeenCalledWith(
|
|
274
|
-
"/opencomputers/hosts/host_abc/agent/dispatch",
|
|
275
|
-
{ task: "run tests" },
|
|
276
|
-
);
|
|
277
|
-
expect(result.id).toBe("sess_1");
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
it("cancel() calls DELETE /opencomputers/hosts/:id/agent/sessions/:id", async () => {
|
|
281
|
-
mockDelete.mockResolvedValue(undefined);
|
|
282
|
-
const oc = new OpenComputers(makeHttp());
|
|
283
|
-
await oc.agents.cancel("host_abc", "sess_1");
|
|
284
|
-
expect(mockDelete).toHaveBeenCalledWith(
|
|
285
|
-
"/opencomputers/hosts/host_abc/agent/sessions/sess_1",
|
|
286
|
-
);
|
|
287
|
-
});
|
|
288
|
-
});
|