Skip to main content

xz_provider/protocol/
mod.rs

1pub mod openai_chat;
2pub use openai_chat::OpenAiChatAdapter;
3
4/// Shared OpenAI wire encode/decode helpers (chat + responses).
5pub(crate) mod openai_wire;
6
7use std::fmt::Debug;
8
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11
12pub mod openai_responses;
13
14use crate::error::ProviderError;
15use crate::types::{CompletionRequest, CompletionResponse, StreamEvent};
16
17#[cfg(feature = "anthropic")]
18pub mod anthropic;
19
20/// Authentication method for a provider.
21///
22/// Represents the various ways providers authenticate API requests.
23/// The [`ProtocolAdapter::build_auth_headers`] method converts this
24/// into the appropriate HTTP headers.
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(tag = "type", rename_all = "snake_case")]
27pub enum AuthMethod {
28    /// No authentication required (e.g., local providers).
29    None,
30    /// Bearer token authentication (used by OpenAI, Anthropic, etc.).
31    Bearer {
32        /// The bearer token value (api key).
33        token: String,
34    },
35    /// Custom API key header authentication.
36    ApiKey {
37        /// The HTTP header name for the API key (e.g., "x-api-key").
38        header_name: String,
39        /// The API key value.
40        key: String,
41    },
42}
43
44/// A protocol adapter translates between the unified request/response types
45/// and a specific provider's API protocol.
46///
47/// This trait is object-safe so it can be used as `&dyn ProtocolAdapter`
48/// or `Box<dyn ProtocolAdapter>`. That means:
49/// - No async methods
50/// - No generic type parameters
51/// - No `impl Trait` return types
52///
53/// Each variant represents a different API protocol:
54/// - OpenAI Chat Completions (`/v1/chat/completions`)
55/// - OpenAI Responses (`/v1/responses`)
56/// - Anthropic Messages (`/v1/messages`)
57/// - Ollama (`/api/chat`)
58///
59/// # Examples
60///
61/// ```rust
62/// use xz_provider::protocol::{ProtocolAdapter, AuthMethod};
63/// use xz_provider::ProviderError;
64///
65/// # #[derive(Debug)]
66/// # struct DummyAdapter;
67/// # impl ProtocolAdapter for DummyAdapter {
68/// #     fn endpoint_path(&self) -> &str { "/v1/test" }
69/// #     fn build_request_body(&self, _: &xz_provider::CompletionRequest, _: bool) -> Result<serde_json::Value, ProviderError> {
70/// #         Ok(serde_json::json!({}))
71/// #     }
72/// #     fn build_auth_headers(&self, _: &AuthMethod) -> Vec<(String, String)> { vec![] }
73/// #     fn parse_response(&self, _: &serde_json::Value) -> Result<xz_provider::CompletionResponse, ProviderError> {
74/// #         Err(ProviderError::Format("not implemented".to_owned()))
75/// #     }
76/// #     fn parse_sse_event(&self, _: &str) -> Result<Option<xz_provider::StreamEvent>, ProviderError> {
77/// #         Ok(None)
78/// #     }
79/// #     fn protocol_name(&self) -> &str { "test" }
80/// # }
81/// let adapter: &dyn ProtocolAdapter = &DummyAdapter;
82/// assert_eq!(adapter.protocol_name(), "test");
83/// ```
84pub trait ProtocolAdapter: Debug + Send + Sync {
85    /// Returns the API endpoint path for this protocol (e.g., `/v1/chat/completions`).
86    ///
87    /// The caller appends this to the provider's base URL.
88    fn endpoint_path(&self) -> &str;
89
90    /// Builds the JSON request body for a completion request.
91    ///
92    /// When `stream` is `true`, the body should include the
93    /// protocol-appropriate streaming flag (e.g., `"stream": true` for
94    /// OpenAI, no change for Anthropic which uses SSE headers instead).
95    fn build_request_body(
96        &self,
97        request: &CompletionRequest,
98        stream: bool,
99    ) -> Result<Value, ProviderError>;
100
101    /// Converts an [`AuthMethod`] into the appropriate HTTP auth headers.
102    ///
103    /// For `AuthMethod::None`, returns an empty vector.
104    /// For `AuthMethod::Bearer`, returns a single `Authorization: Bearer ...` header.
105    /// For `AuthMethod::ApiKey`, returns a single header with the specified name and value.
106    fn build_auth_headers(&self, auth: &AuthMethod) -> Vec<(String, String)>;
107
108    /// Parses a complete (non-streaming) JSON response body into a
109    /// [`CompletionResponse`].
110    fn parse_response(&self, body: &Value) -> Result<CompletionResponse, ProviderError>;
111
112    /// Parses the data portion of a single SSE event into a [`StreamEvent`].
113    ///
114    /// Returns `Ok(None)` when the event is ignorable (e.g., a heartbeat).
115    /// The input `data` string is the content after the `data: ` prefix,
116    /// already trimmed.
117    fn parse_sse_event(&self, data: &str) -> Result<Option<StreamEvent>, ProviderError>;
118
119    /// Returns the human-readable name of this protocol for logging and metrics.
120    fn protocol_name(&self) -> &str;
121}