feat: comprehensive tool + Gitea + bridge tests, search_code tool definition
Tools module: - Added search_code to tool_definitions() (was in execute but not defined) - 8 new unit tests: tool definitions (base/gitea/kratos/all), agent tools, minimal registry, unknown tool dispatch, search_code without OpenSearch Gitea indexer fix: - Direct API calls for directory listings (SDK parses as single object) - PAT-based auth for file fetching - GiteaClient.base_url made public for direct API access Integration tests: - Gitea SDK: list_repos, get_repo, get_file, directory, full code indexing - gRPC bridge: thinking events, tool call mapping, request ID filtering - Evaluator: rule matching, conversation registry lifecycle - Web search: SearXNG round-trip
This commit is contained in:
139
src/tools/mod.rs
139
src/tools/mod.rs
@@ -219,6 +219,25 @@ impl ToolRegistry {
|
||||
tools.extend(identity::tool_definitions());
|
||||
}
|
||||
|
||||
// Code search (OpenSearch code index)
|
||||
tools.push(Tool::new(
|
||||
"search_code".into(),
|
||||
"Search the code index for functions, types, patterns, or concepts across \
|
||||
the current project and Gitea repositories. Supports keyword and semantic search."
|
||||
.into(),
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": { "type": "string", "description": "Search query (natural language or code pattern)" },
|
||||
"language": { "type": "string", "description": "Filter by language: rust, typescript, python (optional)" },
|
||||
"repo": { "type": "string", "description": "Filter by repo name (optional)" },
|
||||
"semantic": { "type": "boolean", "description": "Use semantic search (optional)" },
|
||||
"limit": { "type": "integer", "description": "Max results (default 10)" }
|
||||
},
|
||||
"required": ["query"]
|
||||
}),
|
||||
));
|
||||
|
||||
// Web search (SearXNG — free, self-hosted)
|
||||
tools.push(web_search::tool_definition());
|
||||
|
||||
@@ -436,3 +455,123 @@ impl ToolRegistry {
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_tool_definitions_base() {
|
||||
let tools = ToolRegistry::tool_definitions(false, false);
|
||||
assert!(tools.len() >= 5, "Should have at least 5 base tools");
|
||||
|
||||
let names: Vec<String> = tools.iter().map(|t| t.function.name.clone()).collect();
|
||||
assert!(names.contains(&"search_archive".into()));
|
||||
assert!(names.contains(&"run_script".into()));
|
||||
assert!(names.contains(&"search_web".into()));
|
||||
assert!(names.contains(&"search_code".into()));
|
||||
// No gitea or identity tools when disabled
|
||||
assert!(!names.iter().any(|n| n.starts_with("gitea_")));
|
||||
assert!(!names.iter().any(|n| n.starts_with("identity_")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tool_definitions_with_gitea() {
|
||||
let tools = ToolRegistry::tool_definitions(true, false);
|
||||
let names: Vec<String> = tools.iter().map(|t| t.function.name.clone()).collect();
|
||||
assert!(names.iter().any(|n| n.starts_with("gitea_")), "Should have gitea tools");
|
||||
assert!(!names.iter().any(|n| n.starts_with("identity_")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tool_definitions_with_kratos() {
|
||||
let tools = ToolRegistry::tool_definitions(false, true);
|
||||
let names: Vec<String> = tools.iter().map(|t| t.function.name.clone()).collect();
|
||||
assert!(names.iter().any(|n| n.starts_with("identity_")), "Should have identity tools");
|
||||
assert!(!names.iter().any(|n| n.starts_with("gitea_")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tool_definitions_all_enabled() {
|
||||
let tools = ToolRegistry::tool_definitions(true, true);
|
||||
let names: Vec<String> = tools.iter().map(|t| t.function.name.clone()).collect();
|
||||
assert!(names.iter().any(|n| n.starts_with("gitea_")));
|
||||
assert!(names.iter().any(|n| n.starts_with("identity_")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_agent_tool_definitions() {
|
||||
let tools = ToolRegistry::agent_tool_definitions(false, false);
|
||||
assert!(!tools.is_empty(), "Should have agent tools");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_minimal_registry() {
|
||||
let config = Arc::new(crate::config::Config::from_str(r#"
|
||||
[matrix]
|
||||
homeserver_url = "http://localhost:8008"
|
||||
user_id = "@test:localhost"
|
||||
state_store_path = "/tmp/test"
|
||||
db_path = ":memory:"
|
||||
[opensearch]
|
||||
url = "http://localhost:9200"
|
||||
index = "test"
|
||||
[mistral]
|
||||
[behavior]
|
||||
"#).unwrap());
|
||||
let registry = ToolRegistry::new_minimal(config);
|
||||
assert!(!registry.has_gitea());
|
||||
assert!(!registry.has_kratos());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_execute_unknown_tool() {
|
||||
let config = Arc::new(crate::config::Config::from_str(r#"
|
||||
[matrix]
|
||||
homeserver_url = "http://localhost:8008"
|
||||
user_id = "@test:localhost"
|
||||
state_store_path = "/tmp/test"
|
||||
db_path = ":memory:"
|
||||
[opensearch]
|
||||
url = "http://localhost:9200"
|
||||
index = "test"
|
||||
[mistral]
|
||||
[behavior]
|
||||
"#).unwrap());
|
||||
let registry = ToolRegistry::new_minimal(config);
|
||||
let ctx = crate::orchestrator::event::ToolContext {
|
||||
user_id: "test".into(),
|
||||
scope_key: "test-room".into(),
|
||||
is_direct: true,
|
||||
};
|
||||
let result = registry.execute_with_context("nonexistent_tool", "{}", &ctx).await;
|
||||
assert!(result.is_err(), "Unknown tool should error");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_execute_search_code_without_opensearch() {
|
||||
let config = Arc::new(crate::config::Config::from_str(r#"
|
||||
[matrix]
|
||||
homeserver_url = "http://localhost:8008"
|
||||
user_id = "@test:localhost"
|
||||
state_store_path = "/tmp/test"
|
||||
db_path = ":memory:"
|
||||
[opensearch]
|
||||
url = "http://localhost:9200"
|
||||
index = "test"
|
||||
[mistral]
|
||||
[behavior]
|
||||
"#).unwrap());
|
||||
let registry = ToolRegistry::new_minimal(config);
|
||||
let ctx = crate::context::ResponseContext {
|
||||
matrix_user_id: String::new(),
|
||||
user_id: "test".into(),
|
||||
display_name: None,
|
||||
is_dm: true,
|
||||
is_reply: false,
|
||||
room_id: "test".into(),
|
||||
};
|
||||
let result = registry.execute("search_code", r#"{"query":"test"}"#, &ctx).await;
|
||||
assert!(result.is_err(), "search_code without OpenSearch should error");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user