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