feat: expand desktop computer-use APIs

This commit is contained in:
Nathan Flurry 2026-03-15 17:51:58 -07:00
parent 96dcc3d5f9
commit e638148345
43 changed files with 6359 additions and 493 deletions

View file

@ -23,6 +23,10 @@ import {
type SetSessionModeRequest,
} from "acp-http-client";
import type { SandboxAgentSpawnHandle, SandboxAgentSpawnOptions } from "./spawn.ts";
import {
DesktopStreamSession,
type DesktopStreamConnectOptions,
} from "./desktop-stream.ts";
import {
type AcpServerListResponse,
type AgentInfo,
@ -31,17 +35,26 @@ import {
type AgentListResponse,
type DesktopActionResponse,
type DesktopDisplayInfoResponse,
type DesktopKeyboardDownRequest,
type DesktopKeyboardPressRequest,
type DesktopKeyboardTypeRequest,
type DesktopMouseClickRequest,
type DesktopMouseDownRequest,
type DesktopMouseDragRequest,
type DesktopMouseMoveRequest,
type DesktopMousePositionResponse,
type DesktopMouseScrollRequest,
type DesktopMouseUpRequest,
type DesktopKeyboardUpRequest,
type DesktopRecordingInfo,
type DesktopRecordingListResponse,
type DesktopRecordingStartRequest,
type DesktopRegionScreenshotQuery,
type DesktopScreenshotQuery,
type DesktopStartRequest,
type DesktopStatusResponse,
type DesktopStreamStatusResponse,
type DesktopWindowListResponse,
type FsActionResponse,
type FsDeleteQuery,
type FsEntriesQuery,
@ -66,7 +79,9 @@ import {
type ProcessInfo,
type ProcessInputRequest,
type ProcessInputResponse,
type ProcessListQuery,
type ProcessListResponse,
type ProcessOwner,
type ProcessLogEntry,
type ProcessLogsQuery,
type ProcessLogsResponse,
@ -201,6 +216,7 @@ export interface ProcessTerminalConnectOptions extends ProcessTerminalWebSocketU
}
export type ProcessTerminalSessionOptions = ProcessTerminalConnectOptions;
export type DesktopStreamSessionOptions = DesktopStreamConnectOptions;
export class SandboxAgentError extends Error {
readonly status: number;
@ -1431,7 +1447,7 @@ export class SandboxAgent {
async takeDesktopScreenshot(query: DesktopScreenshotQuery = {}): Promise<Uint8Array> {
const response = await this.requestRaw("GET", `${API_PREFIX}/desktop/screenshot`, {
query,
accept: "image/png",
accept: "image/*",
});
const buffer = await response.arrayBuffer();
return new Uint8Array(buffer);
@ -1440,7 +1456,7 @@ export class SandboxAgent {
async takeDesktopRegionScreenshot(query: DesktopRegionScreenshotQuery): Promise<Uint8Array> {
const response = await this.requestRaw("GET", `${API_PREFIX}/desktop/screenshot/region`, {
query,
accept: "image/png",
accept: "image/*",
});
const buffer = await response.arrayBuffer();
return new Uint8Array(buffer);
@ -1462,6 +1478,18 @@ export class SandboxAgent {
});
}
async mouseDownDesktop(request: DesktopMouseDownRequest): Promise<DesktopMousePositionResponse> {
return this.requestJson("POST", `${API_PREFIX}/desktop/mouse/down`, {
body: request,
});
}
async mouseUpDesktop(request: DesktopMouseUpRequest): Promise<DesktopMousePositionResponse> {
return this.requestJson("POST", `${API_PREFIX}/desktop/mouse/up`, {
body: request,
});
}
async dragDesktopMouse(request: DesktopMouseDragRequest): Promise<DesktopMousePositionResponse> {
return this.requestJson("POST", `${API_PREFIX}/desktop/mouse/drag`, {
body: request,
@ -1486,6 +1514,66 @@ export class SandboxAgent {
});
}
async keyDownDesktop(request: DesktopKeyboardDownRequest): Promise<DesktopActionResponse> {
return this.requestJson("POST", `${API_PREFIX}/desktop/keyboard/down`, {
body: request,
});
}
async keyUpDesktop(request: DesktopKeyboardUpRequest): Promise<DesktopActionResponse> {
return this.requestJson("POST", `${API_PREFIX}/desktop/keyboard/up`, {
body: request,
});
}
async listDesktopWindows(): Promise<DesktopWindowListResponse> {
return this.requestJson("GET", `${API_PREFIX}/desktop/windows`);
}
async startDesktopRecording(
request: DesktopRecordingStartRequest = {},
): Promise<DesktopRecordingInfo> {
return this.requestJson("POST", `${API_PREFIX}/desktop/recording/start`, {
body: request,
});
}
async stopDesktopRecording(): Promise<DesktopRecordingInfo> {
return this.requestJson("POST", `${API_PREFIX}/desktop/recording/stop`);
}
async listDesktopRecordings(): Promise<DesktopRecordingListResponse> {
return this.requestJson("GET", `${API_PREFIX}/desktop/recordings`);
}
async getDesktopRecording(id: string): Promise<DesktopRecordingInfo> {
return this.requestJson("GET", `${API_PREFIX}/desktop/recordings/${encodeURIComponent(id)}`);
}
async downloadDesktopRecording(id: string): Promise<Uint8Array> {
const response = await this.requestRaw(
"GET",
`${API_PREFIX}/desktop/recordings/${encodeURIComponent(id)}/download`,
{
accept: "video/mp4",
},
);
const buffer = await response.arrayBuffer();
return new Uint8Array(buffer);
}
async deleteDesktopRecording(id: string): Promise<void> {
await this.requestRaw("DELETE", `${API_PREFIX}/desktop/recordings/${encodeURIComponent(id)}`);
}
async startDesktopStream(): Promise<DesktopStreamStatusResponse> {
return this.requestJson("POST", `${API_PREFIX}/desktop/stream/start`);
}
async stopDesktopStream(): Promise<DesktopStreamStatusResponse> {
return this.requestJson("POST", `${API_PREFIX}/desktop/stream/stop`);
}
async listAgents(options?: AgentQueryOptions): Promise<AgentListResponse> {
return this.requestJson("GET", `${API_PREFIX}/agents`, {
query: toAgentQuery(options),
@ -1618,8 +1706,10 @@ export class SandboxAgent {
});
}
async listProcesses(): Promise<ProcessListResponse> {
return this.requestJson("GET", `${API_PREFIX}/processes`);
async listProcesses(query?: ProcessListQuery): Promise<ProcessListResponse> {
return this.requestJson("GET", `${API_PREFIX}/processes`, {
query,
});
}
async getProcess(id: string): Promise<ProcessInfo> {
@ -1707,6 +1797,32 @@ export class SandboxAgent {
return new ProcessTerminalSession(this.connectProcessTerminalWebSocket(id, options));
}
buildDesktopStreamWebSocketUrl(options: ProcessTerminalWebSocketUrlOptions = {}): string {
return toWebSocketUrl(
this.buildUrl(`${API_PREFIX}/desktop/stream/ws`, {
access_token: options.accessToken ?? this.token,
}),
);
}
connectDesktopStreamWebSocket(options: DesktopStreamConnectOptions = {}): WebSocket {
const WebSocketCtor = options.WebSocket ?? globalThis.WebSocket;
if (!WebSocketCtor) {
throw new Error("WebSocket API is not available; provide a WebSocket implementation.");
}
return new WebSocketCtor(
this.buildDesktopStreamWebSocketUrl({
accessToken: options.accessToken,
}),
options.protocols,
);
}
connectDesktopStream(options: DesktopStreamSessionOptions = {}): DesktopStreamSession {
return new DesktopStreamSession(this.connectDesktopStreamWebSocket(options));
}
private async getLiveConnection(agent: string): Promise<LiveAcpConnection> {
await this.awaitHealthy();

View file

@ -0,0 +1,236 @@
import type { DesktopMouseButton } from "./types.ts";
const WS_READY_STATE_CONNECTING = 0;
const WS_READY_STATE_OPEN = 1;
const WS_READY_STATE_CLOSED = 3;
export interface DesktopStreamReadyStatus {
type: "ready";
width: number;
height: number;
}
export interface DesktopStreamErrorStatus {
type: "error";
message: string;
}
export type DesktopStreamStatusMessage = DesktopStreamReadyStatus | DesktopStreamErrorStatus;
export interface DesktopStreamConnectOptions {
accessToken?: string;
WebSocket?: typeof WebSocket;
protocols?: string | string[];
}
type DesktopStreamClientFrame =
| {
type: "moveMouse";
x: number;
y: number;
}
| {
type: "mouseDown" | "mouseUp";
x?: number;
y?: number;
button?: DesktopMouseButton;
}
| {
type: "scroll";
x: number;
y: number;
deltaX?: number;
deltaY?: number;
}
| {
type: "keyDown" | "keyUp";
key: string;
}
| {
type: "close";
};
export class DesktopStreamSession {
readonly socket: WebSocket;
readonly closed: Promise<void>;
private readonly readyListeners = new Set<(status: DesktopStreamReadyStatus) => void>();
private readonly frameListeners = new Set<(frame: Uint8Array) => void>();
private readonly errorListeners = new Set<(error: DesktopStreamErrorStatus | Error) => void>();
private readonly closeListeners = new Set<() => void>();
private closeSignalSent = false;
private closedResolve!: () => void;
constructor(socket: WebSocket) {
this.socket = socket;
this.socket.binaryType = "arraybuffer";
this.closed = new Promise<void>((resolve) => {
this.closedResolve = resolve;
});
this.socket.addEventListener("message", (event) => {
void this.handleMessage(event.data);
});
this.socket.addEventListener("error", () => {
this.emitError(new Error("Desktop stream websocket connection failed."));
});
this.socket.addEventListener("close", () => {
this.closedResolve();
for (const listener of this.closeListeners) {
listener();
}
});
}
onReady(listener: (status: DesktopStreamReadyStatus) => void): () => void {
this.readyListeners.add(listener);
return () => {
this.readyListeners.delete(listener);
};
}
onFrame(listener: (frame: Uint8Array) => void): () => void {
this.frameListeners.add(listener);
return () => {
this.frameListeners.delete(listener);
};
}
onError(listener: (error: DesktopStreamErrorStatus | Error) => void): () => void {
this.errorListeners.add(listener);
return () => {
this.errorListeners.delete(listener);
};
}
onClose(listener: () => void): () => void {
this.closeListeners.add(listener);
return () => {
this.closeListeners.delete(listener);
};
}
moveMouse(x: number, y: number): void {
this.sendFrame({ type: "moveMouse", x, y });
}
mouseDown(button?: DesktopMouseButton, x?: number, y?: number): void {
this.sendFrame({ type: "mouseDown", button, x, y });
}
mouseUp(button?: DesktopMouseButton, x?: number, y?: number): void {
this.sendFrame({ type: "mouseUp", button, x, y });
}
scroll(x: number, y: number, deltaX?: number, deltaY?: number): void {
this.sendFrame({ type: "scroll", x, y, deltaX, deltaY });
}
keyDown(key: string): void {
this.sendFrame({ type: "keyDown", key });
}
keyUp(key: string): void {
this.sendFrame({ type: "keyUp", key });
}
close(): void {
if (this.socket.readyState === WS_READY_STATE_CONNECTING) {
this.socket.addEventListener(
"open",
() => {
this.close();
},
{ once: true },
);
return;
}
if (this.socket.readyState === WS_READY_STATE_OPEN) {
if (!this.closeSignalSent) {
this.closeSignalSent = true;
this.sendFrame({ type: "close" });
}
this.socket.close();
return;
}
if (this.socket.readyState !== WS_READY_STATE_CLOSED) {
this.socket.close();
}
}
private async handleMessage(data: unknown): Promise<void> {
try {
if (typeof data === "string") {
const frame = parseStatusFrame(data);
if (!frame) {
this.emitError(new Error("Received invalid desktop stream control frame."));
return;
}
if (frame.type === "ready") {
for (const listener of this.readyListeners) {
listener(frame);
}
return;
}
this.emitError(frame);
return;
}
const bytes = await decodeBinaryFrame(data);
for (const listener of this.frameListeners) {
listener(bytes);
}
} catch (error) {
this.emitError(error instanceof Error ? error : new Error(String(error)));
}
}
private sendFrame(frame: DesktopStreamClientFrame): void {
if (this.socket.readyState !== WS_READY_STATE_OPEN) {
return;
}
this.socket.send(JSON.stringify(frame));
}
private emitError(error: DesktopStreamErrorStatus | Error): void {
for (const listener of this.errorListeners) {
listener(error);
}
}
}
function parseStatusFrame(payload: string): DesktopStreamStatusMessage | null {
const value = JSON.parse(payload) as Record<string, unknown>;
if (value.type === "ready" && typeof value.width === "number" && typeof value.height === "number") {
return {
type: "ready",
width: value.width,
height: value.height,
};
}
if (value.type === "error" && typeof value.message === "string") {
return {
type: "error",
message: value.message,
};
}
return null;
}
async function decodeBinaryFrame(data: unknown): Promise<Uint8Array> {
if (data instanceof ArrayBuffer) {
return new Uint8Array(data);
}
if (ArrayBuffer.isView(data)) {
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
}
if (typeof Blob !== "undefined" && data instanceof Blob) {
return new Uint8Array(await data.arrayBuffer());
}
throw new Error("Unsupported desktop stream binary frame type.");
}

View file

@ -3,6 +3,7 @@
* Do not make direct changes to the file.
*/
export interface paths {
"/v1/acp": {
get: operations["get_v1_acp_servers"];
@ -39,6 +40,14 @@ export interface paths {
*/
get: operations["get_v1_desktop_display_info"];
};
"/v1/desktop/keyboard/down": {
/**
* Press and hold a desktop keyboard key.
* @description Performs a health-gated `xdotool keydown` operation against the managed
* desktop.
*/
post: operations["post_v1_desktop_keyboard_down"];
};
"/v1/desktop/keyboard/press": {
/**
* Press a desktop keyboard shortcut.
@ -55,6 +64,14 @@ export interface paths {
*/
post: operations["post_v1_desktop_keyboard_type"];
};
"/v1/desktop/keyboard/up": {
/**
* Release a desktop keyboard key.
* @description Performs a health-gated `xdotool keyup` operation against the managed
* desktop.
*/
post: operations["post_v1_desktop_keyboard_up"];
};
"/v1/desktop/mouse/click": {
/**
* Click on the desktop.
@ -63,6 +80,14 @@ export interface paths {
*/
post: operations["post_v1_desktop_mouse_click"];
};
"/v1/desktop/mouse/down": {
/**
* Press and hold a desktop mouse button.
* @description Performs a health-gated optional pointer move followed by `xdotool mousedown`
* and returns the resulting mouse position.
*/
post: operations["post_v1_desktop_mouse_down"];
};
"/v1/desktop/mouse/drag": {
/**
* Drag the desktop mouse.
@ -94,11 +119,61 @@ export interface paths {
*/
post: operations["post_v1_desktop_mouse_scroll"];
};
"/v1/desktop/mouse/up": {
/**
* Release a desktop mouse button.
* @description Performs a health-gated optional pointer move followed by `xdotool mouseup`
* and returns the resulting mouse position.
*/
post: operations["post_v1_desktop_mouse_up"];
};
"/v1/desktop/recording/start": {
/**
* Start desktop recording.
* @description Starts an ffmpeg x11grab recording against the managed desktop and returns
* the created recording metadata.
*/
post: operations["post_v1_desktop_recording_start"];
};
"/v1/desktop/recording/stop": {
/**
* Stop desktop recording.
* @description Stops the active desktop recording and returns the finalized recording
* metadata.
*/
post: operations["post_v1_desktop_recording_stop"];
};
"/v1/desktop/recordings": {
/**
* List desktop recordings.
* @description Returns the current desktop recording catalog.
*/
get: operations["get_v1_desktop_recordings"];
};
"/v1/desktop/recordings/{id}": {
/**
* Get desktop recording metadata.
* @description Returns metadata for a single desktop recording.
*/
get: operations["get_v1_desktop_recording"];
/**
* Delete a desktop recording.
* @description Removes a completed desktop recording and its file from disk.
*/
delete: operations["delete_v1_desktop_recording"];
};
"/v1/desktop/recordings/{id}/download": {
/**
* Download a desktop recording.
* @description Serves the recorded MP4 bytes for a completed desktop recording.
*/
get: operations["get_v1_desktop_recording_download"];
};
"/v1/desktop/screenshot": {
/**
* Capture a full desktop screenshot.
* @description Performs a health-gated full-frame screenshot of the managed desktop and
* returns PNG bytes.
* returns the requested image bytes.
*/
get: operations["get_v1_desktop_screenshot"];
};
@ -106,7 +181,7 @@ export interface paths {
/**
* Capture a desktop screenshot region.
* @description Performs a health-gated screenshot crop against the managed desktop and
* returns the requested PNG region bytes.
* returns the requested region image bytes.
*/
get: operations["get_v1_desktop_screenshot_region"];
};
@ -134,6 +209,36 @@ export interface paths {
*/
post: operations["post_v1_desktop_stop"];
};
"/v1/desktop/stream/start": {
/**
* Start desktop streaming.
* @description Enables desktop websocket streaming for the managed desktop.
*/
post: operations["post_v1_desktop_stream_start"];
};
"/v1/desktop/stream/stop": {
/**
* Stop desktop streaming.
* @description Disables desktop websocket streaming for the managed desktop.
*/
post: operations["post_v1_desktop_stream_stop"];
};
"/v1/desktop/stream/ws": {
/**
* Open a desktop websocket streaming session.
* @description Upgrades the connection to a websocket that streams JPEG desktop frames and
* accepts mouse and keyboard control frames.
*/
get: operations["get_v1_desktop_stream_ws"];
};
"/v1/desktop/windows": {
/**
* List visible desktop windows.
* @description Performs a health-gated visible-window enumeration against the managed
* desktop and returns the current window metadata.
*/
get: operations["get_v1_desktop_windows"];
};
"/v1/fs/entries": {
get: operations["get_v1_fs_entries"];
};
@ -347,14 +452,27 @@ export interface components {
code: string;
message: string;
};
DesktopKeyModifiers: {
alt?: boolean | null;
cmd?: boolean | null;
ctrl?: boolean | null;
shift?: boolean | null;
};
DesktopKeyboardDownRequest: {
key: string;
};
DesktopKeyboardPressRequest: {
key: string;
modifiers?: components["schemas"]["DesktopKeyModifiers"] | null;
};
DesktopKeyboardTypeRequest: {
/** Format: int32 */
delayMs?: number | null;
text: string;
};
DesktopKeyboardUpRequest: {
key: string;
};
/** @enum {string} */
DesktopMouseButton: "left" | "middle" | "right";
DesktopMouseClickRequest: {
@ -366,6 +484,13 @@ export interface components {
/** Format: int32 */
y: number;
};
DesktopMouseDownRequest: {
button?: components["schemas"]["DesktopMouseButton"] | null;
/** Format: int32 */
x?: number | null;
/** Format: int32 */
y?: number | null;
};
DesktopMouseDragRequest: {
button?: components["schemas"]["DesktopMouseButton"] | null;
/** Format: int32 */
@ -402,6 +527,13 @@ export interface components {
/** Format: int32 */
y: number;
};
DesktopMouseUpRequest: {
button?: components["schemas"]["DesktopMouseButton"] | null;
/** Format: int32 */
x?: number | null;
/** Format: int32 */
y?: number | null;
};
DesktopProcessInfo: {
logPath?: string | null;
name: string;
@ -409,10 +541,34 @@ export interface components {
pid?: number | null;
running: boolean;
};
DesktopRecordingInfo: {
/** Format: int64 */
bytes: number;
endedAt?: string | null;
fileName: string;
id: string;
processId?: string | null;
startedAt: string;
status: components["schemas"]["DesktopRecordingStatus"];
};
DesktopRecordingListResponse: {
recordings: components["schemas"]["DesktopRecordingInfo"][];
};
DesktopRecordingStartRequest: {
/** Format: int32 */
fps?: number | null;
};
/** @enum {string} */
DesktopRecordingStatus: "recording" | "completed" | "failed";
DesktopRegionScreenshotQuery: {
format?: components["schemas"]["DesktopScreenshotFormat"] | null;
/** Format: int32 */
height: number;
/** Format: int32 */
quality?: number | null;
/** Format: float */
scale?: number | null;
/** Format: int32 */
width: number;
/** Format: int32 */
x: number;
@ -427,7 +583,15 @@ export interface components {
/** Format: int32 */
width: number;
};
DesktopScreenshotQuery: Record<string, never>;
/** @enum {string} */
DesktopScreenshotFormat: "png" | "jpeg" | "webp";
DesktopScreenshotQuery: {
format?: components["schemas"]["DesktopScreenshotFormat"] | null;
/** Format: int32 */
quality?: number | null;
/** Format: float */
scale?: number | null;
};
DesktopStartRequest: {
/** Format: int32 */
dpi?: number | null;
@ -449,24 +613,27 @@ export interface components {
startedAt?: string | null;
state: components["schemas"]["DesktopState"];
};
DesktopStreamStatusResponse: {
active: boolean;
};
DesktopWindowInfo: {
/** Format: int32 */
height: number;
id: string;
isActive: boolean;
title: string;
/** Format: int32 */
width: number;
/** Format: int32 */
x: number;
/** Format: int32 */
y: number;
};
DesktopWindowListResponse: {
windows: components["schemas"]["DesktopWindowInfo"][];
};
/** @enum {string} */
ErrorType:
| "invalid_request"
| "conflict"
| "unsupported_agent"
| "agent_not_installed"
| "install_failed"
| "agent_process_exited"
| "token_invalid"
| "permission_denied"
| "not_acceptable"
| "unsupported_media_type"
| "not_found"
| "session_not_found"
| "session_already_exists"
| "mode_not_supported"
| "stream_error"
| "timeout";
ErrorType: "invalid_request" | "conflict" | "unsupported_agent" | "agent_not_installed" | "install_failed" | "agent_process_exited" | "token_invalid" | "permission_denied" | "not_acceptable" | "unsupported_media_type" | "not_found" | "session_not_found" | "session_already_exists" | "mode_not_supported" | "stream_error" | "timeout";
FsActionResponse: {
path: string;
};
@ -525,37 +692,35 @@ export interface components {
directory: string;
mcpName: string;
};
McpServerConfig:
| {
args?: string[];
command: string;
cwd?: string | null;
enabled?: boolean | null;
env?: {
[key: string]: string;
} | null;
/** Format: int64 */
timeoutMs?: number | null;
/** @enum {string} */
type: "local";
}
| {
bearerTokenEnvVar?: string | null;
enabled?: boolean | null;
envHeaders?: {
[key: string]: string;
} | null;
headers?: {
[key: string]: string;
} | null;
oauth?: Record<string, unknown> | null | null;
/** Format: int64 */
timeoutMs?: number | null;
transport?: string | null;
/** @enum {string} */
type: "remote";
url: string;
};
McpServerConfig: ({
args?: string[];
command: string;
cwd?: string | null;
enabled?: boolean | null;
env?: {
[key: string]: string;
} | null;
/** Format: int64 */
timeoutMs?: number | null;
/** @enum {string} */
type: "local";
}) | ({
bearerTokenEnvVar?: string | null;
enabled?: boolean | null;
envHeaders?: {
[key: string]: string;
} | null;
headers?: {
[key: string]: string;
} | null;
oauth?: Record<string, unknown> | null | null;
/** Format: int64 */
timeoutMs?: number | null;
transport?: string | null;
/** @enum {string} */
type: "remote";
url: string;
});
ProblemDetails: {
detail?: string | null;
instance?: string | null;
@ -597,6 +762,7 @@ export interface components {
exitedAtMs?: number | null;
id: string;
interactive: boolean;
owner: components["schemas"]["ProcessOwner"];
/** Format: int32 */
pid?: number | null;
status: components["schemas"]["ProcessState"];
@ -609,6 +775,9 @@ export interface components {
ProcessInputResponse: {
bytesWritten: number;
};
ProcessListQuery: {
owner?: components["schemas"]["ProcessOwner"] | null;
};
ProcessListResponse: {
processes: components["schemas"]["ProcessInfo"][];
};
@ -635,6 +804,8 @@ export interface components {
};
/** @enum {string} */
ProcessLogsStream: "stdout" | "stderr" | "combined" | "pty";
/** @enum {string} */
ProcessOwner: "user" | "desktop" | "system";
ProcessRunRequest: {
args?: string[];
command: string;
@ -709,6 +880,7 @@ export type $defs = Record<string, never>;
export type external = Record<string, never>;
export interface operations {
get_v1_acp_servers: {
responses: {
/** @description Active ACP server instances */
@ -1070,6 +1242,44 @@ export interface operations {
};
};
};
/**
* Press and hold a desktop keyboard key.
* @description Performs a health-gated `xdotool keydown` operation against the managed
* desktop.
*/
post_v1_desktop_keyboard_down: {
requestBody: {
content: {
"application/json": components["schemas"]["DesktopKeyboardDownRequest"];
};
};
responses: {
/** @description Desktop keyboard action result */
200: {
content: {
"application/json": components["schemas"]["DesktopActionResponse"];
};
};
/** @description Invalid keyboard down request */
400: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Desktop runtime is not ready */
409: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Desktop runtime health or input failed */
502: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
/**
* Press a desktop keyboard shortcut.
* @description Performs a health-gated `xdotool key` operation against the managed
@ -1101,7 +1311,7 @@ export interface operations {
};
};
/** @description Desktop runtime health or input failed */
503: {
502: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
@ -1139,7 +1349,45 @@ export interface operations {
};
};
/** @description Desktop runtime health or input failed */
503: {
502: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
/**
* Release a desktop keyboard key.
* @description Performs a health-gated `xdotool keyup` operation against the managed
* desktop.
*/
post_v1_desktop_keyboard_up: {
requestBody: {
content: {
"application/json": components["schemas"]["DesktopKeyboardUpRequest"];
};
};
responses: {
/** @description Desktop keyboard action result */
200: {
content: {
"application/json": components["schemas"]["DesktopActionResponse"];
};
};
/** @description Invalid keyboard up request */
400: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Desktop runtime is not ready */
409: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Desktop runtime health or input failed */
502: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
@ -1177,7 +1425,45 @@ export interface operations {
};
};
/** @description Desktop runtime health or input failed */
503: {
502: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
/**
* Press and hold a desktop mouse button.
* @description Performs a health-gated optional pointer move followed by `xdotool mousedown`
* and returns the resulting mouse position.
*/
post_v1_desktop_mouse_down: {
requestBody: {
content: {
"application/json": components["schemas"]["DesktopMouseDownRequest"];
};
};
responses: {
/** @description Desktop mouse position after button press */
200: {
content: {
"application/json": components["schemas"]["DesktopMousePositionResponse"];
};
};
/** @description Invalid mouse down request */
400: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Desktop runtime is not ready */
409: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Desktop runtime health or input failed */
502: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
@ -1215,7 +1501,7 @@ export interface operations {
};
};
/** @description Desktop runtime health or input failed */
503: {
502: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
@ -1253,7 +1539,7 @@ export interface operations {
};
};
/** @description Desktop runtime health or input failed */
503: {
502: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
@ -1279,7 +1565,7 @@ export interface operations {
};
};
/** @description Desktop runtime health or input check failed */
503: {
502: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
@ -1317,7 +1603,204 @@ export interface operations {
};
};
/** @description Desktop runtime health or input failed */
503: {
502: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
/**
* Release a desktop mouse button.
* @description Performs a health-gated optional pointer move followed by `xdotool mouseup`
* and returns the resulting mouse position.
*/
post_v1_desktop_mouse_up: {
requestBody: {
content: {
"application/json": components["schemas"]["DesktopMouseUpRequest"];
};
};
responses: {
/** @description Desktop mouse position after button release */
200: {
content: {
"application/json": components["schemas"]["DesktopMousePositionResponse"];
};
};
/** @description Invalid mouse up request */
400: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Desktop runtime is not ready */
409: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Desktop runtime health or input failed */
502: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
/**
* Start desktop recording.
* @description Starts an ffmpeg x11grab recording against the managed desktop and returns
* the created recording metadata.
*/
post_v1_desktop_recording_start: {
requestBody: {
content: {
"application/json": components["schemas"]["DesktopRecordingStartRequest"];
};
};
responses: {
/** @description Desktop recording started */
200: {
content: {
"application/json": components["schemas"]["DesktopRecordingInfo"];
};
};
/** @description Desktop runtime is not ready or a recording is already active */
409: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Desktop recording failed */
502: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
/**
* Stop desktop recording.
* @description Stops the active desktop recording and returns the finalized recording
* metadata.
*/
post_v1_desktop_recording_stop: {
responses: {
/** @description Desktop recording stopped */
200: {
content: {
"application/json": components["schemas"]["DesktopRecordingInfo"];
};
};
/** @description No active desktop recording */
409: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Desktop recording stop failed */
502: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
/**
* List desktop recordings.
* @description Returns the current desktop recording catalog.
*/
get_v1_desktop_recordings: {
responses: {
/** @description Desktop recordings */
200: {
content: {
"application/json": components["schemas"]["DesktopRecordingListResponse"];
};
};
/** @description Desktop recordings query failed */
502: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
/**
* Get desktop recording metadata.
* @description Returns metadata for a single desktop recording.
*/
get_v1_desktop_recording: {
parameters: {
path: {
/** @description Desktop recording ID */
id: string;
};
};
responses: {
/** @description Desktop recording metadata */
200: {
content: {
"application/json": components["schemas"]["DesktopRecordingInfo"];
};
};
/** @description Unknown desktop recording */
404: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
/**
* Delete a desktop recording.
* @description Removes a completed desktop recording and its file from disk.
*/
delete_v1_desktop_recording: {
parameters: {
path: {
/** @description Desktop recording ID */
id: string;
};
};
responses: {
/** @description Desktop recording deleted */
204: {
content: never;
};
/** @description Unknown desktop recording */
404: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Desktop recording is still active */
409: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
/**
* Download a desktop recording.
* @description Serves the recorded MP4 bytes for a completed desktop recording.
*/
get_v1_desktop_recording_download: {
parameters: {
path: {
/** @description Desktop recording ID */
id: string;
};
};
responses: {
/** @description Desktop recording as MP4 bytes */
200: {
content: never;
};
/** @description Unknown desktop recording */
404: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
@ -1327,14 +1810,27 @@ export interface operations {
/**
* Capture a full desktop screenshot.
* @description Performs a health-gated full-frame screenshot of the managed desktop and
* returns PNG bytes.
* returns the requested image bytes.
*/
get_v1_desktop_screenshot: {
parameters: {
query?: {
format?: components["schemas"]["DesktopScreenshotFormat"] | null;
quality?: number | null;
scale?: number | null;
};
};
responses: {
/** @description Desktop screenshot as PNG bytes */
/** @description Desktop screenshot as image bytes */
200: {
content: never;
};
/** @description Invalid screenshot query */
400: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Desktop runtime is not ready */
409: {
content: {
@ -1342,7 +1838,7 @@ export interface operations {
};
};
/** @description Desktop runtime health or screenshot capture failed */
503: {
502: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
@ -1352,23 +1848,22 @@ export interface operations {
/**
* Capture a desktop screenshot region.
* @description Performs a health-gated screenshot crop against the managed desktop and
* returns the requested PNG region bytes.
* returns the requested region image bytes.
*/
get_v1_desktop_screenshot_region: {
parameters: {
query: {
/** @description Region x coordinate */
x: number;
/** @description Region y coordinate */
y: number;
/** @description Region width */
width: number;
/** @description Region height */
height: number;
format?: components["schemas"]["DesktopScreenshotFormat"] | null;
quality?: number | null;
scale?: number | null;
};
};
responses: {
/** @description Desktop screenshot region as PNG bytes */
/** @description Desktop screenshot region as image bytes */
200: {
content: never;
};
@ -1385,7 +1880,7 @@ export interface operations {
};
};
/** @description Desktop runtime health or screenshot capture failed */
503: {
502: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
@ -1478,6 +1973,92 @@ export interface operations {
};
};
};
/**
* Start desktop streaming.
* @description Enables desktop websocket streaming for the managed desktop.
*/
post_v1_desktop_stream_start: {
responses: {
/** @description Desktop streaming started */
200: {
content: {
"application/json": components["schemas"]["DesktopStreamStatusResponse"];
};
};
};
};
/**
* Stop desktop streaming.
* @description Disables desktop websocket streaming for the managed desktop.
*/
post_v1_desktop_stream_stop: {
responses: {
/** @description Desktop streaming stopped */
200: {
content: {
"application/json": components["schemas"]["DesktopStreamStatusResponse"];
};
};
};
};
/**
* Open a desktop websocket streaming session.
* @description Upgrades the connection to a websocket that streams JPEG desktop frames and
* accepts mouse and keyboard control frames.
*/
get_v1_desktop_stream_ws: {
parameters: {
query?: {
/** @description Bearer token alternative for WS auth */
access_token?: string | null;
};
};
responses: {
/** @description WebSocket upgraded */
101: {
content: never;
};
/** @description Desktop runtime or streaming session is not ready */
409: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Desktop stream failed */
502: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
/**
* List visible desktop windows.
* @description Performs a health-gated visible-window enumeration against the managed
* desktop and returns the current window metadata.
*/
get_v1_desktop_windows: {
responses: {
/** @description Visible desktop windows */
200: {
content: {
"application/json": components["schemas"]["DesktopWindowListResponse"];
};
};
/** @description Desktop runtime is not ready */
409: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
/** @description Desktop runtime health or window query failed */
503: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
get_v1_fs_entries: {
parameters: {
query?: {
@ -1633,6 +2214,11 @@ export interface operations {
* by the runtime, sorted by process ID.
*/
get_v1_processes: {
parameters: {
query?: {
owner?: components["schemas"]["ProcessOwner"] | null;
};
};
responses: {
/** @description List processes */
200: {

View file

@ -13,10 +13,18 @@ export {
export { AcpRpcError } from "acp-http-client";
export { buildInspectorUrl } from "./inspector.ts";
export { DesktopStreamSession } from "./desktop-stream.ts";
export type {
DesktopStreamConnectOptions,
DesktopStreamErrorStatus,
DesktopStreamReadyStatus,
DesktopStreamStatusMessage,
} from "./desktop-stream.ts";
export type {
SandboxAgentHealthWaitOptions,
AgentQueryOptions,
DesktopStreamSessionOptions,
ProcessLogFollowQuery,
ProcessLogListener,
ProcessLogSubscription,
@ -51,21 +59,34 @@ export type {
DesktopActionResponse,
DesktopDisplayInfoResponse,
DesktopErrorInfo,
DesktopKeyboardDownRequest,
DesktopKeyboardUpRequest,
DesktopKeyModifiers,
DesktopKeyboardPressRequest,
DesktopKeyboardTypeRequest,
DesktopMouseButton,
DesktopMouseClickRequest,
DesktopMouseDownRequest,
DesktopMouseDragRequest,
DesktopMouseMoveRequest,
DesktopMousePositionResponse,
DesktopMouseScrollRequest,
DesktopMouseUpRequest,
DesktopProcessInfo,
DesktopRecordingInfo,
DesktopRecordingListResponse,
DesktopRecordingStartRequest,
DesktopRecordingStatus,
DesktopRegionScreenshotQuery,
DesktopResolution,
DesktopScreenshotFormat,
DesktopScreenshotQuery,
DesktopStartRequest,
DesktopState,
DesktopStatusResponse,
DesktopStreamStatusResponse,
DesktopWindowInfo,
DesktopWindowListResponse,
FsActionResponse,
FsDeleteQuery,
FsEntriesQuery,
@ -90,10 +111,12 @@ export type {
ProcessInfo,
ProcessInputRequest,
ProcessInputResponse,
ProcessListQuery,
ProcessListResponse,
ProcessLogEntry,
ProcessLogsQuery,
ProcessLogsResponse,
ProcessOwner,
ProcessLogsStream,
ProcessRunRequest,
ProcessRunResponse,

View file

@ -10,6 +10,7 @@ export type DesktopErrorInfo = components["schemas"]["DesktopErrorInfo"];
export type DesktopProcessInfo = components["schemas"]["DesktopProcessInfo"];
export type DesktopStatusResponse = JsonResponse<operations["get_v1_desktop_status"], 200>;
export type DesktopStartRequest = JsonRequestBody<operations["post_v1_desktop_start"]>;
export type DesktopScreenshotFormat = components["schemas"]["DesktopScreenshotFormat"];
export type DesktopScreenshotQuery =
QueryParams<operations["get_v1_desktop_screenshot"]> extends never
? Record<string, never>
@ -19,12 +20,24 @@ export type DesktopMousePositionResponse = JsonResponse<operations["get_v1_deskt
export type DesktopMouseButton = components["schemas"]["DesktopMouseButton"];
export type DesktopMouseMoveRequest = JsonRequestBody<operations["post_v1_desktop_mouse_move"]>;
export type DesktopMouseClickRequest = JsonRequestBody<operations["post_v1_desktop_mouse_click"]>;
export type DesktopMouseDownRequest = JsonRequestBody<operations["post_v1_desktop_mouse_down"]>;
export type DesktopMouseUpRequest = JsonRequestBody<operations["post_v1_desktop_mouse_up"]>;
export type DesktopMouseDragRequest = JsonRequestBody<operations["post_v1_desktop_mouse_drag"]>;
export type DesktopMouseScrollRequest = JsonRequestBody<operations["post_v1_desktop_mouse_scroll"]>;
export type DesktopKeyboardTypeRequest = JsonRequestBody<operations["post_v1_desktop_keyboard_type"]>;
export type DesktopKeyModifiers = components["schemas"]["DesktopKeyModifiers"];
export type DesktopKeyboardPressRequest = JsonRequestBody<operations["post_v1_desktop_keyboard_press"]>;
export type DesktopKeyboardDownRequest = JsonRequestBody<operations["post_v1_desktop_keyboard_down"]>;
export type DesktopKeyboardUpRequest = JsonRequestBody<operations["post_v1_desktop_keyboard_up"]>;
export type DesktopActionResponse = JsonResponse<operations["post_v1_desktop_keyboard_type"], 200>;
export type DesktopDisplayInfoResponse = JsonResponse<operations["get_v1_desktop_display_info"], 200>;
export type DesktopWindowInfo = components["schemas"]["DesktopWindowInfo"];
export type DesktopWindowListResponse = JsonResponse<operations["get_v1_desktop_windows"], 200>;
export type DesktopRecordingStartRequest = JsonRequestBody<operations["post_v1_desktop_recording_start"]>;
export type DesktopRecordingStatus = components["schemas"]["DesktopRecordingStatus"];
export type DesktopRecordingInfo = JsonResponse<operations["post_v1_desktop_recording_start"], 200>;
export type DesktopRecordingListResponse = JsonResponse<operations["get_v1_desktop_recordings"], 200>;
export type DesktopStreamStatusResponse = JsonResponse<operations["post_v1_desktop_stream_start"], 200>;
export type AgentListResponse = JsonResponse<operations["get_v1_agents"], 200>;
export type AgentInfo = components["schemas"]["AgentInfo"];
export type AgentQuery = QueryParams<operations["get_v1_agents"]>;
@ -58,11 +71,13 @@ export type ProcessCreateRequest = JsonRequestBody<operations["post_v1_processes
export type ProcessInfo = components["schemas"]["ProcessInfo"];
export type ProcessInputRequest = JsonRequestBody<operations["post_v1_process_input"]>;
export type ProcessInputResponse = JsonResponse<operations["post_v1_process_input"], 200>;
export type ProcessListQuery = QueryParams<operations["get_v1_processes"]>;
export type ProcessListResponse = JsonResponse<operations["get_v1_processes"], 200>;
export type ProcessLogEntry = components["schemas"]["ProcessLogEntry"];
export type ProcessLogsQuery = QueryParams<operations["get_v1_process_logs"]>;
export type ProcessLogsResponse = JsonResponse<operations["get_v1_process_logs"], 200>;
export type ProcessLogsStream = components["schemas"]["ProcessLogsStream"];
export type ProcessOwner = components["schemas"]["ProcessOwner"];
export type ProcessRunRequest = JsonRequestBody<operations["post_v1_processes_run"]>;
export type ProcessRunResponse = JsonResponse<operations["post_v1_processes_run"], 200>;
export type ProcessSignalQuery = QueryParams<operations["post_v1_process_stop"]>;