We continue the separation between `codex app-server` and `codex mcp-server`. In particular, we introduce a new crate, `codex-app-server-protocol`, and migrate `codex-rs/protocol/src/mcp_protocol.rs` into it, renaming it `codex-rs/app-server-protocol/src/protocol.rs`. Because `ConversationId` was defined in `mcp_protocol.rs`, we move it into its own file, `codex-rs/protocol/src/conversation_id.rs`, and because it is referenced in a ton of places, we have to touch a lot of files as part of this PR. We also decide to get away from proper JSON-RPC 2.0 semantics, so we also introduce `codex-rs/app-server-protocol/src/jsonrpc_lite.rs`, which is basically the same `JSONRPCMessage` type defined in `mcp-types` except with all of the `"jsonrpc": "2.0"` removed. Getting rid of `"jsonrpc": "2.0"` makes our serialization logic considerably simpler, as we can lean heavier on serde to serialize directly into the wire format that we use now.
85 lines
2.5 KiB
Rust
85 lines
2.5 KiB
Rust
use std::num::NonZero;
|
|
use std::num::NonZeroUsize;
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
use std::sync::atomic::AtomicBool;
|
|
|
|
use codex_app_server_protocol::FuzzyFileSearchResult;
|
|
use codex_file_search as file_search;
|
|
use tokio::task::JoinSet;
|
|
use tracing::warn;
|
|
|
|
const LIMIT_PER_ROOT: usize = 50;
|
|
const MAX_THREADS: usize = 12;
|
|
const COMPUTE_INDICES: bool = true;
|
|
|
|
pub(crate) async fn run_fuzzy_file_search(
|
|
query: String,
|
|
roots: Vec<String>,
|
|
cancellation_flag: Arc<AtomicBool>,
|
|
) -> Vec<FuzzyFileSearchResult> {
|
|
#[expect(clippy::expect_used)]
|
|
let limit_per_root =
|
|
NonZero::new(LIMIT_PER_ROOT).expect("LIMIT_PER_ROOT should be a valid non-zero usize");
|
|
|
|
let cores = std::thread::available_parallelism()
|
|
.map(std::num::NonZero::get)
|
|
.unwrap_or(1);
|
|
let threads = cores.min(MAX_THREADS);
|
|
let threads_per_root = (threads / roots.len()).max(1);
|
|
let threads = NonZero::new(threads_per_root).unwrap_or(NonZeroUsize::MIN);
|
|
|
|
let mut files: Vec<FuzzyFileSearchResult> = Vec::new();
|
|
let mut join_set = JoinSet::new();
|
|
|
|
for root in roots {
|
|
let search_dir = PathBuf::from(&root);
|
|
let query = query.clone();
|
|
let cancel_flag = cancellation_flag.clone();
|
|
join_set.spawn_blocking(move || {
|
|
match file_search::run(
|
|
query.as_str(),
|
|
limit_per_root,
|
|
&search_dir,
|
|
Vec::new(),
|
|
threads,
|
|
cancel_flag,
|
|
COMPUTE_INDICES,
|
|
) {
|
|
Ok(res) => Ok((root, res)),
|
|
Err(err) => Err((root, err)),
|
|
}
|
|
});
|
|
}
|
|
|
|
while let Some(res) = join_set.join_next().await {
|
|
match res {
|
|
Ok(Ok((root, res))) => {
|
|
for m in res.matches {
|
|
let result = FuzzyFileSearchResult {
|
|
root: root.clone(),
|
|
path: m.path,
|
|
score: m.score,
|
|
indices: m.indices,
|
|
};
|
|
files.push(result);
|
|
}
|
|
}
|
|
Ok(Err((root, err))) => {
|
|
warn!("fuzzy-file-search in dir '{root}' failed: {err}");
|
|
}
|
|
Err(err) => {
|
|
warn!("fuzzy-file-search join_next failed: {err}");
|
|
}
|
|
}
|
|
}
|
|
|
|
files.sort_by(file_search::cmp_by_score_desc_then_path_asc::<
|
|
FuzzyFileSearchResult,
|
|
_,
|
|
_,
|
|
>(|f| f.score, |f| f.path.as_str()));
|
|
|
|
files
|
|
}
|