Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ zip = { version = "0.6", default-features = false, features = ["deflate"] }

# Misc
url = "2.5"
regex = "1.10"
regress = "0.10"
include_dir = "0.7"
base64 = "0.22"
Expand Down
1 change: 1 addition & 0 deletions server/packages/sandbox-agent/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ tracing-logfmt.workspace = true
tracing-subscriber.workspace = true
include_dir.workspace = true
base64.workspace = true
regex.workspace = true
tempfile = { workspace = true, optional = true }

[target.'cfg(unix)'.dependencies]
Expand Down
1 change: 1 addition & 0 deletions server/packages/sandbox-agent/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod agent_server_logs;
pub mod credentials;
pub mod opencode_compat;
pub mod router;
pub(crate) mod search;
pub mod server_logs;
pub mod telemetry;
pub mod ui;
120 changes: 111 additions & 9 deletions server/packages/sandbox-agent/src/opencode_compat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use std::collections::HashMap;
use std::convert::Infallible;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
Expand All @@ -24,6 +25,7 @@ use tokio::time::interval;
use utoipa::{IntoParams, OpenApi, ToSchema};

use crate::router::{AppState, CreateSessionRequest, PermissionReply};
use crate::search::{FileSearchType, SearchFileParams, SearchSymbolParams, SearchTextParams};
use sandbox_agent_agent_management::agents::AgentId;
use sandbox_agent_error::SandboxError;
use sandbox_agent_universal_agent_schema::{
Expand Down Expand Up @@ -471,18 +473,26 @@ struct ToolQuery {
struct FindTextQuery {
directory: Option<String>,
pattern: Option<String>,
#[serde(rename = "caseSensitive")]
case_sensitive: Option<bool>,
limit: Option<usize>,
}

#[derive(Debug, Deserialize, IntoParams)]
struct FindFilesQuery {
directory: Option<String>,
query: Option<String>,
dirs: Option<String>,
#[serde(rename = "type")]
kind: Option<String>,
limit: Option<usize>,
}

#[derive(Debug, Deserialize, IntoParams)]
struct FindSymbolsQuery {
directory: Option<String>,
query: Option<String>,
limit: Option<usize>,
}

#[derive(Debug, Deserialize, IntoParams)]
Expand Down Expand Up @@ -3855,11 +3865,37 @@ async fn oc_file_status() -> impl IntoResponse {
responses((status = 200)),
tag = "opencode"
)]
async fn oc_find_text(Query(query): Query<FindTextQuery>) -> impl IntoResponse {
if query.pattern.is_none() {
async fn oc_find_text(
State(state): State<Arc<OpenCodeAppState>>,
headers: HeaderMap,
Query(query): Query<FindTextQuery>,
) -> impl IntoResponse {
let Some(pattern) = query.pattern else {
return bad_request("pattern is required").into_response();
};

let directory = state
.opencode
.directory_for(&headers, query.directory.as_ref());
let worktree = state.opencode.worktree_for(&directory);
let search_params = SearchTextParams {
root: PathBuf::from(worktree),
directory: PathBuf::from(directory),
pattern,
case_sensitive: query.case_sensitive,
limit: query.limit,
};

match state
.inner
.session_manager()
.search_service()
.search_text(search_params)
.await
{
Ok(matches) => (StatusCode::OK, Json(matches)).into_response(),
Err(err) => sandbox_error_response(err).into_response(),
}
(StatusCode::OK, Json(json!([]))).into_response()
}

#[utoipa::path(
Expand All @@ -3868,11 +3904,52 @@ async fn oc_find_text(Query(query): Query<FindTextQuery>) -> impl IntoResponse {
responses((status = 200)),
tag = "opencode"
)]
async fn oc_find_files(Query(query): Query<FindFilesQuery>) -> impl IntoResponse {
if query.query.is_none() {
async fn oc_find_files(
State(state): State<Arc<OpenCodeAppState>>,
headers: HeaderMap,
Query(query): Query<FindFilesQuery>,
) -> impl IntoResponse {
let Some(query_value) = query.query else {
return bad_request("query is required").into_response();
};

let include_dirs = match query.dirs.as_deref() {
Some("true") => Some(true),
Some("false") => Some(false),
Some(_) => return bad_request("dirs must be true or false").into_response(),
None => None,
};

let file_type = match query.kind.as_deref() {
Some("file") => Some(FileSearchType::File),
Some("directory") => Some(FileSearchType::Directory),
Some(_) => return bad_request("type must be file or directory").into_response(),
None => None,
};

let directory = state
.opencode
.directory_for(&headers, query.directory.as_ref());
let worktree = state.opencode.worktree_for(&directory);
let search_params = SearchFileParams {
root: PathBuf::from(worktree),
directory: PathBuf::from(directory),
query: query_value,
include_dirs,
file_type,
limit: query.limit,
};

match state
.inner
.session_manager()
.search_service()
.search_files(search_params)
.await
{
Ok(results) => (StatusCode::OK, Json(results)).into_response(),
Err(err) => sandbox_error_response(err).into_response(),
}
(StatusCode::OK, Json(json!([]))).into_response()
}

#[utoipa::path(
Expand All @@ -3881,11 +3958,36 @@ async fn oc_find_files(Query(query): Query<FindFilesQuery>) -> impl IntoResponse
responses((status = 200)),
tag = "opencode"
)]
async fn oc_find_symbols(Query(query): Query<FindSymbolsQuery>) -> impl IntoResponse {
if query.query.is_none() {
async fn oc_find_symbols(
State(state): State<Arc<OpenCodeAppState>>,
headers: HeaderMap,
Query(query): Query<FindSymbolsQuery>,
) -> impl IntoResponse {
let Some(query_value) = query.query else {
return bad_request("query is required").into_response();
};

let directory = state
.opencode
.directory_for(&headers, query.directory.as_ref());
let worktree = state.opencode.worktree_for(&directory);
let search_params = SearchSymbolParams {
root: PathBuf::from(worktree),
directory: PathBuf::from(directory),
query: query_value,
limit: query.limit,
};

match state
.inner
.session_manager()
.search_service()
.search_symbols(search_params)
.await
{
Ok(results) => (StatusCode::OK, Json(results)).into_response(),
Err(err) => sandbox_error_response(err).into_response(),
}
(StatusCode::OK, Json(json!([]))).into_response()
}

#[utoipa::path(
Expand Down
7 changes: 7 additions & 0 deletions server/packages/sandbox-agent/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ use utoipa::{Modify, OpenApi, ToSchema};

use crate::agent_server_logs::AgentServerLogs;
use crate::opencode_compat::{build_opencode_router, OpenCodeAppState};
use crate::search::SearchService;
use crate::ui;
use sandbox_agent_agent_management::agents::{
AgentError as ManagerError, AgentId, AgentManager, InstallOptions, SpawnOptions, StreamingSpawn,
Expand Down Expand Up @@ -818,6 +819,7 @@ pub(crate) struct SessionManager {
sessions: Mutex<Vec<SessionState>>,
server_manager: Arc<AgentServerManager>,
http_client: Client,
search: SearchService,
}

/// Shared Codex app-server process that handles multiple sessions via JSON-RPC.
Expand Down Expand Up @@ -1538,9 +1540,14 @@ impl SessionManager {
sessions: Mutex::new(Vec::new()),
server_manager,
http_client: Client::new(),
search: SearchService::new(),
}
}

pub(crate) fn search_service(&self) -> SearchService {
self.search.clone()
}

fn session_ref<'a>(sessions: &'a [SessionState], session_id: &str) -> Option<&'a SessionState> {
sessions
.iter()
Expand Down
Loading
Loading