Skip to main content

systemprompt_api/services/gateway/protocol/outbound/
mod.rs

1//! Outbound protocol adapters: canonical model to upstream provider.
2//!
3//! The [`OutboundAdapter`] trait sends a [`CanonicalRequest`] to an upstream
4//! provider and yields an [`OutboundOutcome`] — a buffered response or a stream
5//! of canonical events. Adapters register themselves via
6//! [`OutboundAdapterRegistration`] (collected by `inventory`) so the upstream
7//! registry can resolve one by provider tag. Implementations cover Anthropic,
8//! `OpenAI` Chat Completions, and `OpenAI` Responses.
9
10pub mod anthropic;
11pub mod gemini;
12pub mod openai_chat;
13pub mod openai_responses;
14
15use std::sync::Arc;
16
17use anyhow::Result;
18use async_trait::async_trait;
19use futures_util::stream::BoxStream;
20use systemprompt_models::profile::GatewayRoute;
21use systemprompt_models::services::ai::ModelLimits;
22
23use super::canonical::CanonicalRequest;
24use super::canonical_response::{CanonicalEvent, CanonicalResponse};
25
26#[derive(Debug)]
27pub struct OutboundCtx<'a> {
28    pub route: &'a GatewayRoute,
29    pub endpoint: &'a str,
30    pub api_key: &'a str,
31    pub request: &'a CanonicalRequest,
32    pub upstream_model: &'a str,
33    /// Limits from the resolved upstream model card, when the requested model
34    /// maps to a known catalog entry. Codecs use this to clamp
35    /// provider-specific values (e.g. Gemini's `thinkingBudget`) to what
36    /// the upstream accepts.
37    pub model_limits: Option<ModelLimits>,
38}
39
40#[expect(
41    missing_debug_implementations,
42    reason = "variants hold streaming bodies that intentionally do not implement Debug"
43)]
44pub enum OutboundOutcome {
45    Buffered(Box<CanonicalResponse>),
46    Streaming(BoxStream<'static, Result<CanonicalEvent, String>>),
47}
48
49// Why: #[async_trait] is required — the upstream registry stores adapters as
50// `Arc<dyn OutboundAdapter>`, so the trait must stay dyn-compatible.
51#[async_trait]
52pub trait OutboundAdapter: Send + Sync {
53    fn provider_tag(&self) -> &'static str;
54    async fn send(&self, ctx: OutboundCtx<'_>) -> Result<OutboundOutcome>;
55}
56
57#[derive(Debug, Clone, Copy)]
58pub struct OutboundAdapterRegistration {
59    pub tag: &'static str,
60    pub factory: fn() -> Arc<dyn OutboundAdapter>,
61}
62
63inventory::collect!(OutboundAdapterRegistration);