feat(v2): complete multi-LLM providers, TUI redesign, and advanced agent features

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>
This commit is contained in:
2025-12-02 17:24:14 +01:00
parent 09c8c9d83e
commit 10c8e2baae
67 changed files with 11444 additions and 626 deletions

View File

@@ -0,0 +1,114 @@
// 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"));
}