Add Wormhole remote helpers, provider and agent improvements, filesystem diagnostics, model metadata support, and schema-aware settings IntelliSense.
12 KiB
APP-3790: Remote ReadFiles Tool
Problem
The ReadFiles agent tool is disabled for remote SSH sessions. get_supported_tools skips ToolType::ReadFiles when SessionType::WormholedRemote, because the underlying read_local_file_context reads files via async_fs, FileModel::read_text_file, and local image processing — all local-only APIs.
The remote server already runs on the host machine with full filesystem access and has access to the same dependencies (warp_files, warp_util, mime_guess). Rather than building a degraded client-side approximation, we push the file-reading logic to the server so the ReadFiles tool has full feature parity with local: line-range extraction, binary/image support, metadata, and size limits.
Relevant Code
app/src/ai/blocklist/action_model/execute/read_files.rs—ReadFilesExecutor; dispatches toread_local_file_contextapp/src/ai/blocklist/action_model/execute.rs:941—read_local_file_context; per-file reading logic (metadata, binary detection, text/binary read, image processing, byte limits)crates/warp_files/src/lib.rs:528—FileModel::read_text_file; line-range extraction and byte-limit truncationcrates/warp_util/src/file_type.rs:46—is_binary_file; extension-based binary detectionapp/src/util/image.rs:96—process_image_for_agent; image processing for LLM contextcrates/remote_server/proto/remote_server.proto:234-249— currentReadFile/ReadFileResponse/ReadFileSuccessproto (too simple)crates/remote_server/src/client.rs:266—RemoteServerClient::read_file; current simple readapp/src/remote_server/server_model.rs:883—handle_read_file_context; callsread_local_file_contextand converts result to protoapp/src/ai/agent/api/impl.rs:146—get_supported_tools; gates tools on session typeapp/src/ai/agent/api/impl.rs:221—get_supported_cli_agent_tools; same gating for CLI agentapp/src/ai/blocklist/action_model/execute/request_file_edits/apply_diff_model.rs—read_remote_fileadapter; uses oldReadFileproto
Current State
ReadFilesExecutor::execute resolves cwd/shell from ActiveSession, then calls read_local_file_context(locations, cwd, shell, None) inside a BoxFuture.
read_local_file_context iterates over FileLocations and for each file:
- Resolves the absolute path via
host_native_absolute_path - Reads metadata via
async_fs::metadata→last_modified,file_size - Computes effective byte budget (per-file
MAX_FILE_READ_BYTES∩ remaining batch budget) - Binary detection via
is_binary_file(extension-based,warp_util::file_type) - Text path:
FileModel::read_text_file— reads with line-range extraction and byte-limit truncation, returns segments - Binary path: reads raw bytes, checks MIME via
mime_guess, runsprocess_image_for_agentfor supported images, skips oversized files - Returns
ReadFileContextResult { file_contexts, missing_files }
Current ReadFile proto (remote_server.proto): ReadFile { path } → ReadFileSuccess { content, exists }. The server handler just calls tokio::fs::read_to_string — no metadata, no line ranges, no size limits, no binary support.
Tool gating: get_supported_tools excludes ReadFiles for WormholedRemote sessions. get_supported_cli_agent_tools also excludes it.
Proposed Changes
1. Replace ReadFile proto with ReadFileContext (richer batch request/response)
Replace the current ReadFile/ReadFileResponse/ReadFileSuccess messages in-place (no backward compat needed — both changes land in the same release). Reuse field slots 10/11 in ClientMessage/ServerMessage.
remote_server.proto:
// A single file to read, with optional line ranges.
message ReadFileContextFile {
string path = 1;
// 1-indexed line ranges (start..end). Empty = read entire file.
repeated LineRange line_ranges = 2;
}
message LineRange {
uint32 start = 1;
uint32 end = 2;
}
// Client → server: batch read multiple files with full context.
message ReadFileContextRequest {
repeated ReadFileContextFile files = 1;
// Per-file byte limit. Absent = use server default (MAX_FILE_READ_BYTES).
optional uint32 max_file_bytes = 2;
// Cumulative byte budget across all files. Absent = no batch limit.
optional uint32 max_batch_bytes = 3;
}
// Server → client: result of ReadFileContextRequest.
// Per-file failures are reported in `failed_files`, not as a top-level error.
// Catastrophic server errors (malformed request, etc.) use the generic ErrorResponse.
message ReadFileContextResponse {
repeated FileContextProto file_contexts = 1;
repeated FailedFileRead failed_files = 2;
}
message FailedFileRead {
string path = 1;
FileOperationError error = 2; // reuses the existing shared error type
}
message FileContextProto {
string file_name = 1;
oneof content {
string text_content = 2;
bytes binary_content = 3;
}
// Optional 1-indexed line range this segment covers.
optional uint32 line_range_start = 4;
optional uint32 line_range_end = 5;
optional uint64 last_modified_epoch_millis = 6;
uint32 line_count = 7;
}
This is a batch API — one round-trip reads all requested files, avoiding serial latency.
2. Share file-reading logic via read_local_file_context
Rather than extracting a new per-file helper, the server handler calls the existing read_local_file_context directly. This function already implements the full pipeline (metadata → binary detection → text line-range extraction via FileModel::read_text_file → image processing → byte-limit enforcement) and is accessible as pub(crate) from crate::ai::blocklist::read_local_file_context.
The server handler converts its proto request into Vec<FileLocations> (paths are already absolute, so cwd: None and shell: None are passed, causing host_native_absolute_path to act as an identity on absolute paths). The ReadFileContextResult is then converted to proto ReadFileContextResponse via a file_context_result_to_proto helper that maps FileContext → FileContextProto and missing_files → FailedFileRead.
3. Implement handle_read_file_context on the server
New handler in ServerModel:
- Deserialize
ReadFileContextRequest→ convertReadFileContextFilelist intoVec<FileLocations> - Call
read_local_file_context(file_locations, None, None, max_batch_bytes)to read all files - Convert the
ReadFileContextResultto proto viafile_context_result_to_proto - If
read_local_file_contextreturns anErr, convert to a singleFailedFileReadin the response - Return
ReadFileContextResponse
The handler uses spawn_request_handler (like handle_run_command) so it runs on the background executor and is cancellable via Abort.
4. Add RemoteServerClient::read_file_context
New method on the client:
pub async fn read_file_context(
&self,
files: Vec<ReadFileContextFile>,
max_file_bytes: Option<u32>,
max_batch_bytes: Option<u32>,
) -> Result<ReadFileContextResponse, ClientError>
Follows the same pattern as write_file / delete_file — sends request, awaits correlated response, maps error variant.
5. Update ReadFilesExecutor::execute to dispatch on session type
In the execute method, after resolving cwd/shell, check active_session.session_type(ctx):
- Local / None: call
read_local_file_contextas today (unchanged). - WormholedRemote with host_id: resolve
RemoteServerClientviaRemoteServerManager::client_for_host, callclient.read_file_context(...), convertReadFileContextResponse→ReadFileContextResult(mapping protoFileContextProto→FileContext,FailedFileRead→missing_files). - WormholedRemote without host_id: fall through to the local
read_local_file_contextpath.
The remote client lookup uses a unified code path with no cfg gating — RemoteServerManager and RemoteServerClient compile on all targets including WASM. On WASM, client_for_host returns None (since connect_session is a no-op), so the local path is used automatically.
6. Update apply_diff_model.rs to use new proto
The read_remote_file adapter currently uses the old ReadFile/ReadFileSuccess proto. Update it to send a ReadFileContextRequest with a single file (no line ranges, no byte limits) and map the response back to FileReadResult.
7. Enable ReadFiles in get_supported_tools for remote sessions
In get_supported_tools (impl.rs:179), add api::ToolType::ReadFiles alongside ApplyFileDiffs for the WormholedRemote { host_id: Some(_) } arm.
Also in get_supported_cli_agent_tools (impl.rs:234), enable ReadFiles for remote sessions with a connected host.
End-to-End Flow
sequenceDiagram
participant LLM as LLM Server
participant Executor as ReadFilesExecutor
participant Client as RemoteServerClient
participant Server as Remote Server (ServerModel)
participant Shared as read_single_file_context
LLM->>Executor: ReadFiles action (file locations)
Note over Executor: session_type is WormholedRemote with host_id
Executor->>Client: read_file_context(files, max_bytes)
Client->>Server: ReadFileContextRequest { files, max_file_bytes, max_batch_bytes }
loop For each file
Server->>Shared: read_single_file_context(path, line_ranges, max_bytes)
Note over Shared: metadata → binary detect → text/binary read → image process
Shared-->>Server: SingleFileReadResult
end
Server-->>Client: ReadFileContextResponse { file_contexts, failed_files }
Client-->>Executor: Ok(ReadFileContextResponse)
Note over Executor: Convert proto → ReadFileContextResult
Executor-->>LLM: ReadFilesResult::Success { files }
Risks and Mitigations
Network latency for large batches: The batch API sends all files in one round-trip, but the server reads them sequentially. For requests with many files, this could be slow. Mitigation: follow-up to add concurrent reads on the server via futures::join_all.
Large binary files over the wire: Image files after processing can still be significant. Mitigation: the same MAX_FILE_READ_BYTES limit applies server-side, and process_image_for_agent already has its own size guard.
Server crash on malformed request: A malformed ReadFileContextRequest could panic. Mitigation: validate inputs before processing; the spawn_request_handler pattern already handles errors gracefully.
apply_diff_model.rs migration: Replacing the proto in-place means the apply-diff path must be updated atomically in the same PR. Mitigation: the change to apply_diff_model.rs is small — send a single-file ReadFileContextRequest and map the response.
Testing and Validation
- Existing
read_local_file_contexttests: Cover the shared per-file reading logic (text files with/without line ranges, missing files, binary/image files, oversized files, byte-limit enforcement) that the server handler now reuses. - Server handler test: Test
handle_read_file_contextend-to-end — verify it reads existing files, returnsfailed_filesfor non-existent/unreadable files, and respects byte limits. - Proto round-trip test: Encode/decode
ReadFileContextRequest/ReadFileContextResponse. - Client integration: Unit test
read_file_contextwith a mock server response. - Regression: Existing
read_local_file_contexttests anddiff_application_testsremain unchanged (local path untouched, apply-diff adapter updated). - Manual: Connect to a remote SSH session, invoke agent mode, verify the LLM can read text and image files on the remote host with correct line ranges.
Follow-ups
- Concurrent reads on server: The server handler reads files sequentially via
read_local_file_context. Concurrency could be added either inside that function or by splitting files across multiple calls. - Backport
FailedFileReadto local path: UpdateReadFileContextResult::missing_filesfromVec<String>to include failure reasons, matching the richer remote proto.