1use async_trait::async_trait;
2use futures_core::Stream;
3use serde_json::Value;
4use std::pin::Pin;
5
6use crate::types::{AgentResult, ChatMessage, ResponseFormat};
7
8mod anthropic;
9mod openai;
10mod registry;
11mod stream_client;
12
13pub use anthropic::AnthropicClient;
14pub use openai::{LlmClientConfig, OpenAiClient};
15pub use registry::{LlmClientBuilder, LlmProvider};
16pub use stream_client::{LlmClientAdapter, StreamClient, adapt};
17
18#[derive(Clone, Debug)]
19pub enum StreamChunk {
20 Text(String),
21 Thought(String),
22 ToolCall(Value),
23 Usage(UsageInfo),
24 Stop {
30 finish_reason: Option<String>,
31 },
32}
33
34#[derive(Clone, Debug, Default)]
35pub struct UsageInfo {
36 pub prompt_tokens: Option<u32>,
37 pub completion_tokens: Option<u32>,
38 pub total_tokens: Option<u32>,
39}
40
41#[derive(Clone, Debug, Default)]
42pub struct LlmCapabilities {
43 pub supports_streaming: bool,
44 pub supports_tools: bool,
45 pub supports_vision: bool,
46 pub supports_thinking: bool,
47 pub max_context_tokens: Option<u32>,
48 pub max_output_tokens: Option<u32>,
49}
50
51#[derive(Debug, Clone, Default)]
53pub struct ReasoningConfig {
54 pub enabled: Option<bool>,
56 pub budget_tokens: Option<u64>,
58 pub effort: Option<ReasoningEffort>,
60}
61
62#[derive(Debug, Clone, Default)]
64pub enum ReasoningEffort {
65 #[default]
66 None,
67 Low,
68 Medium,
69 High,
70 XHigh,
71}
72
73#[async_trait]
74pub trait LlmClient: Send + Sync {
75 async fn chat(
76 &self,
77 messages: &[ChatMessage],
78 tools: &[Value],
79 reasoning: Option<&ReasoningConfig>,
80 response_format: Option<&ResponseFormat>,
81 ) -> AgentResult<Value>;
82
83 async fn chat_stream(
84 &self,
85 messages: &[ChatMessage],
86 tools: &[Value],
87 reasoning: Option<&ReasoningConfig>,
88 response_format: Option<&ResponseFormat>,
89 ) -> AgentResult<Pin<Box<dyn Stream<Item = AgentResult<StreamChunk>> + Send>>>;
90
91 fn capabilities(&self) -> LlmCapabilities;
92
93 fn model_name(&self) -> &str {
96 "unknown"
97 }
98}