Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,16 @@
"ai"
],
"dependencies": {
"@types/node": "^22.9.1",
"@types/node-fetch": "^2.6.4",
"@types/ws": "^8.18.1",
"form-data": "^4.0.1",
"node-fetch": "2.7.0",
"ws": "^8.19.0",
"zod": "^4.1.12",
"zod-to-json-schema": "^3.25.0"
},
"devDependencies": {
"@types/node": "^22.9.1",
"@types/node-fetch": "^2.6.4",
"@types/ws": "^8.18.1",
"@typescript-eslint/eslint-plugin": "^8.15.0",
"@typescript-eslint/parser": "^8.15.0",
"dotenv": "^17.3.1",
Expand Down
204 changes: 184 additions & 20 deletions src/services/sandboxes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,32 @@ import { SandboxProcessHandle, SandboxProcessesApi } from "../sandbox/process";
import { SandboxTerminalApi } from "../sandbox/terminal";
import { BasicResponse } from "../types/session";
import {
CompleteSandboxImageBuildParams,
CreateSandboxImageBuildParams,
CreateSandboxParams,
Sandbox,
SandboxDetail,
SandboxExposeParams,
SandboxExposeResult,
SandboxExecParams,
SandboxExecOptions,
SandboxImageBuild,
SandboxImageBuildCreateResult,
SandboxImageBuildListParams,
SandboxImageBuildListResponse,
SandboxImageListParams,
SandboxImageListResponse,
SandboxListParams,
SandboxListResponse,
SandboxMemorySnapshotParams,
SandboxMemorySnapshotResult,
SandboxNetworkPolicyPatch,
SandboxNetworkUpdateResult,
SandboxProcessResult,
SandboxSnapshotDeleteResult,
SandboxSnapshotListParams,
SandboxSnapshotListResponse,
SandboxSnapshotSummary,
SandboxUnexposeResult,
} from "../types/sandbox";
import { BaseService } from "./base";
Expand All @@ -43,18 +54,6 @@ type WireSandboxListResponse = Omit<SandboxListResponse, "sandboxes"> & {
sandboxes: WireSandbox[];
};

const validateOptionalPositiveInteger = (
value: number | undefined,
fieldName: "cpu" | "memoryMiB" | "diskMiB"
) => {
if (value === undefined) {
return;
}
if (!Number.isInteger(value) || value < 1) {
throw new HyperbrowserError(`${fieldName} must be a positive integer`, undefined);
}
};

const normalizeSandbox = (sandbox: WireSandbox): Sandbox => {
const { vcpus, memMiB, diskSizeMiB, ...rest } = sandbox;
return {
Expand All @@ -80,11 +79,7 @@ const normalizeSandboxListResponse = (response: WireSandboxListResponse): Sandbo
});

const serializeCreateSandboxParams = (params: CreateSandboxParams): Record<string, unknown> => {
if ("imageName" in params) {
validateOptionalPositiveInteger(params.cpu, "cpu");
validateOptionalPositiveInteger(params.memoryMiB, "memoryMiB");
validateOptionalPositiveInteger(params.diskMiB, "diskMiB");

if (typeof params.imageName === "string") {
return {
imageName: params.imageName,
imageId: params.imageId,
Expand All @@ -96,6 +91,9 @@ const serializeCreateSandboxParams = (params: CreateSandboxParams): Record<strin
vcpus: params.cpu,
memMiB: params.memoryMiB,
diskSizeMiB: params.diskMiB,
allowInternetAccess: params.allowInternetAccess,
allowOut: params.allowOut,
denyOut: params.denyOut,
};
}

Expand Down Expand Up @@ -124,6 +122,9 @@ const serializeCreateSandboxParams = (params: CreateSandboxParams): Record<strin
exposedPorts: snapshotParams.exposedPorts,
mounts: snapshotParams.mounts,
timeoutMinutes: snapshotParams.timeoutMinutes,
allowInternetAccess: snapshotParams.allowInternetAccess,
allowOut: snapshotParams.allowOut,
denyOut: snapshotParams.denyOut,
};
};

Expand Down Expand Up @@ -251,6 +252,10 @@ export class SandboxHandle {
return this.detail.diskMiB;
}

get timeoutMinutes(): number | null | undefined {
return this.detail.timeoutMinutes;
}

get exposedPorts(): SandboxExposeResult[] {
return (this.detail.exposedPorts ?? []).map((entry) => ({ ...entry }));
}
Expand Down Expand Up @@ -309,6 +314,27 @@ export class SandboxHandle {
return buildSandboxExposedUrl(this.runtime, port);
}

get network(): SandboxDetail["network"] {
return this.detail.network;
}

async updateNetwork(policy: SandboxNetworkPolicyPatch): Promise<SandboxNetworkUpdateResult> {
const result = await this.service.updateNetwork(this.id, policy);
this.detail = {
...this.detail,
network: result.network,
};
return result;
}

async clearNetwork(): Promise<SandboxNetworkUpdateResult> {
return this.updateNetwork({
allowInternetAccess: true,
allowOut: [],
denyOut: [],
});
}

async exec(input: string, options?: SandboxExecOptions): Promise<SandboxProcessResult>;
async exec(input: SandboxExecParams): Promise<SandboxProcessResult>;
async exec(
Expand Down Expand Up @@ -413,7 +439,11 @@ export class SandboxHandle {
}

private assertRuntimeAvailable() {
if (this.detail.status === "closed" || this.detail.status === "error") {
if (
this.detail.status === "closed" ||
this.detail.status === "close-error" ||
this.detail.status === "error"
) {
throw new HyperbrowserError(`Sandbox ${this.id} is not running`, {
statusCode: 409,
code: "sandbox_not_running",
Expand Down Expand Up @@ -484,9 +514,14 @@ export class SandboxesService extends BaseService {
}
}

async listImages(): Promise<SandboxImageListResponse> {
async listImages(params: SandboxImageListParams = {}): Promise<SandboxImageListResponse> {
try {
return await this.request<SandboxImageListResponse>("/images");
return await this.request<SandboxImageListResponse>("/images", undefined, {
source: params.source,
search: params.search,
page: params.page,
limit: params.limit,
});
} catch (error) {
if (error instanceof HyperbrowserError) {
throw error;
Expand All @@ -502,6 +537,8 @@ export class SandboxesService extends BaseService {
return await this.request<SandboxSnapshotListResponse>("/snapshots", undefined, {
status: params.status,
imageName: params.imageName,
search: params.search,
page: params.page,
limit: params.limit,
});
} catch (error) {
Expand Down Expand Up @@ -572,6 +609,133 @@ export class SandboxesService extends BaseService {
}
}

async getSnapshot(snapshot: string): Promise<SandboxSnapshotSummary> {
try {
const response = await this.request<{ snapshot: SandboxSnapshotSummary }>(
`/snapshots/${encodeURIComponent(snapshot)}`
);
return response.snapshot;
} catch (error) {
if (error instanceof HyperbrowserError) {
throw error;
}
throw new HyperbrowserError(`Failed to get snapshot ${snapshot}`);
}
}

async deleteSnapshot(snapshot: string): Promise<SandboxSnapshotDeleteResult> {
try {
return await this.request<SandboxSnapshotDeleteResult>(
`/snapshots/${encodeURIComponent(snapshot)}`,
{ method: "DELETE" }
);
} catch (error) {
if (error instanceof HyperbrowserError) {
throw error;
}
throw new HyperbrowserError(`Failed to delete snapshot ${snapshot}`);
}
}

async createImageBuild(
params: CreateSandboxImageBuildParams
): Promise<SandboxImageBuildCreateResult> {
try {
return await this.request<SandboxImageBuildCreateResult>("/images/builds", {
method: "POST",
body: JSON.stringify(params),
});
} catch (error) {
if (error instanceof HyperbrowserError) {
throw error;
}
throw new HyperbrowserError("Failed to create image build");
}
}

async getImageBuild(buildId: string): Promise<SandboxImageBuild> {
try {
const response = await this.request<{ build: SandboxImageBuild }>(
`/images/builds/${encodeURIComponent(buildId)}`
);
return response.build;
} catch (error) {
if (error instanceof HyperbrowserError) {
throw error;
}
throw new HyperbrowserError(`Failed to get image build ${buildId}`);
}
}

async listImageBuilds(
params: SandboxImageBuildListParams = {}
): Promise<SandboxImageBuildListResponse> {
try {
return await this.request<SandboxImageBuildListResponse>("/images/builds", undefined, {
status: params.status,
limit: params.limit,
});
} catch (error) {
if (error instanceof HyperbrowserError) {
throw error;
}
throw new HyperbrowserError("Failed to list image builds");
}
}

async completeImageBuild(
buildId: string,
params: CompleteSandboxImageBuildParams
): Promise<SandboxImageBuild> {
try {
const response = await this.request<{ build: SandboxImageBuild }>(
`/images/builds/${encodeURIComponent(buildId)}/complete`,
{
method: "POST",
body: JSON.stringify(params),
}
);
return response.build;
} catch (error) {
if (error instanceof HyperbrowserError) {
throw error;
}
throw new HyperbrowserError(`Failed to complete image build ${buildId}`);
}
}

async cancelImageBuild(buildId: string): Promise<SandboxImageBuild> {
try {
const response = await this.request<{ build: SandboxImageBuild }>(
`/images/builds/${encodeURIComponent(buildId)}/cancel`,
{ method: "POST" }
);
return response.build;
} catch (error) {
if (error instanceof HyperbrowserError) {
throw error;
}
throw new HyperbrowserError(`Failed to cancel image build ${buildId}`);
}
}

async updateNetwork(
id: string,
policy: SandboxNetworkPolicyPatch
): Promise<SandboxNetworkUpdateResult> {
try {
return await this.request<SandboxNetworkUpdateResult>(`/sandbox/${id}/network`, {
method: "PUT",
body: JSON.stringify(policy),
});
} catch (error) {
if (error instanceof HyperbrowserError) {
throw error;
}
throw new HyperbrowserError(`Failed to update network policy for sandbox ${id}`);
}
}

async unexpose(id: string, port: number): Promise<SandboxUnexposeResult> {
try {
return await this.request<SandboxUnexposeResult>(`/sandbox/${id}/unexpose`, {
Expand Down
10 changes: 7 additions & 3 deletions src/services/volumes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { HyperbrowserError } from "../client";
import { CreateVolumeParams, Volume, VolumeListResponse } from "../types/volume";
import { CreateVolumeParams, Volume, VolumeListParams, VolumeListResponse } from "../types/volume";
import { BaseService } from "./base";

export class VolumesService extends BaseService {
Expand All @@ -23,9 +23,13 @@ export class VolumesService extends BaseService {
/**
* List sandbox volumes for the current team.
*/
async list(): Promise<VolumeListResponse> {
async list(params: VolumeListParams = {}): Promise<VolumeListResponse> {
try {
return await this.request<VolumeListResponse>("/volume");
return await this.request<VolumeListResponse>("/volume", undefined, {
search: params.search,
page: params.page,
limit: params.limit,
});
} catch (error) {
if (error instanceof HyperbrowserError) {
throw error;
Expand Down
1 change: 1 addition & 0 deletions src/types/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export type GrokComputerUseLlm = "grok-4.5";
export type GrokReasoningEffort = "low" | "medium" | "high";

export type SessionRegion =
| "us"
| "us-central"
| "asia-south"
| "us-dev"
Expand Down
16 changes: 15 additions & 1 deletion src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,18 +143,32 @@ export {
export {
SandboxStatus,
SandboxRuntimeTarget,
SandboxNetworkPolicy,
SandboxNetworkPolicyPatch,
SandboxNetworkUpdateResult,
Sandbox,
SandboxDetail,
SandboxVolumeMountType,
SandboxVolumeMount,
SandboxListParams,
SandboxListResponse,
SandboxImageSource,
SandboxImageSummary,
SandboxImageListParams,
SandboxImageListResponse,
SandboxSnapshotStatus,
SandboxSnapshotSummary,
SandboxSnapshotListParams,
SandboxSnapshotListResponse,
SandboxSnapshotDeleteResult,
SandboxImageBuildStatus,
SandboxImageBuildUpload,
SandboxImageBuild,
CreateSandboxImageBuildParams,
CompleteSandboxImageBuildParams,
SandboxImageBuildCreateResult,
SandboxImageBuildListParams,
SandboxImageBuildListResponse,
CreateSandboxParams,
SandboxMemorySnapshotParams,
SandboxMemorySnapshotResult,
Expand Down Expand Up @@ -199,7 +213,7 @@ export {
SandboxTerminalKillParams,
SandboxTerminalEvent,
} from "./sandbox";
export { CreateVolumeParams, Volume, VolumeListResponse } from "./volume";
export { CreateVolumeParams, Volume, VolumeListParams, VolumeListResponse } from "./volume";
export {
CreateProfileParams,
ForkProfileParams,
Expand Down
Loading