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