1use crate::anthropic::schema::MessagesRequest;
2use crate::monitor::MonitorHandle;
3use crate::request_identity::ConversationIdentity;
4use crate::traffic::TrafficCapture;
5use anyhow::Result;
6use async_trait::async_trait;
7use axum::{body::Body, http::StatusCode, response::Response};
8use bytes::Bytes;
9use clap::Subcommand;
10use std::sync::Arc;
11
12#[derive(Debug, Clone, Subcommand)]
13pub enum AuthCommand {
14 Login,
16 Device,
18 Status,
20 Logout,
22}
23
24#[async_trait]
25pub trait Provider: Send + Sync {
26 fn name(&self) -> &'static str;
27 fn supported_models(&self) -> Vec<String>;
28 fn cli(&self) -> &'static dyn CliHandlers;
29 async fn handle_messages(&self, body: MessagesRequest, ctx: RequestContext) -> Response;
30
31 async fn handle_messages_with_conversation_identity(
32 &self,
33 body: MessagesRequest,
34 ctx: RequestContext,
35 conversation_identity: Option<ConversationIdentity>,
36 ) -> Response {
37 let _ = conversation_identity;
38 self.handle_messages(body, ctx).await
39 }
40
41 async fn handle_count_tokens(&self, body: MessagesRequest, ctx: RequestContext) -> Response;
42
43 async fn generate_anthropic_stream(
44 &self,
45 _body: MessagesRequest,
46 _ctx: RequestContext,
47 ) -> Result<Generation, ProviderError> {
48 Err(ProviderError::new(
49 StatusCode::NOT_IMPLEMENTED,
50 ProviderErrorKind::InvalidRequest,
51 format!(
52 "provider '{}' does not support OpenAI-compatible generation",
53 self.name()
54 ),
55 ))
56 }
57}
58
59pub enum GenerationBody {
60 BufferedSse(Bytes),
61 LiveSse(Body),
62}
63
64pub struct Generation {
65 pub body: GenerationBody,
66 pub resolved_model: String,
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum ProviderErrorKind {
71 Authentication,
72 Permission,
73 RateLimit,
74 InvalidRequest,
75 Api,
76}
77
78#[derive(Debug, Clone)]
79pub struct ProviderError {
80 pub status: StatusCode,
81 pub kind: ProviderErrorKind,
82 pub message: String,
83 pub retry_after: Option<String>,
84 pub param: Option<String>,
85 pub code: Option<String>,
86}
87
88impl ProviderError {
89 pub fn new(status: StatusCode, kind: ProviderErrorKind, message: impl Into<String>) -> Self {
90 Self {
91 status,
92 kind,
93 message: message.into(),
94 retry_after: None,
95 param: None,
96 code: None,
97 }
98 }
99
100 pub fn error_type(&self) -> &'static str {
101 match self.kind {
102 ProviderErrorKind::Authentication => "authentication_error",
103 ProviderErrorKind::Permission => "permission_error",
104 ProviderErrorKind::RateLimit => "rate_limit_error",
105 ProviderErrorKind::InvalidRequest => "invalid_request_error",
106 ProviderErrorKind::Api => "api_error",
107 }
108 }
109}
110
111pub trait CliHandlers: Send + Sync {
112 fn login(&self) -> Result<()>;
113 fn device(&self) -> Result<()>;
114 fn status(&self) -> Result<()>;
115 fn logout(&self) -> Result<()>;
116}
117
118#[derive(Debug, Clone)]
119pub struct RequestContext {
120 pub req_id: String,
121 pub session_id: Option<String>,
122 pub session_seq: Option<u64>,
123 pub provider: String,
124 pub traffic: Option<Arc<TrafficCapture>>,
125 pub monitor: Option<MonitorHandle>,
126 pub passthrough: Option<Passthrough>,
130}
131
132#[derive(Debug, Clone)]
134pub struct Passthrough {
135 pub raw_body: axum::body::Bytes,
137 pub headers: axum::http::HeaderMap,
139 pub path_and_query: String,
141}