feat: Phase 5 polish — conditional LSP tools, capabilities, sidecar hooks
- ToolSide enum: documented Sidecar future variant - StartSession.capabilities: client reports LSP availability - Client detects LSP binaries on PATH, sends ["lsp_rust", "lsp_typescript"] - build_tool_definitions() conditionally registers LSP tools only when client has LSP capability — model won't hallucinate unavailable tools - CodeSession stores capabilities, has_lsp(), has_capability() accessors - git_branch() reads from git for breadcrumb scoping - ToolRegistry.gitea_client() accessor for reindex endpoint
This commit is contained in:
@@ -29,6 +29,7 @@ pub struct CodeSession {
|
||||
pub model: String,
|
||||
pub user_id: String,
|
||||
pub prompt_md: String,
|
||||
pub capabilities: Vec<String>,
|
||||
state: Arc<GrpcState>,
|
||||
room: Option<Room>,
|
||||
}
|
||||
@@ -76,6 +77,7 @@ impl CodeSession {
|
||||
model,
|
||||
user_id,
|
||||
prompt_md: start.prompt_md.clone(),
|
||||
capabilities: start.capabilities.clone(),
|
||||
state,
|
||||
room,
|
||||
});
|
||||
@@ -127,6 +129,7 @@ impl CodeSession {
|
||||
model,
|
||||
user_id,
|
||||
prompt_md: start.prompt_md.clone(),
|
||||
capabilities: start.capabilities.clone(),
|
||||
state,
|
||||
room,
|
||||
})
|
||||
@@ -263,9 +266,21 @@ you also have access to server-side tools: search_archive, search_web, research,
|
||||
}
|
||||
|
||||
fn git_branch(&self) -> String {
|
||||
// Stored from StartSession.git_branch, fall back to "mainline"
|
||||
// TODO: store git_branch in CodeSession struct
|
||||
// Use the git branch from StartSession, fall back to "mainline"
|
||||
if self.project_path.is_empty() {
|
||||
"mainline".into()
|
||||
} else {
|
||||
// Try to read from git
|
||||
std::process::Command::new("git")
|
||||
.args(["rev-parse", "--abbrev-ref", "HEAD"])
|
||||
.current_dir(&self.project_path)
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|o| String::from_utf8(o.stdout).ok())
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.unwrap_or_else(|| "mainline".into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Send a user message and run the agent loop.
|
||||
@@ -484,14 +499,19 @@ you also have access to server-side tools: search_archive, search_web, research,
|
||||
("list_directory", "List files and directories. Use path for the directory (default: project root) and optional depth."),
|
||||
];
|
||||
|
||||
// LSP tools — use path, line, column for navigation
|
||||
let lsp_tools = vec![
|
||||
// LSP tools — only registered when client reports LSP capability
|
||||
let has_lsp = self.capabilities.iter().any(|c| c.starts_with("lsp_"));
|
||||
let lsp_tools: Vec<(&str, &str)> = if has_lsp {
|
||||
vec![
|
||||
("lsp_definition", "Go to the definition of the symbol at the given position. Returns file:line:column."),
|
||||
("lsp_references", "Find all references to the symbol at the given position."),
|
||||
("lsp_hover", "Get type information and documentation for the symbol at the given position."),
|
||||
("lsp_diagnostics", "Get compilation errors and warnings for a file."),
|
||||
("lsp_symbols", "List symbols in a file (document outline) or search workspace symbols. Use path for a file, or query for workspace search."),
|
||||
];
|
||||
]
|
||||
} else {
|
||||
vec![] // no LSP on client — don't expose tools the model can't use
|
||||
};
|
||||
|
||||
for (name, desc) in client_tools {
|
||||
tools.push(mistralai_client::v1::agents::AgentTool::function(
|
||||
|
||||
@@ -658,6 +658,7 @@ mod grpc_tests {
|
||||
file_tree: vec![],
|
||||
model: "mistral-medium-latest".into(),
|
||||
client_tools: vec![],
|
||||
capabilities: vec![],
|
||||
})),
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -68,6 +68,10 @@ pub struct TokenUsage {
|
||||
pub enum ToolSide {
|
||||
Server,
|
||||
Client,
|
||||
// Future: Sidecar — server-side execution with synced workspace.
|
||||
// When a sidecar is connected, client tools can optionally run
|
||||
// on the sidecar instead of relaying to the gRPC client.
|
||||
// The orchestrator doesn't distinguish — it just parks on a oneshot.
|
||||
}
|
||||
|
||||
/// Result payload from a client-side tool execution.
|
||||
|
||||
@@ -43,6 +43,16 @@ mod tests {
|
||||
assert_eq!(route("ask_user"), ToolSide::Client);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sidecar_variant_placeholder() {
|
||||
// ToolSide should support future Sidecar variant
|
||||
// For now, any non-client tool routes to Server
|
||||
// When Sidecar is added, this test should be updated
|
||||
assert_eq!(route("lsp_definition"), ToolSide::Client);
|
||||
assert_eq!(route("search_archive"), ToolSide::Server);
|
||||
// Future: assert_eq!(route_with_sidecar("file_read", true), ToolSide::Sidecar);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lsp_tools_are_client_side() {
|
||||
assert_eq!(route("lsp_definition"), ToolSide::Client);
|
||||
|
||||
Reference in New Issue
Block a user