mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-17 08:01:03 +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/*"]
|
members = ["server/packages/*"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.6-rc.1"
|
version = "0.1.6"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = [ "Rivet Gaming, LLC <developer@rivet.gg>" ]
|
authors = [ "Rivet Gaming, LLC <developer@rivet.gg>" ]
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|
@ -12,12 +12,12 @@ description = "Universal API for automatic coding agents in sandboxes. Supprots
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
# Internal crates
|
# Internal crates
|
||||||
sandbox-agent = { version = "0.1.6-rc.1", path = "server/packages/sandbox-agent" }
|
sandbox-agent = { version = "0.1.6", path = "server/packages/sandbox-agent" }
|
||||||
sandbox-agent-error = { version = "0.1.6-rc.1", path = "server/packages/error" }
|
sandbox-agent-error = { version = "0.1.6", path = "server/packages/error" }
|
||||||
sandbox-agent-agent-management = { version = "0.1.6-rc.1", path = "server/packages/agent-management" }
|
sandbox-agent-agent-management = { version = "0.1.6", path = "server/packages/agent-management" }
|
||||||
sandbox-agent-agent-credentials = { version = "0.1.6-rc.1", path = "server/packages/agent-credentials" }
|
sandbox-agent-agent-credentials = { version = "0.1.6", path = "server/packages/agent-credentials" }
|
||||||
sandbox-agent-universal-agent-schema = { version = "0.1.6-rc.1", path = "server/packages/universal-agent-schema" }
|
sandbox-agent-universal-agent-schema = { version = "0.1.6", 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-extracted-agent-schemas = { version = "0.1.6", path = "server/packages/extracted-agent-schemas" }
|
||||||
|
|
||||||
# Serialization
|
# Serialization
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@sandbox-agent/cli-shared",
|
"name": "@sandbox-agent/cli-shared",
|
||||||
"version": "0.1.6-rc.1",
|
"version": "0.1.6",
|
||||||
"description": "Shared helpers for sandbox-agent CLI and SDK",
|
"description": "Shared helpers for sandbox-agent CLI and SDK",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@sandbox-agent/cli",
|
"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",
|
"description": "CLI for sandbox-agent - run AI coding agents in sandboxes",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@sandbox-agent/cli-darwin-arm64",
|
"name": "@sandbox-agent/cli-darwin-arm64",
|
||||||
"version": "0.1.6-rc.1",
|
"version": "0.1.6",
|
||||||
"description": "sandbox-agent CLI binary for macOS ARM64",
|
"description": "sandbox-agent CLI binary for macOS ARM64",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@sandbox-agent/cli-darwin-x64",
|
"name": "@sandbox-agent/cli-darwin-x64",
|
||||||
"version": "0.1.6-rc.1",
|
"version": "0.1.6",
|
||||||
"description": "sandbox-agent CLI binary for macOS x64",
|
"description": "sandbox-agent CLI binary for macOS x64",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@sandbox-agent/cli-linux-arm64",
|
"name": "@sandbox-agent/cli-linux-arm64",
|
||||||
"version": "0.1.6-rc.1",
|
"version": "0.1.6",
|
||||||
"description": "sandbox-agent CLI binary for Linux arm64",
|
"description": "sandbox-agent CLI binary for Linux arm64",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@sandbox-agent/cli-linux-x64",
|
"name": "@sandbox-agent/cli-linux-x64",
|
||||||
"version": "0.1.6-rc.1",
|
"version": "0.1.6",
|
||||||
"description": "sandbox-agent CLI binary for Linux x64",
|
"description": "sandbox-agent CLI binary for Linux x64",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@sandbox-agent/cli-win32-x64",
|
"name": "@sandbox-agent/cli-win32-x64",
|
||||||
"version": "0.1.6-rc.1",
|
"version": "0.1.6",
|
||||||
"description": "sandbox-agent CLI binary for Windows x64",
|
"description": "sandbox-agent CLI binary for Windows x64",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "sandbox-agent",
|
"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.",
|
"description": "Universal API for automatic coding agents in sandboxes. Supprots Claude Code, Codex, OpenCode, and Amp.",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
||||||
|
|
@ -515,7 +515,8 @@ fn run_opencode(cli: &Cli, args: &OpencodeArgs) -> Result<(), CliError> {
|
||||||
let base_url = format!("http://{}:{}", args.host, args.port);
|
let base_url = format!("http://{}:{}", args.host, args.port);
|
||||||
wait_for_health(&mut server_child, &base_url, token.as_deref())?;
|
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}"))?;
|
write_stdout_line(&format!("OpenCode session: {session_id}"))?;
|
||||||
|
|
||||||
let attach_url = format!("{base_url}/opencode");
|
let attach_url = format!("{base_url}/opencode");
|
||||||
|
|
@ -776,7 +777,10 @@ fn create_opencode_session(
|
||||||
};
|
};
|
||||||
let mut request = client.post(&url).json(&body);
|
let mut request = client.post(&url).json(&body);
|
||||||
if let Ok(directory) = std::env::current_dir() {
|
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 {
|
if let Some(token) = token {
|
||||||
request = request.bearer_auth(token);
|
request = request.bearer_auth(token);
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
|
use std::str::FromStr;
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use axum::extract::{Path, Query, State};
|
use axum::extract::{Path, Query, State};
|
||||||
use axum::http::{HeaderMap, StatusCode};
|
use axum::http::{HeaderMap, StatusCode};
|
||||||
|
|
@ -24,12 +24,12 @@ use tokio::time::interval;
|
||||||
use utoipa::{IntoParams, OpenApi, ToSchema};
|
use utoipa::{IntoParams, OpenApi, ToSchema};
|
||||||
|
|
||||||
use crate::router::{AppState, CreateSessionRequest, PermissionReply};
|
use crate::router::{AppState, CreateSessionRequest, PermissionReply};
|
||||||
use sandbox_agent_error::SandboxError;
|
|
||||||
use sandbox_agent_agent_management::agents::AgentId;
|
use sandbox_agent_agent_management::agents::AgentId;
|
||||||
|
use sandbox_agent_error::SandboxError;
|
||||||
use sandbox_agent_universal_agent_schema::{
|
use sandbox_agent_universal_agent_schema::{
|
||||||
ContentPart, ItemDeltaData, ItemEventData, ItemKind, ItemRole, UniversalEvent, UniversalEventData,
|
ContentPart, FileAction, ItemDeltaData, ItemEventData, ItemKind, ItemRole, ItemStatus,
|
||||||
UniversalEventType, UniversalItem, PermissionEventData, PermissionStatus, QuestionEventData,
|
PermissionEventData, PermissionStatus, QuestionEventData, QuestionStatus, UniversalEvent,
|
||||||
QuestionStatus, FileAction, ItemStatus,
|
UniversalEventData, UniversalEventType, UniversalItem,
|
||||||
};
|
};
|
||||||
|
|
||||||
static SESSION_COUNTER: AtomicU64 = AtomicU64::new(1);
|
static SESSION_COUNTER: AtomicU64 = AtomicU64::new(1);
|
||||||
|
|
@ -261,18 +261,12 @@ impl OpenCodeState {
|
||||||
if let Some(value) = query {
|
if let Some(value) = query {
|
||||||
return value.clone();
|
return value.clone();
|
||||||
}
|
}
|
||||||
if let Some(value) = self
|
if let Some(value) = self.config.fixed_directory.as_ref().cloned().or_else(|| {
|
||||||
.config
|
headers
|
||||||
.fixed_directory
|
.get("x-opencode-directory")
|
||||||
.as_ref()
|
.and_then(|v| v.to_str().ok())
|
||||||
.cloned()
|
.map(|v| v.to_string())
|
||||||
.or_else(|| {
|
}) {
|
||||||
headers
|
|
||||||
.get("x-opencode-directory")
|
|
||||||
.and_then(|v| v.to_str().ok())
|
|
||||||
.map(|v| v.to_string())
|
|
||||||
})
|
|
||||||
{
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
std::env::current_dir()
|
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 {
|
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())
|
.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> {
|
fn extract_text_from_content(parts: &[ContentPart]) -> Option<String> {
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
for part in parts {
|
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> {
|
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(
|
async fn upsert_message_info(state: &OpenCodeState, session_id: &str, info: Value) -> Vec<Value> {
|
||||||
state: &OpenCodeState,
|
|
||||||
session_id: &str,
|
|
||||||
info: Value,
|
|
||||||
) -> Vec<Value> {
|
|
||||||
let mut messages = state.messages.lock().await;
|
let mut messages = state.messages.lock().await;
|
||||||
let entry = messages.entry(session_id.to_string()).or_default();
|
let entry = messages.entry(session_id.to_string()).or_default();
|
||||||
let message_id = message_id_from_info(&info);
|
let message_id = message_id_from_info(&info);
|
||||||
if let Some(message_id) = message_id.clone() {
|
if let Some(message_id) = message_id.clone() {
|
||||||
if let Some(existing) = entry
|
if let Some(existing) = entry.iter_mut().find(|record| {
|
||||||
.iter_mut()
|
message_id_from_info(&record.info).as_deref() == Some(message_id.as_str())
|
||||||
.find(|record| message_id_from_info(&record.info).as_deref() == Some(message_id.as_str()))
|
}) {
|
||||||
{
|
|
||||||
existing.info = info.clone();
|
existing.info = info.clone();
|
||||||
} else {
|
} else {
|
||||||
entry.push(OpenCodeMessageRecord {
|
entry.push(OpenCodeMessageRecord {
|
||||||
|
|
@ -1385,7 +1376,9 @@ async fn apply_permission_event(
|
||||||
let mut permissions = state.opencode.permissions.lock().await;
|
let mut permissions = state.opencode.permissions.lock().await;
|
||||||
permissions.insert(record.id.clone(), record);
|
permissions.insert(record.id.clone(), record);
|
||||||
drop(permissions);
|
drop(permissions);
|
||||||
state.opencode.emit_event(permission_event("permission.asked", &value));
|
state
|
||||||
|
.opencode
|
||||||
|
.emit_event(permission_event("permission.asked", &value));
|
||||||
}
|
}
|
||||||
PermissionStatus::Approved | PermissionStatus::Denied => {
|
PermissionStatus::Approved | PermissionStatus::Denied => {
|
||||||
let reply = match permission.status {
|
let reply = match permission.status {
|
||||||
|
|
@ -1441,7 +1434,9 @@ async fn apply_question_event(
|
||||||
let mut questions = state.opencode.questions.lock().await;
|
let mut questions = state.opencode.questions.lock().await;
|
||||||
questions.insert(record.id.clone(), record);
|
questions.insert(record.id.clone(), record);
|
||||||
drop(questions);
|
drop(questions);
|
||||||
state.opencode.emit_event(question_event("question.asked", &value));
|
state
|
||||||
|
.opencode
|
||||||
|
.emit_event(question_event("question.asked", &value));
|
||||||
}
|
}
|
||||||
QuestionStatus::Answered => {
|
QuestionStatus::Answered => {
|
||||||
let answers = question
|
let answers = question
|
||||||
|
|
@ -1525,20 +1520,17 @@ async fn apply_item_event(
|
||||||
}
|
}
|
||||||
if let Some(id) = message_id.clone() {
|
if let Some(id) = message_id.clone() {
|
||||||
if let Some(item_key) = item_id_key.clone() {
|
if let Some(item_key) = item_id_key.clone() {
|
||||||
runtime
|
runtime.message_id_for_item.insert(item_key, id.clone());
|
||||||
.message_id_for_item
|
|
||||||
.insert(item_key, id.clone());
|
|
||||||
}
|
}
|
||||||
if let Some(native_key) = native_id_key.clone() {
|
if let Some(native_key) = native_id_key.clone() {
|
||||||
runtime
|
runtime.message_id_for_item.insert(native_key, id.clone());
|
||||||
.message_id_for_item
|
|
||||||
.insert(native_key, id.clone());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
let message_id = message_id
|
let message_id = message_id.unwrap_or_else(|| {
|
||||||
.unwrap_or_else(|| unique_assistant_message_id(&runtime, parent_id.as_ref(), event.sequence));
|
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 parent_id = parent_id.or_else(|| runtime.last_user_message_id.clone());
|
||||||
let agent = runtime
|
let agent = runtime
|
||||||
.last_agent
|
.last_agent
|
||||||
|
|
@ -1594,7 +1586,9 @@ async fn apply_item_event(
|
||||||
.entry(message_id.clone())
|
.entry(message_id.clone())
|
||||||
.or_insert_with(|| format!("{}_text", message_id))
|
.or_insert_with(|| format!("{}_text", message_id))
|
||||||
.clone();
|
.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);
|
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;
|
upsert_message_part(&state.opencode, &session_id, &message_id, part.clone()).await;
|
||||||
state
|
state
|
||||||
|
|
@ -1619,8 +1613,13 @@ async fn apply_item_event(
|
||||||
let part_id = next_id("part_", &PART_COUNTER);
|
let part_id = next_id("part_", &PART_COUNTER);
|
||||||
let reasoning_part =
|
let reasoning_part =
|
||||||
build_reasoning_part(&session_id, &message_id, &part_id, text, now);
|
build_reasoning_part(&session_id, &message_id, &part_id, text, now);
|
||||||
upsert_message_part(&state.opencode, &session_id, &message_id, reasoning_part.clone())
|
upsert_message_part(
|
||||||
.await;
|
&state.opencode,
|
||||||
|
&session_id,
|
||||||
|
&message_id,
|
||||||
|
reasoning_part.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
state
|
state
|
||||||
.opencode
|
.opencode
|
||||||
.emit_event(part_event("message.part.updated", &reasoning_part));
|
.emit_event(part_event("message.part.updated", &reasoning_part));
|
||||||
|
|
@ -1640,8 +1639,14 @@ async fn apply_item_event(
|
||||||
"input": {"arguments": arguments},
|
"input": {"arguments": arguments},
|
||||||
"raw": arguments,
|
"raw": arguments,
|
||||||
});
|
});
|
||||||
let tool_part =
|
let tool_part = build_tool_part(
|
||||||
build_tool_part(&session_id, &message_id, &part_id, call_id, name, state_value);
|
&session_id,
|
||||||
|
&message_id,
|
||||||
|
&part_id,
|
||||||
|
call_id,
|
||||||
|
name,
|
||||||
|
state_value,
|
||||||
|
);
|
||||||
upsert_message_part(&state.opencode, &session_id, &message_id, tool_part.clone())
|
upsert_message_part(&state.opencode, &session_id, &message_id, tool_part.clone())
|
||||||
.await;
|
.await;
|
||||||
state
|
state
|
||||||
|
|
@ -1704,8 +1709,13 @@ async fn apply_item_event(
|
||||||
FileAction::Patch => "text/x-diff",
|
FileAction::Patch => "text/x-diff",
|
||||||
_ => "text/plain",
|
_ => "text/plain",
|
||||||
};
|
};
|
||||||
let part =
|
let part = build_file_part_from_path(
|
||||||
build_file_part_from_path(&session_id, &message_id, path, mime, diff.as_deref());
|
&session_id,
|
||||||
|
&message_id,
|
||||||
|
path,
|
||||||
|
mime,
|
||||||
|
diff.as_deref(),
|
||||||
|
);
|
||||||
upsert_message_part(&state.opencode, &session_id, &message_id, part.clone()).await;
|
upsert_message_part(&state.opencode, &session_id, &message_id, part.clone()).await;
|
||||||
state
|
state
|
||||||
.opencode
|
.opencode
|
||||||
|
|
@ -1787,14 +1797,10 @@ async fn apply_tool_item_event(
|
||||||
}
|
}
|
||||||
if let Some(id) = message_id.clone() {
|
if let Some(id) = message_id.clone() {
|
||||||
if let Some(item_key) = item_id_key.clone() {
|
if let Some(item_key) = item_id_key.clone() {
|
||||||
runtime
|
runtime.message_id_for_item.insert(item_key, id.clone());
|
||||||
.message_id_for_item
|
|
||||||
.insert(item_key, id.clone());
|
|
||||||
}
|
}
|
||||||
if let Some(native_key) = native_id_key.clone() {
|
if let Some(native_key) = native_id_key.clone() {
|
||||||
runtime
|
runtime.message_id_for_item.insert(native_key, id.clone());
|
||||||
.message_id_for_item
|
|
||||||
.insert(native_key, id.clone());
|
|
||||||
}
|
}
|
||||||
runtime
|
runtime
|
||||||
.tool_message_by_call
|
.tool_message_by_call
|
||||||
|
|
@ -1803,8 +1809,9 @@ async fn apply_tool_item_event(
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let message_id = message_id
|
let message_id = message_id.unwrap_or_else(|| {
|
||||||
.unwrap_or_else(|| unique_assistant_message_id(&runtime, parent_id.as_ref(), event.sequence));
|
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 parent_id = parent_id.or_else(|| runtime.last_user_message_id.clone());
|
||||||
let agent = runtime
|
let agent = runtime
|
||||||
.last_agent
|
.last_agent
|
||||||
|
|
@ -1967,7 +1974,11 @@ async fn apply_item_delta(
|
||||||
delta: String,
|
delta: String,
|
||||||
) {
|
) {
|
||||||
let session_id = event.session_id.clone();
|
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 native_id_key = native_item_id;
|
||||||
let is_user_delta = item_id_key
|
let is_user_delta = item_id_key
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -2003,20 +2014,17 @@ async fn apply_item_delta(
|
||||||
}
|
}
|
||||||
if let Some(id) = message_id.clone() {
|
if let Some(id) = message_id.clone() {
|
||||||
if let Some(item_key) = item_id_key.clone() {
|
if let Some(item_key) = item_id_key.clone() {
|
||||||
runtime
|
runtime.message_id_for_item.insert(item_key, id.clone());
|
||||||
.message_id_for_item
|
|
||||||
.insert(item_key, id.clone());
|
|
||||||
}
|
}
|
||||||
if let Some(native_key) = native_id_key.clone() {
|
if let Some(native_key) = native_id_key.clone() {
|
||||||
runtime
|
runtime.message_id_for_item.insert(native_key, id.clone());
|
||||||
.message_id_for_item
|
|
||||||
.insert(native_key, id.clone());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
let message_id = message_id
|
let message_id = message_id.unwrap_or_else(|| {
|
||||||
.unwrap_or_else(|| unique_assistant_message_id(&runtime, parent_id.as_ref(), event.sequence));
|
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 parent_id = parent_id.or_else(|| runtime.last_user_message_id.clone());
|
||||||
let directory = session_directory(&state.opencode, &session_id).await;
|
let directory = session_directory(&state.opencode, &session_id).await;
|
||||||
let worktree = state.opencode.worktree_for(&directory);
|
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("/event", get(oc_event_subscribe))
|
||||||
.route("/global/event", get(oc_global_event))
|
.route("/global/event", get(oc_global_event))
|
||||||
.route("/global/health", get(oc_global_health))
|
.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("/global/dispose", post(oc_global_dispose))
|
||||||
.route("/instance/dispose", post(oc_instance_dispose))
|
.route("/instance/dispose", post(oc_instance_dispose))
|
||||||
.route("/log", post(oc_log))
|
.route("/log", post(oc_log))
|
||||||
|
|
@ -2124,7 +2135,10 @@ pub fn build_opencode_router(state: Arc<OpenCodeAppState>) -> Router {
|
||||||
"/session/:sessionID/message/:messageID/part/:partID",
|
"/session/:sessionID/message/:messageID/part/:partID",
|
||||||
patch(oc_message_part_update).delete(oc_message_part_delete),
|
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/command", post(oc_session_command))
|
||||||
.route("/session/:sessionID/shell", post(oc_session_shell))
|
.route("/session/:sessionID/shell", post(oc_session_shell))
|
||||||
.route("/session/:sessionID/revert", post(oc_session_revert))
|
.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",
|
"/session/:sessionID/permissions/:permissionID",
|
||||||
post(oc_session_permission_reply),
|
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))
|
.route("/session/:sessionID/todo", get(oc_session_todo))
|
||||||
// Permissions + questions (global)
|
// Permissions + questions (global)
|
||||||
.route("/permission", get(oc_permission_list))
|
.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))
|
.route("/find/symbol", get(oc_find_symbols))
|
||||||
// MCP
|
// MCP
|
||||||
.route("/mcp", get(oc_mcp_list).post(oc_mcp_register))
|
.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/callback", post(oc_mcp_auth_callback))
|
||||||
.route("/mcp/:name/auth/authenticate", post(oc_mcp_authenticate))
|
.route("/mcp/:name/auth/authenticate", post(oc_mcp_authenticate))
|
||||||
.route("/mcp/:name/connect", post(oc_mcp_connect))
|
.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/resource", get(oc_resource_list))
|
||||||
.route(
|
.route(
|
||||||
"/experimental/worktree",
|
"/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))
|
.route("/experimental/worktree/reset", post(oc_worktree_reset))
|
||||||
// Skills
|
// Skills
|
||||||
|
|
@ -2300,7 +2322,9 @@ async fn oc_event_subscribe(
|
||||||
Query(query): Query<DirectoryQuery>,
|
Query(query): Query<DirectoryQuery>,
|
||||||
) -> Sse<impl futures::Stream<Item = Result<Event, Infallible>>> {
|
) -> Sse<impl futures::Stream<Item = Result<Event, Infallible>>> {
|
||||||
let receiver = state.opencode.subscribe();
|
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();
|
let branch = state.opencode.branch_name();
|
||||||
state.opencode.emit_event(json!({
|
state.opencode.emit_event(json!({
|
||||||
"type": "server.connected",
|
"type": "server.connected",
|
||||||
|
|
@ -2318,33 +2342,36 @@ async fn oc_event_subscribe(
|
||||||
"type": "server.heartbeat",
|
"type": "server.heartbeat",
|
||||||
"properties": {}
|
"properties": {}
|
||||||
});
|
});
|
||||||
let stream = stream::unfold((receiver, interval(std::time::Duration::from_secs(30))), move |(mut rx, mut ticker)| {
|
let stream = stream::unfold(
|
||||||
let heartbeat = heartbeat_payload.clone();
|
(receiver, interval(std::time::Duration::from_secs(30))),
|
||||||
async move {
|
move |(mut rx, mut ticker)| {
|
||||||
tokio::select! {
|
let heartbeat = heartbeat_payload.clone();
|
||||||
_ = ticker.tick() => {
|
async move {
|
||||||
let sse_event = Event::default()
|
tokio::select! {
|
||||||
.json_data(&heartbeat)
|
_ = ticker.tick() => {
|
||||||
.unwrap_or_else(|_| Event::default().data("{}"));
|
let sse_event = Event::default()
|
||||||
Some((Ok(sse_event), (rx, ticker)))
|
.json_data(&heartbeat)
|
||||||
}
|
.unwrap_or_else(|_| Event::default().data("{}"));
|
||||||
event = rx.recv() => {
|
Some((Ok(sse_event), (rx, ticker)))
|
||||||
match event {
|
}
|
||||||
Ok(event) => {
|
event = rx.recv() => {
|
||||||
let sse_event = Event::default()
|
match event {
|
||||||
.json_data(&event)
|
Ok(event) => {
|
||||||
.unwrap_or_else(|_| Event::default().data("{}"));
|
let sse_event = Event::default()
|
||||||
Some((Ok(sse_event), (rx, ticker)))
|
.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)))
|
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>,
|
Query(query): Query<DirectoryQuery>,
|
||||||
) -> Sse<impl futures::Stream<Item = Result<Event, Infallible>>> {
|
) -> Sse<impl futures::Stream<Item = Result<Event, Infallible>>> {
|
||||||
let receiver = state.opencode.subscribe();
|
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();
|
let branch = state.opencode.branch_name();
|
||||||
state.opencode.emit_event(json!({
|
state.opencode.emit_event(json!({
|
||||||
"type": "server.connected",
|
"type": "server.connected",
|
||||||
|
|
@ -2381,35 +2410,38 @@ async fn oc_global_event(
|
||||||
"properties": {}
|
"properties": {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let stream = stream::unfold((receiver, interval(std::time::Duration::from_secs(30))), move |(mut rx, mut ticker)| {
|
let stream = stream::unfold(
|
||||||
let directory = directory.clone();
|
(receiver, interval(std::time::Duration::from_secs(30))),
|
||||||
let heartbeat = heartbeat_payload.clone();
|
move |(mut rx, mut ticker)| {
|
||||||
async move {
|
let directory = directory.clone();
|
||||||
tokio::select! {
|
let heartbeat = heartbeat_payload.clone();
|
||||||
_ = ticker.tick() => {
|
async move {
|
||||||
let sse_event = Event::default()
|
tokio::select! {
|
||||||
.json_data(&heartbeat)
|
_ = ticker.tick() => {
|
||||||
.unwrap_or_else(|_| Event::default().data("{}"));
|
let sse_event = Event::default()
|
||||||
Some((Ok(sse_event), (rx, ticker)))
|
.json_data(&heartbeat)
|
||||||
}
|
.unwrap_or_else(|_| Event::default().data("{}"));
|
||||||
event = rx.recv() => {
|
Some((Ok(sse_event), (rx, ticker)))
|
||||||
match event {
|
}
|
||||||
Ok(event) => {
|
event = rx.recv() => {
|
||||||
let payload = json!({"directory": directory, "payload": event});
|
match event {
|
||||||
let sse_event = Event::default()
|
Ok(event) => {
|
||||||
.json_data(&payload)
|
let payload = json!({"directory": directory, "payload": event});
|
||||||
.unwrap_or_else(|_| Event::default().data("{}"));
|
let sse_event = Event::default()
|
||||||
Some((Ok(sse_event), (rx, ticker)))
|
.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)))
|
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)),
|
responses((status = 200)),
|
||||||
tag = "opencode"
|
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 directory = state.opencode.directory_for(&headers, None);
|
||||||
let worktree = state.opencode.worktree_for(&directory);
|
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)),
|
responses((status = 200)),
|
||||||
tag = "opencode"
|
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 directory = state.opencode.directory_for(&headers, None);
|
||||||
let worktree = state.opencode.worktree_for(&directory);
|
let worktree = state.opencode.worktree_for(&directory);
|
||||||
let now = state.opencode.now_ms();
|
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)),
|
responses((status = 200)),
|
||||||
tag = "opencode"
|
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 directory = state.opencode.directory_for(&headers, None);
|
||||||
let worktree = state.opencode.worktree_for(&directory);
|
let worktree = state.opencode.worktree_for(&directory);
|
||||||
let now = state.opencode.now_ms();
|
let now = state.opencode.now_ms();
|
||||||
(
|
(
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
Json(json!({
|
Json(json!({
|
||||||
"id": state.opencode.default_project_id.clone(),
|
"id": state.opencode.default_project_id.clone(),
|
||||||
"worktree": worktree,
|
"worktree": worktree,
|
||||||
"vcs": "git",
|
"vcs": "git",
|
||||||
"name": "sandbox-agent",
|
"name": "sandbox-agent",
|
||||||
"time": {"created": now, "updated": now},
|
"time": {"created": now, "updated": now},
|
||||||
"sandboxes": [],
|
"sandboxes": [],
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2615,7 +2656,9 @@ async fn oc_session_create(
|
||||||
parent_id: None,
|
parent_id: None,
|
||||||
permission: 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 now = state.opencode.now_ms();
|
||||||
let id = next_id("ses_", &SESSION_COUNTER);
|
let id = next_id("ses_", &SESSION_COUNTER);
|
||||||
let slug = format!("session-{}", id);
|
let slug = format!("session-{}", id);
|
||||||
|
|
@ -2792,7 +2835,9 @@ async fn oc_session_fork(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Query(query): Query<DirectoryQuery>,
|
Query(query): Query<DirectoryQuery>,
|
||||||
) -> impl IntoResponse {
|
) -> 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 now = state.opencode.now_ms();
|
||||||
let id = next_id("ses_", &SESSION_COUNTER);
|
let id = next_id("ses_", &SESSION_COUNTER);
|
||||||
let slug = format!("session-{}", id);
|
let slug = format!("session-{}", id);
|
||||||
|
|
@ -2841,9 +2886,7 @@ async fn oc_session_diff() -> impl IntoResponse {
|
||||||
responses((status = 200)),
|
responses((status = 200)),
|
||||||
tag = "opencode"
|
tag = "opencode"
|
||||||
)]
|
)]
|
||||||
async fn oc_session_summarize(
|
async fn oc_session_summarize(Json(body): Json<SessionSummarizeRequest>) -> impl IntoResponse {
|
||||||
Json(body): Json<SessionSummarizeRequest>,
|
|
||||||
) -> impl IntoResponse {
|
|
||||||
if body.provider_id.is_none() || body.model_id.is_none() {
|
if body.provider_id.is_none() || body.model_id.is_none() {
|
||||||
return bad_request("providerID and modelID are required");
|
return bad_request("providerID and modelID are required");
|
||||||
}
|
}
|
||||||
|
|
@ -2886,9 +2929,15 @@ async fn oc_session_message_create(
|
||||||
Json(body): Json<SessionMessageRequest>,
|
Json(body): Json<SessionMessageRequest>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
if std::env::var("OPENCODE_COMPAT_LOG_BODY").is_ok() {
|
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
|
let _ = state
|
||||||
.opencode
|
.opencode
|
||||||
.ensure_session(&session_id, directory.clone())
|
.ensure_session(&session_id, directory.clone())
|
||||||
|
|
@ -3161,7 +3210,9 @@ async fn oc_session_command(
|
||||||
if body.command.is_none() || body.arguments.is_none() {
|
if body.command.is_none() || body.arguments.is_none() {
|
||||||
return bad_request("command and arguments are required").into_response();
|
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 worktree = state.opencode.worktree_for(&directory);
|
||||||
let now = state.opencode.now_ms();
|
let now = state.opencode.now_ms();
|
||||||
let assistant_message_id = next_id("msg_", &MESSAGE_COUNTER);
|
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() {
|
if body.command.is_none() || body.agent.is_none() {
|
||||||
return bad_request("agent and command are required").into_response();
|
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 worktree = state.opencode.worktree_for(&directory);
|
||||||
let now = state.opencode.now_ms();
|
let now = state.opencode.now_ms();
|
||||||
let assistant_message_id = next_id("msg_", &MESSAGE_COUNTER);
|
let assistant_message_id = next_id("msg_", &MESSAGE_COUNTER);
|
||||||
|
|
@ -3355,7 +3408,11 @@ async fn oc_session_todo() -> impl IntoResponse {
|
||||||
tag = "opencode"
|
tag = "opencode"
|
||||||
)]
|
)]
|
||||||
async fn oc_permission_list(State(state): State<Arc<OpenCodeAppState>>) -> impl IntoResponse {
|
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();
|
let mut values = Vec::new();
|
||||||
for item in pending {
|
for item in pending {
|
||||||
let record = OpenCodePermissionRecord {
|
let record = OpenCodePermissionRecord {
|
||||||
|
|
@ -3561,7 +3618,6 @@ async fn oc_provider_auth() -> impl IntoResponse {
|
||||||
(StatusCode::OK, Json(auth))
|
(StatusCode::OK, Json(auth))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
post,
|
post,
|
||||||
path = "/provider/{providerID}/oauth/authorize",
|
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)),
|
responses((status = 200)),
|
||||||
tag = "opencode"
|
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)
|
bool_ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3639,7 +3698,9 @@ async fn oc_pty_create(
|
||||||
Query(query): Query<DirectoryQuery>,
|
Query(query): Query<DirectoryQuery>,
|
||||||
Json(body): Json<PtyCreateRequest>,
|
Json(body): Json<PtyCreateRequest>,
|
||||||
) -> impl IntoResponse {
|
) -> 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 id = next_id("pty_", &PTY_COUNTER);
|
||||||
let record = OpenCodePtyRecord {
|
let record = OpenCodePtyRecord {
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
|
|
@ -3854,10 +3915,7 @@ async fn oc_mcp_register() -> impl IntoResponse {
|
||||||
responses((status = 200)),
|
responses((status = 200)),
|
||||||
tag = "opencode"
|
tag = "opencode"
|
||||||
)]
|
)]
|
||||||
async fn oc_mcp_auth(
|
async fn oc_mcp_auth(Path(_name): Path<String>, _body: Option<Json<Value>>) -> impl IntoResponse {
|
||||||
Path(_name): Path<String>,
|
|
||||||
_body: Option<Json<Value>>,
|
|
||||||
) -> impl IntoResponse {
|
|
||||||
(StatusCode::OK, Json(json!({"status": "needs_auth"})))
|
(StatusCode::OK, Json(json!({"status": "needs_auth"})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3962,7 +4020,10 @@ async fn oc_resource_list() -> impl IntoResponse {
|
||||||
responses((status = 200)),
|
responses((status = 200)),
|
||||||
tag = "opencode"
|
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 directory = state.opencode.directory_for(&headers, None);
|
||||||
let worktree = state.opencode.worktree_for(&directory);
|
let worktree = state.opencode.worktree_for(&directory);
|
||||||
(StatusCode::OK, Json(json!([worktree])))
|
(StatusCode::OK, Json(json!([worktree])))
|
||||||
|
|
@ -3975,7 +4036,10 @@ async fn oc_worktree_list(State(state): State<Arc<OpenCodeAppState>>, headers: H
|
||||||
responses((status = 200)),
|
responses((status = 200)),
|
||||||
tag = "opencode"
|
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 directory = state.opencode.directory_for(&headers, None);
|
||||||
let worktree = state.opencode.worktree_for(&directory);
|
let worktree = state.opencode.worktree_for(&directory);
|
||||||
(
|
(
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ use axum::response::{IntoResponse, Response, Sse};
|
||||||
use axum::routing::{get, post};
|
use axum::routing::{get, post};
|
||||||
use axum::Json;
|
use axum::Json;
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
|
use base64::Engine;
|
||||||
use futures::{stream, StreamExt};
|
use futures::{stream, StreamExt};
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use sandbox_agent_error::{AgentError, ErrorType, ProblemDetails, SandboxError};
|
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::time::sleep;
|
||||||
use tokio_stream::wrappers::BroadcastStream;
|
use tokio_stream::wrappers::BroadcastStream;
|
||||||
use tower_http::trace::TraceLayer;
|
use tower_http::trace::TraceLayer;
|
||||||
use base64::Engine;
|
|
||||||
use tracing::Span;
|
use tracing::Span;
|
||||||
use utoipa::{Modify, OpenApi, ToSchema};
|
use utoipa::{Modify, OpenApi, ToSchema};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,7 @@ impl ServerLogs {
|
||||||
.last_rotation
|
.last_rotation
|
||||||
.date_naive()
|
.date_naive()
|
||||||
.and_hms_opt(0, 0, 0)
|
.and_hms_opt(0, 0, 0)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "invalid date"))?
|
||||||
std::io::Error::new(std::io::ErrorKind::Other, "invalid date")
|
|
||||||
})?
|
|
||||||
+ Duration::days(1)),
|
+ Duration::days(1)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,9 +65,7 @@ impl ServerLogs {
|
||||||
.last_rotation
|
.last_rotation
|
||||||
.date_naive()
|
.date_naive()
|
||||||
.and_hms_opt(0, 0, 0)
|
.and_hms_opt(0, 0, 0)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "invalid date"))?
|
||||||
std::io::Error::new(std::io::ErrorKind::Other, "invalid date")
|
|
||||||
})?
|
|
||||||
+ Duration::days(1)),
|
+ Duration::days(1)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,9 @@ fn official_spec_path() -> PathBuf {
|
||||||
#[test]
|
#[test]
|
||||||
fn opencode_openapi_matches_official_paths() {
|
fn opencode_openapi_matches_official_paths() {
|
||||||
let official_path = official_spec_path();
|
let official_path = official_spec_path();
|
||||||
let official_json = fs::read_to_string(&official_path)
|
let official_json = fs::read_to_string(&official_path).unwrap_or_else(|err| {
|
||||||
.unwrap_or_else(|err| panic!("failed to read official OpenCode spec at {official_path:?}: {err}"));
|
panic!("failed to read official OpenCode spec at {official_path:?}: {err}")
|
||||||
|
});
|
||||||
let official: Value =
|
let official: Value =
|
||||||
serde_json::from_str(&official_json).expect("official OpenCode spec is not valid JSON");
|
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 official_methods = collect_path_methods(&official);
|
||||||
let our_methods = collect_path_methods(&ours_value);
|
let our_methods = collect_path_methods(&ours_value);
|
||||||
|
|
||||||
let missing: Vec<_> = official_methods
|
let missing: Vec<_> = official_methods.difference(&our_methods).cloned().collect();
|
||||||
.difference(&our_methods)
|
let extra: Vec<_> = our_methods.difference(&official_methods).cloned().collect();
|
||||||
.cloned()
|
|
||||||
.collect();
|
|
||||||
let extra: Vec<_> = our_methods
|
|
||||||
.difference(&official_methods)
|
|
||||||
.cloned()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if !missing.is_empty() || !extra.is_empty() {
|
if !missing.is_empty() || !extra.is_empty() {
|
||||||
let mut message = String::new();
|
let mut message = String::new();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue