Multi-LLM Provider Support: - Add llm-core crate with LlmProvider trait abstraction - Implement Anthropic Claude API client with streaming - Implement OpenAI API client with streaming - Add token counting with SimpleTokenCounter and ClaudeTokenCounter - Add retry logic with exponential backoff and jitter Borderless TUI Redesign: - Rewrite theme system with terminal capability detection (Full/Unicode256/Basic) - Add provider tabs component with keybind switching [1]/[2]/[3] - Implement vim-modal input (Normal/Insert/Visual/Command modes) - Redesign chat panel with timestamps and streaming indicators - Add multi-provider status bar with cost tracking - Add Nerd Font icons with graceful ASCII fallbacks - Add syntax highlighting (syntect) and markdown rendering (pulldown-cmark) Advanced Agent Features: - Add system prompt builder with configurable components - Enhance subagent orchestration with parallel execution - Add git integration module for safe command detection - Add streaming tool results via channels - Expand tool set: AskUserQuestion, TodoWrite, LS, MultiEdit, BashOutput, KillShell - Add WebSearch with provider abstraction Plugin System Enhancement: - Add full agent definition parsing from YAML frontmatter - Add skill system with progressive disclosure - Wire plugin hooks into HookManager 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
115 lines
3.7 KiB
Rust
115 lines
3.7 KiB
Rust
// Test that ToolContext properly wires up the placeholder tools
|
|
use agent_core::{ToolContext, execute_tool};
|
|
use permissions::{Mode, PermissionManager};
|
|
use tools_todo::{TodoList, TodoStatus};
|
|
use tools_bash::ShellManager;
|
|
use serde_json::json;
|
|
|
|
#[tokio::test]
|
|
async fn test_todo_write_with_context() {
|
|
let todo_list = TodoList::new();
|
|
let ctx = ToolContext::new().with_todo_list(todo_list.clone());
|
|
let perms = PermissionManager::new(Mode::Code); // Allow all tools
|
|
|
|
let arguments = json!({
|
|
"todos": [
|
|
{
|
|
"content": "First task",
|
|
"status": "pending",
|
|
"active_form": "Working on first task"
|
|
},
|
|
{
|
|
"content": "Second task",
|
|
"status": "in_progress",
|
|
"active_form": "Working on second task"
|
|
}
|
|
]
|
|
});
|
|
|
|
let result = execute_tool("todo_write", &arguments, &perms, &ctx).await;
|
|
assert!(result.is_ok(), "TodoWrite should succeed: {:?}", result);
|
|
|
|
// Verify the todos were written
|
|
let todos = todo_list.read();
|
|
assert_eq!(todos.len(), 2);
|
|
assert_eq!(todos[0].content, "First task");
|
|
assert_eq!(todos[1].status, TodoStatus::InProgress);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_todo_write_without_context() {
|
|
let ctx = ToolContext::new(); // No todo_list
|
|
let perms = PermissionManager::new(Mode::Code);
|
|
|
|
let arguments = json!({
|
|
"todos": []
|
|
});
|
|
|
|
let result = execute_tool("todo_write", &arguments, &perms, &ctx).await;
|
|
assert!(result.is_err(), "TodoWrite should fail without TodoList");
|
|
assert!(result.unwrap_err().to_string().contains("not available"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_bash_output_with_context() {
|
|
let manager = ShellManager::new();
|
|
let ctx = ToolContext::new().with_shell_manager(manager.clone());
|
|
let perms = PermissionManager::new(Mode::Code);
|
|
|
|
// Start a shell and run a command
|
|
let shell_id = manager.start_shell().await.unwrap();
|
|
let _ = manager.execute(&shell_id, "echo test", None).await.unwrap();
|
|
|
|
let arguments = json!({
|
|
"shell_id": shell_id
|
|
});
|
|
|
|
let result = execute_tool("bash_output", &arguments, &perms, &ctx).await;
|
|
assert!(result.is_ok(), "BashOutput should succeed: {:?}", result);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_bash_output_without_context() {
|
|
let ctx = ToolContext::new(); // No shell_manager
|
|
let perms = PermissionManager::new(Mode::Code);
|
|
|
|
let arguments = json!({
|
|
"shell_id": "fake-id"
|
|
});
|
|
|
|
let result = execute_tool("bash_output", &arguments, &perms, &ctx).await;
|
|
assert!(result.is_err(), "BashOutput should fail without ShellManager");
|
|
assert!(result.unwrap_err().to_string().contains("not available"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_kill_shell_with_context() {
|
|
let manager = ShellManager::new();
|
|
let ctx = ToolContext::new().with_shell_manager(manager.clone());
|
|
let perms = PermissionManager::new(Mode::Code);
|
|
|
|
// Start a shell
|
|
let shell_id = manager.start_shell().await.unwrap();
|
|
|
|
let arguments = json!({
|
|
"shell_id": shell_id
|
|
});
|
|
|
|
let result = execute_tool("kill_shell", &arguments, &perms, &ctx).await;
|
|
assert!(result.is_ok(), "KillShell should succeed: {:?}", result);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_ask_user_without_context() {
|
|
let ctx = ToolContext::new(); // No ask_sender
|
|
let perms = PermissionManager::new(Mode::Code);
|
|
|
|
let arguments = json!({
|
|
"questions": []
|
|
});
|
|
|
|
let result = execute_tool("ask_user", &arguments, &perms, &ctx).await;
|
|
assert!(result.is_err(), "AskUser should fail without AskSender");
|
|
assert!(result.unwrap_err().to_string().contains("not available"));
|
|
}
|