Skip to main content

claude_codex/
provider.rs

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    /// Sign in using browser-based authentication
15    Login,
16    /// Sign in using a device code
17    Device,
18    /// Show the current authentication status
19    Status,
20    /// Delete stored authentication credentials
21    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    /// Raw request material for byte-passthrough providers (the Anthropic backend).
127    /// Present on real HTTP requests; None in unit tests. Forwarding these verbatim
128    /// keeps the prompt-cache prefix byte-identical.
129    pub passthrough: Option<Passthrough>,
130}
131
132/// Untranslated request material needed to relay a request to an upstream verbatim.
133#[derive(Debug, Clone)]
134pub struct Passthrough {
135    /// Original request body bytes, forwarded without reserialization.
136    pub raw_body: axum::body::Bytes,
137    /// Original client request headers (carry Authorization + anthropic-beta).
138    pub headers: axum::http::HeaderMap,
139    /// Original path and query, e.g. `/v1/messages?beta=true`.
140    pub path_and_query: String,
141}