← vibesdk
worker/services/sandbox/remoteSandboxService.ts
10.0 KB
Download file
import {
BootstrapResponse,
GetInstanceResponse,
BootstrapStatusResponse,
ShutdownResponse,
WriteFilesRequest,
WriteFilesResponse,
GetFilesResponse,
ExecuteCommandsResponse,
RuntimeErrorResponse,
ClearErrorsResponse,
StaticAnalysisResponse,
DeploymentResult,
GetLogsResponse,
ListInstancesResponse,
BootstrapResponseSchema,
GetInstanceResponseSchema,
BootstrapStatusResponseSchema,
WriteFilesResponseSchema,
GetFilesResponseSchema,
ExecuteCommandsRequest,
ExecuteCommandsResponseSchema,
RuntimeErrorResponseSchema,
ClearErrorsResponseSchema,
DeploymentResultSchema,
ShutdownResponseSchema,
StaticAnalysisResponseSchema,
GitHubPushRequest,
GitHubPushResponse,
GitHubPushResponseSchema,
InstanceCreationRequest,
} from './sandboxTypes';
import { BaseSandboxService } from "./BaseSandboxService";
import { DeploymentTarget } from 'worker/agents/core/types';
import { env } from 'cloudflare:workers'
import z from 'zod';
import { FileOutputType } from 'worker/agents/schemas';
export async function runnerFetch(url: string, method: 'GET' | 'POST' | 'DELETE', headers: Headers, body: string | undefined) {
// Use direct fetch for runner service communication
return await fetch(url, { method, headers, body });
}
/**
* Client for interacting with the Runner Service API.
*/
export class RemoteSandboxServiceClient extends BaseSandboxService{
private static sandboxServiceUrl: string;
private static token: string;
static init(sandboxServiceUrl: string, token: string) {
RemoteSandboxServiceClient.sandboxServiceUrl = sandboxServiceUrl;
RemoteSandboxServiceClient.token = token;
}
constructor(sandboxId: string) {
super(sandboxId)
this.logger.info('RemoteSandboxServiceClient initialized', { sandboxId: this.sandboxId });
}
private async makeRequest<T extends z.ZodTypeAny>(
endpoint: string,
method: 'GET' | 'POST' | 'DELETE',
schema?: T,
body?: unknown,
resetPrevious: boolean = false
): Promise<z.infer<T>> {
const url = `${RemoteSandboxServiceClient.sandboxServiceUrl}${endpoint}`;
try {
const headers = new Headers();
headers.set('Content-Type', 'application/json');
headers.set('Authorization', `Bearer ${RemoteSandboxServiceClient.token}`);
headers.set('x-session-id', this.sandboxId);
if (resetPrevious) {
headers.set('x-container-action', 'reset');
}
const response = await runnerFetch(url, method, headers, body ? JSON.stringify(body) : undefined);
if (!response.ok) {
const errorText = await response.text();
this.logger.error('Runner service request failed', {
status: response.status,
statusText: response.statusText,
errorText,
url
});
return {
success: false,
error: errorText
} as z.infer<T>;
}
const responseData = await response.json();
if(!schema) return responseData as z.infer<T>;
const validation = schema.safeParse(responseData);
if (!validation.success) {
this.logger.error('Failed to validate response from runner service', validation.error.issues, { url, responseData });
return {
success: false,
error: "Failed to validate response"
} as z.infer<T>;
}
// this.logger.info('Response validated', { url });
return validation.data;
} catch (error) {
this.logger.error('Error making request to runner service', error, { url });
return {
success: false,
error: "Failed to validate response"
} as z.infer<T>;
}
}
/**
* Create a new runner instance.
*/
async createInstance(
options: InstanceCreationRequest
): Promise<BootstrapResponse> {
return this.makeRequest('/instances', 'POST', BootstrapResponseSchema, options);
}
/**
* Get details for a specific runner instance.
*/
async getInstanceDetails(instanceId: string): Promise<GetInstanceResponse> {
return this.makeRequest(`/instances/${instanceId}`, 'GET', GetInstanceResponseSchema);
}
/**
* Get status for a specific runner instance.
*/
async getInstanceStatus(instanceId: string): Promise<BootstrapStatusResponse> {
return this.makeRequest(`/instances/${instanceId}/status`, 'GET', BootstrapStatusResponseSchema);
}
/**
* Write files to a runner instance.
*/
async writeFiles(instanceId: string, files: WriteFilesRequest['files'], commitMessage?: string): Promise<WriteFilesResponse> {
const requestBody: WriteFilesRequest = { files, commitMessage };
return this.makeRequest(`/instances/${instanceId}/files`, 'POST', WriteFilesResponseSchema, requestBody);
}
/**
* Get specific files from a runner instance.
* @param instanceId The ID of the runner instance.
* @param filePaths An optional array of file paths to retrieve.
*/
async getFiles(instanceId: string, filePaths?: string[]): Promise<GetFilesResponse> {
// Build query params if filePaths are provided
const queryParams = filePaths && filePaths.length > 0 ? `?filePaths=${encodeURIComponent(JSON.stringify(filePaths))}` : '';
return this.makeRequest(`/instances/${instanceId}/files${queryParams}`, 'GET', GetFilesResponseSchema);
}
/**
* Execute commands in a runner instance.
*/
async executeCommands(instanceId: string, commands: string[], timeout?: number): Promise<ExecuteCommandsResponse> {
const requestBody: ExecuteCommandsRequest = { commands, timeout };
return this.makeRequest(`/instances/${instanceId}/commands`, 'POST', ExecuteCommandsResponseSchema, requestBody);
}
/**
* Get runtime errors from a runner instance.
*/
async getInstanceErrors(instanceId: string): Promise<RuntimeErrorResponse> {
return this.makeRequest(`/instances/${instanceId}/errors`, 'GET', RuntimeErrorResponseSchema);
}
async clearInstanceErrors(instanceId: string): Promise<ClearErrorsResponse> {
return this.makeRequest(`/instances/${instanceId}/errors`, 'DELETE', ClearErrorsResponseSchema);
}
/**
* Perform static code analysis on a runner instance to find potential issues.
* @param instanceId The ID of the runner instance
* @param files Optional comma-separated list of specific files to lint
*/
async runStaticAnalysisCode(instanceId: string, lintFiles?: string[]): Promise<StaticAnalysisResponse> {
const queryParams = lintFiles?.length ? `?files=${lintFiles.join(',')}` : '';
return this.makeRequest(`/instances/${instanceId}/analysis${queryParams}`, 'GET', StaticAnalysisResponseSchema);
}
/**
* Deploy a runner instance to Cloudflare Workers.
* @param instanceId The ID of the runner instance to deploy
* @param credentials Optional Cloudflare deployment credentials
*/
async deployToCloudflareWorkers(instanceId: string, target: DeploymentTarget = 'platform'): Promise<DeploymentResult> {
if (target === 'user') {
return {
success: false,
message: 'User-targeted deployments are not available with remote sandbox runner',
error: 'unsupported_target'
};
}
return this.makeRequest(`/instances/${instanceId}/deploy`, 'POST', DeploymentResultSchema);
}
/**
* Shutdown a runner instance.
*/
async shutdownInstance(instanceId: string): Promise<ShutdownResponse> {
return this.makeRequest(`/instances/${instanceId}`, 'DELETE', ShutdownResponseSchema);
}
/**
* Push instance files to existing GitHub repository
*/
async pushToGitHub(instanceId: string, request: GitHubPushRequest, files: FileOutputType[]): Promise<GitHubPushResponse> {
return this.makeRequest(`/instances/${instanceId}/github/push`, 'POST', GitHubPushResponseSchema, { request, files });
}
/**
* Initialize the client (no-op for remote client)
*/
async initialize(): Promise<void> {
// No initialization needed for remote client
this.logger.info('Remote sandbox service client initialized', { sandboxId: this.sandboxId });
}
/**
* List all instances across all sessions
*/
async listAllInstances(): Promise<ListInstancesResponse> {
return this.makeRequest('/instances', 'GET') as Promise<ListInstancesResponse>;
}
async updateProjectName(instanceId: string, projectName: string): Promise<boolean> {
return this.makeRequest(`/instances/${instanceId}/name`, 'POST', undefined, { projectName }) as Promise<boolean>;
}
/**
* Get logs from a runner instance
*/
async getLogs(instanceId: string, onlyRecent?: boolean, durationSeconds?: number): Promise<GetLogsResponse> {
const params = new URLSearchParams();
if (onlyRecent) params.append('reset', 'true');
if (durationSeconds) params.append('duration', durationSeconds.toString());
const queryString = params.toString() ? `?${params.toString()}` : '';
return this.makeRequest(`/instances/${instanceId}/logs${queryString}`, 'GET') as Promise<GetLogsResponse>;
}
// temp, debug
async writeFileLogs(logName: string, log: string) {
return this.makeRequest('/logs', 'POST', undefined, { logName, log });
}
}
RemoteSandboxServiceClient.init(env.SANDBOX_SERVICE_URL, env.SANDBOX_SERVICE_API_KEY);