first pass of merging in warp (doesn't build)

This commit is contained in:
Ryan Ward
2026-07-01 16:08:58 -05:00
parent 2f64909469
commit 4770ac06b5
3662 changed files with 414574 additions and 89772 deletions
+132 -29
View File
@@ -1,38 +1,35 @@
use ai::index::{
full_source_code_embedding::{
manager::{CodebaseIndexManager, CodebaseIndexManagerEvent},
RetrievalID,
},
locations::CodeContextLocation,
use std::collections::HashSet;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use ai::index::full_source_code_embedding::manager::{
CodebaseIndexManager, CodebaseIndexManagerEvent,
};
use ai::index::full_source_code_embedding::RetrievalID;
use ai::index::locations::CodeContextLocation;
use anyhow::anyhow;
use futures_util::stream::AbortHandle;
use galaxy_core::features::FeatureFlag;
use instant::Instant;
use std::{
collections::HashSet,
path::{Path, PathBuf},
sync::Arc,
};
use galaxyui::{AppContext, Entity, ModelContext, ModelHandle, SingletonEntity};
use galaxyui::{AppContext, Entity, ModelContext, SingletonEntity};
use crate::{
ai::{
agent::AIAgentActionId,
get_relevant_files::api::{FileContext, GetRelevantFiles},
outline::{OutlineStatus, RepoOutlines},
},
report_error, send_telemetry_from_ctx,
server::server_api::{AIApiError, ServerApiProvider},
TelemetryEvent,
};
#[cfg(not(target_family = "wasm"))]
use crate::ai::agent::SearchCodebaseFailureReason;
use crate::ai::agent::{AIAgentActionId, SearchCodebaseResult};
use crate::ai::blocklist::SessionContext;
use crate::ai::get_relevant_files::api::{FileContext as FileContextRequest, GetRelevantFiles};
use crate::ai::outline::{OutlineStatus, RepoOutlines};
use crate::server::server_api::{AIApiError, ServerApiProvider};
use crate::{report_error, send_telemetry_from_ctx, TelemetryEvent};
#[cfg_attr(not(target_family = "wasm"), path = "remote_search/native.rs")]
#[cfg_attr(target_family = "wasm", path = "remote_search/wasm.rs")]
mod remote_search;
#[derive(Debug)]
pub enum GetRelevantFilesControllerEvent {
Success {
action_id: AIAgentActionId,
fragments: Arc<HashSet<CodeContextLocation>>,
result: GetRelevantFilesControllerResult,
},
Error {
action_id: AIAgentActionId,
@@ -48,6 +45,21 @@ impl GetRelevantFilesControllerEvent {
}
}
#[derive(Debug)]
pub enum GetRelevantFilesControllerResult {
Locations(Arc<HashSet<CodeContextLocation>>),
SearchResult(SearchCodebaseResult),
}
pub enum GetRelevantFilesRequestTarget {
Local {
directory: PathBuf,
},
Remote {
session_context: SessionContext,
requested_codebase_path: Option<String>,
},
}
#[derive(Debug, thiserror::Error)]
pub enum GetRelevantFilesError {
#[error("Repo outline is still being computed.")]
@@ -130,6 +142,7 @@ impl GetRelevantFilesController {
fn handle_codebase_manager_event(
&mut self,
_: ModelHandle<CodebaseIndexManager>,
codebase_manager_event: &CodebaseIndexManagerEvent,
ctx: &mut ModelContext<Self>,
) {
@@ -188,6 +201,35 @@ impl GetRelevantFilesController {
/// Start a new search query based on the repo outline.
pub fn send_request(
&mut self,
target: GetRelevantFilesRequestTarget,
query: String,
partial_path_segments: Option<&Vec<String>>,
action_id: AIAgentActionId,
ctx: &mut ModelContext<Self>,
) -> Result<(), GetRelevantFilesError> {
// Cancel any previous request for this action before dispatching to either the local or
// remote implementation.
self.cancel_request_for_action(&action_id, ctx);
match target {
GetRelevantFilesRequestTarget::Local { directory } => {
self.send_local_request(&directory, query, partial_path_segments, action_id, ctx)
}
GetRelevantFilesRequestTarget::Remote {
session_context,
requested_codebase_path,
} => self.send_remote_request(
session_context,
requested_codebase_path,
query,
partial_path_segments.cloned(),
action_id,
ctx,
),
}
}
fn send_local_request(
&mut self,
directory: &Path,
query: String,
@@ -196,7 +238,6 @@ impl GetRelevantFilesController {
ctx: &mut ModelContext<Self>,
) -> Result<(), GetRelevantFilesError> {
const MINIMUM_FILE_COUNT_FOR_API_CALL: usize = 2;
self.cancel_request_for_action(&action_id, ctx);
if FeatureFlag::FullSourceCodeEmbedding.is_enabled() {
let codebase_mgr = CodebaseIndexManager::handle(ctx);
@@ -235,21 +276,21 @@ impl GetRelevantFilesController {
if file_outlines.len() < MINIMUM_FILE_COUNT_FOR_API_CALL {
ctx.emit(GetRelevantFilesControllerEvent::Success {
action_id,
fragments: Arc::new(
result: GetRelevantFilesControllerResult::Locations(Arc::new(
file_outlines
.into_iter()
.map(|file| {
CodeContextLocation::WholeFile(PathBuf::from(file.path))
})
.collect(),
),
)),
});
} else {
let outline_request = GetRelevantFiles {
query,
files: file_outlines
.into_iter()
.map(|outline| FileContext {
.map(|outline| FileContextRequest {
path: outline.path,
symbols: outline.symbols,
})
@@ -302,6 +343,38 @@ impl GetRelevantFilesController {
}
}
fn send_remote_request(
&mut self,
session_context: SessionContext,
requested_codebase_path: Option<String>,
query: String,
partial_path_segments: Option<Vec<String>>,
action_id: AIAgentActionId,
ctx: &mut ModelContext<Self>,
) -> Result<(), GetRelevantFilesError> {
match remote_search::send_request(
query,
partial_path_segments,
session_context,
requested_codebase_path,
action_id.clone(),
ctx,
) {
#[cfg(not(target_family = "wasm"))]
remote_search::RemoteSearchRequest::Pending(abort_handle) => {
self.pending_requests
.insert(action_id, RequestHandle::AbortHandle(abort_handle));
}
remote_search::RemoteSearchRequest::Ready(result) => {
ctx.emit(GetRelevantFilesControllerEvent::Success {
action_id,
result: GetRelevantFilesControllerResult::SearchResult(result),
});
}
}
Ok(())
}
fn handle_relevant_file_paths_result(
&mut self,
relevant_file_locations: anyhow::Result<Arc<HashSet<CodeContextLocation>>>,
@@ -315,7 +388,7 @@ impl GetRelevantFilesController {
Ok(relevant_file_locations) => {
ctx.emit(GetRelevantFilesControllerEvent::Success {
action_id,
fragments: relevant_file_locations,
result: GetRelevantFilesControllerResult::Locations(relevant_file_locations),
});
}
Err(e) => {
@@ -325,6 +398,27 @@ impl GetRelevantFilesController {
};
}
#[cfg(not(target_family = "wasm"))]
fn handle_remote_search_result(
&mut self,
search_result: anyhow::Result<SearchCodebaseResult>,
action_id: AIAgentActionId,
ctx: &mut ModelContext<Self>,
) {
if self.pending_requests.remove(&action_id).is_none() {
return;
}
let result = search_result.unwrap_or_else(|e| SearchCodebaseResult::Failed {
reason: SearchCodebaseFailureReason::ClientError,
message: e.to_string(),
});
ctx.emit(GetRelevantFilesControllerEvent::Success {
action_id,
result: GetRelevantFilesControllerResult::SearchResult(result),
});
}
/// Returns the path to the root directory for a codebase search where pwd is `directory`.
pub fn root_directory_for_search(&self, directory: &Path, app: &AppContext) -> Option<PathBuf> {
let mut start = None;
@@ -338,6 +432,15 @@ impl GetRelevantFilesController {
})
}
pub fn root_directory_for_remote_search(
&self,
session_context: &SessionContext,
requested_codebase_path: Option<&str>,
app: &AppContext,
) -> Option<PathBuf> {
remote_search::root_directory_for_search(session_context, requested_codebase_path, app)
}
pub fn cancel_request_for_action(
&mut self,
action_id: &AIAgentActionId,
@@ -0,0 +1,455 @@
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use ::ai::index::full_source_code_embedding::search_shaping::{
build_fragments_from_file_contents, fragments_to_context_locations,
};
use ::ai::index::full_source_code_embedding::store_client::StoreClient;
use ::ai::index::full_source_code_embedding::{
ContentHash, FragmentMetadata as AiFragmentMetadata, FragmentMetadataLocation, RepoMetadata,
};
use ::ai::index::locations::CodeContextLocation;
use itertools::Itertools;
use remote_server::proto::{
file_context_proto, FragmentMetadata as ProtoFragmentMetadata, LineRange, ReadFileContextFile,
ReadFileContextRequest, ReadFileContextResponse,
};
use string_offset::ByteOffset;
use warpui::{AppContext, ModelContext, SingletonEntity};
use crate::ai::agent::{
AnyFileContent, FileContext, SearchCodebaseFailureReason, SearchCodebaseResult,
};
use crate::ai::blocklist::SessionContext;
use crate::ai::codebase_auto_indexing::{
should_use_codebase_indexing, CodebaseAutoIndexingSurface,
};
use crate::ai::get_relevant_files::controller::GetRelevantFilesController;
use crate::remote_server::codebase_index_model::{
RemoteCodebaseIndexModel, RemoteCodebaseSearchAvailability, RemoteCodebaseSearchContext,
};
use crate::server::server_api::{ServerApi, ServerApiProvider};
pub(super) enum RemoteSearchRequest {
Pending(futures_util::stream::AbortHandle),
Ready(SearchCodebaseResult),
}
pub(super) fn root_directory_for_search(
session_context: &SessionContext,
requested_codebase_path: Option<&str>,
app: &AppContext,
) -> Option<PathBuf> {
RemoteCodebaseIndexModel::as_ref(app)
.active_repo_path(session_context, requested_codebase_path)
.or_else(|| {
requested_codebase_path
.is_none()
.then(|| session_context.current_working_directory().clone())
.flatten()
})
.map(PathBuf::from)
}
pub(super) fn send_request(
query: String,
partial_paths: Option<Vec<String>>,
session_context: SessionContext,
requested_codebase_path: Option<String>,
action_id: crate::ai::agent::AIAgentActionId,
ctx: &mut ModelContext<GetRelevantFilesController>,
) -> RemoteSearchRequest {
if !should_use_codebase_indexing(CodebaseAutoIndexingSurface::Remote, ctx) {
return RemoteSearchRequest::Ready(SearchCodebaseResult::Failed {
reason: SearchCodebaseFailureReason::CodebaseNotIndexed,
message: "Remote codebase search is not enabled.".to_string(),
});
}
let availability = RemoteCodebaseIndexModel::as_ref(ctx)
.active_repo_availability(&session_context, requested_codebase_path.as_deref());
match availability {
RemoteCodebaseSearchAvailability::Ready(search_context) => {
let handle = remote_server::manager::RemoteServerManager::as_ref(ctx)
.host_request_handle(&search_context.remote_path.host_id);
if search_context.is_stale {
let remote_path = search_context.remote_path.clone();
let sync_requested = remote_server::manager::RemoteServerManager::handle(ctx)
.update(ctx, |manager, ctx| {
manager.trigger_codebase_incremental_sync(remote_path, ctx)
});
if !sync_requested {
log::warn!(
"Remote codebase search is using a stale index because incremental sync could not be requested"
);
}
}
let store_client = ServerApiProvider::as_ref(ctx).get();
let abort_handle = ctx
.spawn(
async move {
execute_remote_codebase_search(
query,
partial_paths,
search_context,
handle,
store_client,
)
.await
},
move |me, result, ctx| {
me.handle_remote_search_result(result, action_id, ctx);
},
)
.abort_handle();
RemoteSearchRequest::Pending(abort_handle)
}
availability @ RemoteCodebaseSearchAvailability::NotIndexed { .. } => {
RemoteSearchRequest::Ready(remote_availability_failure(availability))
}
RemoteCodebaseSearchAvailability::NoConnectedHost
| RemoteCodebaseSearchAvailability::NoActiveRepo
| RemoteCodebaseSearchAvailability::Indexing { .. }
| RemoteCodebaseSearchAvailability::Unavailable { .. } => {
RemoteSearchRequest::Ready(remote_availability_failure(availability))
}
}
}
// The controller owns request lifecycle concerns like cancellation, pending request tracking, and
// result emission. This function only contains the remote-specific pipeline: store content hashes
// -> daemon fragment metadata -> remote file reads -> fragment reranking.
async fn execute_remote_codebase_search(
query: String,
partial_paths: Option<Vec<String>>,
search_context: RemoteCodebaseSearchContext,
handle: remote_server::manager::HostRequestHandle,
store_client: Arc<ServerApi>,
) -> Result<SearchCodebaseResult, anyhow::Error> {
let root_hash = search_context.root_hash;
let root_hash_string = root_hash.to_string();
let repo_path = search_context.remote_path.path.as_str().to_string();
let embedding_config = store_client
.codebase_context_config()
.await?
.embedding_config;
log::debug!(
"[Remote codebase indexing] Remote codebase search using embedding config: repo_path={repo_path} embedding_config={embedding_config:?}"
);
let candidate_hashes = store_client
.get_relevant_fragments(
embedding_config,
query.clone(),
root_hash,
RepoMetadata {
path: Some(repo_path.clone()),
},
)
.await?;
if candidate_hashes.is_empty() {
return Ok(SearchCodebaseResult::Success { files: vec![] });
}
let candidate_hash_strings = candidate_hashes
.iter()
.map(ToString::to_string)
.collect_vec();
let metadata_response = handle
.get_fragment_metadata_from_hash(
repo_path.clone(),
root_hash_string,
candidate_hash_strings,
)
.await
.map_err(|e| anyhow::anyhow!("Fragment metadata lookup failed: {e}"))?;
if !metadata_response.missing_hashes.is_empty() {
log::warn!(
"Remote codebase search metadata lookup missed {} hashes for repo {}",
metadata_response.missing_hashes.len(),
repo_path
);
}
let mut remote_fragments = metadata_response.fragments;
if let Some(partial_paths) = partial_paths {
remote_fragments.retain(|fragment| {
partial_paths
.iter()
.any(|partial_path| fragment.path.contains(partial_path))
});
}
if remote_fragments.is_empty() {
return Ok(SearchCodebaseResult::Success { files: vec![] });
}
// Convert daemon protobuf metadata into the shared search-shaping metadata format so
// remote search reuses the same fragment reconstruction and context expansion path as
// local search.
let parsed_fragment_metadata = remote_fragments
.into_iter()
.filter_map(|fragment| match remote_fragment_metadata(fragment) {
Ok(metadata) => Some(metadata),
Err(err) => {
log::warn!("Failed to parse remote codebase fragment metadata: {err:?}");
None
}
})
.collect_vec();
if parsed_fragment_metadata.is_empty() {
return Ok(SearchCodebaseResult::Success { files: vec![] });
}
let response = handle
.read_file_context(read_full_fragment_files_request(&parsed_fragment_metadata))
.await?;
if !response.failed_files.is_empty() && response.file_contexts.is_empty() {
let failed = response
.failed_files
.iter()
.map(|file| {
let reason = file
.error
.as_ref()
.map(|error| error.message.as_str())
.unwrap_or("unknown error");
format!("{}: {reason}", file.path)
})
.join(", ");
return Ok(SearchCodebaseResult::Failed {
reason: SearchCodebaseFailureReason::InvalidFilePaths,
message: format!("Failed to read remote search result files: {failed}"),
});
}
let file_contents = file_contents_from_response(response);
let read_fragment_result = build_fragments_from_file_contents(
parsed_fragment_metadata.iter().cloned(),
&file_contents,
);
if !read_fragment_result.fail_to_read_path.is_empty() {
log::warn!(
"Remote codebase search failed to read {} fragment file(s)",
read_fragment_result.fail_to_read_path.len()
);
}
let fragments = read_fragment_result.successfully_read;
if fragments.is_empty() {
return Ok(SearchCodebaseResult::Success { files: vec![] });
}
let reranked_fragments = store_client.rerank_fragments(query, fragments).await?;
let metadata_by_hash = fragment_metadata_by_hash(&parsed_fragment_metadata);
let locations = fragments_to_context_locations(
reranked_fragments,
|hash| metadata_by_hash.get(hash).map(Vec::as_slice),
RETRIEVE_FRAGMENT_CONTEXT_LENGTH,
);
if locations.is_empty() {
return Ok(SearchCodebaseResult::Success { files: vec![] });
}
let response = handle
.read_file_context(read_context_locations_request(&locations))
.await?;
if !response.failed_files.is_empty() && response.file_contexts.is_empty() {
let failed = response
.failed_files
.iter()
.map(|file| {
let reason = file
.error
.as_ref()
.map(|error| error.message.as_str())
.unwrap_or("unknown error");
format!("{}: {reason}", file.path)
})
.join(", ");
return Ok(SearchCodebaseResult::Failed {
reason: SearchCodebaseFailureReason::InvalidFilePaths,
message: format!("Failed to read remote search result files: {failed}"),
});
}
let files = response
.file_contexts
.into_iter()
.filter_map(proto_file_context_to_file_context)
.collect_vec();
Ok(SearchCodebaseResult::Success { files })
}
const RETRIEVE_FRAGMENT_CONTEXT_LENGTH: usize = 0;
fn remote_fragment_metadata(
fragment: ProtoFragmentMetadata,
) -> anyhow::Result<(ContentHash, AiFragmentMetadata)> {
let content_hash = ContentHash::from_str(&fragment.content_hash)?;
Ok((
content_hash,
AiFragmentMetadata {
absolute_path: PathBuf::from(fragment.path),
location: FragmentMetadataLocation {
start_line: fragment.start_line as usize,
end_line: fragment.end_line as usize,
byte_range: ByteOffset::from(fragment.byte_start as usize)
..ByteOffset::from(fragment.byte_end as usize),
},
},
))
}
fn read_full_fragment_files_request(
metadata: &[(ContentHash, AiFragmentMetadata)],
) -> ReadFileContextRequest {
let mut seen_paths = HashSet::new();
ReadFileContextRequest {
files: metadata
.iter()
.filter_map(|(_, fragment)| {
let path = fragment.absolute_path.to_string_lossy().to_string();
seen_paths
.insert(path.clone())
.then_some(ReadFileContextFile {
path,
line_ranges: vec![],
})
})
.collect(),
max_file_bytes: None,
max_batch_bytes: None,
}
}
fn file_contents_from_response(response: ReadFileContextResponse) -> HashMap<PathBuf, String> {
let mut file_contents = HashMap::new();
for file_context in response.file_contexts {
if file_context.line_range_start.is_some() || file_context.line_range_end.is_some() {
continue;
}
if let Some(file_context_proto::Content::TextContent(content)) = file_context.content {
file_contents.insert(PathBuf::from(file_context.file_name), content);
}
}
file_contents
}
fn fragment_metadata_by_hash(
metadata: &[(ContentHash, AiFragmentMetadata)],
) -> HashMap<ContentHash, Vec<AiFragmentMetadata>> {
let mut metadata_by_hash: HashMap<ContentHash, Vec<AiFragmentMetadata>> = HashMap::new();
for (content_hash, metadata) in metadata {
metadata_by_hash
.entry(content_hash.clone())
.or_default()
.push(metadata.clone());
}
metadata_by_hash
}
fn read_context_locations_request(
locations: &HashSet<CodeContextLocation>,
) -> ReadFileContextRequest {
ReadFileContextRequest {
files: locations
.iter()
.map(|location| match location {
CodeContextLocation::WholeFile(path) => ReadFileContextFile {
path: path.to_string_lossy().to_string(),
line_ranges: vec![],
},
CodeContextLocation::Fragment(fragment) => ReadFileContextFile {
path: fragment.path.to_string_lossy().to_string(),
line_ranges: fragment
.line_ranges
.iter()
.map(|range| LineRange {
start: range.start as u32,
end: range.end as u32,
})
.collect(),
},
})
.collect(),
max_file_bytes: None,
max_batch_bytes: None,
}
}
// Keep this conversion at the AI boundary: `FileContext` lives in the `ai` crate, so the
// `remote_server` protocol/client crate should not depend on it just to return typed agent
// file contexts.
fn proto_file_context_to_file_context(
file_context: remote_server::proto::FileContextProto,
) -> Option<FileContext> {
let content = match file_context.content? {
file_context_proto::Content::TextContent(text) => AnyFileContent::StringContent(text),
file_context_proto::Content::BinaryContent(bytes) => AnyFileContent::BinaryContent(bytes),
};
let line_range = match (file_context.line_range_start, file_context.line_range_end) {
(Some(start), Some(end)) => Some(start as usize..end as usize),
(Some(_), None) | (None, Some(_)) | (None, None) => None,
};
let last_modified = file_context
.last_modified_epoch_millis
.map(|ms| std::time::UNIX_EPOCH + std::time::Duration::from_millis(ms));
Some(FileContext {
file_name: file_context.file_name,
content,
line_range,
last_modified,
line_count: file_context.line_count as usize,
})
}
fn remote_availability_failure(
availability: RemoteCodebaseSearchAvailability,
) -> SearchCodebaseResult {
match availability {
RemoteCodebaseSearchAvailability::NoConnectedHost => SearchCodebaseResult::Failed {
reason: SearchCodebaseFailureReason::ClientError,
message:
"Remote codebase search is unavailable because the remote host is not connected."
.to_string(),
},
RemoteCodebaseSearchAvailability::NoActiveRepo => SearchCodebaseResult::Failed {
reason: SearchCodebaseFailureReason::CodebaseNotIndexed,
message: "The current remote directory is not in a known codebase.".to_string(),
},
RemoteCodebaseSearchAvailability::NotIndexed { remote_path } => {
SearchCodebaseResult::Failed {
reason: SearchCodebaseFailureReason::CodebaseNotIndexed,
message: format!(
"The remote codebase at {} is not indexed yet.",
remote_path.path.as_str()
),
}
}
RemoteCodebaseSearchAvailability::Indexing { remote_path } => {
SearchCodebaseResult::Failed {
reason: SearchCodebaseFailureReason::CodebaseNotIndexed,
message: format!(
"The remote codebase at {} is still being indexed. Try again later.",
remote_path.path.as_str()
),
}
}
RemoteCodebaseSearchAvailability::Unavailable {
remote_path,
message,
} => SearchCodebaseResult::Failed {
reason: SearchCodebaseFailureReason::CodebaseNotIndexed,
message: format!(
"Remote codebase search is unavailable for {}: {message}",
remote_path.path.as_str()
),
},
RemoteCodebaseSearchAvailability::Ready(_) => SearchCodebaseResult::Failed {
reason: SearchCodebaseFailureReason::ClientError,
message: "Remote codebase search was unexpectedly unavailable.".to_string(),
},
}
}
#[cfg(test)]
#[path = "native_tests.rs"]
mod tests;
@@ -0,0 +1,42 @@
use std::path::PathBuf;
use remote_server::proto::{file_context_proto, FileContextProto, ReadFileContextResponse};
use super::file_contents_from_response;
#[test]
fn file_contents_from_response_keeps_only_whole_text_files() {
let response = ReadFileContextResponse {
file_contexts: vec![
FileContextProto {
file_name: "/repo/src/lib.rs".to_string(),
content: Some(file_context_proto::Content::TextContent(
"content".to_string(),
)),
line_range_start: None,
line_range_end: None,
last_modified_epoch_millis: None,
line_count: 1,
},
FileContextProto {
file_name: "/repo/src/fragment.rs".to_string(),
content: Some(file_context_proto::Content::TextContent(
"fragment".to_string(),
)),
line_range_start: Some(1),
line_range_end: Some(2),
last_modified_epoch_millis: None,
line_count: 1,
},
],
failed_files: vec![],
};
let file_contents = file_contents_from_response(response);
assert_eq!(file_contents.len(), 1);
assert_eq!(
file_contents.get(&PathBuf::from("/repo/src/lib.rs")),
Some(&"content".to_string())
);
}
@@ -0,0 +1,33 @@
use std::path::PathBuf;
use warpui::{AppContext, ModelContext};
use crate::ai::agent::{AIAgentActionId, SearchCodebaseFailureReason, SearchCodebaseResult};
use crate::ai::blocklist::SessionContext;
use crate::ai::get_relevant_files::controller::GetRelevantFilesController;
pub(super) enum RemoteSearchRequest {
Ready(SearchCodebaseResult),
}
pub(super) fn root_directory_for_search(
_session_context: &SessionContext,
_requested_codebase_path: Option<&str>,
_app: &AppContext,
) -> Option<PathBuf> {
None
}
pub(super) fn send_request(
_query: String,
_partial_paths: Option<Vec<String>>,
_session_context: SessionContext,
_requested_codebase_path: Option<String>,
_action_id: AIAgentActionId,
_ctx: &mut ModelContext<GetRelevantFilesController>,
) -> RemoteSearchRequest {
RemoteSearchRequest::Ready(SearchCodebaseResult::Failed {
reason: SearchCodebaseFailureReason::CodebaseNotIndexed,
message: "Remote codebase search is not available in this environment.".to_string(),
})
}