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,11 @@
[package]
name = "tools-ask"
version = "0.1.0"
edition.workspace = true
license.workspace = true
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1", features = ["sync"] }
color-eyre = "0.6"

View File

@@ -0,0 +1,60 @@
//! AskUserQuestion tool for interactive user input
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tokio::sync::{mpsc, oneshot};
/// A question option
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuestionOption {
pub label: String,
pub description: String,
}
/// A question to ask the user
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Question {
pub question: String,
pub header: String,
pub options: Vec<QuestionOption>,
pub multi_select: bool,
}
/// Request sent to the UI to ask questions
#[derive(Debug)]
pub struct AskRequest {
pub questions: Vec<Question>,
pub response_tx: oneshot::Sender<HashMap<String, String>>,
}
/// Channel for sending ask requests to the UI
pub type AskSender = mpsc::Sender<AskRequest>;
pub type AskReceiver = mpsc::Receiver<AskRequest>;
/// Create a channel pair for ask requests
pub fn create_ask_channel() -> (AskSender, AskReceiver) {
mpsc::channel(1)
}
/// Ask the user questions (called by agent)
pub async fn ask_user(
sender: &AskSender,
questions: Vec<Question>,
) -> color_eyre::Result<HashMap<String, String>> {
let (response_tx, response_rx) = oneshot::channel();
sender.send(AskRequest { questions, response_tx }).await
.map_err(|_| color_eyre::eyre::eyre!("Failed to send ask request"))?;
response_rx.await
.map_err(|_| color_eyre::eyre::eyre!("Failed to receive ask response"))
}
/// Parse questions from JSON tool input
pub fn parse_questions(input: &serde_json::Value) -> color_eyre::Result<Vec<Question>> {
let questions = input.get("questions")
.ok_or_else(|| color_eyre::eyre::eyre!("Missing 'questions' field"))?;
serde_json::from_value(questions.clone())
.map_err(|e| color_eyre::eyre::eyre!("Invalid questions format: {}", e))
}