mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 04:03:31 +00:00
chore: cargo fmt
This commit is contained in:
parent
14f2743b9a
commit
886f93aaca
15 changed files with 240 additions and 181 deletions
14
Cargo.toml
14
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 <developer@rivet.gg>" ]
|
||||
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"] }
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<AgentId
|
|||
}
|
||||
|
||||
fn normalize_agent_mode(agent: Option<String>) -> 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<String> {
|
||||
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<String> {
|
||||
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<Value> {
|
||||
async fn upsert_message_info(state: &OpenCodeState, session_id: &str, info: Value) -> Vec<Value> {
|
||||
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<OpenCodeAppState>) -> 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<OpenCodeAppState>) -> 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<OpenCodeAppState>) -> 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<OpenCodeAppState>) -> 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<OpenCodeAppState>) -> 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<DirectoryQuery>,
|
||||
) -> Sse<impl futures::Stream<Item = Result<Event, Infallible>>> {
|
||||
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<DirectoryQuery>,
|
||||
) -> Sse<impl futures::Stream<Item = Result<Event, Infallible>>> {
|
||||
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<Arc<OpenCodeAppState>>, headers: HeaderMap) -> impl IntoResponse {
|
||||
async fn oc_path(
|
||||
State(state): State<Arc<OpenCodeAppState>>,
|
||||
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<Arc<OpenCodeAppState>>) -> impl IntoResponse
|
|||
responses((status = 200)),
|
||||
tag = "opencode"
|
||||
)]
|
||||
async fn oc_project_list(State(state): State<Arc<OpenCodeAppState>>, headers: HeaderMap) -> impl IntoResponse {
|
||||
async fn oc_project_list(
|
||||
State(state): State<Arc<OpenCodeAppState>>,
|
||||
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<Arc<OpenCodeAppState>>, headers: He
|
|||
responses((status = 200)),
|
||||
tag = "opencode"
|
||||
)]
|
||||
async fn oc_project_current(State(state): State<Arc<OpenCodeAppState>>, headers: HeaderMap) -> impl IntoResponse {
|
||||
async fn oc_project_current(
|
||||
State(state): State<Arc<OpenCodeAppState>>,
|
||||
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<DirectoryQuery>,
|
||||
) -> 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<SessionSummarizeRequest>,
|
||||
) -> impl IntoResponse {
|
||||
async fn oc_session_summarize(Json(body): Json<SessionSummarizeRequest>) -> 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<SessionMessageRequest>,
|
||||
) -> 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<Arc<OpenCodeAppState>>) -> 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<String>) -> impl In
|
|||
responses((status = 200)),
|
||||
tag = "opencode"
|
||||
)]
|
||||
async fn oc_auth_set(Path(_provider_id): Path<String>, Json(_body): Json<Value>) -> impl IntoResponse {
|
||||
async fn oc_auth_set(
|
||||
Path(_provider_id): Path<String>,
|
||||
Json(_body): Json<Value>,
|
||||
) -> impl IntoResponse {
|
||||
bool_ok(true)
|
||||
}
|
||||
|
||||
|
|
@ -3639,7 +3698,9 @@ async fn oc_pty_create(
|
|||
Query(query): Query<DirectoryQuery>,
|
||||
Json(body): Json<PtyCreateRequest>,
|
||||
) -> 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<String>,
|
||||
_body: Option<Json<Value>>,
|
||||
) -> impl IntoResponse {
|
||||
async fn oc_mcp_auth(Path(_name): Path<String>, _body: Option<Json<Value>>) -> 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<Arc<OpenCodeAppState>>, headers: HeaderMap) -> impl IntoResponse {
|
||||
async fn oc_worktree_list(
|
||||
State(state): State<Arc<OpenCodeAppState>>,
|
||||
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<Arc<OpenCodeAppState>>, headers: H
|
|||
responses((status = 200)),
|
||||
tag = "opencode"
|
||||
)]
|
||||
async fn oc_worktree_create(State(state): State<Arc<OpenCodeAppState>>, headers: HeaderMap) -> impl IntoResponse {
|
||||
async fn oc_worktree_create(
|
||||
State(state): State<Arc<OpenCodeAppState>>,
|
||||
headers: HeaderMap,
|
||||
) -> impl IntoResponse {
|
||||
let directory = state.opencode.directory_for(&headers, None);
|
||||
let worktree = state.opencode.worktree_for(&directory);
|
||||
(
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue