chronicle/provider/
mod.rs1pub mod anthropic;
2pub mod claude_code;
3
4pub use anthropic::AnthropicProvider;
5pub use claude_code::ClaudeCodeProvider;
6
7use crate::error::ProviderError;
8use serde::{Deserialize, Serialize};
9
10pub trait LlmProvider: Send + Sync {
12 fn complete(&self, request: &CompletionRequest) -> Result<CompletionResponse, ProviderError>;
13 fn check_auth(&self) -> Result<AuthStatus, ProviderError>;
14 fn name(&self) -> &str;
15 fn model(&self) -> &str;
16}
17
18#[derive(Debug, Clone)]
19pub enum AuthStatus {
20 Valid,
21 Invalid(String),
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct CompletionRequest {
26 pub system: String,
27 pub messages: Vec<Message>,
28 pub tools: Vec<ToolDefinition>,
29 pub max_tokens: u32,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct CompletionResponse {
34 pub content: Vec<ContentBlock>,
35 pub stop_reason: StopReason,
36 pub usage: TokenUsage,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct Message {
41 pub role: Role,
42 pub content: Vec<ContentBlock>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
46#[serde(rename_all = "snake_case")]
47pub enum Role {
48 User,
49 Assistant,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
53#[serde(tag = "type", rename_all = "snake_case")]
54pub enum ContentBlock {
55 Text {
56 text: String,
57 },
58 ToolUse {
59 id: String,
60 name: String,
61 input: serde_json::Value,
62 },
63 ToolResult {
64 tool_use_id: String,
65 content: String,
66 #[serde(skip_serializing_if = "Option::is_none")]
67 is_error: Option<bool>,
68 },
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
72#[serde(rename_all = "snake_case")]
73pub enum StopReason {
74 EndTurn,
75 ToolUse,
76 MaxTokens,
77 StopSequence,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize, Default)]
81pub struct TokenUsage {
82 pub input_tokens: u32,
83 pub output_tokens: u32,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct ToolDefinition {
88 pub name: String,
89 pub description: String,
90 pub input_schema: serde_json::Value,
91}
92
93pub fn discover_provider() -> Result<Box<dyn LlmProvider>, ProviderError> {
100 use crate::config::user_config::{ProviderType, UserConfig};
101
102 if let Ok(Some(config)) = UserConfig::load() {
104 match config.provider.provider_type {
105 ProviderType::ClaudeCode => {
106 return Ok(Box::new(ClaudeCodeProvider::new(config.provider.model)));
107 }
108 ProviderType::Anthropic => {
109 let key_env = config
110 .provider
111 .api_key_env
112 .unwrap_or_else(|| "ANTHROPIC_API_KEY".to_string());
113 if let Ok(api_key) = std::env::var(&key_env) {
114 if !api_key.is_empty() {
115 return Ok(Box::new(AnthropicProvider::new(
116 api_key,
117 config.provider.model,
118 )));
119 }
120 }
121 }
123 ProviderType::None => {
124 }
126 }
127 }
128
129 if let Ok(api_key) = std::env::var("ANTHROPIC_API_KEY") {
131 if !api_key.is_empty() {
132 return Ok(Box::new(AnthropicProvider::new(api_key, None)));
133 }
134 }
135
136 snafu::ensure!(false, crate::error::provider_error::NoCredentialsSnafu);
137 unreachable!()
138}