diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..96880e9
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,26 @@
+# Build outputs
+target/
+dist/
+build/
+
+# Dependencies
+node_modules/
+
+# Cache
+.cache/
+.turbo/
+*.tsbuildinfo
+
+# Environment
+.env
+.env.*
+
+# IDE
+.idea/
+.vscode/
+
+# OS
+.DS_Store
+
+# Git
+.git/
diff --git a/Cargo.toml b/Cargo.toml
index 65200b4..a55bdf4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,7 +3,7 @@ resolver = "2"
members = ["server/packages/*"]
[workspace.package]
-version = "0.1.0"
+version = "0.1.4-rc.7"
edition = "2021"
authors = [ "Rivet Gaming, LLC " ]
license = "Apache-2.0"
@@ -12,12 +12,12 @@ description = "Universal API for automatic coding agents in sandboxes. Supprots
[workspace.dependencies]
# Internal crates
-sandbox-agent = { version = "0.1.0", path = "server/packages/sandbox-agent" }
-sandbox-agent-error = { version = "0.1.0", path = "server/packages/error" }
-sandbox-agent-agent-management = { version = "0.1.0", path = "server/packages/agent-management" }
-sandbox-agent-agent-credentials = { version = "0.1.0", path = "server/packages/agent-credentials" }
-sandbox-agent-universal-agent-schema = { version = "0.1.0", path = "server/packages/universal-agent-schema" }
-sandbox-agent-extracted-agent-schemas = { version = "0.1.0", path = "server/packages/extracted-agent-schemas" }
+sandbox-agent = { version = "0.1.4-rc.7", path = "server/packages/sandbox-agent" }
+sandbox-agent-error = { version = "0.1.4-rc.7", path = "server/packages/error" }
+sandbox-agent-agent-management = { version = "0.1.4-rc.7", path = "server/packages/agent-management" }
+sandbox-agent-agent-credentials = { version = "0.1.4-rc.7", path = "server/packages/agent-credentials" }
+sandbox-agent-universal-agent-schema = { version = "0.1.4-rc.7", path = "server/packages/universal-agent-schema" }
+sandbox-agent-extracted-agent-schemas = { version = "0.1.4-rc.7", path = "server/packages/extracted-agent-schemas" }
# Serialization
serde = { version = "1.0", features = ["derive"] }
diff --git a/README.md b/README.md
index 9224bf1..1f90841 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@ The Sandbox Agent acts as a universal adapter between your client application an
|-----------|-------------|
| **Server** | Rust daemon (`sandbox-agent server`) exposing the HTTP + SSE API |
| **SDK** | TypeScript client with embedded and server modes |
-| **Inspector** | [inspect.sandboxagent.dev](https://inspect.sandboxagent.dev) for browsing sessions and events |
+| **Inspector** | Built-in UI at inspecting sessions and events |
| **CLI** | `sandbox-agent` (same binary, plus npm wrapper) mirrors the HTTP endpoints |
## Get Started
@@ -172,7 +172,7 @@ npx sandbox-agent --help
### Inspector
-Debug sessions and events with the [Inspector UI](https://inspect.sandboxagent.dev).
+Debug sessions and events with the built-in Inspector UI (e.g., `http://localhost:2468/ui/`).

diff --git a/docker/release/linux-x86_64.Dockerfile b/docker/release/linux-x86_64.Dockerfile
index f838b93..3831cb0 100644
--- a/docker/release/linux-x86_64.Dockerfile
+++ b/docker/release/linux-x86_64.Dockerfile
@@ -11,7 +11,7 @@ COPY frontend/packages/inspector/package.json ./frontend/packages/inspector/
COPY sdks/typescript/package.json ./sdks/typescript/
# Install dependencies
-RUN pnpm install --filter @anthropic-ai/sdk-inspector...
+RUN pnpm install --filter @sandbox-agent/inspector...
# Copy SDK source (with pre-generated types from docs/openapi.json)
COPY docs/openapi.json ./docs/
diff --git a/docker/release/macos-aarch64.Dockerfile b/docker/release/macos-aarch64.Dockerfile
index 29e4b32..ed5e79c 100644
--- a/docker/release/macos-aarch64.Dockerfile
+++ b/docker/release/macos-aarch64.Dockerfile
@@ -11,7 +11,7 @@ COPY frontend/packages/inspector/package.json ./frontend/packages/inspector/
COPY sdks/typescript/package.json ./sdks/typescript/
# Install dependencies
-RUN pnpm install --filter @anthropic-ai/sdk-inspector...
+RUN pnpm install --filter @sandbox-agent/inspector...
# Copy SDK source (with pre-generated types from docs/openapi.json)
COPY docs/openapi.json ./docs/
diff --git a/docker/release/macos-x86_64.Dockerfile b/docker/release/macos-x86_64.Dockerfile
index 5a33b15..34fce08 100644
--- a/docker/release/macos-x86_64.Dockerfile
+++ b/docker/release/macos-x86_64.Dockerfile
@@ -11,7 +11,7 @@ COPY frontend/packages/inspector/package.json ./frontend/packages/inspector/
COPY sdks/typescript/package.json ./sdks/typescript/
# Install dependencies
-RUN pnpm install --filter @anthropic-ai/sdk-inspector...
+RUN pnpm install --filter @sandbox-agent/inspector...
# Copy SDK source (with pre-generated types from docs/openapi.json)
COPY docs/openapi.json ./docs/
diff --git a/docker/release/windows.Dockerfile b/docker/release/windows.Dockerfile
index 2acabf2..914c955 100644
--- a/docker/release/windows.Dockerfile
+++ b/docker/release/windows.Dockerfile
@@ -11,7 +11,7 @@ COPY frontend/packages/inspector/package.json ./frontend/packages/inspector/
COPY sdks/typescript/package.json ./sdks/typescript/
# Install dependencies
-RUN pnpm install --filter @anthropic-ai/sdk-inspector...
+RUN pnpm install --filter @sandbox-agent/inspector...
# Copy SDK source (with pre-generated types from docs/openapi.json)
COPY docs/openapi.json ./docs/
diff --git a/docker/runtime/Dockerfile b/docker/runtime/Dockerfile
index fed9840..520aa79 100644
--- a/docker/runtime/Dockerfile
+++ b/docker/runtime/Dockerfile
@@ -13,7 +13,7 @@ COPY frontend/packages/inspector/package.json ./frontend/packages/inspector/
COPY sdks/typescript/package.json ./sdks/typescript/
# Install dependencies
-RUN pnpm install --filter @anthropic-ai/sdk-inspector...
+RUN pnpm install --filter @sandbox-agent/inspector...
# Copy SDK source (with pre-generated types from docs/openapi.json)
COPY docs/openapi.json ./docs/
diff --git a/docs/cli.mdx b/docs/cli.mdx
index 1d48812..855bd44 100644
--- a/docs/cli.mdx
+++ b/docs/cli.mdx
@@ -19,11 +19,10 @@ sandbox-agent server [OPTIONS]
| `-n, --no-token` | - | Disable authentication (local dev only) |
| `-H, --host ` | `127.0.0.1` | Host to bind to |
| `-p, --port ` | `2468` | Port to bind to |
-| `-O, --cors-allow-origin ` | - | Additional CORS origin (repeatable, cumulative with Inspector) |
+| `-O, --cors-allow-origin ` | - | CORS origin to allow (repeatable) |
| `-M, --cors-allow-method ` | all | CORS allowed method (repeatable) |
| `-A, --cors-allow-header ` | all | CORS allowed header (repeatable) |
| `-C, --cors-allow-credentials` | - | Enable CORS credentials |
-| `--no-inspector-cors` | - | Disable default Inspector CORS |
| `--no-telemetry` | - | Disable anonymous telemetry |
```bash
diff --git a/docs/cors.mdx b/docs/cors.mdx
index c9fe888..5e50888 100644
--- a/docs/cors.mdx
+++ b/docs/cors.mdx
@@ -9,47 +9,26 @@ When calling the Sandbox Agent server from a browser, CORS (Cross-Origin Resourc
## Default Behavior
-By default, the server allows CORS requests from the [Inspector](https://inspect.sandboxagent.dev):
+By default, no CORS origins are allowed. You must explicitly specify origins for browser-based applications:
```bash
-# Inspector CORS is enabled by default
-sandbox-agent server --token "$SANDBOX_TOKEN"
-```
-
-This allows you to use the hosted Inspector to connect to any running Sandbox Agent server without additional configuration.
-
-## Adding Origins
-
-Use `--cors-allow-origin` to allow additional origins. These are **cumulative** with the default Inspector origin:
-
-```bash
-# Allows both Inspector AND localhost:5173
sandbox-agent server \
--token "$SANDBOX_TOKEN" \
--cors-allow-origin "http://localhost:5173"
```
+
+The built-in Inspector UI at `/ui/` is served from the same origin as the server, so it does not require CORS configuration.
+
+
## Options
| Flag | Description |
|------|-------------|
-| `--cors-allow-origin` | Additional origins to allow (cumulative with Inspector) |
+| `--cors-allow-origin` | Origins to allow |
| `--cors-allow-method` | HTTP methods to allow (defaults to all if not specified) |
| `--cors-allow-header` | Headers to allow (defaults to all if not specified) |
| `--cors-allow-credentials` | Allow credentials (cookies, authorization headers) |
-| `--no-inspector-cors` | Disable the default Inspector origin |
-
-## Disabling Inspector CORS
-
-To disable the default Inspector origin and only allow explicitly specified origins:
-
-```bash
-# Only allows localhost:5173, not Inspector
-sandbox-agent server \
- --token "$SANDBOX_TOKEN" \
- --no-inspector-cors \
- --cors-allow-origin "http://localhost:5173"
-```
## Multiple Origins
diff --git a/docs/inspector.mdx b/docs/inspector.mdx
index b095ff2..8e80c22 100644
--- a/docs/inspector.mdx
+++ b/docs/inspector.mdx
@@ -12,9 +12,9 @@ The Inspector is a web-based GUI for debugging and inspecting Sandbox Agent sess
## Open the Inspector
-Visit [inspect.sandboxagent.dev](https://inspect.sandboxagent.dev) and enter your server URL and token to connect.
+The Inspector UI is served at `/ui/` on your sandbox-agent server. For example, if your server is running at `http://localhost:2468`, open `http://localhost:2468/ui/` in your browser.
-You can also generate a pre-filled Inspector URL from the TypeScript SDK:
+You can also generate a pre-filled Inspector URL with authentication from the TypeScript SDK:
```typescript
import { buildInspectorUrl } from "sandbox-agent";
@@ -24,7 +24,7 @@ const url = buildInspectorUrl({
token: process.env.SANDBOX_TOKEN,
});
console.log(url);
-// https://inspect.sandboxagent.dev?url=http%3A%2F%2F127.0.0.1%3A2468&token=...
+// http://127.0.0.1:2468/ui/?token=...
```
## Features
diff --git a/docs/openapi.json b/docs/openapi.json
index 5fa7897..a003943 100644
--- a/docs/openapi.json
+++ b/docs/openapi.json
@@ -10,7 +10,7 @@
"license": {
"name": "Apache-2.0"
},
- "version": "0.1.0"
+ "version": "0.1.4-rc.7"
},
"servers": [
{
diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx
index b8bce19..4902344 100644
--- a/docs/quickstart.mdx
+++ b/docs/quickstart.mdx
@@ -290,7 +290,7 @@ icon: "rocket"
- Open the [Inspector UI](https://inspect.sandboxagent.dev) to inspect session state using a GUI.
+ Open the Inspector UI at `/ui/` on your server (e.g., `http://localhost:2468/ui/`) to inspect session state using a GUI.
diff --git a/docs/sdks/typescript.mdx b/docs/sdks/typescript.mdx
index cbaf598..7ebc165 100644
--- a/docs/sdks/typescript.mdx
+++ b/docs/sdks/typescript.mdx
@@ -132,7 +132,7 @@ const url = buildInspectorUrl({
headers: { "X-Custom-Header": "value" },
});
console.log(url);
-// https://inspect.sandboxagent.dev?url=https%3A%2F%2Fyour-sandbox-agent.example.com&token=...&headers=...
+// https://your-sandbox-agent.example.com/ui/?token=...&headers=...
```
Parameters:
diff --git a/examples/shared/src/sandbox-agent-client.ts b/examples/shared/src/sandbox-agent-client.ts
index 2ccd8f6..ff80f3b 100644
--- a/examples/shared/src/sandbox-agent-client.ts
+++ b/examples/shared/src/sandbox-agent-client.ts
@@ -18,8 +18,6 @@ export function ensureUrl(rawUrl: string): string {
return `https://${rawUrl}`;
}
-const INSPECTOR_URL = "https://inspect.sandboxagent.dev";
-
export function buildInspectorUrl({
baseUrl,
token,
@@ -30,14 +28,15 @@ export function buildInspectorUrl({
headers?: Record;
}): string {
const normalized = normalizeBaseUrl(ensureUrl(baseUrl));
- const params = new URLSearchParams({ url: normalized });
+ const params = new URLSearchParams();
if (token) {
params.set("token", token);
}
if (headers && Object.keys(headers).length > 0) {
params.set("headers", JSON.stringify(headers));
}
- return `${INSPECTOR_URL}?${params.toString()}`;
+ const queryString = params.toString();
+ return `${normalized}/ui/${queryString ? `?${queryString}` : ""}`;
}
export function logInspectorUrl({
@@ -432,11 +431,11 @@ export async function runPrompt(options: RunPromptOptions): Promise {
}
}
- // Print text deltas
- if (event.type === "item.delta") {
+ // Print text deltas (only during assistant turn)
+ if (event.type === "item.delta" && isThinking) {
const delta = (event.data as any)?.delta;
if (delta) {
- if (isThinking && !hasStartedOutput) {
+ if (!hasStartedOutput) {
process.stdout.write("\r\x1b[K"); // Clear line
hasStartedOutput = true;
}
diff --git a/frontend/packages/website/src/components/Inspector.tsx b/frontend/packages/website/src/components/Inspector.tsx
index c0c7b71..7e29185 100644
--- a/frontend/packages/website/src/components/Inspector.tsx
+++ b/frontend/packages/website/src/components/Inspector.tsx
@@ -1,7 +1,5 @@
'use client';
-import { ArrowRight } from 'lucide-react';
-
export function Inspector() {
return (
@@ -13,23 +11,13 @@ export function Inspector() {
Inspect sessions, view event payloads, and troubleshoot without writing code.
-
);
diff --git a/sdks/cli/package.json b/sdks/cli/package.json
index e0ddb18..6001f31 100644
--- a/sdks/cli/package.json
+++ b/sdks/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@sandbox-agent/cli",
- "version": "0.1.0",
+ "version": "0.1.4-rc.7",
"description": "CLI for sandbox-agent - run AI coding agents in sandboxes",
"license": "Apache-2.0",
"repository": {
diff --git a/sdks/cli/platforms/darwin-arm64/package.json b/sdks/cli/platforms/darwin-arm64/package.json
index 51ccb8f..07d5628 100644
--- a/sdks/cli/platforms/darwin-arm64/package.json
+++ b/sdks/cli/platforms/darwin-arm64/package.json
@@ -1,6 +1,6 @@
{
"name": "@sandbox-agent/cli-darwin-arm64",
- "version": "0.1.0",
+ "version": "0.1.4-rc.7",
"description": "sandbox-agent CLI binary for macOS ARM64",
"license": "Apache-2.0",
"repository": {
diff --git a/sdks/cli/platforms/darwin-x64/package.json b/sdks/cli/platforms/darwin-x64/package.json
index 34b6a81..414cf04 100644
--- a/sdks/cli/platforms/darwin-x64/package.json
+++ b/sdks/cli/platforms/darwin-x64/package.json
@@ -1,6 +1,6 @@
{
"name": "@sandbox-agent/cli-darwin-x64",
- "version": "0.1.0",
+ "version": "0.1.4-rc.7",
"description": "sandbox-agent CLI binary for macOS x64",
"license": "Apache-2.0",
"repository": {
diff --git a/sdks/cli/platforms/linux-x64/package.json b/sdks/cli/platforms/linux-x64/package.json
index 9d330e5..ef8ac77 100644
--- a/sdks/cli/platforms/linux-x64/package.json
+++ b/sdks/cli/platforms/linux-x64/package.json
@@ -1,6 +1,6 @@
{
"name": "@sandbox-agent/cli-linux-x64",
- "version": "0.1.0",
+ "version": "0.1.4-rc.7",
"description": "sandbox-agent CLI binary for Linux x64",
"license": "Apache-2.0",
"repository": {
diff --git a/sdks/cli/platforms/win32-x64/package.json b/sdks/cli/platforms/win32-x64/package.json
index d40f4c3..eb2b47d 100644
--- a/sdks/cli/platforms/win32-x64/package.json
+++ b/sdks/cli/platforms/win32-x64/package.json
@@ -1,6 +1,6 @@
{
"name": "@sandbox-agent/cli-win32-x64",
- "version": "0.1.0",
+ "version": "0.1.4-rc.7",
"description": "sandbox-agent CLI binary for Windows x64",
"license": "Apache-2.0",
"repository": {
diff --git a/sdks/typescript/package.json b/sdks/typescript/package.json
index cc08c50..b6cde13 100644
--- a/sdks/typescript/package.json
+++ b/sdks/typescript/package.json
@@ -1,6 +1,6 @@
{
"name": "sandbox-agent",
- "version": "0.1.0",
+ "version": "0.1.4-rc.7",
"description": "Universal API for automatic coding agents in sandboxes. Supprots Claude Code, Codex, OpenCode, and Amp.",
"license": "Apache-2.0",
"repository": {
diff --git a/sdks/typescript/src/inspector.ts b/sdks/typescript/src/inspector.ts
index 9c448e2..dfc830d 100644
--- a/sdks/typescript/src/inspector.ts
+++ b/sdks/typescript/src/inspector.ts
@@ -1,5 +1,3 @@
-const INSPECTOR_URL = "https://inspect.sandboxagent.dev";
-
export interface InspectorUrlOptions {
/**
* Base URL of the sandbox-agent server.
@@ -18,15 +16,17 @@ export interface InspectorUrlOptions {
/**
* Builds a URL to the sandbox-agent inspector UI with the given connection parameters.
+ * The inspector UI is served at /ui/ on the sandbox-agent server.
*/
export function buildInspectorUrl(options: InspectorUrlOptions): string {
const normalized = options.baseUrl.replace(/\/+$/, "");
- const params = new URLSearchParams({ url: normalized });
+ const params = new URLSearchParams();
if (options.token) {
params.set("token", options.token);
}
if (options.headers && Object.keys(options.headers).length > 0) {
params.set("headers", JSON.stringify(options.headers));
}
- return `${INSPECTOR_URL}?${params.toString()}`;
+ const queryString = params.toString();
+ return `${normalized}/ui/${queryString ? `?${queryString}` : ""}`;
}
diff --git a/server/packages/agent-credentials/src/lib.rs b/server/packages/agent-credentials/src/lib.rs
index 64d3d4b..1192241 100644
--- a/server/packages/agent-credentials/src/lib.rs
+++ b/server/packages/agent-credentials/src/lib.rs
@@ -43,7 +43,9 @@ impl CredentialExtractionOptions {
}
}
-pub fn extract_claude_credentials(options: &CredentialExtractionOptions) -> Option {
+pub fn extract_claude_credentials(
+ options: &CredentialExtractionOptions,
+) -> Option {
let home_dir = options.home_dir.clone().unwrap_or_else(default_home_dir);
let include_oauth = options.include_oauth;
@@ -88,8 +90,7 @@ pub fn extract_claude_credentials(options: &CredentialExtractionOptions) -> Opti
};
let access = read_string_field(&data, &["claudeAiOauth", "accessToken"]);
if let Some(token) = access {
- if let Some(expires_at) =
- read_string_field(&data, &["claudeAiOauth", "expiresAt"])
+ if let Some(expires_at) = read_string_field(&data, &["claudeAiOauth", "expiresAt"])
{
if is_expired_rfc3339(&expires_at) {
continue;
@@ -108,7 +109,9 @@ pub fn extract_claude_credentials(options: &CredentialExtractionOptions) -> Opti
None
}
-pub fn extract_codex_credentials(options: &CredentialExtractionOptions) -> Option {
+pub fn extract_codex_credentials(
+ options: &CredentialExtractionOptions,
+) -> Option {
let home_dir = options.home_dir.clone().unwrap_or_else(default_home_dir);
let include_oauth = options.include_oauth;
let path = home_dir.join(".codex").join("auth.json");
@@ -165,18 +168,18 @@ pub fn extract_opencode_credentials(options: &CredentialExtractionOptions) -> Ex
None => continue,
};
- let auth_type = config
- .get("type")
- .and_then(Value::as_str)
- .unwrap_or("");
+ let auth_type = config.get("type").and_then(Value::as_str).unwrap_or("");
let credentials = if auth_type == "api" {
- config.get("key").and_then(Value::as_str).map(|key| ProviderCredentials {
- api_key: key.to_string(),
- source: "opencode".to_string(),
- auth_type: AuthType::ApiKey,
- provider: provider_name.to_string(),
- })
+ config
+ .get("key")
+ .and_then(Value::as_str)
+ .map(|key| ProviderCredentials {
+ api_key: key.to_string(),
+ source: "opencode".to_string(),
+ auth_type: AuthType::ApiKey,
+ provider: provider_name.to_string(),
+ })
} else if auth_type == "oauth" && include_oauth {
let expires = config.get("expires").and_then(Value::as_i64);
if let Some(expires) = expires {
@@ -214,7 +217,9 @@ pub fn extract_opencode_credentials(options: &CredentialExtractionOptions) -> Ex
} else if provider_name == "openai" {
result.openai = Some(credentials.clone());
} else {
- result.other.insert(provider_name.to_string(), credentials.clone());
+ result
+ .other
+ .insert(provider_name.to_string(), credentials.clone());
}
}
}
@@ -222,7 +227,9 @@ pub fn extract_opencode_credentials(options: &CredentialExtractionOptions) -> Ex
result
}
-pub fn extract_amp_credentials(options: &CredentialExtractionOptions) -> Option {
+pub fn extract_amp_credentials(
+ options: &CredentialExtractionOptions,
+) -> Option {
let home_dir = options.home_dir.clone().unwrap_or_else(default_home_dir);
let path = home_dir.join(".amp").join("config.json");
let data = read_json_file(&path)?;
diff --git a/server/packages/agent-management/src/agents.rs b/server/packages/agent-management/src/agents.rs
index 65c7051..e110c96 100644
--- a/server/packages/agent-management/src/agents.rs
+++ b/server/packages/agent-management/src/agents.rs
@@ -3,15 +3,7 @@ use std::fmt;
use std::fs;
use std::io::{self, BufRead, BufReader, Read, Write};
use std::path::{Path, PathBuf};
-use std::process::{
- Child,
- ChildStderr,
- ChildStdin,
- ChildStdout,
- Command,
- ExitStatus,
- Stdio,
-};
+use std::process::{Child, ChildStderr, ChildStdin, ChildStdout, Command, ExitStatus, Stdio};
use std::time::{Duration, Instant};
use flate2::read::GzDecoder;
@@ -124,17 +116,18 @@ impl AgentManager {
})
}
- pub fn with_platform(
- install_dir: impl Into,
- platform: Platform,
- ) -> Self {
+ pub fn with_platform(install_dir: impl Into, platform: Platform) -> Self {
Self {
install_dir: install_dir.into(),
platform,
}
}
- pub fn install(&self, agent: AgentId, options: InstallOptions) -> Result {
+ pub fn install(
+ &self,
+ agent: AgentId,
+ options: InstallOptions,
+ ) -> Result {
let install_path = self.binary_path(agent);
if !options.reinstall {
if let Ok(existing_path) = self.resolve_binary(agent) {
@@ -148,9 +141,15 @@ impl AgentManager {
fs::create_dir_all(&self.install_dir)?;
match agent {
- AgentId::Claude => install_claude(&install_path, self.platform, options.version.as_deref())?,
- AgentId::Codex => install_codex(&install_path, self.platform, options.version.as_deref())?,
- AgentId::Opencode => install_opencode(&install_path, self.platform, options.version.as_deref())?,
+ AgentId::Claude => {
+ install_claude(&install_path, self.platform, options.version.as_deref())?
+ }
+ AgentId::Codex => {
+ install_codex(&install_path, self.platform, options.version.as_deref())?
+ }
+ AgentId::Opencode => {
+ install_opencode(&install_path, self.platform, options.version.as_deref())?
+ }
AgentId::Amp => install_amp(&install_path, self.platform, options.version.as_deref())?,
AgentId::Mock => {
if !install_path.exists() {
@@ -256,10 +255,7 @@ impl AgentManager {
command.arg("app-server");
}
AgentId::Opencode => {
- command
- .arg("run")
- .arg("--format")
- .arg("json");
+ command.arg("run").arg("--format").arg("json");
if let Some(model) = options.model.as_deref() {
command.arg("-m").arg(model);
}
@@ -346,10 +342,15 @@ impl AgentManager {
fn spawn_codex_app_server(&self, options: SpawnOptions) -> Result {
if options.session_id.is_some() {
- return Err(AgentError::ResumeUnsupported { agent: AgentId::Codex });
+ return Err(AgentError::ResumeUnsupported {
+ agent: AgentId::Codex,
+ });
}
let mut command = self.build_command(AgentId::Codex, &options)?;
- command.stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped());
+ command
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped());
for (key, value) in options.env {
command.env(key, value);
}
@@ -417,11 +418,11 @@ impl AgentManager {
Ok(value) => value,
Err(_) => continue,
};
- let message: codex_schema::JsonrpcMessage =
- match serde_json::from_value(value.clone()) {
- Ok(message) => message,
- Err(_) => continue,
- };
+ let message: codex_schema::JsonrpcMessage = match serde_json::from_value(value.clone())
+ {
+ Ok(message) => message,
+ Err(_) => continue,
+ };
match message {
codex_schema::JsonrpcMessage::Response(response) => {
let response_id = response.id.to_string();
@@ -443,11 +444,16 @@ impl AgentManager {
params.cwd = cwd.clone();
send_json_line(
&mut stdin,
- &codex_schema::ClientRequest::ThreadStart { id: request_id, params },
+ &codex_schema::ClientRequest::ThreadStart {
+ id: request_id,
+ params,
+ },
)?;
thread_start_id = Some(request_id_str);
thread_start_sent = true;
- } else if thread_start_id.as_deref() == Some(&response_id) && thread_id.is_none() {
+ } else if thread_start_id.as_deref() == Some(&response_id)
+ && thread_id.is_none()
+ {
thread_id = codex_thread_id_from_response(&response.result);
}
events.push(value);
@@ -466,8 +472,11 @@ impl AgentManager {
) {
completed = true;
}
- if let codex_schema::ServerNotification::ItemCompleted(params) = ¬ification {
- if matches!(params.item, codex_schema::ThreadItem::AgentMessage { .. }) {
+ if let codex_schema::ServerNotification::ItemCompleted(params) =
+ ¬ification
+ {
+ if matches!(params.item, codex_schema::ThreadItem::AgentMessage { .. })
+ {
completed = true;
}
}
@@ -809,17 +818,35 @@ fn codex_thread_id_from_notification(
codex_schema::ServerNotification::TurnCompleted(params) => Some(params.thread_id.clone()),
codex_schema::ServerNotification::ItemStarted(params) => Some(params.thread_id.clone()),
codex_schema::ServerNotification::ItemCompleted(params) => Some(params.thread_id.clone()),
- codex_schema::ServerNotification::ItemAgentMessageDelta(params) => Some(params.thread_id.clone()),
- codex_schema::ServerNotification::ItemReasoningTextDelta(params) => Some(params.thread_id.clone()),
- codex_schema::ServerNotification::ItemReasoningSummaryTextDelta(params) => Some(params.thread_id.clone()),
- codex_schema::ServerNotification::ItemCommandExecutionOutputDelta(params) => Some(params.thread_id.clone()),
- codex_schema::ServerNotification::ItemFileChangeOutputDelta(params) => Some(params.thread_id.clone()),
- codex_schema::ServerNotification::ItemMcpToolCallProgress(params) => Some(params.thread_id.clone()),
- codex_schema::ServerNotification::ThreadTokenUsageUpdated(params) => Some(params.thread_id.clone()),
+ codex_schema::ServerNotification::ItemAgentMessageDelta(params) => {
+ Some(params.thread_id.clone())
+ }
+ codex_schema::ServerNotification::ItemReasoningTextDelta(params) => {
+ Some(params.thread_id.clone())
+ }
+ codex_schema::ServerNotification::ItemReasoningSummaryTextDelta(params) => {
+ Some(params.thread_id.clone())
+ }
+ codex_schema::ServerNotification::ItemCommandExecutionOutputDelta(params) => {
+ Some(params.thread_id.clone())
+ }
+ codex_schema::ServerNotification::ItemFileChangeOutputDelta(params) => {
+ Some(params.thread_id.clone())
+ }
+ codex_schema::ServerNotification::ItemMcpToolCallProgress(params) => {
+ Some(params.thread_id.clone())
+ }
+ codex_schema::ServerNotification::ThreadTokenUsageUpdated(params) => {
+ Some(params.thread_id.clone())
+ }
codex_schema::ServerNotification::TurnDiffUpdated(params) => Some(params.thread_id.clone()),
codex_schema::ServerNotification::TurnPlanUpdated(params) => Some(params.thread_id.clone()),
- codex_schema::ServerNotification::ItemCommandExecutionTerminalInteraction(params) => Some(params.thread_id.clone()),
- codex_schema::ServerNotification::ItemReasoningSummaryPartAdded(params) => Some(params.thread_id.clone()),
+ codex_schema::ServerNotification::ItemCommandExecutionTerminalInteraction(params) => {
+ Some(params.thread_id.clone())
+ }
+ codex_schema::ServerNotification::ItemReasoningSummaryPartAdded(params) => {
+ Some(params.thread_id.clone())
+ }
codex_schema::ServerNotification::ThreadCompacted(params) => Some(params.thread_id.clone()),
_ => None,
}
@@ -859,36 +886,36 @@ fn extract_session_id(agent: AgentId, events: &[Value]) -> Option {
return Some(id.to_string());
}
}
- AgentId::Codex => {
- if let Ok(notification) =
- serde_json::from_value::(event.clone())
- {
- match notification {
- codex_schema::ServerNotification::ThreadStarted(params) => {
- return Some(params.thread.id);
+ AgentId::Codex => {
+ if let Ok(notification) =
+ serde_json::from_value::(event.clone())
+ {
+ match notification {
+ codex_schema::ServerNotification::ThreadStarted(params) => {
+ return Some(params.thread.id);
+ }
+ codex_schema::ServerNotification::TurnStarted(params) => {
+ return Some(params.thread_id);
+ }
+ codex_schema::ServerNotification::TurnCompleted(params) => {
+ return Some(params.thread_id);
+ }
+ codex_schema::ServerNotification::ItemStarted(params) => {
+ return Some(params.thread_id);
+ }
+ codex_schema::ServerNotification::ItemCompleted(params) => {
+ return Some(params.thread_id);
+ }
+ _ => {}
}
- codex_schema::ServerNotification::TurnStarted(params) => {
- return Some(params.thread_id);
- }
- codex_schema::ServerNotification::TurnCompleted(params) => {
- return Some(params.thread_id);
- }
- codex_schema::ServerNotification::ItemStarted(params) => {
- return Some(params.thread_id);
- }
- codex_schema::ServerNotification::ItemCompleted(params) => {
- return Some(params.thread_id);
- }
- _ => {}
+ }
+ if let Some(id) = event.get("thread_id").and_then(Value::as_str) {
+ return Some(id.to_string());
+ }
+ if let Some(id) = event.get("threadId").and_then(Value::as_str) {
+ return Some(id.to_string());
}
}
- if let Some(id) = event.get("thread_id").and_then(Value::as_str) {
- return Some(id.to_string());
- }
- if let Some(id) = event.get("threadId").and_then(Value::as_str) {
- return Some(id.to_string());
- }
- }
AgentId::Opencode => {
if let Some(id) = event.get("session_id").and_then(Value::as_str) {
return Some(id.to_string());
@@ -902,7 +929,8 @@ fn extract_session_id(agent: AgentId, events: &[Value]) -> Option {
if let Some(id) = extract_nested_string(event, &["properties", "sessionID"]) {
return Some(id);
}
- if let Some(id) = extract_nested_string(event, &["properties", "part", "sessionID"]) {
+ if let Some(id) = extract_nested_string(event, &["properties", "part", "sessionID"])
+ {
return Some(id);
}
if let Some(id) = extract_nested_string(event, &["session", "id"]) {
@@ -925,7 +953,9 @@ fn extract_result_text(agent: AgentId, events: &[Value]) -> Option {
if let Some(result) = event.get("result").and_then(Value::as_str) {
return Some(result.to_string());
}
- if let Some(text) = extract_nested_string(event, &["message", "content", "0", "text"]) {
+ if let Some(text) =
+ extract_nested_string(event, &["message", "content", "0", "text"])
+ {
return Some(text);
}
}
@@ -974,7 +1004,9 @@ fn extract_result_text(agent: AgentId, events: &[Value]) -> Option {
if let Some(delta) = extract_nested_string(event, &["properties", "delta"]) {
buffer.push_str(&delta);
}
- if let Some(content) = extract_nested_string(event, &["properties", "part", "content"]) {
+ if let Some(content) =
+ extract_nested_string(event, &["properties", "part", "content"])
+ {
buffer.push_str(&content);
}
}
@@ -1178,14 +1210,19 @@ fn download_bytes(url: &Url) -> Result, AgentError> {
Ok(bytes)
}
-fn install_claude(path: &Path, platform: Platform, version: Option<&str>) -> Result<(), AgentError> {
+fn install_claude(
+ path: &Path,
+ platform: Platform,
+ version: Option<&str>,
+) -> Result<(), AgentError> {
let version = match version {
Some(version) => version.to_string(),
None => {
let url = Url::parse(
"https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/latest",
)?;
- let text = String::from_utf8(download_bytes(&url)?).map_err(|err| AgentError::ExtractFailed(err.to_string()))?;
+ let text = String::from_utf8(download_bytes(&url)?)
+ .map_err(|err| AgentError::ExtractFailed(err.to_string()))?;
text.trim().to_string()
}
};
@@ -1210,8 +1247,11 @@ fn install_amp(path: &Path, platform: Platform, version: Option<&str>) -> Result
let version = match version {
Some(version) => version.to_string(),
None => {
- let url = Url::parse("https://storage.googleapis.com/amp-public-assets-prod-0/cli/cli-version.txt")?;
- let text = String::from_utf8(download_bytes(&url)?).map_err(|err| AgentError::ExtractFailed(err.to_string()))?;
+ let url = Url::parse(
+ "https://storage.googleapis.com/amp-public-assets-prod-0/cli/cli-version.txt",
+ )?;
+ let text = String::from_utf8(download_bytes(&url)?)
+ .map_err(|err| AgentError::ExtractFailed(err.to_string()))?;
text.trim().to_string()
}
};
@@ -1261,7 +1301,11 @@ fn install_codex(path: &Path, platform: Platform, version: Option<&str>) -> Resu
Ok(())
}
-fn install_opencode(path: &Path, platform: Platform, version: Option<&str>) -> Result<(), AgentError> {
+fn install_opencode(
+ path: &Path,
+ platform: Platform,
+ version: Option<&str>,
+) -> Result<(), AgentError> {
match platform {
Platform::MacosArm64 => {
let url = match version {
@@ -1317,7 +1361,8 @@ fn install_opencode(path: &Path, platform: Platform, version: Option<&str>) -> R
fn install_zip_binary(path: &Path, url: &Url, binary_name: &str) -> Result<(), AgentError> {
let bytes = download_bytes(url)?;
let reader = io::Cursor::new(bytes);
- let mut archive = zip::ZipArchive::new(reader).map_err(|err| AgentError::ExtractFailed(err.to_string()))?;
+ let mut archive =
+ zip::ZipArchive::new(reader).map_err(|err| AgentError::ExtractFailed(err.to_string()))?;
let temp_dir = tempfile::tempdir()?;
for i in 0..archive.len() {
let mut file = archive
diff --git a/server/packages/agent-management/src/testing.rs b/server/packages/agent-management/src/testing.rs
index 3543f56..6ba411d 100644
--- a/server/packages/agent-management/src/testing.rs
+++ b/server/packages/agent-management/src/testing.rs
@@ -285,10 +285,7 @@ fn handle_health_response(
})
}
-fn run_blocking_check(
- provider: &str,
- check: F,
-) -> Result<(), TestAgentConfigError>
+fn run_blocking_check(provider: &str, check: F) -> Result<(), TestAgentConfigError>
where
F: FnOnce() -> Result<(), TestAgentConfigError> + Send + 'static,
{
@@ -301,7 +298,12 @@ where
}
fn detect_system_agents() -> Vec {
- let candidates = [AgentId::Claude, AgentId::Codex, AgentId::Opencode, AgentId::Amp];
+ let candidates = [
+ AgentId::Claude,
+ AgentId::Codex,
+ AgentId::Opencode,
+ AgentId::Amp,
+ ];
let install_dir = default_install_dir();
candidates
.into_iter()
diff --git a/server/packages/error/src/lib.rs b/server/packages/error/src/lib.rs
index 60609c2..c24a6e1 100644
--- a/server/packages/error/src/lib.rs
+++ b/server/packages/error/src/lib.rs
@@ -123,7 +123,10 @@ pub enum SandboxError {
#[error("agent not installed: {agent}")]
AgentNotInstalled { agent: String },
#[error("install failed: {agent}")]
- InstallFailed { agent: String, stderr: Option },
+ InstallFailed {
+ agent: String,
+ stderr: Option,
+ },
#[error("agent process exited: {agent}")]
AgentProcessExited {
agent: String,
@@ -167,9 +170,7 @@ impl SandboxError {
pub fn to_agent_error(&self) -> AgentError {
let (agent, session_id, details) = match self {
Self::InvalidRequest { .. } => (None, None, None),
- Self::UnsupportedAgent { agent } => {
- (Some(agent.clone()), None, None)
- }
+ Self::UnsupportedAgent { agent } => (Some(agent.clone()), None, None),
Self::AgentNotInstalled { agent } => (Some(agent.clone()), None, None),
Self::InstallFailed { agent, stderr } => {
let mut map = Map::new();
@@ -179,7 +180,11 @@ impl SandboxError {
(
Some(agent.clone()),
None,
- if map.is_empty() { None } else { Some(Value::Object(map)) },
+ if map.is_empty() {
+ None
+ } else {
+ Some(Value::Object(map))
+ },
)
}
Self::AgentProcessExited {
@@ -200,7 +205,11 @@ impl SandboxError {
(
Some(agent.clone()),
None,
- if map.is_empty() { None } else { Some(Value::Object(map)) },
+ if map.is_empty() {
+ None
+ } else {
+ Some(Value::Object(map))
+ },
)
}
Self::TokenInvalid { message } => {
@@ -219,20 +228,12 @@ impl SandboxError {
});
(None, None, details)
}
- Self::SessionNotFound { session_id } => {
- (None, Some(session_id.clone()), None)
- }
- Self::SessionAlreadyExists { session_id } => {
- (None, Some(session_id.clone()), None)
- }
+ Self::SessionNotFound { session_id } => (None, Some(session_id.clone()), None),
+ Self::SessionAlreadyExists { session_id } => (None, Some(session_id.clone()), None),
Self::ModeNotSupported { agent, mode } => {
let mut map = Map::new();
map.insert("mode".to_string(), Value::String(mode.clone()));
- (
- Some(agent.clone()),
- None,
- Some(Value::Object(map)),
- )
+ (Some(agent.clone()), None, Some(Value::Object(map)))
}
Self::StreamError { message } => {
let mut map = Map::new();
diff --git a/server/packages/extracted-agent-schemas/build.rs b/server/packages/extracted-agent-schemas/build.rs
index cdf328b..5807bfc 100644
--- a/server/packages/extracted-agent-schemas/build.rs
+++ b/server/packages/extracted-agent-schemas/build.rs
@@ -17,10 +17,7 @@ fn main() {
let schema_path = schema_dir.join(file);
// Tell cargo to rerun if schema changes
- emit_stdout(&format!(
- "cargo:rerun-if-changed={}",
- schema_path.display()
- ));
+ emit_stdout(&format!("cargo:rerun-if-changed={}", schema_path.display()));
if !schema_path.exists() {
emit_stdout(&format!(
@@ -48,9 +45,10 @@ fn main() {
let contents = type_space.to_stream();
// Format the generated code
- let formatted = prettyplease::unparse(&syn::parse2(contents.clone()).unwrap_or_else(|e| {
- panic!("Failed to parse generated code for {}: {}", name, e)
- }));
+ let formatted = prettyplease::unparse(
+ &syn::parse2(contents.clone())
+ .unwrap_or_else(|e| panic!("Failed to parse generated code for {}: {}", name, e)),
+ );
let out_path = Path::new(&out_dir).join(format!("{}.rs", name));
fs::write(&out_path, formatted)
diff --git a/server/packages/extracted-agent-schemas/tests/schema_roundtrip.rs b/server/packages/extracted-agent-schemas/tests/schema_roundtrip.rs
index 7c992b0..db7003b 100644
--- a/server/packages/extracted-agent-schemas/tests/schema_roundtrip.rs
+++ b/server/packages/extracted-agent-schemas/tests/schema_roundtrip.rs
@@ -17,16 +17,14 @@ fn test_claude_bash_input() {
#[test]
fn test_codex_server_notification() {
- let notification = codex::ServerNotification::ItemCompleted(
- codex::ItemCompletedNotification {
- item: codex::ThreadItem::AgentMessage {
- id: "msg-123".to_string(),
- text: "Hello from Codex".to_string(),
- },
- thread_id: "thread-123".to_string(),
- turn_id: "turn-456".to_string(),
+ let notification = codex::ServerNotification::ItemCompleted(codex::ItemCompletedNotification {
+ item: codex::ThreadItem::AgentMessage {
+ id: "msg-123".to_string(),
+ text: "Hello from Codex".to_string(),
},
- );
+ thread_id: "thread-123".to_string(),
+ turn_id: "turn-456".to_string(),
+ });
let json = serde_json::to_string(¬ification).unwrap();
assert!(json.contains("item/completed"));
diff --git a/server/packages/openapi-gen/build.rs b/server/packages/openapi-gen/build.rs
index 2ac2ce3..b3c0c90 100644
--- a/server/packages/openapi-gen/build.rs
+++ b/server/packages/openapi-gen/build.rs
@@ -13,8 +13,7 @@ fn main() {
let out_path = Path::new(&out_dir).join("openapi.json");
let openapi = ApiDoc::openapi();
- let json = serde_json::to_string_pretty(&openapi)
- .expect("Failed to serialize OpenAPI spec");
+ let json = serde_json::to_string_pretty(&openapi).expect("Failed to serialize OpenAPI spec");
fs::write(&out_path, json).expect("Failed to write OpenAPI spec");
emit_stdout(&format!(
diff --git a/server/packages/openapi-gen/src/main.rs b/server/packages/openapi-gen/src/main.rs
index 7b74a6f..439a4be 100644
--- a/server/packages/openapi-gen/src/main.rs
+++ b/server/packages/openapi-gen/src/main.rs
@@ -47,7 +47,11 @@ fn init_logging() {
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
tracing_subscriber::registry()
.with(filter)
- .with(tracing_logfmt::builder().layer().with_writer(std::io::stderr))
+ .with(
+ tracing_logfmt::builder()
+ .layer()
+ .with_writer(std::io::stderr),
+ )
.init();
}
diff --git a/server/packages/sandbox-agent/src/agent_server_logs/unix.rs b/server/packages/sandbox-agent/src/agent_server_logs/unix.rs
index 326af42..2c875d7 100644
--- a/server/packages/sandbox-agent/src/agent_server_logs/unix.rs
+++ b/server/packages/sandbox-agent/src/agent_server_logs/unix.rs
@@ -3,8 +3,8 @@ use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};
use sandbox_agent_error::SandboxError;
-use time::{Duration, OffsetDateTime};
use sandbox_agent_universal_agent_schema::StderrOutput;
+use time::{Duration, OffsetDateTime};
const LOG_RETENTION_DAYS: i64 = 7;
const LOG_HEAD_LINES: usize = 20;
diff --git a/server/packages/sandbox-agent/src/lib.rs b/server/packages/sandbox-agent/src/lib.rs
index 1767f73..098d0b3 100644
--- a/server/packages/sandbox-agent/src/lib.rs
+++ b/server/packages/sandbox-agent/src/lib.rs
@@ -1,7 +1,7 @@
//! Sandbox agent core utilities.
-pub mod credentials;
mod agent_server_logs;
+pub mod credentials;
pub mod router;
pub mod telemetry;
pub mod ui;
diff --git a/server/packages/sandbox-agent/src/main.rs b/server/packages/sandbox-agent/src/main.rs
index c0a57ff..8904ddd 100644
--- a/server/packages/sandbox-agent/src/main.rs
+++ b/server/packages/sandbox-agent/src/main.rs
@@ -79,10 +79,6 @@ struct ServerArgs {
#[arg(long = "cors-allow-credentials", short = 'C')]
cors_allow_credentials: bool,
- /// Disable default CORS for the inspector (https://inspect.sandboxagent.dev)
- #[arg(long = "no-inspector-cors")]
- no_inspector_cors: bool,
-
#[arg(long = "no-telemetry")]
no_telemetry: bool,
}
@@ -848,19 +844,11 @@ fn available_providers(credentials: &ExtractedCredentials) -> Vec {
providers
}
-const INSPECTOR_ORIGIN: &str = "https://inspect.sandboxagent.dev";
-
fn build_cors_layer(server: &ServerArgs) -> Result {
let mut cors = CorsLayer::new();
- // Build origins list: inspector by default + any additional origins
+ // Build origins list from provided origins
let mut origins = Vec::new();
- if !server.no_inspector_cors {
- let inspector_origin = INSPECTOR_ORIGIN
- .parse()
- .map_err(|_| CliError::InvalidCorsOrigin(INSPECTOR_ORIGIN.to_string()))?;
- origins.push(inspector_origin);
- }
for origin in &server.cors_allow_origin {
let value = origin
.parse()
diff --git a/server/packages/sandbox-agent/src/router.rs b/server/packages/sandbox-agent/src/router.rs
index fa587c0..5f16582 100644
--- a/server/packages/sandbox-agent/src/router.rs
+++ b/server/packages/sandbox-agent/src/router.rs
@@ -25,8 +25,7 @@ use sandbox_agent_universal_agent_schema::{
ItemDeltaData, ItemEventData, ItemKind, ItemRole, ItemStatus, PermissionEventData,
PermissionStatus, QuestionEventData, QuestionStatus, ReasoningVisibility, SessionEndReason,
SessionEndedData, SessionStartedData, StderrOutput, TerminatedBy, UniversalEvent,
- UniversalEventData,
- UniversalEventType, UniversalItem,
+ UniversalEventData, UniversalEventType, UniversalItem,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -37,6 +36,7 @@ use tokio_stream::wrappers::BroadcastStream;
use tower_http::trace::TraceLayer;
use utoipa::{Modify, OpenApi, ToSchema};
+use crate::agent_server_logs::AgentServerLogs;
use crate::ui;
use sandbox_agent_agent_management::agents::{
AgentError as ManagerError, AgentId, AgentManager, InstallOptions, SpawnOptions, StreamingSpawn,
@@ -44,7 +44,6 @@ use sandbox_agent_agent_management::agents::{
use sandbox_agent_agent_management::credentials::{
extract_all_credentials, CredentialExtractionOptions, ExtractedCredentials,
};
-use crate::agent_server_logs::AgentServerLogs;
const MOCK_EVENT_DELAY_MS: u64 = 200;
static USER_MESSAGE_COUNTER: AtomicU64 = AtomicU64::new(1);
@@ -99,7 +98,10 @@ pub fn build_router_with_state(shared: Arc) -> (Router, Arc)
.route("/sessions", get(list_sessions))
.route("/sessions/:session_id", post(create_session))
.route("/sessions/:session_id/messages", post(post_message))
- .route("/sessions/:session_id/messages/stream", post(post_message_stream))
+ .route(
+ "/sessions/:session_id/messages/stream",
+ post(post_message_stream),
+ )
.route("/sessions/:session_id/terminate", post(terminate_session))
.route("/sessions/:session_id/events", get(get_events))
.route("/sessions/:session_id/events/sse", get(get_events_sse))
@@ -126,7 +128,8 @@ pub fn build_router_with_state(shared: Arc) -> (Router, Arc)
let mut router = Router::new()
.route("/", get(get_root))
- .nest("/v1", v1_router);
+ .nest("/v1", v1_router)
+ .fallback(not_found);
if ui::is_enabled() {
router = router.merge(ui::router());
@@ -1141,8 +1144,8 @@ impl AgentServerManager {
) -> Result<(String, Arc>>), SandboxError> {
let manager = self.agent_manager.clone();
let log_dir = self.log_base_dir.clone();
- let (base_url, child) =
- tokio::task::spawn_blocking(move || -> Result<(String, std::process::Child), SandboxError> {
+ let (base_url, child) = tokio::task::spawn_blocking(
+ move || -> Result<(String, std::process::Child), SandboxError> {
let path = manager
.resolve_binary(agent)
.map_err(|err| map_spawn_error(agent, err))?;
@@ -1159,16 +1162,14 @@ impl AgentServerManager {
message: err.to_string(),
})?;
Ok((format!("http://127.0.0.1:{port}"), child))
- })
- .await
- .map_err(|err| SandboxError::StreamError {
- message: err.to_string(),
- })??;
+ },
+ )
+ .await
+ .map_err(|err| SandboxError::StreamError {
+ message: err.to_string(),
+ })??;
- Ok((
- base_url,
- Arc::new(std::sync::Mutex::new(Some(child))),
- ))
+ Ok((base_url, Arc::new(std::sync::Mutex::new(Some(child)))))
}
async fn spawn_stdio_server(
@@ -1187,59 +1188,66 @@ impl AgentServerManager {
let (stdin_tx, stdin_rx) = mpsc::unbounded_channel::();
let (stdout_tx, stdout_rx) = mpsc::unbounded_channel::();
- let child = tokio::task::spawn_blocking(move || -> Result {
- let path = manager
- .resolve_binary(agent)
- .map_err(|err| map_spawn_error(agent, err))?;
- let mut command = std::process::Command::new(path);
- let stderr = AgentServerLogs::new(log_dir, agent.as_str()).open()?;
- command
- .arg("app-server")
- .stdin(Stdio::piped())
- .stdout(Stdio::piped())
- .stderr(stderr);
+ let child =
+ tokio::task::spawn_blocking(move || -> Result {
+ let path = manager
+ .resolve_binary(agent)
+ .map_err(|err| map_spawn_error(agent, err))?;
+ let mut command = std::process::Command::new(path);
+ let stderr = AgentServerLogs::new(log_dir, agent.as_str()).open()?;
+ command
+ .arg("app-server")
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .stderr(stderr);
- let mut child = command.spawn().map_err(|err| SandboxError::StreamError {
+ let mut child = command.spawn().map_err(|err| SandboxError::StreamError {
+ message: err.to_string(),
+ })?;
+
+ let stdin = child
+ .stdin
+ .take()
+ .ok_or_else(|| SandboxError::StreamError {
+ message: "codex stdin unavailable".to_string(),
+ })?;
+ let stdout = child
+ .stdout
+ .take()
+ .ok_or_else(|| SandboxError::StreamError {
+ message: "codex stdout unavailable".to_string(),
+ })?;
+
+ let stdin_rx_mut = std::sync::Mutex::new(stdin_rx);
+ std::thread::spawn(move || {
+ let mut stdin = stdin;
+ let mut rx = stdin_rx_mut.lock().unwrap();
+ while let Some(line) = rx.blocking_recv() {
+ if writeln!(stdin, "{line}").is_err() {
+ break;
+ }
+ if stdin.flush().is_err() {
+ break;
+ }
+ }
+ });
+
+ std::thread::spawn(move || {
+ let reader = BufReader::new(stdout);
+ for line in reader.lines() {
+ let Ok(line) = line else { break };
+ if stdout_tx.send(line).is_err() {
+ break;
+ }
+ }
+ });
+
+ Ok(child)
+ })
+ .await
+ .map_err(|err| SandboxError::StreamError {
message: err.to_string(),
- })?;
-
- let stdin = child.stdin.take().ok_or_else(|| SandboxError::StreamError {
- message: "codex stdin unavailable".to_string(),
- })?;
- let stdout = child.stdout.take().ok_or_else(|| SandboxError::StreamError {
- message: "codex stdout unavailable".to_string(),
- })?;
-
- let stdin_rx_mut = std::sync::Mutex::new(stdin_rx);
- std::thread::spawn(move || {
- let mut stdin = stdin;
- let mut rx = stdin_rx_mut.lock().unwrap();
- while let Some(line) = rx.blocking_recv() {
- if writeln!(stdin, "{line}").is_err() {
- break;
- }
- if stdin.flush().is_err() {
- break;
- }
- }
- });
-
- std::thread::spawn(move || {
- let reader = BufReader::new(stdout);
- for line in reader.lines() {
- let Ok(line) = line else { break };
- if stdout_tx.send(line).is_err() {
- break;
- }
- }
- });
-
- Ok(child)
- })
- .await
- .map_err(|err| SandboxError::StreamError {
- message: err.to_string(),
- })??;
+ })??;
let server = Arc::new(CodexServer::new(stdin_tx));
@@ -1347,7 +1355,10 @@ impl AgentServerManager {
}
}
- async fn ensure_server_for_restart(self: Arc, agent: AgentId) -> Result<(), SandboxError> {
+ async fn ensure_server_for_restart(
+ self: Arc,
+ agent: AgentId,
+ ) -> Result<(), SandboxError> {
sleep(Duration::from_millis(500)).await;
match agent {
AgentId::Opencode => {
@@ -1445,26 +1456,24 @@ impl SessionManager {
}
}
- fn session_ref<'a>(
- sessions: &'a [SessionState],
- session_id: &str,
- ) -> Option<&'a SessionState> {
- sessions.iter().find(|session| session.session_id == session_id)
+ fn session_ref<'a>(sessions: &'a [SessionState], session_id: &str) -> Option<&'a SessionState> {
+ sessions
+ .iter()
+ .find(|session| session.session_id == session_id)
}
fn session_mut<'a>(
sessions: &'a mut [SessionState],
session_id: &str,
) -> Option<&'a mut SessionState> {
- sessions.iter_mut().find(|session| session.session_id == session_id)
+ sessions
+ .iter_mut()
+ .find(|session| session.session_id == session_id)
}
/// Read agent stderr for error diagnostics
fn read_agent_stderr(&self, agent: AgentId) -> Option {
- let logs = AgentServerLogs::new(
- self.server_manager.log_base_dir.clone(),
- agent.as_str(),
- );
+ let logs = AgentServerLogs::new(self.server_manager.log_base_dir.clone(), agent.as_str());
logs.read_stderr()
}
@@ -1476,7 +1485,10 @@ impl SessionManager {
let agent_id = parse_agent_id(&request.agent)?;
{
let sessions = self.sessions.lock().await;
- if sessions.iter().any(|session| session.session_id == session_id) {
+ if sessions
+ .iter()
+ .any(|session| session.session_id == session_id)
+ {
return Err(SandboxError::SessionAlreadyExists { session_id });
}
}
@@ -1675,10 +1687,7 @@ impl SessionManager {
Ok(())
}
- async fn emit_synthetic_assistant_start(
- &self,
- session_id: &str,
- ) -> Result<(), SandboxError> {
+ async fn emit_synthetic_assistant_start(&self, session_id: &str) -> Result<(), SandboxError> {
let conversion = {
let mut sessions = self.sessions.lock().await;
let session = Self::session_mut(&mut sessions, session_id).ok_or_else(|| {
@@ -1688,7 +1697,9 @@ impl SessionManager {
})?;
session.enqueue_pending_assistant_start()
};
- let _ = self.record_conversions(session_id, vec![conversion]).await?;
+ let _ = self
+ .record_conversions(session_id, vec![conversion])
+ .await?;
Ok(())
}
@@ -1905,12 +1916,16 @@ impl SessionManager {
let sender = claude_sender.ok_or_else(|| SandboxError::InvalidRequest {
message: "Claude session is not active".to_string(),
})?;
- let session_id = native_session_id.clone().unwrap_or_else(|| session_id.to_string());
+ let session_id = native_session_id
+ .clone()
+ .unwrap_or_else(|| session_id.to_string());
let response_text = response.clone().unwrap_or_default();
let line = claude_tool_result_line(&session_id, question_id, &response_text, false);
- sender.send(line).map_err(|_| SandboxError::InvalidRequest {
- message: "Claude session is not active".to_string(),
- })?;
+ sender
+ .send(line)
+ .map_err(|_| SandboxError::InvalidRequest {
+ message: "Claude session is not active".to_string(),
+ })?;
} else {
// TODO: Forward question replies to subprocess agents.
}
@@ -1976,16 +1991,20 @@ impl SessionManager {
let sender = claude_sender.ok_or_else(|| SandboxError::InvalidRequest {
message: "Claude session is not active".to_string(),
})?;
- let session_id = native_session_id.clone().unwrap_or_else(|| session_id.to_string());
+ let session_id = native_session_id
+ .clone()
+ .unwrap_or_else(|| session_id.to_string());
let line = claude_tool_result_line(
&session_id,
question_id,
"User rejected the question.",
true,
);
- sender.send(line).map_err(|_| SandboxError::InvalidRequest {
- message: "Claude session is not active".to_string(),
- })?;
+ sender
+ .send(line)
+ .map_err(|_| SandboxError::InvalidRequest {
+ message: "Claude session is not active".to_string(),
+ })?;
} else {
// TODO: Forward question rejections to subprocess agents.
}
@@ -2139,9 +2158,11 @@ impl SessionManager {
};
let line = claude_control_response_line(permission_id, behavior, response_value);
- sender.send(line).map_err(|_| SandboxError::InvalidRequest {
- message: "Claude session is not active".to_string(),
- })?;
+ sender
+ .send(line)
+ .map_err(|_| SandboxError::InvalidRequest {
+ message: "Claude session is not active".to_string(),
+ })?;
} else {
// TODO: Forward permission replies to subprocess agents.
}
@@ -2811,7 +2832,8 @@ impl SessionManager {
Err(_) => continue,
};
- let message: codex_schema::JsonrpcMessage = match serde_json::from_value(value.clone()) {
+ let message: codex_schema::JsonrpcMessage = match serde_json::from_value(value.clone())
+ {
Ok(m) => m,
Err(_) => continue,
};
@@ -2828,12 +2850,17 @@ impl SessionManager {
if let Ok(notification) =
serde_json::from_value::(value.clone())
{
- if let Some(thread_id) = codex_thread_id_from_server_notification(¬ification) {
+ if let Some(thread_id) =
+ codex_thread_id_from_server_notification(¬ification)
+ {
if let Some(session_id) = server.session_for_thread(&thread_id) {
- let conversions = match convert_codex::notification_to_universal(¬ification) {
- Ok(c) => c,
- Err(err) => vec![agent_unparsed("codex", &err, value.clone())],
- };
+ let conversions =
+ match convert_codex::notification_to_universal(¬ification) {
+ Ok(c) => c,
+ Err(err) => {
+ vec![agent_unparsed("codex", &err, value.clone())]
+ }
+ };
let _ = self.record_conversions(&session_id, conversions).await;
}
}
@@ -2851,7 +2878,8 @@ impl SessionManager {
for conversion in &mut conversions {
conversion.raw = Some(value.clone());
}
- let _ = self.record_conversions(&session_id, conversions).await;
+ let _ =
+ self.record_conversions(&session_id, conversions).await;
}
Err(err) => {
let _ = self
@@ -2982,12 +3010,13 @@ impl SessionManager {
) -> Result<(), SandboxError> {
let server = self.ensure_codex_server().await?;
- let thread_id = session
- .native_session_id
- .as_ref()
- .ok_or_else(|| SandboxError::InvalidRequest {
- message: "missing Codex thread id".to_string(),
- })?;
+ let thread_id =
+ session
+ .native_session_id
+ .as_ref()
+ .ok_or_else(|| SandboxError::InvalidRequest {
+ message: "missing Codex thread id".to_string(),
+ })?;
let id = server.next_request_id();
let prompt_text = codex_prompt_for_mode(prompt, Some(&session.agent_mode));
@@ -3430,14 +3459,22 @@ pub struct EventsQuery {
pub offset: Option,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit: Option,
- #[serde(default, skip_serializing_if = "Option::is_none", alias = "include_raw")]
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ alias = "include_raw"
+ )]
pub include_raw: Option,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct TurnStreamQuery {
- #[serde(default, skip_serializing_if = "Option::is_none", alias = "include_raw")]
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ alias = "include_raw"
+ )]
pub include_raw: Option,
}
@@ -3541,10 +3578,22 @@ async fn get_agent_modes(
Ok(Json(AgentModesResponse { modes }))
}
+const SERVER_INFO: &str = "\
+This is a Sandbox Agent server. Available endpoints:\n\
+ - GET / - Server info\n\
+ - GET /v1/health - Health check\n\
+ - GET /ui/ - Inspector UI\n\n\
+See https://sandboxagent.dev for API documentation.";
+
async fn get_root() -> &'static str {
- "This is a Sandbox Agent server for orchestrating coding agents.\n\
- See https://sandboxagent.dev for more information.\n\
- Visit /ui/ for the inspector UI."
+ SERVER_INFO
+}
+
+async fn not_found() -> (StatusCode, String) {
+ (
+ StatusCode::NOT_FOUND,
+ format!("404 Not Found\n\n{SERVER_INFO}"),
+ )
}
#[utoipa::path(
@@ -3571,48 +3620,47 @@ async fn list_agents(
let manager = state.agent_manager.clone();
let server_statuses = state.session_manager.server_manager.status_snapshot().await;
- let agents = tokio::task::spawn_blocking(move || {
- all_agents()
- .into_iter()
- .map(|agent_id| {
- let installed = manager.is_installed(agent_id);
- let version = manager.version(agent_id).ok().flatten();
- let path = manager.resolve_binary(agent_id).ok();
- let capabilities = agent_capabilities_for(agent_id);
+ let agents =
+ tokio::task::spawn_blocking(move || {
+ all_agents()
+ .into_iter()
+ .map(|agent_id| {
+ let installed = manager.is_installed(agent_id);
+ let version = manager.version(agent_id).ok().flatten();
+ let path = manager.resolve_binary(agent_id).ok();
+ let capabilities = agent_capabilities_for(agent_id);
- // Add server_status for agents with shared processes
- let server_status = if capabilities.shared_process {
- Some(
- server_statuses
- .get(&agent_id)
- .cloned()
- .unwrap_or(ServerStatusInfo {
- status: ServerStatus::Stopped,
- base_url: None,
- uptime_ms: None,
- restart_count: 0,
- last_error: None,
- }),
- )
- } else {
- None
- };
+ // Add server_status for agents with shared processes
+ let server_status =
+ if capabilities.shared_process {
+ Some(server_statuses.get(&agent_id).cloned().unwrap_or(
+ ServerStatusInfo {
+ status: ServerStatus::Stopped,
+ base_url: None,
+ uptime_ms: None,
+ restart_count: 0,
+ last_error: None,
+ },
+ ))
+ } else {
+ None
+ };
- AgentInfo {
- id: agent_id.as_str().to_string(),
- installed,
- version,
- path: path.map(|path| path.to_string_lossy().to_string()),
- capabilities,
- server_status,
- }
- })
- .collect::>()
- })
- .await
- .map_err(|err| SandboxError::StreamError {
- message: err.to_string(),
- })?;
+ AgentInfo {
+ id: agent_id.as_str().to_string(),
+ installed,
+ version,
+ path: path.map(|path| path.to_string_lossy().to_string()),
+ capabilities,
+ server_status,
+ }
+ })
+ .collect::>()
+ })
+ .await
+ .map_err(|err| SandboxError::StreamError {
+ message: err.to_string(),
+ })?;
Ok(Json(AgentListResponse { agents }))
}
@@ -3898,7 +3946,10 @@ fn all_agents() -> [AgentId; 5] {
/// Returns true if the agent supports resuming a session after its process exits.
/// These agents can use --resume/--continue to continue a conversation.
fn agent_supports_resume(agent: AgentId) -> bool {
- matches!(agent, AgentId::Claude | AgentId::Amp | AgentId::Opencode | AgentId::Codex)
+ matches!(
+ agent,
+ AgentId::Claude | AgentId::Amp | AgentId::Opencode | AgentId::Codex
+ )
}
fn agent_supports_item_started(agent: AgentId) -> bool {
@@ -4066,16 +4117,18 @@ fn agent_modes_for(agent: AgentId) -> Vec {
name: "Build".to_string(),
description: "Default build mode".to_string(),
}],
- AgentId::Mock => vec![AgentModeInfo {
- id: "build".to_string(),
- name: "Build".to_string(),
- description: "Mock agent for UI testing".to_string(),
- },
- AgentModeInfo {
- id: "plan".to_string(),
- name: "Plan".to_string(),
- description: "Plan-only mock mode".to_string(),
- }],
+ AgentId::Mock => vec![
+ AgentModeInfo {
+ id: "build".to_string(),
+ name: "Build".to_string(),
+ description: "Mock agent for UI testing".to_string(),
+ },
+ AgentModeInfo {
+ id: "plan".to_string(),
+ name: "Plan".to_string(),
+ description: "Plan-only mock mode".to_string(),
+ },
+ ],
}
}
@@ -4307,16 +4360,9 @@ fn claude_tool_result_line(
.to_string()
}
-fn claude_control_response_line(
- request_id: &str,
- behavior: &str,
- response: Value,
-) -> String {
+fn claude_control_response_line(request_id: &str, behavior: &str, response: Value) -> String {
let mut response_obj = serde_json::Map::new();
- response_obj.insert(
- "behavior".to_string(),
- Value::String(behavior.to_string()),
- );
+ response_obj.insert("behavior".to_string(), Value::String(behavior.to_string()));
if let Some(message) = response.get("message") {
response_obj.insert("message".to_string(), message.clone());
}
@@ -5022,7 +5068,8 @@ pub mod test_utils {
impl TestHarness {
pub async fn new() -> Self {
let temp_dir = TempDir::new().expect("temp dir");
- let agent_manager = Arc::new(AgentManager::new(temp_dir.path()).expect("agent manager"));
+ let agent_manager =
+ Arc::new(AgentManager::new(temp_dir.path()).expect("agent manager"));
let session_manager = Arc::new(SessionManager::new(agent_manager));
session_manager
.server_manager
@@ -5058,11 +5105,7 @@ pub mod test_utils {
.await;
}
- pub async fn has_session_mapping(
- &self,
- agent: AgentId,
- session_id: &str,
- ) -> bool {
+ pub async fn has_session_mapping(&self, agent: AgentId, session_id: &str) -> bool {
let sessions = self.session_manager.server_manager.sessions.lock().await;
sessions
.get(&agent)
@@ -5101,8 +5144,8 @@ pub mod test_utils {
variant: None,
agent_version: None,
};
- let mut session = SessionState::new(session_id.to_string(), agent, &request)
- .expect("session");
+ let mut session =
+ SessionState::new(session_id.to_string(), agent, &request).expect("session");
session.native_session_id = native_session_id.map(|id| id.to_string());
self.session_manager.sessions.lock().await.push(session);
}
@@ -5116,38 +5159,48 @@ pub mod test_utils {
let (stdin_tx, _stdin_rx) = mpsc::unbounded_channel::();
let server = Arc::new(CodexServer::new(stdin_tx));
let child = Arc::new(std::sync::Mutex::new(child));
- self.session_manager.server_manager.servers.lock().await.insert(
- agent,
- ManagedServer {
- kind: ManagedServerKind::Stdio { server },
- child: child.clone(),
- status: ServerStatus::Running,
- start_time: Some(Instant::now()),
- restart_count: 0,
- last_error: None,
- shutdown_requested: false,
- instance_id,
- },
- );
+ self.session_manager
+ .server_manager
+ .servers
+ .lock()
+ .await
+ .insert(
+ agent,
+ ManagedServer {
+ kind: ManagedServerKind::Stdio { server },
+ child: child.clone(),
+ status: ServerStatus::Running,
+ start_time: Some(Instant::now()),
+ restart_count: 0,
+ last_error: None,
+ shutdown_requested: false,
+ instance_id,
+ },
+ );
child
}
pub async fn insert_http_server(&self, agent: AgentId, instance_id: u64) {
- self.session_manager.server_manager.servers.lock().await.insert(
- agent,
- ManagedServer {
- kind: ManagedServerKind::Http {
- base_url: "http://127.0.0.1:1".to_string(),
+ self.session_manager
+ .server_manager
+ .servers
+ .lock()
+ .await
+ .insert(
+ agent,
+ ManagedServer {
+ kind: ManagedServerKind::Http {
+ base_url: "http://127.0.0.1:1".to_string(),
+ },
+ child: Arc::new(std::sync::Mutex::new(None)),
+ status: ServerStatus::Running,
+ start_time: Some(Instant::now()),
+ restart_count: 0,
+ last_error: None,
+ shutdown_requested: false,
+ instance_id,
},
- child: Arc::new(std::sync::Mutex::new(None)),
- status: ServerStatus::Running,
- start_time: Some(Instant::now()),
- restart_count: 0,
- last_error: None,
- shutdown_requested: false,
- instance_id,
- },
- );
+ );
}
pub async fn handle_process_exit(
@@ -5236,7 +5289,12 @@ pub mod test_utils {
fn default_log_dir() -> PathBuf {
dirs::data_dir()
.map(|dir| dir.join("sandbox-agent").join("logs").join("servers"))
- .unwrap_or_else(|| PathBuf::from(".").join(".sandbox-agent").join("logs").join("servers"))
+ .unwrap_or_else(|| {
+ PathBuf::from(".")
+ .join(".sandbox-agent")
+ .join("logs")
+ .join("servers")
+ })
}
fn find_available_port() -> Result {
@@ -5287,7 +5345,6 @@ impl SseAccumulator {
}
}
-
fn parse_opencode_modes(value: &Value) -> Vec {
let mut modes = Vec::new();
let mut seen = HashSet::new();
diff --git a/server/packages/sandbox-agent/src/telemetry.rs b/server/packages/sandbox-agent/src/telemetry.rs
index 74ba268..6ff221b 100644
--- a/server/packages/sandbox-agent/src/telemetry.rs
+++ b/server/packages/sandbox-agent/src/telemetry.rs
@@ -90,7 +90,8 @@ pub fn spawn_telemetry_task() {
attempt_send(&client).await;
let start = Instant::now() + Duration::from_secs(TELEMETRY_INTERVAL_SECS);
- let mut interval = tokio::time::interval_at(start, Duration::from_secs(TELEMETRY_INTERVAL_SECS));
+ let mut interval =
+ tokio::time::interval_at(start, Duration::from_secs(TELEMETRY_INTERVAL_SECS));
loop {
interval.tick().await;
attempt_send(&client).await;
@@ -150,7 +151,12 @@ fn load_or_create_id() -> String {
}
}
- if let Ok(mut file) = fs::OpenOptions::new().create(true).write(true).truncate(true).open(&path) {
+ if let Ok(mut file) = fs::OpenOptions::new()
+ .create(true)
+ .write(true)
+ .truncate(true)
+ .open(&path)
+ {
let _ = file.write_all(id.as_bytes());
}
id
@@ -194,7 +200,12 @@ fn write_last_sent(timestamp: i64) {
return;
}
}
- if let Ok(mut file) = fs::OpenOptions::new().create(true).write(true).truncate(true).open(&path) {
+ if let Ok(mut file) = fs::OpenOptions::new()
+ .create(true)
+ .write(true)
+ .truncate(true)
+ .open(&path)
+ {
let _ = file.write_all(timestamp.to_string().as_bytes());
}
}
@@ -266,7 +277,8 @@ fn detect_provider() -> ProviderInfo {
};
}
- if env::var("MODAL_IS_REMOTE").as_deref() == Ok("1") || env::var("MODAL_CLOUD_PROVIDER").is_ok() {
+ if env::var("MODAL_IS_REMOTE").as_deref() == Ok("1") || env::var("MODAL_CLOUD_PROVIDER").is_ok()
+ {
let metadata = metadata_or_none([
("cloudProvider", env::var("MODAL_CLOUD_PROVIDER").ok()),
("region", env::var("MODAL_REGION").ok()),
@@ -395,7 +407,9 @@ fn detect_docker() -> bool {
false
}
-fn filter_metadata(pairs: impl IntoIterator- )>) -> HashMap {
+fn filter_metadata(
+ pairs: impl IntoIterator
- )>,
+) -> HashMap {
let mut map = HashMap::new();
for (key, value) in pairs {
if let Some(value) = value {
@@ -407,7 +421,9 @@ fn filter_metadata(pairs: impl IntoIterator
-
map
}
-fn metadata_or_none(pairs: impl IntoIterator
- )>) -> Option> {
+fn metadata_or_none(
+ pairs: impl IntoIterator
- )>,
+) -> Option> {
let map = filter_metadata(pairs);
if map.is_empty() {
None
diff --git a/server/packages/sandbox-agent/src/ui.rs b/server/packages/sandbox-agent/src/ui.rs
index c1757d5..3bb475f 100644
--- a/server/packages/sandbox-agent/src/ui.rs
+++ b/server/packages/sandbox-agent/src/ui.rs
@@ -37,7 +37,11 @@ fn serve_path(path: &str) -> Response {
};
let trimmed = path.trim_start_matches('/');
- let target = if trimmed.is_empty() { "index.html" } else { trimmed };
+ let target = if trimmed.is_empty() {
+ "index.html"
+ } else {
+ trimmed
+ };
if let Some(file) = dir.get_file(target) {
return file_response(file);
diff --git a/server/packages/sandbox-agent/tests/agent-flows/agent_file_edit_flow.rs b/server/packages/sandbox-agent/tests/agent-flows/agent_file_edit_flow.rs
index de6bb67..4f1d349 100644
--- a/server/packages/sandbox-agent/tests/agent-flows/agent_file_edit_flow.rs
+++ b/server/packages/sandbox-agent/tests/agent-flows/agent_file_edit_flow.rs
@@ -1,13 +1,13 @@
#[path = "../common/mod.rs"]
mod common;
+use axum::http::Method;
use common::*;
-use sandbox_agent_agent_management::testing::test_agents_from_env;
use sandbox_agent_agent_management::agents::AgentId;
+use sandbox_agent_agent_management::testing::test_agents_from_env;
use serde_json::Value;
use std::fs;
use std::time::{Duration, Instant};
-use axum::http::Method;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn agent_file_edit_flow() {
@@ -77,9 +77,7 @@ Do not change any other files. Reply only with DONE after editing.",
let _ = send_status(
&app.app,
Method::POST,
- &format!(
- "/v1/sessions/{session_id}/permissions/{permission_id}/reply"
- ),
+ &format!("/v1/sessions/{session_id}/permissions/{permission_id}/reply"),
Some(serde_json::json!({ "reply": "once" })),
)
.await;
diff --git a/server/packages/sandbox-agent/tests/agent-flows/agent_permission_flow.rs b/server/packages/sandbox-agent/tests/agent-flows/agent_permission_flow.rs
index 4698d0c..d5711ef 100644
--- a/server/packages/sandbox-agent/tests/agent-flows/agent_permission_flow.rs
+++ b/server/packages/sandbox-agent/tests/agent-flows/agent_permission_flow.rs
@@ -1,11 +1,11 @@
#[path = "../common/mod.rs"]
mod common;
+use axum::http::Method;
use common::*;
use sandbox_agent_agent_management::testing::test_agents_from_env;
-use std::time::Duration;
-use axum::http::Method;
use serde_json::json;
+use std::time::Duration;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn agent_permission_flow() {
@@ -41,14 +41,20 @@ async fn agent_permission_flow() {
Some(json!({ "reply": "once" })),
)
.await;
- assert_eq!(status, axum::http::StatusCode::NO_CONTENT, "permission reply");
+ assert_eq!(
+ status,
+ axum::http::StatusCode::NO_CONTENT,
+ "permission reply"
+ );
- let resolved = poll_events_until(&app.app, &session_id, Duration::from_secs(120), |events| {
- events.iter().any(|event| {
- event.get("type").and_then(serde_json::Value::as_str) == Some("permission.resolved")
+ let resolved =
+ poll_events_until(&app.app, &session_id, Duration::from_secs(120), |events| {
+ events.iter().any(|event| {
+ event.get("type").and_then(serde_json::Value::as_str)
+ == Some("permission.resolved")
+ })
})
- })
- .await;
+ .await;
assert!(
resolved.iter().any(|event| {
diff --git a/server/packages/sandbox-agent/tests/agent-flows/agent_question_flow.rs b/server/packages/sandbox-agent/tests/agent-flows/agent_question_flow.rs
index e5a85b7..b3064ad 100644
--- a/server/packages/sandbox-agent/tests/agent-flows/agent_question_flow.rs
+++ b/server/packages/sandbox-agent/tests/agent-flows/agent_question_flow.rs
@@ -1,11 +1,11 @@
#[path = "../common/mod.rs"]
mod common;
+use axum::http::Method;
use common::*;
use sandbox_agent_agent_management::testing::test_agents_from_env;
-use std::time::Duration;
-use axum::http::Method;
use serde_json::json;
+use std::time::Duration;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn agent_question_flow() {
@@ -44,12 +44,14 @@ async fn agent_question_flow() {
.await;
assert_eq!(status, axum::http::StatusCode::NO_CONTENT, "question reply");
- let resolved = poll_events_until(&app.app, &session_id, Duration::from_secs(120), |events| {
- events.iter().any(|event| {
- event.get("type").and_then(serde_json::Value::as_str) == Some("question.resolved")
+ let resolved =
+ poll_events_until(&app.app, &session_id, Duration::from_secs(120), |events| {
+ events.iter().any(|event| {
+ event.get("type").and_then(serde_json::Value::as_str)
+ == Some("question.resolved")
+ })
})
- })
- .await;
+ .await;
assert!(
resolved.iter().any(|event| {
diff --git a/server/packages/sandbox-agent/tests/agent-flows/agent_termination.rs b/server/packages/sandbox-agent/tests/agent-flows/agent_termination.rs
index 961197a..0bff5f3 100644
--- a/server/packages/sandbox-agent/tests/agent-flows/agent_termination.rs
+++ b/server/packages/sandbox-agent/tests/agent-flows/agent_termination.rs
@@ -1,11 +1,11 @@
#[path = "../common/mod.rs"]
mod common;
+use axum::http::Method;
use common::*;
use sandbox_agent_agent_management::testing::test_agents_from_env;
-use std::time::Duration;
-use axum::http::Method;
use serde_json::json;
+use std::time::Duration;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn agent_termination() {
@@ -26,13 +26,20 @@ async fn agent_termination() {
None,
)
.await;
- assert_eq!(status, axum::http::StatusCode::NO_CONTENT, "terminate session");
+ assert_eq!(
+ status,
+ axum::http::StatusCode::NO_CONTENT,
+ "terminate session"
+ );
let events = poll_events_until(&app.app, &session_id, Duration::from_secs(30), |events| {
has_event_type(events, "session.ended")
})
.await;
- assert!(has_event_type(&events, "session.ended"), "missing session.ended");
+ assert!(
+ has_event_type(&events, "session.ended"),
+ "missing session.ended"
+ );
let status = send_status(
&app.app,
@@ -41,6 +48,9 @@ async fn agent_termination() {
Some(json!({ "message": PROMPT })),
)
.await;
- assert!(!status.is_success(), "terminated session should reject messages");
+ assert!(
+ !status.is_success(),
+ "terminated session should reject messages"
+ );
}
}
diff --git a/server/packages/sandbox-agent/tests/agent-flows/agent_tool_flow.rs b/server/packages/sandbox-agent/tests/agent-flows/agent_tool_flow.rs
index 3674c34..66e38e0 100644
--- a/server/packages/sandbox-agent/tests/agent-flows/agent_tool_flow.rs
+++ b/server/packages/sandbox-agent/tests/agent-flows/agent_tool_flow.rs
@@ -1,11 +1,11 @@
#[path = "../common/mod.rs"]
mod common;
+use axum::http::Method;
use common::*;
use sandbox_agent_agent_management::testing::test_agents_from_env;
use serde_json::Value;
use std::time::{Duration, Instant};
-use axum::http::Method;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn agent_tool_flow() {
@@ -61,9 +61,7 @@ async fn agent_tool_flow() {
let _ = send_status(
&app.app,
Method::POST,
- &format!(
- "/v1/sessions/{session_id}/permissions/{permission_id}/reply"
- ),
+ &format!("/v1/sessions/{session_id}/permissions/{permission_id}/reply"),
Some(serde_json::json!({ "reply": "once" })),
)
.await;
diff --git a/server/packages/sandbox-agent/tests/agent-management/agents.rs b/server/packages/sandbox-agent/tests/agent-management/agents.rs
index 79aad4c..ab1e7ae 100644
--- a/server/packages/sandbox-agent/tests/agent-management/agents.rs
+++ b/server/packages/sandbox-agent/tests/agent-management/agents.rs
@@ -36,11 +36,19 @@ fn test_agents_install_version_spawn() -> Result<(), Box>
let env = build_env();
assert!(!env.is_empty(), "expected credentials to be available");
- let agents = [AgentId::Claude, AgentId::Codex, AgentId::Opencode, AgentId::Amp];
+ let agents = [
+ AgentId::Claude,
+ AgentId::Codex,
+ AgentId::Opencode,
+ AgentId::Amp,
+ ];
for agent in agents {
let install = manager.install(agent, InstallOptions::default())?;
assert!(install.path.exists(), "expected install for {agent}");
- assert!(manager.is_installed(agent), "expected is_installed for {agent}");
+ assert!(
+ manager.is_installed(agent),
+ "expected is_installed for {agent}"
+ );
manager.install(
agent,
InstallOptions {
@@ -70,9 +78,15 @@ fn test_agents_install_version_spawn() -> Result<(), Box>
);
let combined = format!("{}{}", result.stdout, result.stderr);
let output = result.result.clone().unwrap_or(combined);
- assert!(output.contains("OK"), "expected OK for {agent}, got: {output}");
+ assert!(
+ output.contains("OK"),
+ "expected OK for {agent}, got: {output}"
+ );
- if agent == AgentId::Claude || agent == AgentId::Opencode || (agent == AgentId::Amp && amp_configured()) {
+ if agent == AgentId::Claude
+ || agent == AgentId::Opencode
+ || (agent == AgentId::Amp && amp_configured())
+ {
let mut resume = SpawnOptions::new(prompt_ok("OK2"));
resume.env = env.clone();
resume.session_id = result.session_id.clone();
@@ -84,12 +98,17 @@ fn test_agents_install_version_spawn() -> Result<(), Box>
);
let combined = format!("{}{}", resumed.stdout, resumed.stderr);
let output = resumed.result.clone().unwrap_or(combined);
- assert!(output.contains("OK2"), "expected OK2 for {agent}, got: {output}");
+ assert!(
+ output.contains("OK2"),
+ "expected OK2 for {agent}, got: {output}"
+ );
} else if agent == AgentId::Codex {
let mut resume = SpawnOptions::new(prompt_ok("OK2"));
resume.env = env.clone();
resume.session_id = result.session_id.clone();
- let err = manager.spawn(agent, resume).expect_err("expected resume error for codex");
+ let err = manager
+ .spawn(agent, resume)
+ .expect_err("expected resume error for codex");
assert!(matches!(err, AgentError::ResumeUnsupported { .. }));
}
@@ -105,7 +124,10 @@ fn test_agents_install_version_spawn() -> Result<(), Box>
);
let combined = format!("{}{}", planned.stdout, planned.stderr);
let output = planned.result.clone().unwrap_or(combined);
- assert!(output.contains("OK3"), "expected OK3 for {agent}, got: {output}");
+ assert!(
+ output.contains("OK3"),
+ "expected OK3 for {agent}, got: {output}"
+ );
}
}
}
diff --git a/server/packages/sandbox-agent/tests/common/mod.rs b/server/packages/sandbox-agent/tests/common/mod.rs
index 1d2be09..ea98b47 100644
--- a/server/packages/sandbox-agent/tests/common/mod.rs
+++ b/server/packages/sandbox-agent/tests/common/mod.rs
@@ -9,12 +9,7 @@ use serde_json::{json, Value};
use tempfile::TempDir;
use tower::util::ServiceExt;
-use sandbox_agent::router::{
- build_router,
- AgentCapabilities,
- AgentListResponse,
- AuthConfig,
-};
+use sandbox_agent::router::{build_router, AgentCapabilities, AgentListResponse, AuthConfig};
use sandbox_agent_agent_credentials::ExtractedCredentials;
use sandbox_agent_agent_management::agents::{AgentId, AgentManager};
@@ -32,8 +27,7 @@ pub struct TestApp {
impl TestApp {
pub fn new() -> Self {
let install_dir = tempfile::tempdir().expect("create temp install dir");
- let manager = AgentManager::new(install_dir.path())
- .expect("create agent manager");
+ let manager = AgentManager::new(install_dir.path()).expect("create agent manager");
let state = sandbox_agent::router::AppState::new(AuthConfig::disabled(), manager);
let app = build_router(state);
Self {
@@ -59,7 +53,12 @@ impl Drop for EnvGuard {
}
pub fn apply_credentials(creds: &ExtractedCredentials) -> EnvGuard {
- let keys = ["ANTHROPIC_API_KEY", "CLAUDE_API_KEY", "OPENAI_API_KEY", "CODEX_API_KEY"];
+ let keys = [
+ "ANTHROPIC_API_KEY",
+ "CLAUDE_API_KEY",
+ "OPENAI_API_KEY",
+ "CODEX_API_KEY",
+ ];
let mut saved = HashMap::new();
for key in keys {
saved.insert(key.to_string(), std::env::var(key).ok());
@@ -100,13 +99,11 @@ pub async fn send_json(
.method(method)
.uri(path)
.header("content-type", "application/json")
- .body(Body::from(body.map(|value| value.to_string()).unwrap_or_default()))
+ .body(Body::from(
+ body.map(|value| value.to_string()).unwrap_or_default(),
+ ))
.expect("request");
- let response = app
- .clone()
- .oneshot(request)
- .await
- .expect("response");
+ let response = app.clone().oneshot(request).await.expect("response");
let status = response.status();
let bytes = response
.into_body()
@@ -140,15 +137,15 @@ pub async fn install_agent(app: &Router, agent: AgentId) {
Some(json!({})),
)
.await;
- assert_eq!(status, StatusCode::NO_CONTENT, "install agent {}", agent.as_str());
+ assert_eq!(
+ status,
+ StatusCode::NO_CONTENT,
+ "install agent {}",
+ agent.as_str()
+ );
}
-pub async fn create_session(
- app: &Router,
- agent: AgentId,
- session_id: &str,
- permission_mode: &str,
-) {
+pub async fn create_session(app: &Router, agent: AgentId, session_id: &str, permission_mode: &str) {
let status = send_status(
app,
Method::POST,
diff --git a/server/packages/sandbox-agent/tests/server-manager/agent_server_manager.rs b/server/packages/sandbox-agent/tests/server-manager/agent_server_manager.rs
index 7ef46a8..9b98e4a 100644
--- a/server/packages/sandbox-agent/tests/server-manager/agent_server_manager.rs
+++ b/server/packages/sandbox-agent/tests/server-manager/agent_server_manager.rs
@@ -28,11 +28,7 @@ async fn register_and_unregister_sessions() {
.register_session(AgentId::Codex, "sess-1", Some("thread-1"))
.await;
- assert!(
- harness
- .has_session_mapping(AgentId::Codex, "sess-1")
- .await
- );
+ assert!(harness.has_session_mapping(AgentId::Codex, "sess-1").await);
assert_eq!(
harness
.native_mapping(AgentId::Codex, "thread-1")
@@ -45,17 +41,11 @@ async fn register_and_unregister_sessions() {
.unregister_session(AgentId::Codex, "sess-1", Some("thread-1"))
.await;
- assert!(
- !harness
- .has_session_mapping(AgentId::Codex, "sess-1")
- .await
- );
- assert!(
- harness
- .native_mapping(AgentId::Codex, "thread-1")
- .await
- .is_none()
- );
+ assert!(!harness.has_session_mapping(AgentId::Codex, "sess-1").await);
+ assert!(harness
+ .native_mapping(AgentId::Codex, "thread-1")
+ .await
+ .is_none());
}
#[tokio::test]
@@ -92,9 +82,7 @@ async fn handle_process_exit_marks_error_and_ends_sessions() {
harness
.register_session(AgentId::Codex, "sess-1", Some("thread-1"))
.await;
- harness
- .insert_stdio_server(AgentId::Codex, None, 1)
- .await;
+ harness.insert_stdio_server(AgentId::Codex, None, 1).await;
harness
.handle_process_exit(AgentId::Codex, 1, exit_status(7))
@@ -104,13 +92,11 @@ async fn handle_process_exit_marks_error_and_ends_sessions() {
harness.server_status(AgentId::Codex).await,
Some(sandbox_agent::router::ServerStatus::Error)
));
- assert!(
- harness
- .server_last_error(AgentId::Codex)
- .await
- .unwrap_or_default()
- .contains("exited")
- );
+ assert!(harness
+ .server_last_error(AgentId::Codex)
+ .await
+ .unwrap_or_default()
+ .contains("exited"));
assert!(harness.session_ended("sess-1").await);
assert!(matches!(
harness.session_end_reason("sess-1").await,
diff --git a/server/packages/sandbox-agent/tests/sessions/mod.rs b/server/packages/sandbox-agent/tests/sessions/mod.rs
index a740914..c4ade0b 100644
--- a/server/packages/sandbox-agent/tests/sessions/mod.rs
+++ b/server/packages/sandbox-agent/tests/sessions/mod.rs
@@ -1,6 +1,6 @@
-mod session_lifecycle;
mod multi_turn;
mod permissions;
mod questions;
mod reasoning;
+mod session_lifecycle;
mod status;
diff --git a/server/packages/sandbox-agent/tests/sessions/multi_turn.rs b/server/packages/sandbox-agent/tests/sessions/multi_turn.rs
index 7cba8ba..2f6bf2d 100644
--- a/server/packages/sandbox-agent/tests/sessions/multi_turn.rs
+++ b/server/packages/sandbox-agent/tests/sessions/multi_turn.rs
@@ -81,8 +81,13 @@ async fn multi_turn_snapshots() {
install_agent(&app.app, config.agent).await;
let session_id = format!("multi-turn-{}", config.agent.as_str());
- create_session(&app.app, config.agent, &session_id, test_permission_mode(config.agent))
- .await;
+ create_session(
+ &app.app,
+ config.agent,
+ &session_id,
+ test_permission_mode(config.agent),
+ )
+ .await;
send_message_with_text(&app.app, &session_id, FIRST_PROMPT).await;
let (first_events, offset) =
@@ -100,13 +105,8 @@ async fn multi_turn_snapshots() {
);
send_message_with_text(&app.app, &session_id, SECOND_PROMPT).await;
- let (second_events, _offset) = poll_events_until_from(
- &app.app,
- &session_id,
- offset,
- Duration::from_secs(120),
- )
- .await;
+ let (second_events, _offset) =
+ poll_events_until_from(&app.app, &session_id, offset, Duration::from_secs(120)).await;
let second_events = truncate_after_first_stop(&second_events);
assert!(
!second_events.is_empty(),
diff --git a/server/packages/sandbox-agent/tests/sessions/permissions.rs b/server/packages/sandbox-agent/tests/sessions/permissions.rs
index 996522d..a114236 100644
--- a/server/packages/sandbox-agent/tests/sessions/permissions.rs
+++ b/server/packages/sandbox-agent/tests/sessions/permissions.rs
@@ -55,9 +55,7 @@ async fn permission_flow_snapshots() {
let status = send_status(
&app.app,
Method::POST,
- &format!(
- "/v1/sessions/{permission_session}/permissions/{permission_id}/reply"
- ),
+ &format!("/v1/sessions/{permission_session}/permissions/{permission_id}/reply"),
Some(json!({ "reply": "once" })),
)
.await;
@@ -67,9 +65,7 @@ async fn permission_flow_snapshots() {
let (status, payload) = send_json(
&app.app,
Method::POST,
- &format!(
- "/v1/sessions/{permission_session}/permissions/missing-permission/reply"
- ),
+ &format!("/v1/sessions/{permission_session}/permissions/missing-permission/reply"),
Some(json!({ "reply": "once" })),
)
.await;
diff --git a/server/packages/sandbox-agent/tests/sessions/questions.rs b/server/packages/sandbox-agent/tests/sessions/questions.rs
index 889f0d4..db14fb1 100644
--- a/server/packages/sandbox-agent/tests/sessions/questions.rs
+++ b/server/packages/sandbox-agent/tests/sessions/questions.rs
@@ -55,9 +55,7 @@ async fn question_flow_snapshots() {
let status = send_status(
&app.app,
Method::POST,
- &format!(
- "/v1/sessions/{question_reply_session}/questions/{question_id}/reply"
- ),
+ &format!("/v1/sessions/{question_reply_session}/questions/{question_id}/reply"),
Some(json!({ "answers": answers })),
)
.await;
@@ -67,9 +65,7 @@ async fn question_flow_snapshots() {
let (status, payload) = send_json(
&app.app,
Method::POST,
- &format!(
- "/v1/sessions/{question_reply_session}/questions/missing-question/reply"
- ),
+ &format!("/v1/sessions/{question_reply_session}/questions/missing-question/reply"),
Some(json!({ "answers": [] })),
)
.await;
@@ -92,7 +88,11 @@ async fn question_flow_snapshots() {
Some(json!({ "message": QUESTION_PROMPT })),
)
.await;
- assert_eq!(status, StatusCode::NO_CONTENT, "send question prompt reject");
+ assert_eq!(
+ status,
+ StatusCode::NO_CONTENT,
+ "send question prompt reject"
+ );
let reject_events = poll_events_until_match(
&app.app,
@@ -108,9 +108,7 @@ async fn question_flow_snapshots() {
let status = send_status(
&app.app,
Method::POST,
- &format!(
- "/v1/sessions/{question_reject_session}/questions/{question_id}/reject"
- ),
+ &format!("/v1/sessions/{question_reject_session}/questions/{question_id}/reject"),
None,
)
.await;
@@ -126,7 +124,10 @@ async fn question_flow_snapshots() {
None,
)
.await;
- assert!(!status.is_success(), "missing question id reject should error");
+ assert!(
+ !status.is_success(),
+ "missing question id reject should error"
+ );
assert_session_snapshot(
"question_reject_missing",
json!({
diff --git a/server/packages/sandbox-agent/tests/sessions/reasoning.rs b/server/packages/sandbox-agent/tests/sessions/reasoning.rs
index 9be1919..a4f5acc 100644
--- a/server/packages/sandbox-agent/tests/sessions/reasoning.rs
+++ b/server/packages/sandbox-agent/tests/sessions/reasoning.rs
@@ -23,8 +23,13 @@ async fn reasoning_events_present() {
install_agent(&app.app, config.agent).await;
let session_id = format!("reasoning-{}", config.agent.as_str());
- create_session(&app.app, config.agent, &session_id, test_permission_mode(config.agent))
- .await;
+ create_session(
+ &app.app,
+ config.agent,
+ &session_id,
+ test_permission_mode(config.agent),
+ )
+ .await;
let status = send_status(
&app.app,
Method::POST,
@@ -34,13 +39,11 @@ async fn reasoning_events_present() {
.await;
assert_eq!(status, StatusCode::NO_CONTENT, "send reasoning prompt");
- let events = poll_events_until_match(
- &app.app,
- &session_id,
- Duration::from_secs(120),
- |events| events_have_content_type(events, "reasoning") || events.iter().any(is_error_event),
- )
- .await;
+ let events =
+ poll_events_until_match(&app.app, &session_id, Duration::from_secs(120), |events| {
+ events_have_content_type(events, "reasoning") || events.iter().any(is_error_event)
+ })
+ .await;
assert!(
events_have_content_type(&events, "reasoning"),
"expected reasoning content for {}",
diff --git a/server/packages/sandbox-agent/tests/sessions/status.rs b/server/packages/sandbox-agent/tests/sessions/status.rs
index ce2faae..cdd9b28 100644
--- a/server/packages/sandbox-agent/tests/sessions/status.rs
+++ b/server/packages/sandbox-agent/tests/sessions/status.rs
@@ -28,8 +28,13 @@ async fn status_events_present() {
install_agent(&app.app, config.agent).await;
let session_id = format!("status-{}", config.agent.as_str());
- create_session(&app.app, config.agent, &session_id, test_permission_mode(config.agent))
- .await;
+ create_session(
+ &app.app,
+ config.agent,
+ &session_id,
+ test_permission_mode(config.agent),
+ )
+ .await;
let status = send_status(
&app.app,
Method::POST,
@@ -39,13 +44,11 @@ async fn status_events_present() {
.await;
assert_eq!(status, StatusCode::NO_CONTENT, "send status prompt");
- let events = poll_events_until_match(
- &app.app,
- &session_id,
- Duration::from_secs(120),
- |events| events_have_status(events) || events.iter().any(is_error_event),
- )
- .await;
+ let events =
+ poll_events_until_match(&app.app, &session_id, Duration::from_secs(120), |events| {
+ events_have_status(events) || events.iter().any(is_error_event)
+ })
+ .await;
assert!(
events_have_status(&events),
"expected status events for {}",
diff --git a/server/packages/sandbox-agent/tests/ui/inspector_ui.rs b/server/packages/sandbox-agent/tests/ui/inspector_ui.rs
index f98a758..dd5e5b1 100644
--- a/server/packages/sandbox-agent/tests/ui/inspector_ui.rs
+++ b/server/packages/sandbox-agent/tests/ui/inspector_ui.rs
@@ -1,9 +1,9 @@
use axum::body::Body;
use axum::http::{Request, StatusCode};
use http_body_util::BodyExt;
-use sandbox_agent_agent_management::agents::AgentManager;
use sandbox_agent::router::{build_router, AppState, AuthConfig};
use sandbox_agent::ui;
+use sandbox_agent_agent_management::agents::AgentManager;
use tempfile::TempDir;
use tower::util::ServiceExt;
@@ -22,10 +22,7 @@ async fn serves_inspector_ui() {
.uri("/ui")
.body(Body::empty())
.expect("build request");
- let response = app
- .oneshot(request)
- .await
- .expect("request handled");
+ let response = app.oneshot(request).await.expect("request handled");
assert_eq!(response.status(), StatusCode::OK);
diff --git a/server/packages/universal-agent-schema/src/agents/amp.rs b/server/packages/universal-agent-schema/src/agents/amp.rs
index ab96765..75326fc 100644
--- a/server/packages/universal-agent-schema/src/agents/amp.rs
+++ b/server/packages/universal-agent-schema/src/agents/amp.rs
@@ -4,20 +4,9 @@ use serde_json::Value;
use crate::amp as schema;
use crate::{
- ContentPart,
- ErrorData,
- EventConversion,
- ItemDeltaData,
- ItemEventData,
- ItemKind,
- ItemRole,
- ItemStatus,
- SessionEndedData,
- SessionEndReason,
- TerminatedBy,
- UniversalEventData,
- UniversalEventType,
- UniversalItem,
+ ContentPart, ErrorData, EventConversion, ItemDeltaData, ItemEventData, ItemKind, ItemRole,
+ ItemStatus, SessionEndReason, SessionEndedData, TerminatedBy, UniversalEventData,
+ UniversalEventType, UniversalItem,
};
static TEMP_ID: AtomicU64 = AtomicU64::new(1);
@@ -27,7 +16,9 @@ fn next_temp_id(prefix: &str) -> String {
format!("{prefix}_{id}")
}
-pub fn event_to_universal(event: &schema::StreamJsonMessage) -> Result, String> {
+pub fn event_to_universal(
+ event: &schema::StreamJsonMessage,
+) -> Result, String> {
let mut events = Vec::new();
match event.type_ {
schema::StreamJsonMessageType::Message => {
@@ -49,12 +40,17 @@ pub fn event_to_universal(event: &schema::StreamJsonMessage) -> Result text,
schema::ToolCallArguments::Variant1(map) => {
- serde_json::to_string(&Value::Object(map)).unwrap_or_else(|_| "{}".to_string())
+ serde_json::to_string(&Value::Object(map))
+ .unwrap_or_else(|_| "{}".to_string())
}
};
(call.name, arguments, call.id)
} else {
- ("unknown".to_string(), "{}".to_string(), next_temp_id("tmp_amp_tool"))
+ (
+ "unknown".to_string(),
+ "{}".to_string(),
+ next_temp_id("tmp_amp_tool"),
+ )
};
let item = UniversalItem {
item_id: next_temp_id("tmp_amp_tool_call"),
@@ -83,16 +79,16 @@ pub fn event_to_universal(event: &schema::StreamJsonMessage) -> Result {
- let message = event.error.clone().unwrap_or_else(|| "amp error".to_string());
+ let message = event
+ .error
+ .clone()
+ .unwrap_or_else(|| "amp error".to_string());
events.push(EventConversion::new(
UniversalEventType::Error,
UniversalEventData::Error(ErrorData {
diff --git a/server/packages/universal-agent-schema/src/agents/claude.rs b/server/packages/universal-agent-schema/src/agents/claude.rs
index 3a62285..5e5c7bc 100644
--- a/server/packages/universal-agent-schema/src/agents/claude.rs
+++ b/server/packages/universal-agent-schema/src/agents/claude.rs
@@ -3,21 +3,9 @@ use std::sync::atomic::{AtomicU64, Ordering};
use serde_json::Value;
use crate::{
- ContentPart,
- EventConversion,
- ItemEventData,
- ItemDeltaData,
- ItemKind,
- ItemRole,
- ItemStatus,
- PermissionEventData,
- PermissionStatus,
- QuestionEventData,
- QuestionStatus,
- SessionStartedData,
- UniversalEventData,
- UniversalEventType,
- UniversalItem,
+ ContentPart, EventConversion, ItemDeltaData, ItemEventData, ItemKind, ItemRole, ItemStatus,
+ PermissionEventData, PermissionStatus, QuestionEventData, QuestionStatus, SessionStartedData,
+ UniversalEventData, UniversalEventType, UniversalItem,
};
static TEMP_ID: AtomicU64 = AtomicU64::new(1);
@@ -56,8 +44,11 @@ fn system_event_to_universal(event: &Value) -> EventConversion {
let data = SessionStartedData {
metadata: Some(event.clone()),
};
- EventConversion::new(UniversalEventType::SessionStarted, UniversalEventData::SessionStarted(data))
- .with_raw(Some(event.clone()))
+ EventConversion::new(
+ UniversalEventType::SessionStarted,
+ UniversalEventData::SessionStarted(data),
+ )
+ .with_raw(Some(event.clone()))
}
fn assistant_event_to_universal(event: &Value, session_id: &str) -> Vec {
@@ -97,12 +88,15 @@ fn assistant_event_to_universal(event: &Value, session_id: &str) -> Vec Vec Vec Result, S
.get("request")
.and_then(Value::as_object)
.ok_or_else(|| "missing request".to_string())?;
- let subtype = request
- .get("subtype")
- .and_then(Value::as_str)
- .unwrap_or("");
+ let subtype = request.get("subtype").and_then(Value::as_str).unwrap_or("");
if subtype != "can_use_tool" {
- return Err(format!("unsupported Claude control_request subtype: {subtype}"));
+ return Err(format!(
+ "unsupported Claude control_request subtype: {subtype}"
+ ));
}
let tool_name = request
@@ -387,10 +378,7 @@ fn control_request_to_universal(event: &Value) -> Result, S
.get("permission_suggestions")
.cloned()
.unwrap_or(Value::Null);
- let blocked_path = request
- .get("blocked_path")
- .cloned()
- .unwrap_or(Value::Null);
+ let blocked_path = request.get("blocked_path").cloned().unwrap_or(Value::Null);
let metadata = serde_json::json!({
"toolName": tool_name,
diff --git a/server/packages/universal-agent-schema/src/agents/codex.rs b/server/packages/universal-agent-schema/src/agents/codex.rs
index 78dc917..470e406 100644
--- a/server/packages/universal-agent-schema/src/agents/codex.rs
+++ b/server/packages/universal-agent-schema/src/agents/codex.rs
@@ -2,22 +2,9 @@ use serde_json::Value;
use crate::codex as schema;
use crate::{
- ContentPart,
- ErrorData,
- EventConversion,
- ItemDeltaData,
- ItemEventData,
- ItemKind,
- ItemRole,
- ItemStatus,
- ReasoningVisibility,
- SessionEndedData,
- SessionEndReason,
- SessionStartedData,
- TerminatedBy,
- UniversalEventData,
- UniversalEventType,
- UniversalItem,
+ ContentPart, ErrorData, EventConversion, ItemDeltaData, ItemEventData, ItemKind, ItemRole,
+ ItemStatus, ReasoningVisibility, SessionEndReason, SessionEndedData, SessionStartedData,
+ TerminatedBy, UniversalEventData, UniversalEventType, UniversalItem,
};
/// Convert a Codex ServerNotification to universal events.
@@ -30,14 +17,12 @@ pub fn notification_to_universal(
let data = SessionStartedData {
metadata: serde_json::to_value(¶ms.thread).ok(),
};
- Ok(vec![
- EventConversion::new(
- UniversalEventType::SessionStarted,
- UniversalEventData::SessionStarted(data),
- )
- .with_native_session(Some(params.thread.id.clone()))
- .with_raw(raw),
- ])
+ Ok(vec![EventConversion::new(
+ UniversalEventType::SessionStarted,
+ UniversalEventData::SessionStarted(data),
+ )
+ .with_native_session(Some(params.thread.id.clone()))
+ .with_raw(raw)])
}
schema::ServerNotification::ThreadCompacted(params) => Ok(vec![status_event(
"thread.compacted",
@@ -77,28 +62,24 @@ pub fn notification_to_universal(
)]),
schema::ServerNotification::ItemStarted(params) => {
let item = thread_item_to_item(¶ms.item, ItemStatus::InProgress);
- Ok(vec![
- EventConversion::new(
- UniversalEventType::ItemStarted,
- UniversalEventData::Item(ItemEventData { item }),
- )
- .with_native_session(Some(params.thread_id.clone()))
- .with_raw(raw),
- ])
+ Ok(vec![EventConversion::new(
+ UniversalEventType::ItemStarted,
+ UniversalEventData::Item(ItemEventData { item }),
+ )
+ .with_native_session(Some(params.thread_id.clone()))
+ .with_raw(raw)])
}
schema::ServerNotification::ItemCompleted(params) => {
let item = thread_item_to_item(¶ms.item, ItemStatus::Completed);
- Ok(vec![
- EventConversion::new(
- UniversalEventType::ItemCompleted,
- UniversalEventData::Item(ItemEventData { item }),
- )
- .with_native_session(Some(params.thread_id.clone()))
- .with_raw(raw),
- ])
+ Ok(vec![EventConversion::new(
+ UniversalEventType::ItemCompleted,
+ UniversalEventData::Item(ItemEventData { item }),
+ )
+ .with_native_session(Some(params.thread_id.clone()))
+ .with_raw(raw)])
}
- schema::ServerNotification::ItemAgentMessageDelta(params) => Ok(vec![
- EventConversion::new(
+ schema::ServerNotification::ItemAgentMessageDelta(params) => {
+ Ok(vec![EventConversion::new(
UniversalEventType::ItemDelta,
UniversalEventData::ItemDelta(ItemDeltaData {
item_id: String::new(),
@@ -107,10 +88,10 @@ pub fn notification_to_universal(
}),
)
.with_native_session(Some(params.thread_id.clone()))
- .with_raw(raw),
- ]),
- schema::ServerNotification::ItemReasoningTextDelta(params) => Ok(vec![
- EventConversion::new(
+ .with_raw(raw)])
+ }
+ schema::ServerNotification::ItemReasoningTextDelta(params) => {
+ Ok(vec![EventConversion::new(
UniversalEventType::ItemDelta,
UniversalEventData::ItemDelta(ItemDeltaData {
item_id: String::new(),
@@ -119,10 +100,10 @@ pub fn notification_to_universal(
}),
)
.with_native_session(Some(params.thread_id.clone()))
- .with_raw(raw),
- ]),
- schema::ServerNotification::ItemReasoningSummaryTextDelta(params) => Ok(vec![
- EventConversion::new(
+ .with_raw(raw)])
+ }
+ schema::ServerNotification::ItemReasoningSummaryTextDelta(params) => {
+ Ok(vec![EventConversion::new(
UniversalEventType::ItemDelta,
UniversalEventData::ItemDelta(ItemDeltaData {
item_id: String::new(),
@@ -131,10 +112,10 @@ pub fn notification_to_universal(
}),
)
.with_native_session(Some(params.thread_id.clone()))
- .with_raw(raw),
- ]),
- schema::ServerNotification::ItemCommandExecutionOutputDelta(params) => Ok(vec![
- EventConversion::new(
+ .with_raw(raw)])
+ }
+ schema::ServerNotification::ItemCommandExecutionOutputDelta(params) => {
+ Ok(vec![EventConversion::new(
UniversalEventType::ItemDelta,
UniversalEventData::ItemDelta(ItemDeltaData {
item_id: String::new(),
@@ -143,10 +124,10 @@ pub fn notification_to_universal(
}),
)
.with_native_session(Some(params.thread_id.clone()))
- .with_raw(raw),
- ]),
- schema::ServerNotification::ItemFileChangeOutputDelta(params) => Ok(vec![
- EventConversion::new(
+ .with_raw(raw)])
+ }
+ schema::ServerNotification::ItemFileChangeOutputDelta(params) => {
+ Ok(vec![EventConversion::new(
UniversalEventType::ItemDelta,
UniversalEventData::ItemDelta(ItemDeltaData {
item_id: String::new(),
@@ -155,10 +136,10 @@ pub fn notification_to_universal(
}),
)
.with_native_session(Some(params.thread_id.clone()))
- .with_raw(raw),
- ]),
- schema::ServerNotification::ItemCommandExecutionTerminalInteraction(params) => Ok(vec![
- EventConversion::new(
+ .with_raw(raw)])
+ }
+ schema::ServerNotification::ItemCommandExecutionTerminalInteraction(params) => {
+ Ok(vec![EventConversion::new(
UniversalEventType::ItemDelta,
UniversalEventData::ItemDelta(ItemDeltaData {
item_id: String::new(),
@@ -167,33 +148,34 @@ pub fn notification_to_universal(
}),
)
.with_native_session(Some(params.thread_id.clone()))
- .with_raw(raw),
- ]),
+ .with_raw(raw)])
+ }
schema::ServerNotification::ItemMcpToolCallProgress(params) => Ok(vec![status_event(
"mcp.progress",
serde_json::to_string(params).ok(),
Some(params.thread_id.clone()),
raw,
)]),
- schema::ServerNotification::ItemReasoningSummaryPartAdded(params) => Ok(vec![
- status_event(
+ schema::ServerNotification::ItemReasoningSummaryPartAdded(params) => {
+ Ok(vec![status_event(
"reasoning.summary.part_added",
serde_json::to_string(params).ok(),
Some(params.thread_id.clone()),
raw,
- ),
- ]),
+ )])
+ }
schema::ServerNotification::Error(params) => {
let data = ErrorData {
message: params.error.message.clone(),
code: None,
details: serde_json::to_value(params).ok(),
};
- Ok(vec![
- EventConversion::new(UniversalEventType::Error, UniversalEventData::Error(data))
- .with_native_session(Some(params.thread_id.clone()))
- .with_raw(raw),
- ])
+ Ok(vec![EventConversion::new(
+ UniversalEventType::Error,
+ UniversalEventData::Error(data),
+ )
+ .with_native_session(Some(params.thread_id.clone()))
+ .with_raw(raw)])
}
schema::ServerNotification::RawResponseItemCompleted(params) => Ok(vec![status_event(
"raw.item.completed",
@@ -239,7 +221,11 @@ fn thread_item_to_item(item: &schema::ThreadItem, status: ItemStatus) -> Univers
content: vec![ContentPart::Text { text: text.clone() }],
status,
},
- schema::ThreadItem::Reasoning { content, id, summary } => {
+ schema::ThreadItem::Reasoning {
+ content,
+ id,
+ summary,
+ } => {
let mut parts = Vec::new();
for line in content {
parts.push(ContentPart::Reasoning {
@@ -295,7 +281,11 @@ fn thread_item_to_item(item: &schema::ThreadItem, status: ItemStatus) -> Univers
status,
}
}
- schema::ThreadItem::FileChange { changes, id, status: file_status } => UniversalItem {
+ schema::ThreadItem::FileChange {
+ changes,
+ id,
+ status: file_status,
+ } => UniversalItem {
item_id: String::new(),
native_item_id: Some(id.clone()),
parent_id: None,
@@ -339,7 +329,8 @@ fn thread_item_to_item(item: &schema::ThreadItem, status: ItemStatus) -> Univers
output,
});
} else {
- let arguments = serde_json::to_string(arguments).unwrap_or_else(|_| "{}".to_string());
+ let arguments =
+ serde_json::to_string(arguments).unwrap_or_else(|_| "{}".to_string());
parts.push(ContentPart::ToolCall {
name: format!("{server}.{tool}"),
arguments,
@@ -433,18 +424,12 @@ fn thread_item_to_item(item: &schema::ThreadItem, status: ItemStatus) -> Univers
}],
status,
},
- schema::ThreadItem::EnteredReviewMode { id, review } => status_item_internal(
- id,
- "review.entered",
- Some(review.clone()),
- status,
- ),
- schema::ThreadItem::ExitedReviewMode { id, review } => status_item_internal(
- id,
- "review.exited",
- Some(review.clone()),
- status,
- ),
+ schema::ThreadItem::EnteredReviewMode { id, review } => {
+ status_item_internal(id, "review.entered", Some(review.clone()), status)
+ }
+ schema::ThreadItem::ExitedReviewMode { id, review } => {
+ status_item_internal(id, "review.exited", Some(review.clone()), status)
+ }
}
}
@@ -463,7 +448,12 @@ fn status_item(label: &str, detail: Option) -> UniversalItem {
}
}
-fn status_item_internal(id: &str, label: &str, detail: Option, status: ItemStatus) -> UniversalItem {
+fn status_item_internal(
+ id: &str,
+ label: &str,
+ detail: Option,
+ status: ItemStatus,
+) -> UniversalItem {
UniversalItem {
item_id: String::new(),
native_item_id: Some(id.to_string()),
diff --git a/server/packages/universal-agent-schema/src/agents/opencode.rs b/server/packages/universal-agent-schema/src/agents/opencode.rs
index b1bc3d9..ee0941c 100644
--- a/server/packages/universal-agent-schema/src/agents/opencode.rs
+++ b/server/packages/universal-agent-schema/src/agents/opencode.rs
@@ -2,46 +2,42 @@ use serde_json::Value;
use crate::opencode as schema;
use crate::{
- ContentPart,
- EventConversion,
- ItemDeltaData,
- ItemEventData,
- ItemKind,
- ItemRole,
- ItemStatus,
- PermissionEventData,
- PermissionStatus,
- QuestionEventData,
- QuestionStatus,
- SessionStartedData,
- UniversalEventData,
- UniversalEventType,
- UniversalItem,
+ ContentPart, EventConversion, ItemDeltaData, ItemEventData, ItemKind, ItemRole, ItemStatus,
+ PermissionEventData, PermissionStatus, QuestionEventData, QuestionStatus, SessionStartedData,
+ UniversalEventData, UniversalEventType, UniversalItem,
};
pub fn event_to_universal(event: &schema::Event) -> Result, String> {
let raw = serde_json::to_value(event).ok();
match event {
schema::Event::MessageUpdated(updated) => {
- let schema::EventMessageUpdated { properties, type_: _ } = updated;
+ let schema::EventMessageUpdated {
+ properties,
+ type_: _,
+ } = updated;
let schema::EventMessageUpdatedProperties { info } = properties;
let (mut item, completed, session_id) = message_to_item(info);
- item.status = if completed { ItemStatus::Completed } else { ItemStatus::InProgress };
+ item.status = if completed {
+ ItemStatus::Completed
+ } else {
+ ItemStatus::InProgress
+ };
let event_type = if completed {
UniversalEventType::ItemCompleted
} else {
UniversalEventType::ItemStarted
};
- let conversion = EventConversion::new(
- event_type,
- UniversalEventData::Item(ItemEventData { item }),
- )
- .with_native_session(session_id)
- .with_raw(raw);
+ let conversion =
+ EventConversion::new(event_type, UniversalEventData::Item(ItemEventData { item }))
+ .with_native_session(session_id)
+ .with_raw(raw);
Ok(vec![conversion])
}
schema::Event::MessagePartUpdated(updated) => {
- let schema::EventMessagePartUpdated { properties, type_: _ } = updated;
+ let schema::EventMessagePartUpdated {
+ properties,
+ type_: _,
+ } = updated;
let schema::EventMessagePartUpdatedProperties { part, delta } = properties;
let mut events = Vec::new();
let (session_id, message_id) = part_session_message(part);
@@ -122,11 +118,16 @@ pub fn event_to_universal(event: &schema::Event) -> Result,
schema::Part::Variant4(tool_part) => {
let tool_events = tool_part_to_events(&tool_part, &message_id);
for event in tool_events {
- events.push(event.with_native_session(session_id.clone()).with_raw(raw.clone()));
+ events.push(
+ event
+ .with_native_session(session_id.clone())
+ .with_raw(raw.clone()),
+ );
}
}
schema::Part::Variant1 { .. } => {
- let detail = serde_json::to_string(part).unwrap_or_else(|_| "subtask".to_string());
+ let detail =
+ serde_json::to_string(part).unwrap_or_else(|_| "subtask".to_string());
let item = status_item("subtask", Some(detail));
events.push(
EventConversion::new(
@@ -160,7 +161,10 @@ pub fn event_to_universal(event: &schema::Event) -> Result,
Ok(events)
}
schema::Event::QuestionAsked(asked) => {
- let schema::EventQuestionAsked { properties, type_: _ } = asked;
+ let schema::EventQuestionAsked {
+ properties,
+ type_: _,
+ } = asked;
let question = question_from_opencode(properties);
let conversion = EventConversion::new(
UniversalEventType::QuestionRequested,
@@ -171,7 +175,10 @@ pub fn event_to_universal(event: &schema::Event) -> Result,
Ok(vec![conversion])
}
schema::Event::PermissionAsked(asked) => {
- let schema::EventPermissionAsked { properties, type_: _ } = asked;
+ let schema::EventPermissionAsked {
+ properties,
+ type_: _,
+ } = asked;
let permission = permission_from_opencode(properties);
let conversion = EventConversion::new(
UniversalEventType::PermissionRequested,
@@ -182,7 +189,10 @@ pub fn event_to_universal(event: &schema::Event) -> Result,
Ok(vec![conversion])
}
schema::Event::SessionCreated(created) => {
- let schema::EventSessionCreated { properties, type_: _ } = created;
+ let schema::EventSessionCreated {
+ properties,
+ type_: _,
+ } = created;
let metadata = serde_json::to_value(&properties.info).ok();
let conversion = EventConversion::new(
UniversalEventType::SessionStarted,
@@ -193,8 +203,12 @@ pub fn event_to_universal(event: &schema::Event) -> Result,
Ok(vec![conversion])
}
schema::Event::SessionStatus(status) => {
- let schema::EventSessionStatus { properties, type_: _ } = status;
- let detail = serde_json::to_string(&properties.status).unwrap_or_else(|_| "status".to_string());
+ let schema::EventSessionStatus {
+ properties,
+ type_: _,
+ } = status;
+ let detail =
+ serde_json::to_string(&properties.status).unwrap_or_else(|_| "status".to_string());
let item = status_item("session.status", Some(detail));
let conversion = EventConversion::new(
UniversalEventType::ItemCompleted,
@@ -205,7 +219,10 @@ pub fn event_to_universal(event: &schema::Event) -> Result,
Ok(vec![conversion])
}
schema::Event::SessionIdle(idle) => {
- let schema::EventSessionIdle { properties, type_: _ } = idle;
+ let schema::EventSessionIdle {
+ properties,
+ type_: _,
+ } = idle;
let item = status_item("session.idle", None);
let conversion = EventConversion::new(
UniversalEventType::ItemCompleted,
@@ -216,8 +233,12 @@ pub fn event_to_universal(event: &schema::Event) -> Result,
Ok(vec![conversion])
}
schema::Event::SessionError(error) => {
- let schema::EventSessionError { properties, type_: _ } = error;
- let detail = serde_json::to_string(&properties.error).unwrap_or_else(|_| "session error".to_string());
+ let schema::EventSessionError {
+ properties,
+ type_: _,
+ } = error;
+ let detail = serde_json::to_string(&properties.error)
+ .unwrap_or_else(|_| "session error".to_string());
let item = status_item("session.error", Some(detail));
let conversion = EventConversion::new(
UniversalEventType::ItemCompleted,
@@ -270,7 +291,11 @@ fn message_to_item(message: &schema::Message) -> (UniversalItem, bool, Option (UniversalItem, bool, Option (Option, String) {
match part {
- schema::Part::Variant0(text_part) => {
- (Some(text_part.session_id.clone()), text_part.message_id.clone())
- }
+ schema::Part::Variant0(text_part) => (
+ Some(text_part.session_id.clone()),
+ text_part.message_id.clone(),
+ ),
schema::Part::Variant1 {
session_id,
message_id,
diff --git a/server/packages/universal-agent-schema/src/lib.rs b/server/packages/universal-agent-schema/src/lib.rs
index 428e7a5..f4735f0 100644
--- a/server/packages/universal-agent-schema/src/lib.rs
+++ b/server/packages/universal-agent-schema/src/lib.rs
@@ -1,13 +1,16 @@
+use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
-use schemars::JsonSchema;
use utoipa::ToSchema;
pub use sandbox_agent_extracted_agent_schemas::{amp, claude, codex, opencode};
pub mod agents;
-pub use agents::{amp as convert_amp, claude as convert_claude, codex as convert_codex, opencode as convert_opencode};
+pub use agents::{
+ amp as convert_amp, claude as convert_claude, codex as convert_codex,
+ opencode as convert_opencode,
+};
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct UniversalEvent {
@@ -221,14 +224,38 @@ pub enum ItemStatus {
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentPart {
- Text { text: String },
- Json { json: Value },
- ToolCall { name: String, arguments: String, call_id: String },
- ToolResult { call_id: String, output: String },
- FileRef { path: String, action: FileAction, diff: Option },
- Reasoning { text: String, visibility: ReasoningVisibility },
- Image { path: String, mime: Option },
- Status { label: String, detail: Option },
+ Text {
+ text: String,
+ },
+ Json {
+ json: Value,
+ },
+ ToolCall {
+ name: String,
+ arguments: String,
+ call_id: String,
+ },
+ ToolResult {
+ call_id: String,
+ output: String,
+ },
+ FileRef {
+ path: String,
+ action: FileAction,
+ diff: Option,
+ },
+ Reasoning {
+ text: String,
+ visibility: ReasoningVisibility,
+ },
+ Image {
+ path: String,
+ mime: Option,
+ },
+ Status {
+ label: String,
+ detail: Option,
+ },
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema)]