diff --git a/Cargo.toml b/Cargo.toml index 3907eee..74906c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["server/packages/*"] [workspace.package] -version = "0.1.6-rc.1" +version = "0.1.6" 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.6-rc.1", path = "server/packages/sandbox-agent" } -sandbox-agent-error = { version = "0.1.6-rc.1", path = "server/packages/error" } -sandbox-agent-agent-management = { version = "0.1.6-rc.1", path = "server/packages/agent-management" } -sandbox-agent-agent-credentials = { version = "0.1.6-rc.1", path = "server/packages/agent-credentials" } -sandbox-agent-universal-agent-schema = { version = "0.1.6-rc.1", path = "server/packages/universal-agent-schema" } -sandbox-agent-extracted-agent-schemas = { version = "0.1.6-rc.1", path = "server/packages/extracted-agent-schemas" } +sandbox-agent = { version = "0.1.6", path = "server/packages/sandbox-agent" } +sandbox-agent-error = { version = "0.1.6", path = "server/packages/error" } +sandbox-agent-agent-management = { version = "0.1.6", path = "server/packages/agent-management" } +sandbox-agent-agent-credentials = { version = "0.1.6", path = "server/packages/agent-credentials" } +sandbox-agent-universal-agent-schema = { version = "0.1.6", path = "server/packages/universal-agent-schema" } +sandbox-agent-extracted-agent-schemas = { version = "0.1.6", path = "server/packages/extracted-agent-schemas" } # Serialization serde = { version = "1.0", features = ["derive"] } diff --git a/sdks/cli-shared/package.json b/sdks/cli-shared/package.json index d6d28ab..0c041d1 100644 --- a/sdks/cli-shared/package.json +++ b/sdks/cli-shared/package.json @@ -1,6 +1,6 @@ { "name": "@sandbox-agent/cli-shared", - "version": "0.1.6-rc.1", + "version": "0.1.6", "description": "Shared helpers for sandbox-agent CLI and SDK", "license": "Apache-2.0", "repository": { diff --git a/sdks/cli/package.json b/sdks/cli/package.json index 0f85a6e..fde9b8b 100644 --- a/sdks/cli/package.json +++ b/sdks/cli/package.json @@ -1,6 +1,6 @@ { "name": "@sandbox-agent/cli", - "version": "0.1.6-rc.1", + "version": "0.1.6", "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 cd66e2b..cfff424 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.6-rc.1", + "version": "0.1.6", "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 02d8f00..8fa6330 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.6-rc.1", + "version": "0.1.6", "description": "sandbox-agent CLI binary for macOS x64", "license": "Apache-2.0", "repository": { diff --git a/sdks/cli/platforms/linux-arm64/package.json b/sdks/cli/platforms/linux-arm64/package.json index f4b12f7..41db961 100644 --- a/sdks/cli/platforms/linux-arm64/package.json +++ b/sdks/cli/platforms/linux-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@sandbox-agent/cli-linux-arm64", - "version": "0.1.6-rc.1", + "version": "0.1.6", "description": "sandbox-agent CLI binary for Linux arm64", "license": "Apache-2.0", "repository": { diff --git a/sdks/cli/platforms/linux-x64/package.json b/sdks/cli/platforms/linux-x64/package.json index b07eb77..28e3b13 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.6-rc.1", + "version": "0.1.6", "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 6dbb302..e1f3001 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.6-rc.1", + "version": "0.1.6", "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 29bb10e..8b135bf 100644 --- a/sdks/typescript/package.json +++ b/sdks/typescript/package.json @@ -1,6 +1,6 @@ { "name": "sandbox-agent", - "version": "0.1.6-rc.1", + "version": "0.1.6", "description": "Universal API for automatic coding agents in sandboxes. Supprots Claude Code, Codex, OpenCode, and Amp.", "license": "Apache-2.0", "repository": { diff --git a/server/packages/sandbox-agent/src/main.rs b/server/packages/sandbox-agent/src/main.rs index e3e6540..46a9efe 100644 --- a/server/packages/sandbox-agent/src/main.rs +++ b/server/packages/sandbox-agent/src/main.rs @@ -515,7 +515,8 @@ fn run_opencode(cli: &Cli, args: &OpencodeArgs) -> Result<(), CliError> { let base_url = format!("http://{}:{}", args.host, args.port); wait_for_health(&mut server_child, &base_url, token.as_deref())?; - let session_id = create_opencode_session(&base_url, token.as_deref(), args.session_title.as_deref())?; + let session_id = + create_opencode_session(&base_url, token.as_deref(), args.session_title.as_deref())?; write_stdout_line(&format!("OpenCode session: {session_id}"))?; let attach_url = format!("{base_url}/opencode"); @@ -776,7 +777,10 @@ fn create_opencode_session( }; let mut request = client.post(&url).json(&body); if let Ok(directory) = std::env::current_dir() { - request = request.header("x-opencode-directory", directory.to_string_lossy().to_string()); + request = request.header( + "x-opencode-directory", + directory.to_string_lossy().to_string(), + ); } if let Some(token) = token { request = request.bearer_auth(token); diff --git a/server/packages/sandbox-agent/src/opencode_compat.rs b/server/packages/sandbox-agent/src/opencode_compat.rs index 55b7050..427d440 100644 --- a/server/packages/sandbox-agent/src/opencode_compat.rs +++ b/server/packages/sandbox-agent/src/opencode_compat.rs @@ -6,9 +6,9 @@ use std::collections::HashMap; use std::convert::Infallible; +use std::str::FromStr; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; -use std::str::FromStr; use axum::extract::{Path, Query, State}; use axum::http::{HeaderMap, StatusCode}; @@ -24,12 +24,12 @@ use tokio::time::interval; use utoipa::{IntoParams, OpenApi, ToSchema}; use crate::router::{AppState, CreateSessionRequest, PermissionReply}; -use sandbox_agent_error::SandboxError; use sandbox_agent_agent_management::agents::AgentId; +use sandbox_agent_error::SandboxError; use sandbox_agent_universal_agent_schema::{ - ContentPart, ItemDeltaData, ItemEventData, ItemKind, ItemRole, UniversalEvent, UniversalEventData, - UniversalEventType, UniversalItem, PermissionEventData, PermissionStatus, QuestionEventData, - QuestionStatus, FileAction, ItemStatus, + ContentPart, FileAction, ItemDeltaData, ItemEventData, ItemKind, ItemRole, ItemStatus, + PermissionEventData, PermissionStatus, QuestionEventData, QuestionStatus, UniversalEvent, + UniversalEventData, UniversalEventType, UniversalItem, }; static SESSION_COUNTER: AtomicU64 = AtomicU64::new(1); @@ -261,18 +261,12 @@ impl OpenCodeState { if let Some(value) = query { return value.clone(); } - if let Some(value) = self - .config - .fixed_directory - .as_ref() - .cloned() - .or_else(|| { - headers - .get("x-opencode-directory") - .and_then(|v| v.to_str().ok()) - .map(|v| v.to_string()) - }) - { + if let Some(value) = self.config.fixed_directory.as_ref().cloned().or_else(|| { + headers + .get("x-opencode-directory") + .and_then(|v| v.to_str().ok()) + .map(|v| v.to_string()) + }) { return value; } std::env::current_dir() @@ -602,7 +596,8 @@ fn resolve_agent_from_model(provider_id: &str, model_id: &str) -> Option) -> String { - agent.filter(|value| !value.is_empty()) + agent + .filter(|value| !value.is_empty()) .unwrap_or_else(|| default_agent_mode().to_string()) } @@ -962,7 +957,6 @@ fn unique_assistant_message_id( } } - fn extract_text_from_content(parts: &[ContentPart]) -> Option { let mut text = String::new(); for part in parts { @@ -1146,22 +1140,19 @@ fn question_event(event_type: &str, question: &Value) -> Value { } fn message_id_from_info(info: &Value) -> Option { - info.get("id").and_then(|v| v.as_str()).map(|v| v.to_string()) + info.get("id") + .and_then(|v| v.as_str()) + .map(|v| v.to_string()) } -async fn upsert_message_info( - state: &OpenCodeState, - session_id: &str, - info: Value, -) -> Vec { +async fn upsert_message_info(state: &OpenCodeState, session_id: &str, info: Value) -> Vec { let mut messages = state.messages.lock().await; let entry = messages.entry(session_id.to_string()).or_default(); let message_id = message_id_from_info(&info); if let Some(message_id) = message_id.clone() { - if let Some(existing) = entry - .iter_mut() - .find(|record| message_id_from_info(&record.info).as_deref() == Some(message_id.as_str())) - { + if let Some(existing) = entry.iter_mut().find(|record| { + message_id_from_info(&record.info).as_deref() == Some(message_id.as_str()) + }) { existing.info = info.clone(); } else { entry.push(OpenCodeMessageRecord { @@ -1385,7 +1376,9 @@ async fn apply_permission_event( let mut permissions = state.opencode.permissions.lock().await; permissions.insert(record.id.clone(), record); drop(permissions); - state.opencode.emit_event(permission_event("permission.asked", &value)); + state + .opencode + .emit_event(permission_event("permission.asked", &value)); } PermissionStatus::Approved | PermissionStatus::Denied => { let reply = match permission.status { @@ -1441,7 +1434,9 @@ async fn apply_question_event( let mut questions = state.opencode.questions.lock().await; questions.insert(record.id.clone(), record); drop(questions); - state.opencode.emit_event(question_event("question.asked", &value)); + state + .opencode + .emit_event(question_event("question.asked", &value)); } QuestionStatus::Answered => { let answers = question @@ -1525,20 +1520,17 @@ async fn apply_item_event( } if let Some(id) = message_id.clone() { if let Some(item_key) = item_id_key.clone() { - runtime - .message_id_for_item - .insert(item_key, id.clone()); + runtime.message_id_for_item.insert(item_key, id.clone()); } if let Some(native_key) = native_id_key.clone() { - runtime - .message_id_for_item - .insert(native_key, id.clone()); + runtime.message_id_for_item.insert(native_key, id.clone()); } } }) .await; - let message_id = message_id - .unwrap_or_else(|| unique_assistant_message_id(&runtime, parent_id.as_ref(), event.sequence)); + let message_id = message_id.unwrap_or_else(|| { + unique_assistant_message_id(&runtime, parent_id.as_ref(), event.sequence) + }); let parent_id = parent_id.or_else(|| runtime.last_user_message_id.clone()); let agent = runtime .last_agent @@ -1594,7 +1586,9 @@ async fn apply_item_event( .entry(message_id.clone()) .or_insert_with(|| format!("{}_text", message_id)) .clone(); - runtime.text_by_message.insert(message_id.clone(), text.clone()); + runtime + .text_by_message + .insert(message_id.clone(), text.clone()); let part = build_text_part_with_id(&session_id, &message_id, &part_id, &text); upsert_message_part(&state.opencode, &session_id, &message_id, part.clone()).await; state @@ -1619,8 +1613,13 @@ async fn apply_item_event( let part_id = next_id("part_", &PART_COUNTER); let reasoning_part = build_reasoning_part(&session_id, &message_id, &part_id, text, now); - upsert_message_part(&state.opencode, &session_id, &message_id, reasoning_part.clone()) - .await; + upsert_message_part( + &state.opencode, + &session_id, + &message_id, + reasoning_part.clone(), + ) + .await; state .opencode .emit_event(part_event("message.part.updated", &reasoning_part)); @@ -1640,8 +1639,14 @@ async fn apply_item_event( "input": {"arguments": arguments}, "raw": arguments, }); - let tool_part = - build_tool_part(&session_id, &message_id, &part_id, call_id, name, state_value); + let tool_part = build_tool_part( + &session_id, + &message_id, + &part_id, + call_id, + name, + state_value, + ); upsert_message_part(&state.opencode, &session_id, &message_id, tool_part.clone()) .await; state @@ -1704,8 +1709,13 @@ async fn apply_item_event( FileAction::Patch => "text/x-diff", _ => "text/plain", }; - let part = - build_file_part_from_path(&session_id, &message_id, path, mime, diff.as_deref()); + let part = build_file_part_from_path( + &session_id, + &message_id, + path, + mime, + diff.as_deref(), + ); upsert_message_part(&state.opencode, &session_id, &message_id, part.clone()).await; state .opencode @@ -1787,14 +1797,10 @@ async fn apply_tool_item_event( } if let Some(id) = message_id.clone() { if let Some(item_key) = item_id_key.clone() { - runtime - .message_id_for_item - .insert(item_key, id.clone()); + runtime.message_id_for_item.insert(item_key, id.clone()); } if let Some(native_key) = native_id_key.clone() { - runtime - .message_id_for_item - .insert(native_key, id.clone()); + runtime.message_id_for_item.insert(native_key, id.clone()); } runtime .tool_message_by_call @@ -1803,8 +1809,9 @@ async fn apply_tool_item_event( }) .await; - let message_id = message_id - .unwrap_or_else(|| unique_assistant_message_id(&runtime, parent_id.as_ref(), event.sequence)); + let message_id = message_id.unwrap_or_else(|| { + unique_assistant_message_id(&runtime, parent_id.as_ref(), event.sequence) + }); let parent_id = parent_id.or_else(|| runtime.last_user_message_id.clone()); let agent = runtime .last_agent @@ -1967,7 +1974,11 @@ async fn apply_item_delta( delta: String, ) { let session_id = event.session_id.clone(); - let item_id_key = if item_id.is_empty() { None } else { Some(item_id) }; + let item_id_key = if item_id.is_empty() { + None + } else { + Some(item_id) + }; let native_id_key = native_item_id; let is_user_delta = item_id_key .as_ref() @@ -2003,20 +2014,17 @@ async fn apply_item_delta( } if let Some(id) = message_id.clone() { if let Some(item_key) = item_id_key.clone() { - runtime - .message_id_for_item - .insert(item_key, id.clone()); + runtime.message_id_for_item.insert(item_key, id.clone()); } if let Some(native_key) = native_id_key.clone() { - runtime - .message_id_for_item - .insert(native_key, id.clone()); + runtime.message_id_for_item.insert(native_key, id.clone()); } } }) .await; - let message_id = message_id - .unwrap_or_else(|| unique_assistant_message_id(&runtime, parent_id.as_ref(), event.sequence)); + let message_id = message_id.unwrap_or_else(|| { + unique_assistant_message_id(&runtime, parent_id.as_ref(), event.sequence) + }); let parent_id = parent_id.or_else(|| runtime.last_user_message_id.clone()); let directory = session_directory(&state.opencode, &session_id).await; let worktree = state.opencode.worktree_for(&directory); @@ -2086,7 +2094,10 @@ pub fn build_opencode_router(state: Arc) -> Router { .route("/event", get(oc_event_subscribe)) .route("/global/event", get(oc_global_event)) .route("/global/health", get(oc_global_health)) - .route("/global/config", get(oc_global_config_get).patch(oc_global_config_patch)) + .route( + "/global/config", + get(oc_global_config_get).patch(oc_global_config_patch), + ) .route("/global/dispose", post(oc_global_dispose)) .route("/instance/dispose", post(oc_instance_dispose)) .route("/log", post(oc_log)) @@ -2124,7 +2135,10 @@ pub fn build_opencode_router(state: Arc) -> Router { "/session/:sessionID/message/:messageID/part/:partID", patch(oc_message_part_update).delete(oc_message_part_delete), ) - .route("/session/:sessionID/prompt_async", post(oc_session_prompt_async)) + .route( + "/session/:sessionID/prompt_async", + post(oc_session_prompt_async), + ) .route("/session/:sessionID/command", post(oc_session_command)) .route("/session/:sessionID/shell", post(oc_session_shell)) .route("/session/:sessionID/revert", post(oc_session_revert)) @@ -2133,7 +2147,10 @@ pub fn build_opencode_router(state: Arc) -> Router { "/session/:sessionID/permissions/:permissionID", post(oc_session_permission_reply), ) - .route("/session/:sessionID/share", post(oc_session_share).delete(oc_session_unshare)) + .route( + "/session/:sessionID/share", + post(oc_session_share).delete(oc_session_unshare), + ) .route("/session/:sessionID/todo", get(oc_session_todo)) // Permissions + questions (global) .route("/permission", get(oc_permission_list)) @@ -2171,7 +2188,10 @@ pub fn build_opencode_router(state: Arc) -> Router { .route("/find/symbol", get(oc_find_symbols)) // MCP .route("/mcp", get(oc_mcp_list).post(oc_mcp_register)) - .route("/mcp/:name/auth", post(oc_mcp_auth).delete(oc_mcp_auth_remove)) + .route( + "/mcp/:name/auth", + post(oc_mcp_auth).delete(oc_mcp_auth_remove), + ) .route("/mcp/:name/auth/callback", post(oc_mcp_auth_callback)) .route("/mcp/:name/auth/authenticate", post(oc_mcp_authenticate)) .route("/mcp/:name/connect", post(oc_mcp_connect)) @@ -2182,7 +2202,9 @@ pub fn build_opencode_router(state: Arc) -> Router { .route("/experimental/resource", get(oc_resource_list)) .route( "/experimental/worktree", - get(oc_worktree_list).post(oc_worktree_create).delete(oc_worktree_delete), + get(oc_worktree_list) + .post(oc_worktree_create) + .delete(oc_worktree_delete), ) .route("/experimental/worktree/reset", post(oc_worktree_reset)) // Skills @@ -2300,7 +2322,9 @@ async fn oc_event_subscribe( Query(query): Query, ) -> Sse>> { let receiver = state.opencode.subscribe(); - let directory = state.opencode.directory_for(&headers, query.directory.as_ref()); + let directory = state + .opencode + .directory_for(&headers, query.directory.as_ref()); let branch = state.opencode.branch_name(); state.opencode.emit_event(json!({ "type": "server.connected", @@ -2318,33 +2342,36 @@ async fn oc_event_subscribe( "type": "server.heartbeat", "properties": {} }); - let stream = stream::unfold((receiver, interval(std::time::Duration::from_secs(30))), move |(mut rx, mut ticker)| { - let heartbeat = heartbeat_payload.clone(); - async move { - tokio::select! { - _ = ticker.tick() => { - let sse_event = Event::default() - .json_data(&heartbeat) - .unwrap_or_else(|_| Event::default().data("{}")); - Some((Ok(sse_event), (rx, ticker))) - } - event = rx.recv() => { - match event { - Ok(event) => { - let sse_event = Event::default() - .json_data(&event) - .unwrap_or_else(|_| Event::default().data("{}")); - Some((Ok(sse_event), (rx, ticker))) + let stream = stream::unfold( + (receiver, interval(std::time::Duration::from_secs(30))), + move |(mut rx, mut ticker)| { + let heartbeat = heartbeat_payload.clone(); + async move { + tokio::select! { + _ = ticker.tick() => { + let sse_event = Event::default() + .json_data(&heartbeat) + .unwrap_or_else(|_| Event::default().data("{}")); + Some((Ok(sse_event), (rx, ticker))) + } + event = rx.recv() => { + match event { + Ok(event) => { + let sse_event = Event::default() + .json_data(&event) + .unwrap_or_else(|_| Event::default().data("{}")); + Some((Ok(sse_event), (rx, ticker))) + } + Err(broadcast::error::RecvError::Lagged(_)) => { + Some((Ok(Event::default().comment("lagged")), (rx, ticker))) + } + Err(broadcast::error::RecvError::Closed) => None, } - Err(broadcast::error::RecvError::Lagged(_)) => { - Some((Ok(Event::default().comment("lagged")), (rx, ticker))) - } - Err(broadcast::error::RecvError::Closed) => None, } } } - } - }); + }, + ); Sse::new(stream).keep_alive(KeepAlive::new().interval(std::time::Duration::from_secs(15))) } @@ -2361,7 +2388,9 @@ async fn oc_global_event( Query(query): Query, ) -> Sse>> { let receiver = state.opencode.subscribe(); - let directory = state.opencode.directory_for(&headers, query.directory.as_ref()); + let directory = state + .opencode + .directory_for(&headers, query.directory.as_ref()); let branch = state.opencode.branch_name(); state.opencode.emit_event(json!({ "type": "server.connected", @@ -2381,35 +2410,38 @@ async fn oc_global_event( "properties": {} } }); - let stream = stream::unfold((receiver, interval(std::time::Duration::from_secs(30))), move |(mut rx, mut ticker)| { - let directory = directory.clone(); - let heartbeat = heartbeat_payload.clone(); - async move { - tokio::select! { - _ = ticker.tick() => { - let sse_event = Event::default() - .json_data(&heartbeat) - .unwrap_or_else(|_| Event::default().data("{}")); - Some((Ok(sse_event), (rx, ticker))) - } - event = rx.recv() => { - match event { - Ok(event) => { - let payload = json!({"directory": directory, "payload": event}); - let sse_event = Event::default() - .json_data(&payload) - .unwrap_or_else(|_| Event::default().data("{}")); - Some((Ok(sse_event), (rx, ticker))) + let stream = stream::unfold( + (receiver, interval(std::time::Duration::from_secs(30))), + move |(mut rx, mut ticker)| { + let directory = directory.clone(); + let heartbeat = heartbeat_payload.clone(); + async move { + tokio::select! { + _ = ticker.tick() => { + let sse_event = Event::default() + .json_data(&heartbeat) + .unwrap_or_else(|_| Event::default().data("{}")); + Some((Ok(sse_event), (rx, ticker))) + } + event = rx.recv() => { + match event { + Ok(event) => { + let payload = json!({"directory": directory, "payload": event}); + let sse_event = Event::default() + .json_data(&payload) + .unwrap_or_else(|_| Event::default().data("{}")); + Some((Ok(sse_event), (rx, ticker))) + } + Err(broadcast::error::RecvError::Lagged(_)) => { + Some((Ok(Event::default().comment("lagged")), (rx, ticker))) + } + Err(broadcast::error::RecvError::Closed) => None, } - Err(broadcast::error::RecvError::Lagged(_)) => { - Some((Ok(Event::default().comment("lagged")), (rx, ticker))) - } - Err(broadcast::error::RecvError::Closed) => None, } } } - } - }); + }, + ); Sse::new(stream).keep_alive(KeepAlive::new().interval(std::time::Duration::from_secs(15))) } @@ -2507,7 +2539,10 @@ async fn oc_formatter_status() -> impl IntoResponse { responses((status = 200)), tag = "opencode" )] -async fn oc_path(State(state): State>, headers: HeaderMap) -> impl IntoResponse { +async fn oc_path( + State(state): State>, + headers: HeaderMap, +) -> impl IntoResponse { let directory = state.opencode.directory_for(&headers, None); let worktree = state.opencode.worktree_for(&directory); ( @@ -2543,7 +2578,10 @@ async fn oc_vcs(State(state): State>) -> impl IntoResponse responses((status = 200)), tag = "opencode" )] -async fn oc_project_list(State(state): State>, headers: HeaderMap) -> impl IntoResponse { +async fn oc_project_list( + State(state): State>, + headers: HeaderMap, +) -> impl IntoResponse { let directory = state.opencode.directory_for(&headers, None); let worktree = state.opencode.worktree_for(&directory); let now = state.opencode.now_ms(); @@ -2564,20 +2602,23 @@ async fn oc_project_list(State(state): State>, headers: He responses((status = 200)), tag = "opencode" )] -async fn oc_project_current(State(state): State>, headers: HeaderMap) -> impl IntoResponse { +async fn oc_project_current( + State(state): State>, + headers: HeaderMap, +) -> impl IntoResponse { let directory = state.opencode.directory_for(&headers, None); let worktree = state.opencode.worktree_for(&directory); let now = state.opencode.now_ms(); ( StatusCode::OK, Json(json!({ - "id": state.opencode.default_project_id.clone(), - "worktree": worktree, - "vcs": "git", - "name": "sandbox-agent", - "time": {"created": now, "updated": now}, - "sandboxes": [], - })), + "id": state.opencode.default_project_id.clone(), + "worktree": worktree, + "vcs": "git", + "name": "sandbox-agent", + "time": {"created": now, "updated": now}, + "sandboxes": [], + })), ) } @@ -2615,7 +2656,9 @@ async fn oc_session_create( parent_id: None, permission: None, }); - let directory = state.opencode.directory_for(&headers, query.directory.as_ref()); + let directory = state + .opencode + .directory_for(&headers, query.directory.as_ref()); let now = state.opencode.now_ms(); let id = next_id("ses_", &SESSION_COUNTER); let slug = format!("session-{}", id); @@ -2792,7 +2835,9 @@ async fn oc_session_fork( headers: HeaderMap, Query(query): Query, ) -> impl IntoResponse { - let directory = state.opencode.directory_for(&headers, query.directory.as_ref()); + let directory = state + .opencode + .directory_for(&headers, query.directory.as_ref()); let now = state.opencode.now_ms(); let id = next_id("ses_", &SESSION_COUNTER); let slug = format!("session-{}", id); @@ -2841,9 +2886,7 @@ async fn oc_session_diff() -> impl IntoResponse { responses((status = 200)), tag = "opencode" )] -async fn oc_session_summarize( - Json(body): Json, -) -> impl IntoResponse { +async fn oc_session_summarize(Json(body): Json) -> impl IntoResponse { if body.provider_id.is_none() || body.model_id.is_none() { return bad_request("providerID and modelID are required"); } @@ -2886,9 +2929,15 @@ async fn oc_session_message_create( Json(body): Json, ) -> impl IntoResponse { if std::env::var("OPENCODE_COMPAT_LOG_BODY").is_ok() { - tracing::info!(target = "sandbox_agent::opencode", ?body, "opencode prompt body"); + tracing::info!( + target = "sandbox_agent::opencode", + ?body, + "opencode prompt body" + ); } - let directory = state.opencode.directory_for(&headers, query.directory.as_ref()); + let directory = state + .opencode + .directory_for(&headers, query.directory.as_ref()); let _ = state .opencode .ensure_session(&session_id, directory.clone()) @@ -3161,7 +3210,9 @@ async fn oc_session_command( if body.command.is_none() || body.arguments.is_none() { return bad_request("command and arguments are required").into_response(); } - let directory = state.opencode.directory_for(&headers, query.directory.as_ref()); + let directory = state + .opencode + .directory_for(&headers, query.directory.as_ref()); let worktree = state.opencode.worktree_for(&directory); let now = state.opencode.now_ms(); let assistant_message_id = next_id("msg_", &MESSAGE_COUNTER); @@ -3206,7 +3257,9 @@ async fn oc_session_shell( if body.command.is_none() || body.agent.is_none() { return bad_request("agent and command are required").into_response(); } - let directory = state.opencode.directory_for(&headers, query.directory.as_ref()); + let directory = state + .opencode + .directory_for(&headers, query.directory.as_ref()); let worktree = state.opencode.worktree_for(&directory); let now = state.opencode.now_ms(); let assistant_message_id = next_id("msg_", &MESSAGE_COUNTER); @@ -3355,7 +3408,11 @@ async fn oc_session_todo() -> impl IntoResponse { tag = "opencode" )] async fn oc_permission_list(State(state): State>) -> impl IntoResponse { - let pending = state.inner.session_manager().list_pending_permissions().await; + let pending = state + .inner + .session_manager() + .list_pending_permissions() + .await; let mut values = Vec::new(); for item in pending { let record = OpenCodePermissionRecord { @@ -3561,7 +3618,6 @@ async fn oc_provider_auth() -> impl IntoResponse { (StatusCode::OK, Json(auth)) } - #[utoipa::path( post, path = "/provider/{providerID}/oauth/authorize", @@ -3599,7 +3655,10 @@ async fn oc_provider_oauth_callback(Path(_provider_id): Path) -> impl In responses((status = 200)), tag = "opencode" )] -async fn oc_auth_set(Path(_provider_id): Path, Json(_body): Json) -> impl IntoResponse { +async fn oc_auth_set( + Path(_provider_id): Path, + Json(_body): Json, +) -> impl IntoResponse { bool_ok(true) } @@ -3639,7 +3698,9 @@ async fn oc_pty_create( Query(query): Query, Json(body): Json, ) -> impl IntoResponse { - let directory = state.opencode.directory_for(&headers, query.directory.as_ref()); + let directory = state + .opencode + .directory_for(&headers, query.directory.as_ref()); let id = next_id("pty_", &PTY_COUNTER); let record = OpenCodePtyRecord { id: id.clone(), @@ -3854,10 +3915,7 @@ async fn oc_mcp_register() -> impl IntoResponse { responses((status = 200)), tag = "opencode" )] -async fn oc_mcp_auth( - Path(_name): Path, - _body: Option>, -) -> impl IntoResponse { +async fn oc_mcp_auth(Path(_name): Path, _body: Option>) -> impl IntoResponse { (StatusCode::OK, Json(json!({"status": "needs_auth"}))) } @@ -3962,7 +4020,10 @@ async fn oc_resource_list() -> impl IntoResponse { responses((status = 200)), tag = "opencode" )] -async fn oc_worktree_list(State(state): State>, headers: HeaderMap) -> impl IntoResponse { +async fn oc_worktree_list( + State(state): State>, + headers: HeaderMap, +) -> impl IntoResponse { let directory = state.opencode.directory_for(&headers, None); let worktree = state.opencode.worktree_for(&directory); (StatusCode::OK, Json(json!([worktree]))) @@ -3975,7 +4036,10 @@ async fn oc_worktree_list(State(state): State>, headers: H responses((status = 200)), tag = "opencode" )] -async fn oc_worktree_create(State(state): State>, headers: HeaderMap) -> impl IntoResponse { +async fn oc_worktree_create( + State(state): State>, + headers: HeaderMap, +) -> impl IntoResponse { let directory = state.opencode.directory_for(&headers, None); let worktree = state.opencode.worktree_for(&directory); ( diff --git a/server/packages/sandbox-agent/src/router.rs b/server/packages/sandbox-agent/src/router.rs index 3ca437a..92460d5 100644 --- a/server/packages/sandbox-agent/src/router.rs +++ b/server/packages/sandbox-agent/src/router.rs @@ -16,6 +16,7 @@ use axum::response::{IntoResponse, Response, Sse}; use axum::routing::{get, post}; use axum::Json; use axum::Router; +use base64::Engine; use futures::{stream, StreamExt}; use reqwest::Client; use sandbox_agent_error::{AgentError, ErrorType, ProblemDetails, SandboxError}; @@ -34,7 +35,6 @@ use tokio::sync::{broadcast, mpsc, oneshot, Mutex}; use tokio::time::sleep; use tokio_stream::wrappers::BroadcastStream; use tower_http::trace::TraceLayer; -use base64::Engine; use tracing::Span; use utoipa::{Modify, OpenApi, ToSchema}; diff --git a/server/packages/sandbox-agent/src/server_logs/unix.rs b/server/packages/sandbox-agent/src/server_logs/unix.rs index 20bad79..8bfa33f 100644 --- a/server/packages/sandbox-agent/src/server_logs/unix.rs +++ b/server/packages/sandbox-agent/src/server_logs/unix.rs @@ -56,9 +56,7 @@ impl ServerLogs { .last_rotation .date_naive() .and_hms_opt(0, 0, 0) - .ok_or_else(|| { - std::io::Error::new(std::io::ErrorKind::Other, "invalid date") - })? + .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "invalid date"))? + Duration::days(1)), ); diff --git a/server/packages/sandbox-agent/src/server_logs/windows.rs b/server/packages/sandbox-agent/src/server_logs/windows.rs index 594a38c..8186f4e 100644 --- a/server/packages/sandbox-agent/src/server_logs/windows.rs +++ b/server/packages/sandbox-agent/src/server_logs/windows.rs @@ -65,9 +65,7 @@ impl ServerLogs { .last_rotation .date_naive() .and_hms_opt(0, 0, 0) - .ok_or_else(|| { - std::io::Error::new(std::io::ErrorKind::Other, "invalid date") - })? + .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "invalid date"))? + Duration::days(1)), ); diff --git a/server/packages/sandbox-agent/tests/opencode_openapi.rs b/server/packages/sandbox-agent/tests/opencode_openapi.rs index e4d9c79..346d722 100644 --- a/server/packages/sandbox-agent/tests/opencode_openapi.rs +++ b/server/packages/sandbox-agent/tests/opencode_openapi.rs @@ -34,8 +34,9 @@ fn official_spec_path() -> PathBuf { #[test] fn opencode_openapi_matches_official_paths() { let official_path = official_spec_path(); - let official_json = fs::read_to_string(&official_path) - .unwrap_or_else(|err| panic!("failed to read official OpenCode spec at {official_path:?}: {err}")); + let official_json = fs::read_to_string(&official_path).unwrap_or_else(|err| { + panic!("failed to read official OpenCode spec at {official_path:?}: {err}") + }); let official: Value = serde_json::from_str(&official_json).expect("official OpenCode spec is not valid JSON"); @@ -45,14 +46,8 @@ fn opencode_openapi_matches_official_paths() { let official_methods = collect_path_methods(&official); let our_methods = collect_path_methods(&ours_value); - let missing: Vec<_> = official_methods - .difference(&our_methods) - .cloned() - .collect(); - let extra: Vec<_> = our_methods - .difference(&official_methods) - .cloned() - .collect(); + let missing: Vec<_> = official_methods.difference(&our_methods).cloned().collect(); + let extra: Vec<_> = our_methods.difference(&official_methods).cloned().collect(); if !missing.is_empty() || !extra.is_empty() { let mut message = String::new();