Skip to main content

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

1//! Inbound adapter for the `OpenAI` Chat Completions wire protocol.
2//!
3//! [`OpenAiChatInbound`] parses Chat Completions request bodies into the
4//! canonical request model and renders canonical responses, streaming chunks,
5//! and errors back in Chat Completions format. This is the surface `OpenCode`,
6//! VS Code Copilot BYOK, and other OpenAI-SDK clients speak.
7//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11use bytes::Bytes;
12use http::StatusCode;
13use serde_json::Value;
14use systemprompt_models::profile::WireProtocol;
15
16use super::super::canonical::CanonicalRequest;
17use super::super::canonical_response::{CanonicalEvent, CanonicalResponse};
18use super::{InboundAdapter, InboundParseError};
19
20mod parse;
21mod render;
22mod render_terminal;
23
24#[cfg(feature = "test-api")]
25pub mod test_api {
26    pub use super::parse::parse as parse_request;
27    pub use super::render::{render_event_frame, render_response_object};
28    pub use super::render_terminal::render_terminal_event_frame;
29}
30
31#[derive(Debug, Clone, Copy, Default)]
32pub struct OpenAiChatInbound;
33
34impl InboundAdapter for OpenAiChatInbound {
35    fn wire_name(&self) -> &'static str {
36        "openai.chat"
37    }
38
39    fn passthrough_wire(&self) -> Option<WireProtocol> {
40        Some(WireProtocol::OpenAiChat)
41    }
42
43    fn parse_request(&self, raw: &Bytes) -> Result<CanonicalRequest, InboundParseError> {
44        let value: Value = serde_json::from_slice(raw)
45            .map_err(|e| InboundParseError::InvalidJson(e.to_string()))?;
46        parse::parse(&value)
47    }
48
49    fn render_response(&self, response: &CanonicalResponse) -> Bytes {
50        let value = render::render_response_object(response);
51        Bytes::from(serde_json::to_vec(&value).unwrap_or_else(|_| b"{}".to_vec()))
52    }
53
54    fn render_event(&self, event: &CanonicalEvent, model: &str) -> Option<Bytes> {
55        render::render_event_frame(event, model)
56    }
57
58    fn render_terminal_event(
59        &self,
60        event: &CanonicalEvent,
61        snapshot: &CanonicalResponse,
62        _model: &str,
63    ) -> Option<Bytes> {
64        render_terminal::render_terminal_event_frame(event, snapshot)
65    }
66
67    fn render_error(&self, _status: StatusCode, message: &str) -> Bytes {
68        let escaped = message.replace('\\', "\\\\").replace('"', "\\\"");
69        let body = format!("{{\"error\":{{\"type\":\"api_error\",\"message\":\"{escaped}\"}}}}");
70        Bytes::from(body)
71    }
72}