Skip to main content

rai_sdk/provider/
mod.rs

1//! Provider implementations and the low-level streaming event they emit.
2//!
3//! Each provider module wraps one HTTP API and translates the crate's
4//! provider-agnostic [`Prompt`](crate::Prompt)/[`Response`](crate::Response)
5//! types to and from that API's wire format. Modules are gated behind the
6//! matching Cargo feature (`openai`, `anthropic`, `openrouter`), all of which
7//! are enabled by default.
8//!
9//! `openai_compatible` is the exception to one module per service: it targets
10//! the OpenAI Chat Completions *format* as implemented by self-hosted and
11//! third-party servers, so the endpoint is named per client. It shares the
12//! `openai` feature because it also shares that module's request builder and
13//! stream parser.
14//!
15//! Most code should go through [`Client`](crate::Client) instead of using these
16//! types directly; they are public so that advanced callers can drive a single
17//! provider, and because streaming surfaces [`ProviderStreamEvent`].
18
19#[cfg(feature = "openai")]
20pub mod openai;
21
22#[cfg(feature = "openai")]
23pub mod openai_compatible;
24
25#[cfg(feature = "anthropic")]
26pub mod anthropic;
27
28#[cfg(feature = "openrouter")]
29pub mod openrouter;
30
31#[cfg(feature = "openai")]
32pub use openai::OpenAIProvider;
33
34#[cfg(feature = "openai")]
35pub use openai_compatible::OpenAICompatibleProvider;
36
37#[cfg(feature = "anthropic")]
38pub use anthropic::AnthropicProvider;
39
40#[cfg(feature = "openrouter")]
41pub use openrouter::OpenRouterProvider;
42
43use crate::message::Usage;
44
45/// Build the HTTP client with a deterministic TLS backend.
46///
47/// Cargo features are additive, so a downstream dependency can enable both
48/// reqwest backends even when this crate requested only one. reqwest 0.13
49/// otherwise prefers native-tls in that situation. rai-sdk keeps its documented
50/// rustls default by selecting rustls explicitly whenever it is available.
51#[cfg(any(feature = "openai", feature = "anthropic", feature = "openrouter"))]
52pub(crate) fn http_client_builder() -> reqwest::ClientBuilder {
53    let builder = reqwest::Client::builder();
54
55    #[cfg(feature = "rustls-tls")]
56    let builder = builder.tls_backend_rustls();
57
58    #[cfg(all(not(feature = "rustls-tls"), feature = "native-tls"))]
59    let builder = builder.tls_backend_native();
60
61    builder
62}
63
64/// A low-level event decoded from a provider's server-sent event stream.
65///
66/// This is the raw shape yielded by
67/// [`RequestBuilder::stream`](crate::RequestBuilder::stream). Providers differ
68/// in how they chunk tool calls, so tool arguments arrive as a
69/// [`ProviderStreamEvent::ToolCallStart`] followed by any number of
70/// [`ProviderStreamEvent::ToolCallChunk`]s that must be concatenated. For a
71/// pre-assembled view, use
72/// [`RequestBuilder::generate_stream_events`](crate::RequestBuilder::generate_stream_events).
73#[derive(Debug, Clone)]
74pub enum ProviderStreamEvent {
75    /// A fragment of assistant text to append to the output so far.
76    Text(String),
77    /// A tool call has begun; its arguments follow in later chunks.
78    ToolCallStart {
79        /// Provider-assigned call identifier.
80        id: String,
81        /// Name of the tool the model wants to run.
82        name: String,
83    },
84    /// A fragment of the JSON argument string for a tool call.
85    ToolCallChunk {
86        /// Identifier of the call these arguments belong to.
87        id: String,
88        /// Partial JSON text to append to previously received fragments.
89        arguments: String,
90    },
91    /// Generation finished.
92    ///
93    /// Depending on the provider, the finish reason and usage may arrive in
94    /// separate `Done` events.
95    Done {
96        /// Why generation stopped, e.g. `"stop"` or `"tool_use"`.
97        finish_reason: Option<String>,
98        /// Token usage, when the provider reports it.
99        usage: Option<Usage>,
100    },
101}