Skip to main content

sim_codec_chat/
canonical.rs

1//! The `codec:chat` decoder/encoder pair and its host-registered lib. Decodes
2//! the canonical `SIMCHAT1` transcript text into checked `Expr` and encodes
3//! checked transcripts back out, validating the transcript shape on both sides.
4
5use std::sync::Arc;
6
7use sim_codec::{
8    DecodeBudget, Decoder, DomainCodecLib, Encoder, Input, Output, ReadCx, domain_input_text,
9};
10use sim_kernel::{CodecId, Export, Lib, LibManifest, Linker, LoadCx, Result, Symbol, WriteCx};
11
12use crate::cookbook::{
13    ChatProviderProfilesReport, ChatTranscriptRoundtripReport, provider_profiles_symbol,
14    transcript_roundtrip_symbol,
15};
16use crate::{
17    expr::{decode_chat_text, encode_chat_text},
18    validate_chat_transcript,
19};
20
21/// Provider-neutral transcript codec.
22pub struct ChatCodec;
23
24impl Decoder for ChatCodec {
25    fn decode(&self, cx: &mut ReadCx<'_>, input: Input) -> Result<sim_kernel::Expr> {
26        let source = domain_input_text(cx.codec, input)?;
27        let mut budget = DecodeBudget::new(cx.limits);
28        budget.check_input_bytes(cx.codec, source.len())?;
29        let expr = decode_chat_text(cx.codec, &source, &mut budget)?;
30        validate_chat_transcript(&expr)?;
31        Ok(expr)
32    }
33}
34
35impl Encoder for ChatCodec {
36    fn encode(&self, _cx: &mut WriteCx<'_>, expr: &sim_kernel::Expr) -> Result<Output> {
37        validate_chat_transcript(expr)?;
38        Ok(Output::Text(encode_chat_text(expr)))
39    }
40}
41
42/// Host-registered lib for `codec:chat`, built on the shared
43/// [`DomainCodecLib`] scaffold.
44pub struct ChatCodecLib {
45    symbol: Symbol,
46    codec_id: CodecId,
47}
48
49impl ChatCodecLib {
50    /// Creates the lib bound to the given runtime-assigned codec id.
51    pub fn new(id: CodecId) -> Self {
52        Self {
53            symbol: Symbol::qualified("codec", "chat"),
54            codec_id: id,
55        }
56    }
57
58    fn domain_lib(&self) -> DomainCodecLib {
59        DomainCodecLib::new(
60            self.symbol.clone(),
61            self.codec_id,
62            Arc::new(ChatCodec),
63            Arc::new(ChatCodec),
64            Symbol::qualified("codec", "ChatTranscript"),
65        )
66    }
67}
68
69impl Lib for ChatCodecLib {
70    fn manifest(&self) -> LibManifest {
71        let mut manifest = self.domain_lib().manifest();
72        manifest.exports.extend([
73            Export::Function {
74                symbol: transcript_roundtrip_symbol(),
75                function_id: None,
76            },
77            Export::Function {
78                symbol: provider_profiles_symbol(),
79                function_id: None,
80            },
81        ]);
82        manifest
83    }
84
85    fn load(&self, cx: &mut LoadCx, linker: &mut Linker<'_>) -> Result<()> {
86        self.domain_lib().load(cx, linker)?;
87        linker.function_value(
88            transcript_roundtrip_symbol(),
89            cx.factory()
90                .opaque(Arc::new(ChatTranscriptRoundtripReport))?,
91        )?;
92        linker.function_value(
93            provider_profiles_symbol(),
94            cx.factory().opaque(Arc::new(ChatProviderProfilesReport))?,
95        )?;
96        Ok(())
97    }
98}