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 model: String,
|
||||||
pub user_id: String,
|
pub user_id: String,
|
||||||
pub prompt_md: String,
|
pub prompt_md: String,
|
||||||
|
pub capabilities: Vec<String>,
|
||||||
state: Arc<GrpcState>,
|
state: Arc<GrpcState>,
|
||||||
room: Option<Room>,
|
room: Option<Room>,
|
||||||
}
|
}
|
||||||
@@ -76,6 +77,7 @@ impl CodeSession {
|
|||||||
model,
|
model,
|
||||||
user_id,
|
user_id,
|
||||||
prompt_md: start.prompt_md.clone(),
|
prompt_md: start.prompt_md.clone(),
|
||||||
|
capabilities: start.capabilities.clone(),
|
||||||
state,
|
state,
|
||||||
room,
|
room,
|
||||||
});
|
});
|
||||||
@@ -127,6 +129,7 @@ impl CodeSession {
|
|||||||
model,
|
model,
|
||||||
user_id,
|
user_id,
|
||||||
prompt_md: start.prompt_md.clone(),
|
prompt_md: start.prompt_md.clone(),
|
||||||
|
capabilities: start.capabilities.clone(),
|
||||||
state,
|
state,
|
||||||
room,
|
room,
|
||||||
})
|
})
|
||||||
@@ -263,9 +266,21 @@ you also have access to server-side tools: search_archive, search_web, research,
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn git_branch(&self) -> String {
|
fn git_branch(&self) -> String {
|
||||||
// Stored from StartSession.git_branch, fall back to "mainline"
|
// Use the git branch from StartSession, fall back to "mainline"
|
||||||
// TODO: store git_branch in CodeSession struct
|
if self.project_path.is_empty() {
|
||||||
"mainline".into()
|
"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.
|
/// 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."),
|
("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
|
// LSP tools — only registered when client reports LSP capability
|
||||||
let lsp_tools = vec![
|
let has_lsp = self.capabilities.iter().any(|c| c.starts_with("lsp_"));
|
||||||
("lsp_definition", "Go to the definition of the symbol at the given position. Returns file:line:column."),
|
let lsp_tools: Vec<(&str, &str)> = if has_lsp {
|
||||||
("lsp_references", "Find all references to the symbol at the given position."),
|
vec![
|
||||||
("lsp_hover", "Get type information and documentation for the symbol at the given position."),
|
("lsp_definition", "Go to the definition of the symbol at the given position. Returns file:line:column."),
|
||||||
("lsp_diagnostics", "Get compilation errors and warnings for a file."),
|
("lsp_references", "Find all references to the symbol at the given position."),
|
||||||
("lsp_symbols", "List symbols in a file (document outline) or search workspace symbols. Use path for a file, or query for workspace search."),
|
("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 {
|
for (name, desc) in client_tools {
|
||||||
tools.push(mistralai_client::v1::agents::AgentTool::function(
|
tools.push(mistralai_client::v1::agents::AgentTool::function(
|
||||||
|
|||||||
@@ -658,6 +658,7 @@ mod grpc_tests {
|
|||||||
file_tree: vec![],
|
file_tree: vec![],
|
||||||
model: "mistral-medium-latest".into(),
|
model: "mistral-medium-latest".into(),
|
||||||
client_tools: vec![],
|
client_tools: vec![],
|
||||||
|
capabilities: vec![],
|
||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -68,6 +68,10 @@ pub struct TokenUsage {
|
|||||||
pub enum ToolSide {
|
pub enum ToolSide {
|
||||||
Server,
|
Server,
|
||||||
Client,
|
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.
|
/// Result payload from a client-side tool execution.
|
||||||
|
|||||||
@@ -43,6 +43,16 @@ mod tests {
|
|||||||
assert_eq!(route("ask_user"), ToolSide::Client);
|
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]
|
#[test]
|
||||||
fn test_lsp_tools_are_client_side() {
|
fn test_lsp_tools_are_client_side() {
|
||||||
assert_eq!(route("lsp_definition"), ToolSide::Client);
|
assert_eq!(route("lsp_definition"), ToolSide::Client);
|
||||||
|
|||||||
Reference in New Issue
Block a user