diff --git a/docs/openapi.json b/docs/openapi.json index 11ff9566..1104d20b 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -1,26 +1,32 @@ { "openapi": "3.0.3", "info": { - "title": "sandbox-agent", - "description": "Universal API for automatic coding agents in sandboxes. Supports Claude Code, Codex, OpenCode, and Amp.", + "title": "Sandbox Agent", + "description": "HTTP API for Sandbox Agent, a universal runtime for AI coding agents. Provides sandbox filesystem, process, desktop, and configuration management alongside an ACP proxy for agent communication.", "contact": { - "name": "Rivet Gaming, LLC", - "email": "developer@rivet.gg" + "name": "Sandbox Agent", + "url": "https://sandboxagent.dev" }, "license": { - "name": "Apache-2.0" + "name": "Apache-2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0" }, "version": "0.5.0-rc.1" }, "servers": [ { - "url": "http://localhost:2468" + "url": "http://localhost:2468", + "description": "Local development server" } ], "paths": { "/v1/acp": { "get": { - "tags": ["v1"], + "tags": [ + "acp" + ], + "summary": "List active ACP server instances.", + "description": "Returns all currently running ACP proxy server instances with their\nbound agent and creation timestamp.", "operationId": "get_v1_acp_servers", "responses": { "200": { @@ -38,7 +44,11 @@ }, "/v1/acp/{server_id}": { "get": { - "tags": ["v1"], + "tags": [ + "acp" + ], + "summary": "Open an SSE stream for an ACP server.", + "description": "Returns a Server-Sent Events stream carrying JSON-RPC envelopes from\nthe agent process. Requires `Accept: text/event-stream`.", "operationId": "get_v1_acp", "parameters": [ { @@ -53,7 +63,14 @@ ], "responses": { "200": { - "description": "SSE stream of ACP envelopes" + "description": "SSE stream of ACP envelopes", + "content": { + "text/event-stream": { + "schema": { + "type": "string" + } + } + } }, "400": { "description": "Invalid request", @@ -88,7 +105,11 @@ } }, "post": { - "tags": ["v1"], + "tags": [ + "acp" + ], + "summary": "Send a JSON-RPC envelope to an ACP server.", + "description": "Posts a JSON-RPC request or notification to the specified ACP server\ninstance. The first POST to a new server_id must include the `agent`\nquery parameter to bind the server to an agent.", "operationId": "post_v1_acp", "parameters": [ { @@ -198,7 +219,11 @@ } }, "delete": { - "tags": ["v1"], + "tags": [ + "acp" + ], + "summary": "Close an ACP server instance.", + "description": "Terminates the ACP proxy server and its associated agent process.", "operationId": "delete_v1_acp", "parameters": [ { @@ -220,7 +245,11 @@ }, "/v1/agents": { "get": { - "tags": ["v1"], + "tags": [ + "agents" + ], + "summary": "List available agents.", + "description": "Returns all known agents with their installation status, credential\navailability, and optionally their version and configuration options.", "operationId": "get_v1_agents", "parameters": [ { @@ -246,7 +275,7 @@ ], "responses": { "200": { - "description": "List of v1 agents", + "description": "List of agents", "content": { "application/json": { "schema": { @@ -270,7 +299,11 @@ }, "/v1/agents/{agent}": { "get": { - "tags": ["v1"], + "tags": [ + "agents" + ], + "summary": "Get a single agent.", + "description": "Returns detailed information for one agent, including installation status,\ncredential availability, and optionally version and configuration options.", "operationId": "get_v1_agent", "parameters": [ { @@ -339,7 +372,11 @@ }, "/v1/agents/{agent}/install": { "post": { - "tags": ["v1"], + "tags": [ + "agents" + ], + "summary": "Install or reinstall an agent.", + "description": "Downloads and installs the agent binary and its process wrapper. Returns\nthe list of installed artifacts and whether the agent was already present.", "operationId": "post_v1_agent_install", "parameters": [ { @@ -398,7 +435,11 @@ }, "/v1/config/mcp": { "get": { - "tags": ["v1"], + "tags": [ + "config" + ], + "summary": "Get an MCP server configuration entry.", + "description": "Reads a single named MCP server entry from the project-scoped\nconfiguration file in the given directory.", "operationId": "get_v1_config_mcp", "parameters": [ { @@ -422,7 +463,7 @@ ], "responses": { "200": { - "description": "MCP entry", + "description": "MCP server configuration entry", "content": { "application/json": { "schema": { @@ -444,7 +485,11 @@ } }, "put": { - "tags": ["v1"], + "tags": [ + "config" + ], + "summary": "Create or update an MCP server configuration entry.", + "description": "Writes a named MCP server entry into the project-scoped configuration\nfile, creating the file and parent directories as needed.", "operationId": "put_v1_config_mcp", "parameters": [ { @@ -483,7 +528,11 @@ } }, "delete": { - "tags": ["v1"], + "tags": [ + "config" + ], + "summary": "Delete an MCP server configuration entry.", + "description": "Removes a named MCP server entry from the project-scoped configuration\nfile.", "operationId": "delete_v1_config_mcp", "parameters": [ { @@ -514,7 +563,11 @@ }, "/v1/config/skills": { "get": { - "tags": ["v1"], + "tags": [ + "config" + ], + "summary": "Get a skills configuration entry.", + "description": "Reads a single named skills entry from the project-scoped configuration\nfile in the given directory.", "operationId": "get_v1_config_skills", "parameters": [ { @@ -538,7 +591,7 @@ ], "responses": { "200": { - "description": "Skills entry", + "description": "Skills configuration entry", "content": { "application/json": { "schema": { @@ -560,7 +613,11 @@ } }, "put": { - "tags": ["v1"], + "tags": [ + "config" + ], + "summary": "Create or update a skills configuration entry.", + "description": "Writes a named skills entry into the project-scoped configuration file,\ncreating the file and parent directories as needed.", "operationId": "put_v1_config_skills", "parameters": [ { @@ -599,7 +656,11 @@ } }, "delete": { - "tags": ["v1"], + "tags": [ + "config" + ], + "summary": "Delete a skills configuration entry.", + "description": "Removes a named skills entry from the project-scoped configuration file.", "operationId": "delete_v1_config_skills", "parameters": [ { @@ -630,7 +691,9 @@ }, "/v1/desktop/clipboard": { "get": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Read the desktop clipboard.", "description": "Returns the current text content of the X11 clipboard.", "operationId": "get_v1_desktop_clipboard", @@ -638,6 +701,7 @@ { "name": "selection", "in": "query", + "description": "X11 selection name (e.g. \"clipboard\" or \"primary\"). Defaults to \"clipboard\".", "required": false, "schema": { "type": "string", @@ -679,7 +743,9 @@ } }, "post": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Write to the desktop clipboard.", "description": "Sets the text content of the X11 clipboard.", "operationId": "post_v1_desktop_clipboard", @@ -729,7 +795,9 @@ }, "/v1/desktop/display/info": { "get": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Get desktop display information.", "description": "Performs a health-gated display query against the managed desktop and\nreturns the current display identifier and resolution.", "operationId": "get_v1_desktop_display_info", @@ -769,7 +837,9 @@ }, "/v1/desktop/keyboard/down": { "post": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Press and hold a desktop keyboard key.", "description": "Performs a health-gated `xdotool keydown` operation against the managed\ndesktop.", "operationId": "post_v1_desktop_keyboard_down", @@ -829,7 +899,9 @@ }, "/v1/desktop/keyboard/press": { "post": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Press a desktop keyboard shortcut.", "description": "Performs a health-gated `xdotool key` operation against the managed\ndesktop.", "operationId": "post_v1_desktop_keyboard_press", @@ -889,7 +961,9 @@ }, "/v1/desktop/keyboard/type": { "post": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Type desktop keyboard text.", "description": "Performs a health-gated `xdotool type` operation against the managed\ndesktop.", "operationId": "post_v1_desktop_keyboard_type", @@ -949,7 +1023,9 @@ }, "/v1/desktop/keyboard/up": { "post": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Release a desktop keyboard key.", "description": "Performs a health-gated `xdotool keyup` operation against the managed\ndesktop.", "operationId": "post_v1_desktop_keyboard_up", @@ -1009,7 +1085,9 @@ }, "/v1/desktop/launch": { "post": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Launch a desktop application.", "description": "Launches an application by name on the managed desktop, optionally waiting\nfor its window to appear.", "operationId": "post_v1_desktop_launch", @@ -1059,7 +1137,9 @@ }, "/v1/desktop/mouse/click": { "post": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Click on the desktop.", "description": "Performs a health-gated pointer move and click against the managed desktop\nand returns the resulting mouse position.", "operationId": "post_v1_desktop_mouse_click", @@ -1119,7 +1199,9 @@ }, "/v1/desktop/mouse/down": { "post": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Press and hold a desktop mouse button.", "description": "Performs a health-gated optional pointer move followed by `xdotool mousedown`\nand returns the resulting mouse position.", "operationId": "post_v1_desktop_mouse_down", @@ -1179,7 +1261,9 @@ }, "/v1/desktop/mouse/drag": { "post": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Drag the desktop mouse.", "description": "Performs a health-gated drag gesture against the managed desktop and\nreturns the resulting mouse position.", "operationId": "post_v1_desktop_mouse_drag", @@ -1239,7 +1323,9 @@ }, "/v1/desktop/mouse/move": { "post": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Move the desktop mouse.", "description": "Performs a health-gated absolute pointer move on the managed desktop and\nreturns the resulting mouse position.", "operationId": "post_v1_desktop_mouse_move", @@ -1299,7 +1385,9 @@ }, "/v1/desktop/mouse/position": { "get": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Get the current desktop mouse position.", "description": "Performs a health-gated mouse position query against the managed desktop.", "operationId": "get_v1_desktop_mouse_position", @@ -1339,7 +1427,9 @@ }, "/v1/desktop/mouse/scroll": { "post": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Scroll the desktop mouse wheel.", "description": "Performs a health-gated scroll gesture at the requested coordinates and\nreturns the resulting mouse position.", "operationId": "post_v1_desktop_mouse_scroll", @@ -1399,7 +1489,9 @@ }, "/v1/desktop/mouse/up": { "post": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Release a desktop mouse button.", "description": "Performs a health-gated optional pointer move followed by `xdotool mouseup`\nand returns the resulting mouse position.", "operationId": "post_v1_desktop_mouse_up", @@ -1459,7 +1551,9 @@ }, "/v1/desktop/open": { "post": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Open a file or URL with the default handler.", "description": "Opens a file path or URL using xdg-open on the managed desktop.", "operationId": "post_v1_desktop_open", @@ -1499,7 +1593,9 @@ }, "/v1/desktop/recording/start": { "post": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Start desktop recording.", "description": "Starts an ffmpeg x11grab recording against the managed desktop and returns\nthe created recording metadata.", "operationId": "post_v1_desktop_recording_start", @@ -1549,7 +1645,9 @@ }, "/v1/desktop/recording/stop": { "post": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Stop desktop recording.", "description": "Stops the active desktop recording and returns the finalized recording\nmetadata.", "operationId": "post_v1_desktop_recording_stop", @@ -1589,7 +1687,9 @@ }, "/v1/desktop/recordings": { "get": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "List desktop recordings.", "description": "Returns the current desktop recording catalog.", "operationId": "get_v1_desktop_recordings", @@ -1619,7 +1719,9 @@ }, "/v1/desktop/recordings/{id}": { "get": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Get desktop recording metadata.", "description": "Returns metadata for a single desktop recording.", "operationId": "get_v1_desktop_recording", @@ -1658,7 +1760,9 @@ } }, "delete": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Delete a desktop recording.", "description": "Removes a completed desktop recording and its file from disk.", "operationId": "delete_v1_desktop_recording", @@ -1702,7 +1806,9 @@ }, "/v1/desktop/recordings/{id}/download": { "get": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Download a desktop recording.", "description": "Serves the recorded MP4 bytes for a completed desktop recording.", "operationId": "get_v1_desktop_recording_download", @@ -1719,7 +1825,14 @@ ], "responses": { "200": { - "description": "Desktop recording as MP4 bytes" + "description": "Desktop recording as MP4 bytes", + "content": { + "video/mp4": { + "schema": { + "type": "string" + } + } + } }, "404": { "description": "Unknown desktop recording", @@ -1736,7 +1849,9 @@ }, "/v1/desktop/screenshot": { "get": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Capture a full desktop screenshot.", "description": "Performs a health-gated full-frame screenshot of the managed desktop and\nreturns the requested image bytes.", "operationId": "get_v1_desktop_screenshot", @@ -1744,6 +1859,7 @@ { "name": "format", "in": "query", + "description": "Image format (png, jpeg, or webp). Defaults to png.", "required": false, "schema": { "allOf": [ @@ -1757,6 +1873,7 @@ { "name": "quality", "in": "query", + "description": "Image quality (0-100). Only applies to jpeg and webp formats.", "required": false, "schema": { "type": "integer", @@ -1768,6 +1885,7 @@ { "name": "scale", "in": "query", + "description": "Scale factor for the output image.", "required": false, "schema": { "type": "number", @@ -1778,6 +1896,7 @@ { "name": "showCursor", "in": "query", + "description": "Whether to include the mouse cursor in the screenshot.", "required": false, "schema": { "type": "boolean", @@ -1787,7 +1906,14 @@ ], "responses": { "200": { - "description": "Desktop screenshot as image bytes" + "description": "Desktop screenshot as image bytes", + "content": { + "image/png": { + "schema": { + "type": "string" + } + } + } }, "400": { "description": "Invalid screenshot query", @@ -1824,7 +1950,9 @@ }, "/v1/desktop/screenshot/region": { "get": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Capture a desktop screenshot region.", "description": "Performs a health-gated screenshot crop against the managed desktop and\nreturns the requested region image bytes.", "operationId": "get_v1_desktop_screenshot_region", @@ -1832,6 +1960,7 @@ { "name": "x", "in": "query", + "description": "Left edge of the capture region in pixels.", "required": true, "schema": { "type": "integer", @@ -1841,6 +1970,7 @@ { "name": "y", "in": "query", + "description": "Top edge of the capture region in pixels.", "required": true, "schema": { "type": "integer", @@ -1850,6 +1980,7 @@ { "name": "width", "in": "query", + "description": "Width of the capture region in pixels.", "required": true, "schema": { "type": "integer", @@ -1860,6 +1991,7 @@ { "name": "height", "in": "query", + "description": "Height of the capture region in pixels.", "required": true, "schema": { "type": "integer", @@ -1870,6 +2002,7 @@ { "name": "format", "in": "query", + "description": "Image format (png, jpeg, or webp). Defaults to png.", "required": false, "schema": { "allOf": [ @@ -1883,6 +2016,7 @@ { "name": "quality", "in": "query", + "description": "Image quality (0-100). Only applies to jpeg and webp formats.", "required": false, "schema": { "type": "integer", @@ -1894,6 +2028,7 @@ { "name": "scale", "in": "query", + "description": "Scale factor for the output image.", "required": false, "schema": { "type": "number", @@ -1904,6 +2039,7 @@ { "name": "showCursor", "in": "query", + "description": "Whether to include the mouse cursor in the screenshot.", "required": false, "schema": { "type": "boolean", @@ -1913,7 +2049,14 @@ ], "responses": { "200": { - "description": "Desktop screenshot region as image bytes" + "description": "Desktop screenshot region as image bytes", + "content": { + "image/png": { + "schema": { + "type": "string" + } + } + } }, "400": { "description": "Invalid screenshot region", @@ -1950,7 +2093,9 @@ }, "/v1/desktop/start": { "post": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Start the private desktop runtime.", "description": "Lazily launches the managed Xvfb/openbox stack, validates display health,\nand returns the resulting desktop status snapshot.", "operationId": "post_v1_desktop_start", @@ -2020,7 +2165,9 @@ }, "/v1/desktop/status": { "get": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Get desktop runtime status.", "description": "Returns the current desktop runtime state, dependency status, active\ndisplay metadata, and supervised process information.", "operationId": "get_v1_desktop_status", @@ -2050,7 +2197,9 @@ }, "/v1/desktop/stop": { "post": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Stop the private desktop runtime.", "description": "Terminates the managed openbox/Xvfb/dbus processes owned by the desktop\nruntime and returns the resulting status snapshot.", "operationId": "post_v1_desktop_stop", @@ -2080,7 +2229,9 @@ }, "/v1/desktop/stream/signaling": { "get": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Open a desktop WebRTC signaling session.", "description": "Upgrades the connection to a WebSocket used for WebRTC signaling between\nthe browser client and the desktop streaming process. Also accepts mouse\nand keyboard input frames as a fallback transport.", "operationId": "get_v1_desktop_stream_ws", @@ -2125,7 +2276,9 @@ }, "/v1/desktop/stream/start": { "post": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Start desktop streaming.", "description": "Enables desktop websocket streaming for the managed desktop.", "operationId": "post_v1_desktop_stream_start", @@ -2145,7 +2298,9 @@ }, "/v1/desktop/stream/status": { "get": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Get desktop stream status.", "description": "Returns the current state of the desktop WebRTC streaming session.", "operationId": "get_v1_desktop_stream_status", @@ -2165,7 +2320,9 @@ }, "/v1/desktop/stream/stop": { "post": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Stop desktop streaming.", "description": "Disables desktop websocket streaming for the managed desktop.", "operationId": "post_v1_desktop_stream_stop", @@ -2185,7 +2342,9 @@ }, "/v1/desktop/windows": { "get": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "List visible desktop windows.", "description": "Performs a health-gated visible-window enumeration against the managed\ndesktop and returns the current window metadata.", "operationId": "get_v1_desktop_windows", @@ -2225,7 +2384,9 @@ }, "/v1/desktop/windows/focused": { "get": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Get the currently focused desktop window.", "description": "Returns information about the window that currently has input focus.", "operationId": "get_v1_desktop_windows_focused", @@ -2265,7 +2426,9 @@ }, "/v1/desktop/windows/{id}/focus": { "post": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Focus a desktop window.", "description": "Brings the specified window to the foreground and gives it input focus.", "operationId": "post_v1_desktop_window_focus", @@ -2316,7 +2479,9 @@ }, "/v1/desktop/windows/{id}/move": { "post": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Move a desktop window.", "description": "Moves the specified window to the given position.", "operationId": "post_v1_desktop_window_move", @@ -2377,7 +2542,9 @@ }, "/v1/desktop/windows/{id}/resize": { "post": { - "tags": ["v1"], + "tags": [ + "desktop" + ], "summary": "Resize a desktop window.", "description": "Resizes the specified window to the given dimensions.", "operationId": "post_v1_desktop_window_resize", @@ -2438,7 +2605,11 @@ }, "/v1/fs/entries": { "get": { - "tags": ["v1"], + "tags": [ + "filesystem" + ], + "summary": "List directory entries.", + "description": "Returns the immediate children of the specified directory, including\nfile names, sizes, types, and last-modified timestamps.", "operationId": "get_v1_fs_entries", "parameters": [ { @@ -2471,7 +2642,11 @@ }, "/v1/fs/entry": { "delete": { - "tags": ["v1"], + "tags": [ + "filesystem" + ], + "summary": "Delete a file or directory.", + "description": "Removes the entry at the given path. For directories, set `recursive`\nto true to remove contents recursively.", "operationId": "delete_v1_fs_entry", "parameters": [ { @@ -2510,7 +2685,11 @@ }, "/v1/fs/file": { "get": { - "tags": ["v1"], + "tags": [ + "filesystem" + ], + "summary": "Read a file.", + "description": "Returns the raw bytes of the file at the given path.", "operationId": "get_v1_fs_file", "parameters": [ { @@ -2525,12 +2704,23 @@ ], "responses": { "200": { - "description": "File content" + "description": "File content", + "content": { + "application/octet-stream": { + "schema": { + "type": "string" + } + } + } } } }, "put": { - "tags": ["v1"], + "tags": [ + "filesystem" + ], + "summary": "Write a file.", + "description": "Writes raw bytes to the file at the given path, creating parent\ndirectories as needed. Returns the resolved path and bytes written.", "operationId": "put_v1_fs_file", "parameters": [ { @@ -2546,7 +2736,7 @@ "requestBody": { "description": "Raw file bytes", "content": { - "text/plain": { + "application/octet-stream": { "schema": { "type": "string" } @@ -2570,7 +2760,11 @@ }, "/v1/fs/mkdir": { "post": { - "tags": ["v1"], + "tags": [ + "filesystem" + ], + "summary": "Create a directory.", + "description": "Creates the directory at the given path, including any missing parent\ndirectories.", "operationId": "post_v1_fs_mkdir", "parameters": [ { @@ -2599,7 +2793,11 @@ }, "/v1/fs/move": { "post": { - "tags": ["v1"], + "tags": [ + "filesystem" + ], + "summary": "Move or rename a file or directory.", + "description": "Moves the entry from one path to another. Set `overwrite` to true to\nreplace an existing destination.", "operationId": "post_v1_fs_move", "requestBody": { "content": { @@ -2627,7 +2825,11 @@ }, "/v1/fs/stat": { "get": { - "tags": ["v1"], + "tags": [ + "filesystem" + ], + "summary": "Get file or directory metadata.", + "description": "Returns the type, size, and last-modified timestamp for the entry at\nthe given path.", "operationId": "get_v1_fs_stat", "parameters": [ { @@ -2656,7 +2858,11 @@ }, "/v1/fs/upload-batch": { "post": { - "tags": ["v1"], + "tags": [ + "filesystem" + ], + "summary": "Upload files as a tar archive.", + "description": "Accepts a tar archive body, extracts its contents into the destination\ndirectory, and returns the list of extracted paths.", "operationId": "post_v1_fs_upload_batch", "parameters": [ { @@ -2673,7 +2879,7 @@ "requestBody": { "description": "tar archive body", "content": { - "text/plain": { + "application/x-tar": { "schema": { "type": "string" } @@ -2697,7 +2903,11 @@ }, "/v1/health": { "get": { - "tags": ["v1"], + "tags": [ + "health" + ], + "summary": "Check service health.", + "description": "Returns a simple health status indicating the service is running and\nreachable.", "operationId": "get_v1_health", "responses": { "200": { @@ -2715,7 +2925,9 @@ }, "/v1/processes": { "get": { - "tags": ["v1"], + "tags": [ + "processes" + ], "summary": "List all managed processes.", "description": "Returns a list of all processes (running and exited) currently tracked\nby the runtime, sorted by process ID.", "operationId": "get_v1_processes", @@ -2723,6 +2935,7 @@ { "name": "owner", "in": "query", + "description": "Filter processes by owner (user, desktop, or system).", "required": false, "schema": { "allOf": [ @@ -2758,7 +2971,9 @@ } }, "post": { - "tags": ["v1"], + "tags": [ + "processes" + ], "summary": "Create a long-lived managed process.", "description": "Spawns a new process with the given command and arguments. Supports both\npipe-based and PTY (tty) modes. Returns the process descriptor on success.", "operationId": "post_v1_processes", @@ -2818,7 +3033,9 @@ }, "/v1/processes/config": { "get": { - "tags": ["v1"], + "tags": [ + "processes" + ], "summary": "Get process runtime configuration.", "description": "Returns the current runtime configuration for the process management API,\nincluding limits for concurrency, timeouts, and buffer sizes.", "operationId": "get_v1_processes_config", @@ -2846,7 +3063,9 @@ } }, "post": { - "tags": ["v1"], + "tags": [ + "processes" + ], "summary": "Update process runtime configuration.", "description": "Replaces the runtime configuration for the process management API.\nValidates that all values are non-zero and clamps default timeout to max.", "operationId": "post_v1_processes_config", @@ -2896,7 +3115,9 @@ }, "/v1/processes/run": { "post": { - "tags": ["v1"], + "tags": [ + "processes" + ], "summary": "Run a one-shot command.", "description": "Executes a command to completion and returns its stdout, stderr, exit code,\nand duration. Supports configurable timeout and output size limits.", "operationId": "post_v1_processes_run", @@ -2946,7 +3167,9 @@ }, "/v1/processes/{id}": { "get": { - "tags": ["v1"], + "tags": [ + "processes" + ], "summary": "Get a single process by ID.", "description": "Returns the current state of a managed process including its status,\nPID, exit code, and creation/exit timestamps.", "operationId": "get_v1_process", @@ -2995,7 +3218,9 @@ } }, "delete": { - "tags": ["v1"], + "tags": [ + "processes" + ], "summary": "Delete a process record.", "description": "Removes a stopped process from the runtime. Returns 409 if the process\nis still running; stop or kill it first.", "operationId": "delete_v1_process", @@ -3049,7 +3274,9 @@ }, "/v1/processes/{id}/input": { "post": { - "tags": ["v1"], + "tags": [ + "processes" + ], "summary": "Write input to a process.", "description": "Sends data to a process's stdin (pipe mode) or PTY writer (tty mode).\nData can be encoded as base64, utf8, or text. Returns 413 if the decoded\npayload exceeds the configured `maxInputBytesPerRequest` limit.", "operationId": "post_v1_process_input", @@ -3130,7 +3357,9 @@ }, "/v1/processes/{id}/kill": { "post": { - "tags": ["v1"], + "tags": [ + "processes" + ], "summary": "Send SIGKILL to a process.", "description": "Sends SIGKILL to the process and optionally waits up to `waitMs`\nmilliseconds for the process to exit before returning.", "operationId": "post_v1_process_kill", @@ -3193,7 +3422,9 @@ }, "/v1/processes/{id}/logs": { "get": { - "tags": ["v1"], + "tags": [ + "processes" + ], "summary": "Fetch process logs.", "description": "Returns buffered log entries for a process. Supports filtering by stream\ntype, tail count, and sequence-based resumption. When `follow=true`,\nreturns an SSE stream that replays buffered entries then streams live output.", "operationId": "get_v1_process_logs", @@ -3291,7 +3522,9 @@ }, "/v1/processes/{id}/stop": { "post": { - "tags": ["v1"], + "tags": [ + "processes" + ], "summary": "Send SIGTERM to a process.", "description": "Sends SIGTERM to the process and optionally waits up to `waitMs`\nmilliseconds for the process to exit before returning.", "operationId": "post_v1_process_stop", @@ -3354,7 +3587,9 @@ }, "/v1/processes/{id}/terminal/resize": { "post": { - "tags": ["v1"], + "tags": [ + "processes" + ], "summary": "Resize a process terminal.", "description": "Sets the PTY window size (columns and rows) for a tty-mode process and\nsends SIGWINCH so the child process can adapt.", "operationId": "post_v1_process_terminal_resize", @@ -3435,7 +3670,9 @@ }, "/v1/processes/{id}/terminal/ws": { "get": { - "tags": ["v1"], + "tags": [ + "processes" + ], "summary": "Open an interactive WebSocket terminal session.", "description": "Upgrades the connection to a WebSocket for bidirectional PTY I/O. Accepts\n`access_token` query param for browser-based auth (WebSocket API cannot\nsend custom headers). Streams raw PTY output as binary frames and accepts\nJSON control frames for input, resize, and close.", "operationId": "get_v1_process_terminal_ws", @@ -3512,7 +3749,9 @@ "schemas": { "AcpEnvelope": { "type": "object", - "required": ["jsonrpc"], + "required": [ + "jsonrpc" + ], "properties": { "error": { "nullable": true @@ -3535,18 +3774,13 @@ } } }, - "AcpPostQuery": { - "type": "object", - "properties": { - "agent": { - "type": "string", - "nullable": true - } - } - }, "AcpServerInfo": { "type": "object", - "required": ["serverId", "agent", "createdAtMs"], + "required": [ + "serverId", + "agent", + "createdAtMs" + ], "properties": { "agent": { "type": "string" @@ -3562,7 +3796,9 @@ }, "AcpServerListResponse": { "type": "object", - "required": ["servers"], + "required": [ + "servers" + ], "properties": { "servers": { "type": "array", @@ -3653,7 +3889,12 @@ }, "AgentInfo": { "type": "object", - "required": ["id", "installed", "credentialsAvailable", "capabilities"], + "required": [ + "id", + "installed", + "credentialsAvailable", + "capabilities" + ], "properties": { "capabilities": { "$ref": "#/components/schemas/AgentCapabilities" @@ -3696,7 +3937,11 @@ }, "AgentInstallArtifact": { "type": "object", - "required": ["kind", "path", "source"], + "required": [ + "kind", + "path", + "source" + ], "properties": { "kind": { "type": "string" @@ -3732,7 +3977,10 @@ }, "AgentInstallResponse": { "type": "object", - "required": ["already_installed", "artifacts"], + "required": [ + "already_installed", + "artifacts" + ], "properties": { "already_installed": { "type": "boolean" @@ -3747,7 +3995,9 @@ }, "AgentListResponse": { "type": "object", - "required": ["agents"], + "required": [ + "agents" + ], "properties": { "agents": { "type": "array", @@ -3759,25 +4009,21 @@ }, "DesktopActionResponse": { "type": "object", - "required": ["ok"], + "required": [ + "ok" + ], "properties": { "ok": { "type": "boolean" } } }, - "DesktopClipboardQuery": { - "type": "object", - "properties": { - "selection": { - "type": "string", - "nullable": true - } - } - }, "DesktopClipboardResponse": { "type": "object", - "required": ["text", "selection"], + "required": [ + "text", + "selection" + ], "properties": { "selection": { "type": "string" @@ -3789,7 +4035,9 @@ }, "DesktopClipboardWriteRequest": { "type": "object", - "required": ["text"], + "required": [ + "text" + ], "properties": { "selection": { "type": "string", @@ -3802,7 +4050,10 @@ }, "DesktopDisplayInfoResponse": { "type": "object", - "required": ["display", "resolution"], + "required": [ + "display", + "resolution" + ], "properties": { "display": { "type": "string" @@ -3814,7 +4065,10 @@ }, "DesktopErrorInfo": { "type": "object", - "required": ["code", "message"], + "required": [ + "code", + "message" + ], "properties": { "code": { "type": "string" @@ -3847,7 +4101,9 @@ }, "DesktopKeyboardDownRequest": { "type": "object", - "required": ["key"], + "required": [ + "key" + ], "properties": { "key": { "type": "string" @@ -3856,7 +4112,9 @@ }, "DesktopKeyboardPressRequest": { "type": "object", - "required": ["key"], + "required": [ + "key" + ], "properties": { "key": { "type": "string" @@ -3873,7 +4131,9 @@ }, "DesktopKeyboardTypeRequest": { "type": "object", - "required": ["text"], + "required": [ + "text" + ], "properties": { "delayMs": { "type": "integer", @@ -3888,7 +4148,9 @@ }, "DesktopKeyboardUpRequest": { "type": "object", - "required": ["key"], + "required": [ + "key" + ], "properties": { "key": { "type": "string" @@ -3897,7 +4159,9 @@ }, "DesktopLaunchRequest": { "type": "object", - "required": ["app"], + "required": [ + "app" + ], "properties": { "app": { "type": "string" @@ -3917,7 +4181,9 @@ }, "DesktopLaunchResponse": { "type": "object", - "required": ["processId"], + "required": [ + "processId" + ], "properties": { "pid": { "type": "integer", @@ -3936,11 +4202,18 @@ }, "DesktopMouseButton": { "type": "string", - "enum": ["left", "middle", "right"] + "enum": [ + "left", + "middle", + "right" + ] }, "DesktopMouseClickRequest": { "type": "object", - "required": ["x", "y"], + "required": [ + "x", + "y" + ], "properties": { "button": { "allOf": [ @@ -3991,7 +4264,12 @@ }, "DesktopMouseDragRequest": { "type": "object", - "required": ["startX", "startY", "endX", "endY"], + "required": [ + "startX", + "startY", + "endX", + "endY" + ], "properties": { "button": { "allOf": [ @@ -4021,7 +4299,10 @@ }, "DesktopMouseMoveRequest": { "type": "object", - "required": ["x", "y"], + "required": [ + "x", + "y" + ], "properties": { "x": { "type": "integer", @@ -4035,7 +4316,10 @@ }, "DesktopMousePositionResponse": { "type": "object", - "required": ["x", "y"], + "required": [ + "x", + "y" + ], "properties": { "screen": { "type": "integer", @@ -4058,7 +4342,10 @@ }, "DesktopMouseScrollRequest": { "type": "object", - "required": ["x", "y"], + "required": [ + "x", + "y" + ], "properties": { "deltaX": { "type": "integer", @@ -4105,7 +4392,9 @@ }, "DesktopOpenRequest": { "type": "object", - "required": ["target"], + "required": [ + "target" + ], "properties": { "target": { "type": "string" @@ -4114,7 +4403,9 @@ }, "DesktopOpenResponse": { "type": "object", - "required": ["processId"], + "required": [ + "processId" + ], "properties": { "pid": { "type": "integer", @@ -4129,7 +4420,10 @@ }, "DesktopProcessInfo": { "type": "object", - "required": ["name", "running"], + "required": [ + "name", + "running" + ], "properties": { "logPath": { "type": "string", @@ -4151,7 +4445,13 @@ }, "DesktopRecordingInfo": { "type": "object", - "required": ["id", "status", "fileName", "bytes", "startedAt"], + "required": [ + "id", + "status", + "fileName", + "bytes", + "startedAt" + ], "properties": { "bytes": { "type": "integer", @@ -4182,7 +4482,9 @@ }, "DesktopRecordingListResponse": { "type": "object", - "required": ["recordings"], + "required": [ + "recordings" + ], "properties": { "recordings": { "type": "array", @@ -4205,58 +4507,18 @@ }, "DesktopRecordingStatus": { "type": "string", - "enum": ["recording", "completed", "failed"] - }, - "DesktopRegionScreenshotQuery": { - "type": "object", - "required": ["x", "y", "width", "height"], - "properties": { - "format": { - "allOf": [ - { - "$ref": "#/components/schemas/DesktopScreenshotFormat" - } - ], - "nullable": true - }, - "height": { - "type": "integer", - "format": "int32", - "minimum": 0 - }, - "quality": { - "type": "integer", - "format": "int32", - "nullable": true, - "minimum": 0 - }, - "scale": { - "type": "number", - "format": "float", - "nullable": true - }, - "showCursor": { - "type": "boolean", - "nullable": true - }, - "width": { - "type": "integer", - "format": "int32", - "minimum": 0 - }, - "x": { - "type": "integer", - "format": "int32" - }, - "y": { - "type": "integer", - "format": "int32" - } - } + "enum": [ + "recording", + "completed", + "failed" + ] }, "DesktopResolution": { "type": "object", - "required": ["width", "height"], + "required": [ + "width", + "height" + ], "properties": { "dpi": { "type": "integer", @@ -4278,35 +4540,11 @@ }, "DesktopScreenshotFormat": { "type": "string", - "enum": ["png", "jpeg", "webp"] - }, - "DesktopScreenshotQuery": { - "type": "object", - "properties": { - "format": { - "allOf": [ - { - "$ref": "#/components/schemas/DesktopScreenshotFormat" - } - ], - "nullable": true - }, - "quality": { - "type": "integer", - "format": "int32", - "nullable": true, - "minimum": 0 - }, - "scale": { - "type": "number", - "format": "float", - "nullable": true - }, - "showCursor": { - "type": "boolean", - "nullable": true - } - } + "enum": [ + "png", + "jpeg", + "webp" + ] }, "DesktopStartRequest": { "type": "object", @@ -4366,11 +4604,20 @@ }, "DesktopState": { "type": "string", - "enum": ["inactive", "install_required", "starting", "active", "stopping", "failed"] + "enum": [ + "inactive", + "install_required", + "starting", + "active", + "stopping", + "failed" + ] }, "DesktopStatusResponse": { "type": "object", - "required": ["state"], + "required": [ + "state" + ], "properties": { "display": { "type": "string", @@ -4430,7 +4677,9 @@ }, "DesktopStreamStatusResponse": { "type": "object", - "required": ["active"], + "required": [ + "active" + ], "properties": { "active": { "type": "boolean" @@ -4447,7 +4696,15 @@ }, "DesktopWindowInfo": { "type": "object", - "required": ["id", "title", "x", "y", "width", "height", "isActive"], + "required": [ + "id", + "title", + "x", + "y", + "width", + "height", + "isActive" + ], "properties": { "height": { "type": "integer", @@ -4480,7 +4737,9 @@ }, "DesktopWindowListResponse": { "type": "object", - "required": ["windows"], + "required": [ + "windows" + ], "properties": { "windows": { "type": "array", @@ -4492,7 +4751,10 @@ }, "DesktopWindowMoveRequest": { "type": "object", - "required": ["x", "y"], + "required": [ + "x", + "y" + ], "properties": { "x": { "type": "integer", @@ -4506,7 +4768,10 @@ }, "DesktopWindowResizeRequest": { "type": "object", - "required": ["width", "height"], + "required": [ + "width", + "height" + ], "properties": { "height": { "type": "integer", @@ -4543,38 +4808,23 @@ }, "FsActionResponse": { "type": "object", - "required": ["path"], - "properties": { - "path": { - "type": "string" - } - } - }, - "FsDeleteQuery": { - "type": "object", - "required": ["path"], + "required": [ + "path" + ], "properties": { "path": { "type": "string" - }, - "recursive": { - "type": "boolean", - "nullable": true - } - } - }, - "FsEntriesQuery": { - "type": "object", - "properties": { - "path": { - "type": "string", - "nullable": true } } }, "FsEntry": { "type": "object", - "required": ["name", "path", "entryType", "size"], + "required": [ + "name", + "path", + "entryType", + "size" + ], "properties": { "entryType": { "$ref": "#/components/schemas/FsEntryType" @@ -4598,11 +4848,17 @@ }, "FsEntryType": { "type": "string", - "enum": ["file", "directory"] + "enum": [ + "file", + "directory" + ] }, "FsMoveRequest": { "type": "object", - "required": ["from", "to"], + "required": [ + "from", + "to" + ], "properties": { "from": { "type": "string" @@ -4618,7 +4874,10 @@ }, "FsMoveResponse": { "type": "object", - "required": ["from", "to"], + "required": [ + "from", + "to" + ], "properties": { "from": { "type": "string" @@ -4628,18 +4887,13 @@ } } }, - "FsPathQuery": { - "type": "object", - "required": ["path"], - "properties": { - "path": { - "type": "string" - } - } - }, "FsStat": { "type": "object", - "required": ["path", "entryType", "size"], + "required": [ + "path", + "entryType", + "size" + ], "properties": { "entryType": { "$ref": "#/components/schemas/FsEntryType" @@ -4658,18 +4912,12 @@ } } }, - "FsUploadBatchQuery": { - "type": "object", - "properties": { - "path": { - "type": "string", - "nullable": true - } - } - }, "FsUploadBatchResponse": { "type": "object", - "required": ["paths", "truncated"], + "required": [ + "paths", + "truncated" + ], "properties": { "paths": { "type": "array", @@ -4684,7 +4932,10 @@ }, "FsWriteResponse": { "type": "object", - "required": ["path", "bytesWritten"], + "required": [ + "path", + "bytesWritten" + ], "properties": { "bytesWritten": { "type": "integer", @@ -4698,30 +4949,70 @@ }, "HealthResponse": { "type": "object", - "required": ["status"], + "required": [ + "status" + ], "properties": { "status": { "type": "string" } } }, - "McpConfigQuery": { + "McpCommand": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "McpOAuthConfig": { "type": "object", - "required": ["directory", "mcpName"], "properties": { - "directory": { - "type": "string" + "clientId": { + "type": "string", + "nullable": true }, - "mcpName": { - "type": "string" + "clientSecret": { + "type": "string", + "nullable": true + }, + "scope": { + "type": "string", + "nullable": true } } }, + "McpOAuthConfigOrDisabled": { + "oneOf": [ + { + "$ref": "#/components/schemas/McpOAuthConfig" + }, + { + "type": "boolean" + } + ] + }, + "McpRemoteTransport": { + "type": "string", + "enum": [ + "http", + "sse" + ] + }, "McpServerConfig": { "oneOf": [ { "type": "object", - "required": ["command", "type"], + "required": [ + "command", + "type" + ], "properties": { "args": { "type": "array", @@ -4755,13 +5046,18 @@ }, "type": { "type": "string", - "enum": ["local"] + "enum": [ + "local" + ] } } }, { "type": "object", - "required": ["url", "type"], + "required": [ + "url", + "type" + ], "properties": { "bearerTokenEnvVar": { "type": "string", @@ -4809,7 +5105,9 @@ }, "type": { "type": "string", - "enum": ["remote"] + "enum": [ + "remote" + ] }, "url": { "type": "string" @@ -4823,7 +5121,11 @@ }, "ProblemDetails": { "type": "object", - "required": ["type", "title", "status"], + "required": [ + "type", + "title", + "status" + ], "properties": { "detail": { "type": "string", @@ -4849,7 +5151,14 @@ }, "ProcessConfig": { "type": "object", - "required": ["maxConcurrentProcesses", "defaultRunTimeoutMs", "maxRunTimeoutMs", "maxOutputBytes", "maxLogBytesPerProcess", "maxInputBytesPerRequest"], + "required": [ + "maxConcurrentProcesses", + "defaultRunTimeoutMs", + "maxRunTimeoutMs", + "maxOutputBytes", + "maxLogBytesPerProcess", + "maxInputBytesPerRequest" + ], "properties": { "defaultRunTimeoutMs": { "type": "integer", @@ -4881,7 +5190,9 @@ }, "ProcessCreateRequest": { "type": "object", - "required": ["command"], + "required": [ + "command" + ], "properties": { "args": { "type": "array", @@ -4912,7 +5223,16 @@ }, "ProcessInfo": { "type": "object", - "required": ["id", "command", "args", "tty", "interactive", "owner", "status", "createdAtMs"], + "required": [ + "id", + "command", + "args", + "tty", + "interactive", + "owner", + "status", + "createdAtMs" + ], "properties": { "args": { "type": "array", @@ -4966,7 +5286,9 @@ }, "ProcessInputRequest": { "type": "object", - "required": ["data"], + "required": [ + "data" + ], "properties": { "data": { "type": "string" @@ -4979,7 +5301,9 @@ }, "ProcessInputResponse": { "type": "object", - "required": ["bytesWritten"], + "required": [ + "bytesWritten" + ], "properties": { "bytesWritten": { "type": "integer", @@ -4987,22 +5311,11 @@ } } }, - "ProcessListQuery": { - "type": "object", - "properties": { - "owner": { - "allOf": [ - { - "$ref": "#/components/schemas/ProcessOwner" - } - ], - "nullable": true - } - } - }, "ProcessListResponse": { "type": "object", - "required": ["processes"], + "required": [ + "processes" + ], "properties": { "processes": { "type": "array", @@ -5014,7 +5327,13 @@ }, "ProcessLogEntry": { "type": "object", - "required": ["sequence", "stream", "timestampMs", "data", "encoding"], + "required": [ + "sequence", + "stream", + "timestampMs", + "data", + "encoding" + ], "properties": { "data": { "type": "string" @@ -5036,37 +5355,13 @@ } } }, - "ProcessLogsQuery": { - "type": "object", - "properties": { - "follow": { - "type": "boolean", - "nullable": true - }, - "since": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "stream": { - "allOf": [ - { - "$ref": "#/components/schemas/ProcessLogsStream" - } - ], - "nullable": true - }, - "tail": { - "type": "integer", - "nullable": true, - "minimum": 0 - } - } - }, "ProcessLogsResponse": { "type": "object", - "required": ["processId", "stream", "entries"], + "required": [ + "processId", + "stream", + "entries" + ], "properties": { "entries": { "type": "array", @@ -5084,15 +5379,26 @@ }, "ProcessLogsStream": { "type": "string", - "enum": ["stdout", "stderr", "combined", "pty"] + "enum": [ + "stdout", + "stderr", + "combined", + "pty" + ] }, "ProcessOwner": { "type": "string", - "enum": ["user", "desktop", "system"] + "enum": [ + "user", + "desktop", + "system" + ] }, "ProcessRunRequest": { "type": "object", - "required": ["command"], + "required": [ + "command" + ], "properties": { "args": { "type": "array", @@ -5128,7 +5434,14 @@ }, "ProcessRunResponse": { "type": "object", - "required": ["timedOut", "stdout", "stderr", "stdoutTruncated", "stderrTruncated", "durationMs"], + "required": [ + "timedOut", + "stdout", + "stderr", + "stdoutTruncated", + "stderrTruncated", + "durationMs" + ], "properties": { "durationMs": { "type": "integer", @@ -5157,24 +5470,19 @@ } } }, - "ProcessSignalQuery": { - "type": "object", - "properties": { - "waitMs": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - } - } - }, "ProcessState": { "type": "string", - "enum": ["running", "exited"] + "enum": [ + "running", + "exited" + ] }, "ProcessTerminalResizeRequest": { "type": "object", - "required": ["cols", "rows"], + "required": [ + "cols", + "rows" + ], "properties": { "cols": { "type": "integer", @@ -5190,7 +5498,10 @@ }, "ProcessTerminalResizeResponse": { "type": "object", - "required": ["cols", "rows"], + "required": [ + "cols", + "rows" + ], "properties": { "cols": { "type": "integer", @@ -5206,11 +5517,16 @@ }, "ServerStatus": { "type": "string", - "enum": ["running", "stopped"] + "enum": [ + "running", + "stopped" + ] }, "ServerStatusInfo": { "type": "object", - "required": ["status"], + "required": [ + "status" + ], "properties": { "status": { "$ref": "#/components/schemas/ServerStatus" @@ -5225,7 +5541,10 @@ }, "SkillSource": { "type": "object", - "required": ["type", "source"], + "required": [ + "type", + "source" + ], "properties": { "ref": { "type": "string", @@ -5252,7 +5571,9 @@ }, "SkillsConfig": { "type": "object", - "required": ["sources"], + "required": [ + "sources" + ], "properties": { "sources": { "type": "array", @@ -5261,25 +5582,37 @@ } } } - }, - "SkillsConfigQuery": { - "type": "object", - "required": ["directory", "skillName"], - "properties": { - "directory": { - "type": "string" - }, - "skillName": { - "type": "string" - } - } } } }, "tags": [ { - "name": "v1", - "description": "ACP proxy v1 API" + "name": "health", + "description": "Service health checks" + }, + { + "name": "desktop", + "description": "Virtual desktop runtime management, input control, screenshots, recordings, and streaming" + }, + { + "name": "agents", + "description": "Agent discovery, status, and installation" + }, + { + "name": "filesystem", + "description": "Sandbox filesystem operations: read, write, delete, move, and batch upload" + }, + { + "name": "processes", + "description": "Process lifecycle management, logs, input, and terminal access" + }, + { + "name": "config", + "description": "Project-scoped configuration for MCP servers and skills" + }, + { + "name": "acp", + "description": "Agent Client Protocol proxy for JSON-RPC communication with agents" } ] -} +} \ No newline at end of file diff --git a/sdks/typescript/scripts/patch-openapi-types.mjs b/sdks/typescript/scripts/patch-openapi-types.mjs index d0e663cf..2a0012b0 100644 --- a/sdks/typescript/scripts/patch-openapi-types.mjs +++ b/sdks/typescript/scripts/patch-openapi-types.mjs @@ -4,11 +4,10 @@ import { resolve } from "node:path"; const target = resolve(process.cwd(), "src/generated/openapi.ts"); let source = readFileSync(target, "utf8"); -const replacements = [ - ['components["schemas"]["McpCommand"]', "string"], - ['components["schemas"]["McpOAuthConfigOrDisabled"]', "Record | null"], - ['components["schemas"]["McpRemoteTransport"]', "string"], -]; +// McpCommand, McpOAuthConfigOrDisabled, and McpRemoteTransport are now +// properly defined in the OpenAPI spec, so openapi-typescript generates +// correct types for them directly. No patching needed. +const replacements = []; for (const [from, to] of replacements) { source = source.split(from).join(to); diff --git a/server/packages/openapi-gen/build.rs b/server/packages/openapi-gen/build.rs index b3c0c905..0e674d45 100644 --- a/server/packages/openapi-gen/build.rs +++ b/server/packages/openapi-gen/build.rs @@ -7,7 +7,11 @@ use utoipa::OpenApi; fn main() { emit_stdout("cargo:rerun-if-changed=../sandbox-agent/src/router.rs"); + emit_stdout("cargo:rerun-if-changed=../sandbox-agent/src/router/types.rs"); + emit_stdout("cargo:rerun-if-changed=../sandbox-agent/src/desktop_types.rs"); + emit_stdout("cargo:rerun-if-changed=../sandbox-agent/src/universal_events.rs"); emit_stdout("cargo:rerun-if-changed=../sandbox-agent/src/lib.rs"); + emit_stdout("cargo:rerun-if-changed=../error/src/lib.rs"); let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not set"); let out_path = Path::new(&out_dir).join("openapi.json"); diff --git a/server/packages/sandbox-agent/src/desktop_types.rs b/server/packages/sandbox-agent/src/desktop_types.rs index 912e62fe..c8e0e0bf 100644 --- a/server/packages/sandbox-agent/src/desktop_types.rs +++ b/server/packages/sandbox-agent/src/desktop_types.rs @@ -93,12 +93,16 @@ pub struct DesktopStartRequest { #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema, IntoParams, Default)] #[serde(rename_all = "camelCase")] pub struct DesktopScreenshotQuery { + /// Image format (png, jpeg, or webp). Defaults to png. #[serde(default, skip_serializing_if = "Option::is_none")] pub format: Option, + /// Image quality (0-100). Only applies to jpeg and webp formats. #[serde(default, skip_serializing_if = "Option::is_none")] pub quality: Option, + /// Scale factor for the output image. #[serde(default, skip_serializing_if = "Option::is_none")] pub scale: Option, + /// Whether to include the mouse cursor in the screenshot. #[serde(default, skip_serializing_if = "Option::is_none")] pub show_cursor: Option, } @@ -114,16 +118,24 @@ pub enum DesktopScreenshotFormat { #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema, IntoParams)] #[serde(rename_all = "camelCase")] pub struct DesktopRegionScreenshotQuery { + /// Left edge of the capture region in pixels. pub x: i32, + /// Top edge of the capture region in pixels. pub y: i32, + /// Width of the capture region in pixels. pub width: u32, + /// Height of the capture region in pixels. pub height: u32, + /// Image format (png, jpeg, or webp). Defaults to png. #[serde(default, skip_serializing_if = "Option::is_none")] pub format: Option, + /// Image quality (0-100). Only applies to jpeg and webp formats. #[serde(default, skip_serializing_if = "Option::is_none")] pub quality: Option, + /// Scale factor for the output image. #[serde(default, skip_serializing_if = "Option::is_none")] pub scale: Option, + /// Whether to include the mouse cursor in the screenshot. #[serde(default, skip_serializing_if = "Option::is_none")] pub show_cursor: Option, } @@ -336,6 +348,7 @@ pub struct DesktopClipboardResponse { #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema, IntoParams, Default)] #[serde(rename_all = "camelCase")] pub struct DesktopClipboardQuery { + /// X11 selection name (e.g. "clipboard" or "primary"). Defaults to "clipboard". #[serde(default, skip_serializing_if = "Option::is_none")] pub selection: Option, } diff --git a/server/packages/sandbox-agent/src/router.rs b/server/packages/sandbox-agent/src/router.rs index 195a5cdd..f63b8232 100644 --- a/server/packages/sandbox-agent/src/router.rs +++ b/server/packages/sandbox-agent/src/router.rs @@ -409,7 +409,9 @@ pub async fn shutdown_servers(state: &Arc) { #[derive(OpenApi)] #[openapi( paths( + // Health get_v1_health, + // Desktop get_v1_desktop_status, post_v1_desktop_start, post_v1_desktop_stop, @@ -446,9 +448,11 @@ pub async fn shutdown_servers(state: &Arc) { post_v1_desktop_stream_start, post_v1_desktop_stream_stop, get_v1_desktop_stream_ws, + // Agents get_v1_agents, get_v1_agent, post_v1_agent_install, + // Filesystem get_v1_fs_entries, get_v1_fs_file, put_v1_fs_file, @@ -457,6 +461,7 @@ pub async fn shutdown_servers(state: &Arc) { post_v1_fs_move, get_v1_fs_stat, post_v1_fs_upload_batch, + // Processes get_v1_processes_config, post_v1_processes_config, post_v1_processes, @@ -470,12 +475,14 @@ pub async fn shutdown_servers(state: &Arc) { post_v1_process_input, post_v1_process_terminal_resize, get_v1_process_terminal_ws, + // Config get_v1_config_mcp, put_v1_config_mcp, delete_v1_config_mcp, get_v1_config_skills, put_v1_config_skills, delete_v1_config_skills, + // ACP get_v1_acp_servers, post_v1_acp, get_v1_acp, @@ -483,16 +490,16 @@ pub async fn shutdown_servers(state: &Arc) { ), components( schemas( + // Health HealthResponse, + // Desktop DesktopState, DesktopResolution, DesktopErrorInfo, DesktopProcessInfo, DesktopStatusResponse, DesktopStartRequest, - DesktopScreenshotQuery, DesktopScreenshotFormat, - DesktopRegionScreenshotQuery, DesktopMousePositionResponse, DesktopMouseButton, DesktopMouseMoveRequest, @@ -516,7 +523,6 @@ pub async fn shutdown_servers(state: &Arc) { DesktopRecordingListResponse, DesktopStreamStatusResponse, DesktopClipboardResponse, - DesktopClipboardQuery, DesktopClipboardWriteRequest, DesktopLaunchRequest, DesktopLaunchResponse, @@ -524,6 +530,7 @@ pub async fn shutdown_servers(state: &Arc) { DesktopOpenResponse, DesktopWindowMoveRequest, DesktopWindowResizeRequest, + // Agents ServerStatus, ServerStatusInfo, AgentCapabilities, @@ -532,10 +539,7 @@ pub async fn shutdown_servers(state: &Arc) { AgentInstallRequest, AgentInstallArtifact, AgentInstallResponse, - FsPathQuery, - FsEntriesQuery, - FsDeleteQuery, - FsUploadBatchQuery, + // Filesystem FsEntryType, FsEntry, FsStat, @@ -544,6 +548,7 @@ pub async fn shutdown_servers(state: &Arc) { FsMoveResponse, FsActionResponse, FsUploadBatchResponse, + // Processes ProcessConfig, ProcessOwner, ProcessCreateRequest, @@ -552,31 +557,38 @@ pub async fn shutdown_servers(state: &Arc) { ProcessState, ProcessInfo, ProcessListResponse, - ProcessListQuery, ProcessLogsStream, - ProcessLogsQuery, ProcessLogEntry, ProcessLogsResponse, ProcessInputRequest, ProcessInputResponse, - ProcessSignalQuery, ProcessTerminalResizeRequest, ProcessTerminalResizeResponse, - AcpPostQuery, - AcpServerInfo, - AcpServerListResponse, - McpConfigQuery, - SkillsConfigQuery, + // Config McpServerConfig, + McpCommand, + McpRemoteTransport, + McpOAuthConfig, + McpOAuthConfigOrDisabled, SkillsConfig, SkillSource, + // ACP + AcpServerInfo, + AcpServerListResponse, + AcpEnvelope, + // Errors ProblemDetails, - ErrorType, - AcpEnvelope + ErrorType ) ), tags( - (name = "v1", description = "ACP proxy v1 API") + (name = "health", description = "Service health checks"), + (name = "desktop", description = "Virtual desktop runtime management, input control, screenshots, recordings, and streaming"), + (name = "agents", description = "Agent discovery, status, and installation"), + (name = "filesystem", description = "Sandbox filesystem operations: read, write, delete, move, and batch upload"), + (name = "processes", description = "Process lifecycle management, logs, input, and terminal access"), + (name = "config", description = "Project-scoped configuration for MCP servers and skills"), + (name = "acp", description = "Agent Client Protocol proxy for JSON-RPC communication with agents") ), modifiers(&ServerAddon) )] @@ -586,7 +598,24 @@ struct ServerAddon; impl Modify for ServerAddon { fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) { - openapi.servers = Some(vec![utoipa::openapi::Server::new("http://localhost:2468")]); + openapi.info.title = "Sandbox Agent".to_string(); + openapi.info.description = Some( + "HTTP API for Sandbox Agent, a universal runtime for AI coding agents. \ + Provides sandbox filesystem, process, desktop, and configuration management \ + alongside an ACP proxy for agent communication." + .to_string(), + ); + if let Some(ref mut license) = openapi.info.license { + license.url = Some("https://www.apache.org/licenses/LICENSE-2.0".to_string()); + } + openapi.info.contact = Some(utoipa::openapi::ContactBuilder::new() + .name(Some("Sandbox Agent")) + .url(Some("https://sandboxagent.dev")) + .build()); + openapi.servers = Some(vec![utoipa::openapi::ServerBuilder::new() + .url("http://localhost:2468") + .description(Some("Local development server")) + .build()]); } } @@ -634,10 +663,14 @@ async fn get_root() -> Json { })) } +/// Check service health. +/// +/// Returns a simple health status indicating the service is running and +/// reachable. #[utoipa::path( get, path = "/v1/health", - tag = "v1", + tag = "health", responses( (status = 200, description = "Service health response", body = HealthResponse) ) @@ -655,7 +688,7 @@ async fn get_v1_health() -> Json { #[utoipa::path( get, path = "/v1/desktop/status", - tag = "v1", + tag = "desktop", responses( (status = 200, description = "Desktop runtime status", body = DesktopStatusResponse), (status = 401, description = "Authentication required", body = ProblemDetails) @@ -674,7 +707,7 @@ async fn get_v1_desktop_status( #[utoipa::path( post, path = "/v1/desktop/start", - tag = "v1", + tag = "desktop", request_body = DesktopStartRequest, responses( (status = 200, description = "Desktop runtime status after start", body = DesktopStatusResponse), @@ -699,7 +732,7 @@ async fn post_v1_desktop_start( #[utoipa::path( post, path = "/v1/desktop/stop", - tag = "v1", + tag = "desktop", responses( (status = 200, description = "Desktop runtime status after stop", body = DesktopStatusResponse), (status = 409, description = "Desktop runtime is already transitioning", body = ProblemDetails) @@ -719,10 +752,10 @@ async fn post_v1_desktop_stop( #[utoipa::path( get, path = "/v1/desktop/screenshot", - tag = "v1", + tag = "desktop", params(DesktopScreenshotQuery), responses( - (status = 200, description = "Desktop screenshot as image bytes"), + (status = 200, description = "Desktop screenshot as image bytes", body = String, content_type = "image/png"), (status = 400, description = "Invalid screenshot query", body = ProblemDetails), (status = 409, description = "Desktop runtime is not ready", body = ProblemDetails), (status = 502, description = "Desktop runtime health or screenshot capture failed", body = ProblemDetails) @@ -747,10 +780,10 @@ async fn get_v1_desktop_screenshot( #[utoipa::path( get, path = "/v1/desktop/screenshot/region", - tag = "v1", + tag = "desktop", params(DesktopRegionScreenshotQuery), responses( - (status = 200, description = "Desktop screenshot region as image bytes"), + (status = 200, description = "Desktop screenshot region as image bytes", body = String, content_type = "image/png"), (status = 400, description = "Invalid screenshot region", body = ProblemDetails), (status = 409, description = "Desktop runtime is not ready", body = ProblemDetails), (status = 502, description = "Desktop runtime health or screenshot capture failed", body = ProblemDetails) @@ -774,7 +807,7 @@ async fn get_v1_desktop_screenshot_region( #[utoipa::path( get, path = "/v1/desktop/mouse/position", - tag = "v1", + tag = "desktop", responses( (status = 200, description = "Desktop mouse position", body = DesktopMousePositionResponse), (status = 409, description = "Desktop runtime is not ready", body = ProblemDetails), @@ -795,7 +828,7 @@ async fn get_v1_desktop_mouse_position( #[utoipa::path( post, path = "/v1/desktop/mouse/move", - tag = "v1", + tag = "desktop", request_body = DesktopMouseMoveRequest, responses( (status = 200, description = "Desktop mouse position after move", body = DesktopMousePositionResponse), @@ -819,7 +852,7 @@ async fn post_v1_desktop_mouse_move( #[utoipa::path( post, path = "/v1/desktop/mouse/click", - tag = "v1", + tag = "desktop", request_body = DesktopMouseClickRequest, responses( (status = 200, description = "Desktop mouse position after click", body = DesktopMousePositionResponse), @@ -843,7 +876,7 @@ async fn post_v1_desktop_mouse_click( #[utoipa::path( post, path = "/v1/desktop/mouse/down", - tag = "v1", + tag = "desktop", request_body = DesktopMouseDownRequest, responses( (status = 200, description = "Desktop mouse position after button press", body = DesktopMousePositionResponse), @@ -867,7 +900,7 @@ async fn post_v1_desktop_mouse_down( #[utoipa::path( post, path = "/v1/desktop/mouse/up", - tag = "v1", + tag = "desktop", request_body = DesktopMouseUpRequest, responses( (status = 200, description = "Desktop mouse position after button release", body = DesktopMousePositionResponse), @@ -891,7 +924,7 @@ async fn post_v1_desktop_mouse_up( #[utoipa::path( post, path = "/v1/desktop/mouse/drag", - tag = "v1", + tag = "desktop", request_body = DesktopMouseDragRequest, responses( (status = 200, description = "Desktop mouse position after drag", body = DesktopMousePositionResponse), @@ -915,7 +948,7 @@ async fn post_v1_desktop_mouse_drag( #[utoipa::path( post, path = "/v1/desktop/mouse/scroll", - tag = "v1", + tag = "desktop", request_body = DesktopMouseScrollRequest, responses( (status = 200, description = "Desktop mouse position after scroll", body = DesktopMousePositionResponse), @@ -939,7 +972,7 @@ async fn post_v1_desktop_mouse_scroll( #[utoipa::path( post, path = "/v1/desktop/keyboard/type", - tag = "v1", + tag = "desktop", request_body = DesktopKeyboardTypeRequest, responses( (status = 200, description = "Desktop keyboard action result", body = DesktopActionResponse), @@ -963,7 +996,7 @@ async fn post_v1_desktop_keyboard_type( #[utoipa::path( post, path = "/v1/desktop/keyboard/press", - tag = "v1", + tag = "desktop", request_body = DesktopKeyboardPressRequest, responses( (status = 200, description = "Desktop keyboard action result", body = DesktopActionResponse), @@ -987,7 +1020,7 @@ async fn post_v1_desktop_keyboard_press( #[utoipa::path( post, path = "/v1/desktop/keyboard/down", - tag = "v1", + tag = "desktop", request_body = DesktopKeyboardDownRequest, responses( (status = 200, description = "Desktop keyboard action result", body = DesktopActionResponse), @@ -1011,7 +1044,7 @@ async fn post_v1_desktop_keyboard_down( #[utoipa::path( post, path = "/v1/desktop/keyboard/up", - tag = "v1", + tag = "desktop", request_body = DesktopKeyboardUpRequest, responses( (status = 200, description = "Desktop keyboard action result", body = DesktopActionResponse), @@ -1035,7 +1068,7 @@ async fn post_v1_desktop_keyboard_up( #[utoipa::path( get, path = "/v1/desktop/display/info", - tag = "v1", + tag = "desktop", responses( (status = 200, description = "Desktop display information", body = DesktopDisplayInfoResponse), (status = 409, description = "Desktop runtime is not ready", body = ProblemDetails), @@ -1056,7 +1089,7 @@ async fn get_v1_desktop_display_info( #[utoipa::path( get, path = "/v1/desktop/windows", - tag = "v1", + tag = "desktop", responses( (status = 200, description = "Visible desktop windows", body = DesktopWindowListResponse), (status = 409, description = "Desktop runtime is not ready", body = ProblemDetails), @@ -1076,7 +1109,7 @@ async fn get_v1_desktop_windows( #[utoipa::path( get, path = "/v1/desktop/windows/focused", - tag = "v1", + tag = "desktop", responses( (status = 200, description = "Focused window info", body = DesktopWindowInfo), (status = 404, description = "No window is focused", body = ProblemDetails), @@ -1096,7 +1129,7 @@ async fn get_v1_desktop_windows_focused( #[utoipa::path( post, path = "/v1/desktop/windows/{id}/focus", - tag = "v1", + tag = "desktop", params( ("id" = String, Path, description = "X11 window ID") ), @@ -1120,7 +1153,7 @@ async fn post_v1_desktop_window_focus( #[utoipa::path( post, path = "/v1/desktop/windows/{id}/move", - tag = "v1", + tag = "desktop", params( ("id" = String, Path, description = "X11 window ID") ), @@ -1146,7 +1179,7 @@ async fn post_v1_desktop_window_move( #[utoipa::path( post, path = "/v1/desktop/windows/{id}/resize", - tag = "v1", + tag = "desktop", params( ("id" = String, Path, description = "X11 window ID") ), @@ -1172,7 +1205,7 @@ async fn post_v1_desktop_window_resize( #[utoipa::path( get, path = "/v1/desktop/clipboard", - tag = "v1", + tag = "desktop", params(DesktopClipboardQuery), responses( (status = 200, description = "Clipboard contents", body = DesktopClipboardResponse), @@ -1197,7 +1230,7 @@ async fn get_v1_desktop_clipboard( #[utoipa::path( post, path = "/v1/desktop/clipboard", - tag = "v1", + tag = "desktop", request_body = DesktopClipboardWriteRequest, responses( (status = 200, description = "Clipboard updated", body = DesktopActionResponse), @@ -1220,7 +1253,7 @@ async fn post_v1_desktop_clipboard( #[utoipa::path( post, path = "/v1/desktop/launch", - tag = "v1", + tag = "desktop", request_body = DesktopLaunchRequest, responses( (status = 200, description = "Application launched", body = DesktopLaunchResponse), @@ -1242,7 +1275,7 @@ async fn post_v1_desktop_launch( #[utoipa::path( post, path = "/v1/desktop/open", - tag = "v1", + tag = "desktop", request_body = DesktopOpenRequest, responses( (status = 200, description = "Target opened", body = DesktopOpenResponse), @@ -1264,7 +1297,7 @@ async fn post_v1_desktop_open( #[utoipa::path( post, path = "/v1/desktop/recording/start", - tag = "v1", + tag = "desktop", request_body = DesktopRecordingStartRequest, responses( (status = 200, description = "Desktop recording started", body = DesktopRecordingInfo), @@ -1287,7 +1320,7 @@ async fn post_v1_desktop_recording_start( #[utoipa::path( post, path = "/v1/desktop/recording/stop", - tag = "v1", + tag = "desktop", responses( (status = 200, description = "Desktop recording stopped", body = DesktopRecordingInfo), (status = 409, description = "No active desktop recording", body = ProblemDetails), @@ -1307,7 +1340,7 @@ async fn post_v1_desktop_recording_stop( #[utoipa::path( get, path = "/v1/desktop/recordings", - tag = "v1", + tag = "desktop", responses( (status = 200, description = "Desktop recordings", body = DesktopRecordingListResponse), (status = 502, description = "Desktop recordings query failed", body = ProblemDetails) @@ -1326,7 +1359,7 @@ async fn get_v1_desktop_recordings( #[utoipa::path( get, path = "/v1/desktop/recordings/{id}", - tag = "v1", + tag = "desktop", params( ("id" = String, Path, description = "Desktop recording ID") ), @@ -1349,12 +1382,12 @@ async fn get_v1_desktop_recording( #[utoipa::path( get, path = "/v1/desktop/recordings/{id}/download", - tag = "v1", + tag = "desktop", params( ("id" = String, Path, description = "Desktop recording ID") ), responses( - (status = 200, description = "Desktop recording as MP4 bytes"), + (status = 200, description = "Desktop recording as MP4 bytes", body = String, content_type = "video/mp4"), (status = 404, description = "Unknown desktop recording", body = ProblemDetails) ) )] @@ -1377,7 +1410,7 @@ async fn get_v1_desktop_recording_download( #[utoipa::path( delete, path = "/v1/desktop/recordings/{id}", - tag = "v1", + tag = "desktop", params( ("id" = String, Path, description = "Desktop recording ID") ), @@ -1401,7 +1434,7 @@ async fn delete_v1_desktop_recording( #[utoipa::path( post, path = "/v1/desktop/stream/start", - tag = "v1", + tag = "desktop", responses( (status = 200, description = "Desktop streaming started", body = DesktopStreamStatusResponse) ) @@ -1418,7 +1451,7 @@ async fn post_v1_desktop_stream_start( #[utoipa::path( post, path = "/v1/desktop/stream/stop", - tag = "v1", + tag = "desktop", responses( (status = 200, description = "Desktop streaming stopped", body = DesktopStreamStatusResponse) ) @@ -1435,7 +1468,7 @@ async fn post_v1_desktop_stream_stop( #[utoipa::path( get, path = "/v1/desktop/stream/status", - tag = "v1", + tag = "desktop", responses( (status = 200, description = "Desktop stream status", body = DesktopStreamStatusResponse) ) @@ -1454,7 +1487,7 @@ async fn get_v1_desktop_stream_status( #[utoipa::path( get, path = "/v1/desktop/stream/signaling", - tag = "v1", + tag = "desktop", params( ("access_token" = Option, Query, description = "Bearer token alternative for WS auth") ), @@ -1475,16 +1508,20 @@ async fn get_v1_desktop_stream_ws( .into_response()) } +/// List available agents. +/// +/// Returns all known agents with their installation status, credential +/// availability, and optionally their version and configuration options. #[utoipa::path( get, path = "/v1/agents", - tag = "v1", + tag = "agents", params( ("config" = Option, Query, description = "When true, include version/path/configOptions (slower)"), ("no_cache" = Option, Query, description = "When true, bypass version cache") ), responses( - (status = 200, description = "List of v1 agents", body = AgentListResponse), + (status = 200, description = "List of agents", body = AgentListResponse), (status = 401, description = "Authentication required", body = ProblemDetails) ) )] @@ -1610,10 +1647,14 @@ async fn get_v1_agents( Ok(Json(AgentListResponse { agents })) } +/// Get a single agent. +/// +/// Returns detailed information for one agent, including installation status, +/// credential availability, and optionally version and configuration options. #[utoipa::path( get, path = "/v1/agents/{agent}", - tag = "v1", + tag = "agents", params( ("agent" = String, Path, description = "Agent id"), ("config" = Option, Query, description = "When true, include version/path/configOptions (slower)"), @@ -1790,10 +1831,14 @@ async fn get_v1_agent( // } // } +/// Install or reinstall an agent. +/// +/// Downloads and installs the agent binary and its process wrapper. Returns +/// the list of installed artifacts and whether the agent was already present. #[utoipa::path( post, path = "/v1/agents/{agent}/install", - tag = "v1", + tag = "agents", params( ("agent" = String, Path, description = "Agent id") ), @@ -1841,10 +1886,14 @@ async fn post_v1_agent_install( Ok(Json(map_install_result(install_result))) } +/// List directory entries. +/// +/// Returns the immediate children of the specified directory, including +/// file names, sizes, types, and last-modified timestamps. #[utoipa::path( get, path = "/v1/fs/entries", - tag = "v1", + tag = "filesystem", params( ("path" = Option, Query, description = "Directory path") ), @@ -1894,15 +1943,18 @@ async fn get_v1_fs_entries( Ok(Json(entries)) } +/// Read a file. +/// +/// Returns the raw bytes of the file at the given path. #[utoipa::path( get, path = "/v1/fs/file", - tag = "v1", + tag = "filesystem", params( ("path" = String, Query, description = "File path") ), responses( - (status = 200, description = "File content") + (status = 200, description = "File content", body = String, content_type = "application/octet-stream") ) )] async fn get_v1_fs_file(Query(query): Query) -> Result { @@ -1922,14 +1974,18 @@ async fn get_v1_fs_file(Query(query): Query) -> Result, Query, description = "Delete directory recursively") @@ -1980,10 +2040,14 @@ async fn delete_v1_fs_entry( })) } +/// Create a directory. +/// +/// Creates the directory at the given path, including any missing parent +/// directories. #[utoipa::path( post, path = "/v1/fs/mkdir", - tag = "v1", + tag = "filesystem", params( ("path" = String, Query, description = "Directory path") ), @@ -2001,10 +2065,14 @@ async fn post_v1_fs_mkdir( })) } +/// Move or rename a file or directory. +/// +/// Moves the entry from one path to another. Set `overwrite` to true to +/// replace an existing destination. #[utoipa::path( post, path = "/v1/fs/move", - tag = "v1", + tag = "filesystem", request_body = FsMoveRequest, responses( (status = 200, description = "Move result", body = FsMoveResponse) @@ -2042,10 +2110,14 @@ async fn post_v1_fs_move( })) } +/// Get file or directory metadata. +/// +/// Returns the type, size, and last-modified timestamp for the entry at +/// the given path. #[utoipa::path( get, path = "/v1/fs/stat", - tag = "v1", + tag = "filesystem", params( ("path" = String, Query, description = "Path to stat") ), @@ -2073,14 +2145,18 @@ async fn get_v1_fs_stat(Query(query): Query) -> Result })) } +/// Upload files as a tar archive. +/// +/// Accepts a tar archive body, extracts its contents into the destination +/// directory, and returns the list of extracted paths. #[utoipa::path( post, path = "/v1/fs/upload-batch", - tag = "v1", + tag = "filesystem", params( ("path" = Option, Query, description = "Destination path") ), - request_body(content = String, description = "tar archive body"), + request_body(content = String, content_type = "application/x-tar", description = "tar archive body"), responses( (status = 200, description = "Upload/extract result", body = FsUploadBatchResponse) ) @@ -2157,7 +2233,7 @@ async fn post_v1_fs_upload_batch( #[utoipa::path( get, path = "/v1/processes/config", - tag = "v1", + tag = "processes", responses( (status = 200, description = "Current runtime process config", body = ProcessConfig), (status = 501, description = "Process API unsupported on this platform", body = ProblemDetails) @@ -2181,7 +2257,7 @@ async fn get_v1_processes_config( #[utoipa::path( post, path = "/v1/processes/config", - tag = "v1", + tag = "processes", request_body = ProcessConfig, responses( (status = 200, description = "Updated runtime process config", body = ProcessConfig), @@ -2211,7 +2287,7 @@ async fn post_v1_processes_config( #[utoipa::path( post, path = "/v1/processes", - tag = "v1", + tag = "processes", request_body = ProcessCreateRequest, responses( (status = 200, description = "Started process", body = ProcessInfo), @@ -2252,7 +2328,7 @@ async fn post_v1_processes( #[utoipa::path( post, path = "/v1/processes/run", - tag = "v1", + tag = "processes", request_body = ProcessRunRequest, responses( (status = 200, description = "One-off command result", body = ProcessRunResponse), @@ -2298,7 +2374,7 @@ async fn post_v1_processes_run( #[utoipa::path( get, path = "/v1/processes", - tag = "v1", + tag = "processes", params(ProcessListQuery), responses( (status = 200, description = "List processes", body = ProcessListResponse), @@ -2329,7 +2405,7 @@ async fn get_v1_processes( #[utoipa::path( get, path = "/v1/processes/{id}", - tag = "v1", + tag = "processes", params( ("id" = String, Path, description = "Process ID") ), @@ -2358,7 +2434,7 @@ async fn get_v1_process( #[utoipa::path( post, path = "/v1/processes/{id}/stop", - tag = "v1", + tag = "processes", params( ("id" = String, Path, description = "Process ID"), ("waitMs" = Option, Query, description = "Wait up to N ms for process to exit") @@ -2392,7 +2468,7 @@ async fn post_v1_process_stop( #[utoipa::path( post, path = "/v1/processes/{id}/kill", - tag = "v1", + tag = "processes", params( ("id" = String, Path, description = "Process ID"), ("waitMs" = Option, Query, description = "Wait up to N ms for process to exit") @@ -2426,7 +2502,7 @@ async fn post_v1_process_kill( #[utoipa::path( delete, path = "/v1/processes/{id}", - tag = "v1", + tag = "processes", params( ("id" = String, Path, description = "Process ID") ), @@ -2457,7 +2533,7 @@ async fn delete_v1_process( #[utoipa::path( get, path = "/v1/processes/{id}/logs", - tag = "v1", + tag = "processes", params( ("id" = String, Path, description = "Process ID"), ("stream" = Option, Query, description = "stdout|stderr|combined|pty"), @@ -2561,7 +2637,7 @@ async fn get_v1_process_logs( #[utoipa::path( post, path = "/v1/processes/{id}/input", - tag = "v1", + tag = "processes", params( ("id" = String, Path, description = "Process ID") ), @@ -2605,7 +2681,7 @@ async fn post_v1_process_input( #[utoipa::path( post, path = "/v1/processes/{id}/terminal/resize", - tag = "v1", + tag = "processes", params( ("id" = String, Path, description = "Process ID") ), @@ -2646,7 +2722,7 @@ async fn post_v1_process_terminal_resize( #[utoipa::path( get, path = "/v1/processes/{id}/terminal/ws", - tag = "v1", + tag = "processes", params( ("id" = String, Path, description = "Process ID"), ("access_token" = Option, Query, description = "Bearer token alternative for WS auth") @@ -2940,16 +3016,20 @@ async fn send_ws_error(socket: &mut WebSocket, message: &str) -> Result<(), ()> .await } +/// Get an MCP server configuration entry. +/// +/// Reads a single named MCP server entry from the project-scoped +/// configuration file in the given directory. #[utoipa::path( get, path = "/v1/config/mcp", - tag = "v1", + tag = "config", params( ("directory" = String, Query, description = "Target directory"), ("mcpName" = String, Query, description = "MCP entry name") ), responses( - (status = 200, description = "MCP entry", body = McpServerConfig), + (status = 200, description = "MCP server configuration entry", body = McpServerConfig), (status = 404, description = "Entry not found", body = ProblemDetails) ) )] @@ -2971,10 +3051,14 @@ async fn get_v1_config_mcp( Ok(Json(value)) } +/// Create or update an MCP server configuration entry. +/// +/// Writes a named MCP server entry into the project-scoped configuration +/// file, creating the file and parent directories as needed. #[utoipa::path( put, path = "/v1/config/mcp", - tag = "v1", + tag = "config", params( ("directory" = String, Query, description = "Target directory"), ("mcpName" = String, Query, description = "MCP entry name") @@ -2998,10 +3082,14 @@ async fn put_v1_config_mcp( Ok(StatusCode::NO_CONTENT) } +/// Delete an MCP server configuration entry. +/// +/// Removes a named MCP server entry from the project-scoped configuration +/// file. #[utoipa::path( delete, path = "/v1/config/mcp", - tag = "v1", + tag = "config", params( ("directory" = String, Query, description = "Target directory"), ("mcpName" = String, Query, description = "MCP entry name") @@ -3021,16 +3109,20 @@ async fn delete_v1_config_mcp(Query(query): Query) -> Result, Query, description = "Agent id required for first POST") @@ -3142,11 +3250,11 @@ async fn get_v1_acp_servers( responses( (status = 200, description = "JSON-RPC response envelope", body = AcpEnvelope), (status = 202, description = "JSON-RPC notification accepted"), - (status = 406, description = "Client does not accept JSON responses", body = ProblemDetails), - (status = 415, description = "Unsupported media type", body = ProblemDetails), (status = 400, description = "Invalid ACP envelope", body = ProblemDetails), (status = 404, description = "Unknown ACP server", body = ProblemDetails), + (status = 406, description = "Client does not accept JSON responses", body = ProblemDetails), (status = 409, description = "ACP server bound to different agent", body = ProblemDetails), + (status = 415, description = "Unsupported media type", body = ProblemDetails), (status = 504, description = "ACP agent process response timeout", body = ProblemDetails) ) )] @@ -3196,18 +3304,22 @@ async fn post_v1_acp( } } +/// Open an SSE stream for an ACP server. +/// +/// Returns a Server-Sent Events stream carrying JSON-RPC envelopes from +/// the agent process. Requires `Accept: text/event-stream`. #[utoipa::path( get, path = "/v1/acp/{server_id}", - tag = "v1", + tag = "acp", params( ("server_id" = String, Path, description = "Client-defined ACP server id") ), responses( - (status = 200, description = "SSE stream of ACP envelopes"), - (status = 406, description = "Client does not accept SSE responses", body = ProblemDetails), + (status = 200, description = "SSE stream of ACP envelopes", body = String, content_type = "text/event-stream"), + (status = 400, description = "Invalid request", body = ProblemDetails), (status = 404, description = "Unknown ACP server", body = ProblemDetails), - (status = 400, description = "Invalid request", body = ProblemDetails) + (status = 406, description = "Client does not accept SSE responses", body = ProblemDetails) ) )] async fn get_v1_acp( @@ -3232,10 +3344,13 @@ async fn get_v1_acp( )) } +/// Close an ACP server instance. +/// +/// Terminates the ACP proxy server and its associated agent process. #[utoipa::path( delete, path = "/v1/acp/{server_id}", - tag = "v1", + tag = "acp", params( ("server_id" = String, Path, description = "Client-defined ACP server id") ), diff --git a/server/packages/sandbox-agent/src/router/types.rs b/server/packages/sandbox-agent/src/router/types.rs index 218ad773..1e9dfab4 100644 --- a/server/packages/sandbox-agent/src/router/types.rs +++ b/server/packages/sandbox-agent/src/router/types.rs @@ -463,6 +463,7 @@ pub struct ProcessListResponse { #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema, IntoParams)] #[serde(rename_all = "camelCase")] pub struct ProcessListQuery { + /// Filter processes by owner (user, desktop, or system). #[serde(default, skip_serializing_if = "Option::is_none")] pub owner: Option, }