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;
21
22use super::canonical::CanonicalRequest;
23use super::canonical_response::{CanonicalEvent, CanonicalResponse};
24
25#[derive(Debug)]
26pub struct OutboundCtx<'a> {
27    pub route: &'a GatewayRoute,
28    pub endpoint: &'a str,
29    pub api_key: &'a str,
30    pub request: &'a CanonicalRequest,
31    pub upstream_model: &'a str,
32}
33
34#[expect(
35    missing_debug_implementations,
36    reason = "variants hold streaming bodies that intentionally do not implement Debug"
37)]
38pub enum OutboundOutcome {
39    Buffered(Box<CanonicalResponse>),
40    Streaming(BoxStream<'static, Result<CanonicalEvent, String>>),
41}
42
43// Why: #[async_trait] is required — the upstream registry stores adapters as
44// `Arc<dyn OutboundAdapter>`, so the trait must stay dyn-compatible.
45#[async_trait]
46pub trait OutboundAdapter: Send + Sync {
47    fn provider_tag(&self) -> &'static str;
48    async fn send(&self, ctx: OutboundCtx<'_>) -> Result<OutboundOutcome>;
49}
50
51#[derive(Debug, Clone, Copy)]
52pub struct OutboundAdapterRegistration {
53    pub tag: &'static str,
54    pub factory: fn() -> Arc<dyn OutboundAdapter>,
55}
56
57inventory::collect!(OutboundAdapterRegistration);