Skip to main content

sim_lib_openai_server/codec_openai/
mod.rs

1use std::sync::Arc;
2
3use sim_codec::{
4    CodecDefaultDecode, CodecRuntime, Decoder, Encoder, Input, Output, ReadCx, codec_value,
5};
6use sim_kernel::{CodecId, DefaultFactory, Error, Factory, Linker, Result, Symbol, WriteCx};
7
8/// Decoding of OpenAI request/response JSON into SIM transcript expressions.
9pub mod decode;
10/// Encoding of SIM transcript expressions into OpenAI request/response JSON.
11pub mod encode;
12/// Validated transcript and codec-option shapes for the OpenAI codec.
13pub mod shapes;
14/// Server-sent-event streaming of gateway events on OpenAI surfaces.
15pub mod streaming;
16
17pub use decode::{decode_openai_request, decode_openai_response};
18pub use encode::{encode_openai_request, encode_openai_response, encode_openai_responses_response};
19pub use shapes::{ChatTranscript, OpenAiCodecOptions, OpenAiRequestOptions};
20pub use streaming::{
21    GatewayEventData, OpenAiSseSurface, StreamSink, encode_gateway_events_sse,
22    gateway_event_data_from_packet, gateway_event_data_kind, gateway_event_data_packets,
23};
24
25const OPENAI_CODEC_RUNTIME_ID: CodecId = CodecId(0);
26
27/// Runtime codec for OpenAI-compatible chat-completion JSON fixtures.
28pub struct OpenAiCodec;
29
30impl Decoder for OpenAiCodec {
31    fn decode(&self, cx: &mut ReadCx<'_>, input: Input) -> Result<sim_kernel::Expr> {
32        decode::decode_openai_request_for_codec(cx.codec, input)
33    }
34}
35
36impl Encoder for OpenAiCodec {
37    fn encode(&self, cx: &mut WriteCx<'_>, expr: &sim_kernel::Expr) -> Result<Output> {
38        encode::encode_openai_response_for_codec(cx.codec, expr).map(Output::Text)
39    }
40}
41
42/// Returns the codec symbol `codec/openai`.
43pub fn openai_codec_symbol() -> Symbol {
44    Symbol::qualified("codec", "openai")
45}
46
47/// Registers the OpenAI codec with the linker, resolving its expression and
48/// options shapes and installing it as a runtime codec value.
49pub fn install_openai_codec(linker: &mut Linker<'_>) -> Result<()> {
50    let factory = DefaultFactory;
51    let expr_shape = linker
52        .registry()
53        .shape_by_symbol(&Symbol::qualified("codec", "ChatTranscript"))
54        .or_else(|| {
55            linker
56                .registry()
57                .shape_by_symbol(&Symbol::qualified("core", "Expr"))
58        })
59        .or_else(|| {
60            linker
61                .registry()
62                .shape_by_symbol(&Symbol::qualified("core", "Any"))
63        })
64        .cloned()
65        .unwrap_or(factory.nil()?);
66    let options_shape = linker
67        .registry()
68        .shape_by_symbol(&Symbol::qualified("codec", "OpenAiCodecOptions"))
69        .or_else(|| {
70            linker
71                .registry()
72                .shape_by_symbol(&Symbol::qualified("core", "EncodeOptions"))
73        })
74        .or_else(|| {
75            linker
76                .registry()
77                .shape_by_symbol(&Symbol::qualified("core", "Any"))
78        })
79        .cloned()
80        .unwrap_or(factory.nil()?);
81
82    let symbol = openai_codec_symbol();
83    linker.codec_value(
84        symbol.clone(),
85        codec_value(CodecRuntime {
86            id: OPENAI_CODEC_RUNTIME_ID,
87            symbol,
88            decoder: Some(Arc::new(OpenAiCodec)),
89            located_decoder: None,
90            tree_decoder: None,
91            encoder: Some(Arc::new(OpenAiCodec)),
92            located_encoder: None,
93            tree_encoder: None,
94            expr_shape,
95            options_shape,
96            default_decode: CodecDefaultDecode::Datum,
97        }),
98    )?;
99    Ok(())
100}
101
102pub(crate) fn input_text(codec: CodecId, input: Input) -> Result<String> {
103    match input {
104        Input::Text(text) => Ok(text),
105        Input::Bytes(bytes) => String::from_utf8(bytes).map_err(|err| codec_error(codec, err)),
106    }
107}
108
109pub(crate) fn codec_error(codec: CodecId, message: impl ToString) -> Error {
110    Error::CodecError {
111        codec,
112        message: message.to_string(),
113    }
114}