Skip to main content

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

1use anyhow::{Result, anyhow};
2use async_trait::async_trait;
3use serde_json::Value;
4
5use super::{OutboundAdapter, OutboundCtx, OutboundOutcome};
6
7mod request;
8mod response;
9mod streaming;
10
11#[derive(Debug, Clone, Copy, Default)]
12pub struct OpenAiChatOutbound;
13
14#[async_trait]
15impl OutboundAdapter for OpenAiChatOutbound {
16    fn provider_tag(&self) -> &'static str {
17        "openai"
18    }
19
20    async fn send(&self, ctx: OutboundCtx<'_>) -> Result<OutboundOutcome> {
21        let body = request::build_request_body(ctx.request, ctx.upstream_model);
22        let url = format!(
23            "{}/chat/completions",
24            ctx.route.endpoint.trim_end_matches('/')
25        );
26
27        let client = reqwest::Client::new();
28        let mut req = client
29            .post(&url)
30            .header("authorization", format!("Bearer {}", ctx.api_key))
31            .header("content-type", "application/json")
32            .json(&body);
33        for (name, value) in &ctx.route.extra_headers {
34            req = req.header(name.as_str(), value.as_str());
35        }
36
37        let upstream_response = req
38            .send()
39            .await
40            .map_err(|e| anyhow!("Upstream OpenAI-compatible request failed: {e}"))?;
41
42        let status = upstream_response.status();
43        if !status.is_success() {
44            let err = upstream_response.text().await.unwrap_or_default();
45            return Err(anyhow!("Upstream error {status}: {err}"));
46        }
47
48        if ctx.request.stream {
49            let stream = upstream_response.bytes_stream();
50            let event_stream =
51                streaming::sse_to_canonical_events(stream, ctx.request.model.clone());
52            return Ok(OutboundOutcome::Streaming(event_stream));
53        }
54
55        let bytes = upstream_response
56            .bytes()
57            .await
58            .map_err(|e| anyhow!("Failed to read OpenAI response: {e}"))?;
59        let value: Value = serde_json::from_slice(&bytes)
60            .map_err(|e| anyhow!("OpenAI response not valid JSON: {e}"))?;
61        let canon = response::parse_response(&value, &ctx.request.model);
62        Ok(OutboundOutcome::Buffered(canon))
63    }
64}