Skip to main content

sim_codec_chat/providers/
lm_studio.rs

1//! LM Studio OpenAI-compatible provider wire codec.
2//!
3//! LM Studio accepts OpenAI chat-completions request and response bodies. This
4//! module delegates the wire translation to the OpenAI codec while preserving
5//! the native `lm-studio` provider identity on decoded transcripts.
6
7use std::sync::Arc;
8
9use sim_codec::{Decoder, DomainCodecLib, Encoder, Input, Output, ReadCx};
10use sim_kernel::{CodecId, Expr, Lib, LibManifest, Linker, LoadCx, Result, Symbol, WriteCx};
11
12use super::openai::OpenAiCodecOptions;
13use super::openai_compat::{
14    decode_request_for_provider, decode_response_for_provider, decode_stream_for_provider,
15    encode_request_for_provider, encode_response_for_codec, encode_response_for_provider,
16};
17
18const LM_STUDIO_CODEC_ID: CodecId = CodecId(0);
19const PROVIDER: &str = "lm-studio";
20
21/// Options for LM Studio OpenAI-compatible request JSON generation.
22pub type LmStudioCodecOptions = OpenAiCodecOptions;
23
24/// Alias for [`LmStudioCodecOptions`] when used for request generation.
25pub type LmStudioRequestOptions = LmStudioCodecOptions;
26
27/// Runtime codec for LM Studio OpenAI-compatible chat-completion JSON.
28pub struct LmStudioCodec;
29
30impl Decoder for LmStudioCodec {
31    fn decode(&self, cx: &mut ReadCx<'_>, input: Input) -> Result<Expr> {
32        decode_request_for_provider(cx.codec, input, PROVIDER)
33    }
34}
35
36impl Encoder for LmStudioCodec {
37    fn encode(&self, cx: &mut WriteCx<'_>, expr: &Expr) -> Result<Output> {
38        encode_response_for_codec(cx.codec, expr).map(Output::Text)
39    }
40}
41
42/// Host-registered lib for `codec:lm-studio`.
43pub struct LmStudioCodecLib {
44    symbol: Symbol,
45    codec_id: CodecId,
46}
47
48impl LmStudioCodecLib {
49    /// Creates the lib bound to the given runtime-assigned codec id.
50    pub fn new(id: CodecId) -> Self {
51        Self {
52            symbol: lm_studio_codec_symbol(),
53            codec_id: id,
54        }
55    }
56
57    fn domain_lib(&self) -> DomainCodecLib {
58        DomainCodecLib::new(
59            self.symbol.clone(),
60            self.codec_id,
61            Arc::new(LmStudioCodec),
62            Arc::new(LmStudioCodec),
63            Symbol::qualified("codec", "LmStudioTranscript"),
64        )
65    }
66}
67
68impl Lib for LmStudioCodecLib {
69    fn manifest(&self) -> LibManifest {
70        self.domain_lib().manifest()
71    }
72
73    fn load(&self, cx: &mut LoadCx, linker: &mut Linker<'_>) -> Result<()> {
74        self.domain_lib().load(cx, linker)
75    }
76}
77
78/// Returns the codec symbol `codec:lm-studio`.
79pub fn lm_studio_codec_symbol() -> Symbol {
80    Symbol::qualified("codec", PROVIDER)
81}
82
83/// Decodes an LM Studio OpenAI-compatible request body.
84pub fn decode_lm_studio_request(input: Input) -> Result<Expr> {
85    decode_request_for_provider(LM_STUDIO_CODEC_ID, input, PROVIDER)
86}
87
88/// Encodes a model-request transcript into an LM Studio request body.
89pub fn encode_lm_studio_request(expr: &Expr, options: &LmStudioRequestOptions) -> Result<Vec<u8>> {
90    encode_request_for_provider(expr, options)
91}
92
93/// Decodes an LM Studio OpenAI-compatible response body.
94pub fn decode_lm_studio_response(
95    runner: Symbol,
96    model: &str,
97    body: &[u8],
98    include_raw: bool,
99) -> Result<Expr> {
100    decode_response_for_provider(runner, model, body, include_raw, PROVIDER)
101}
102
103/// Decodes LM Studio OpenAI-compatible SSE chunks into a response transcript.
104pub fn decode_lm_studio_stream(
105    runner: Symbol,
106    model: &str,
107    body: &[u8],
108    include_raw: bool,
109) -> Result<Expr> {
110    decode_stream_for_provider(runner, model, body, include_raw, PROVIDER)
111}
112
113/// Encodes a model-response transcript into an LM Studio response body.
114pub fn encode_lm_studio_response(expr: &Expr) -> Result<Vec<u8>> {
115    encode_response_for_provider(expr)
116}