1use std::collections::BTreeMap;
7use std::sync::Arc;
8
9use serde_json::Value;
10
11use crate::LlmResponseStreamEvent;
12use crate::codecs::FormatCodec;
13use crate::codecs::anthropic::AnthropicMessagesCodec;
14use crate::codecs::openai_chat::OpenAiChatCodec;
15use crate::codecs::responses::OpenAiResponsesCodec;
16use crate::codecs::stream::{
17 StreamCodecRegistry, StreamTranslationState, encode_response_stream_event,
18};
19use crate::diagnostic::TranslationDiagnostic;
20use crate::error::{Result, TranslationError};
21use crate::format::FormatId;
22use crate::llm::{AggLlmResponse, LlmRequest};
23use crate::policy::TranslationPolicy;
24
25#[derive(Debug)]
27pub struct TranslationOutput {
28 pub body: Value,
29 pub diagnostics: Vec<TranslationDiagnostic>,
30}
31
32#[derive(Debug)]
34pub struct RequestIrOutput {
35 pub request: LlmRequest,
36 pub diagnostics: Vec<TranslationDiagnostic>,
37}
38
39#[derive(Debug)]
41pub struct ResponseIrOutput {
42 pub response: AggLlmResponse,
43 pub diagnostics: Vec<TranslationDiagnostic>,
44}
45
46#[derive(Default)]
48pub struct FormatRegistry {
49 codecs: BTreeMap<FormatId, Arc<dyn FormatCodec>>,
50}
51
52impl FormatRegistry {
53 pub fn new() -> Self {
55 Self::default()
56 }
57
58 pub fn with_builtins() -> Self {
60 let mut registry = Self::new();
61 registry.register(OpenAiChatCodec);
62 registry.register(AnthropicMessagesCodec);
63 registry.register(OpenAiResponsesCodec);
64 registry
65 }
66
67 pub fn register(&mut self, codec: impl FormatCodec + 'static) {
69 self.codecs.insert(codec.format(), Arc::new(codec));
70 }
71
72 pub fn codec(&self, format: impl Into<FormatId>) -> Result<Arc<dyn FormatCodec>> {
74 let format = format.into();
75 self.codecs
76 .get(&format)
77 .cloned()
78 .ok_or_else(|| TranslationError::Other(format!("no codec registered for {format}")))
79 }
80}
81
82pub struct TranslationEngine {
84 registry: FormatRegistry,
85 stream_registry: StreamCodecRegistry,
86}
87
88impl Default for TranslationEngine {
89 fn default() -> Self {
90 Self {
91 registry: FormatRegistry::with_builtins(),
92 stream_registry: StreamCodecRegistry::with_builtins(),
93 }
94 }
95}
96
97impl TranslationEngine {
98 pub fn new(registry: FormatRegistry) -> Self {
100 Self {
101 registry,
102 stream_registry: StreamCodecRegistry::with_builtins(),
103 }
104 }
105
106 pub fn with_registries(registry: FormatRegistry, stream_registry: StreamCodecRegistry) -> Self {
108 Self {
109 registry,
110 stream_registry,
111 }
112 }
113
114 pub fn decode_request(
116 &self,
117 source: impl Into<FormatId>,
118 body: &Value,
119 policy: &TranslationPolicy,
120 ) -> Result<RequestIrOutput> {
121 let source = source.into();
122 let decoded = self.registry.codec(source)?.decode_request(body, policy)?;
123 Ok(RequestIrOutput {
124 request: decoded.request,
125 diagnostics: decoded.diagnostics,
126 })
127 }
128
129 pub fn encode_request(
131 &self,
132 target: impl Into<FormatId>,
133 request: &LlmRequest,
134 policy: &TranslationPolicy,
135 ) -> Result<TranslationOutput> {
136 let target = target.into();
137 let encoded = self
138 .registry
139 .codec(target)?
140 .encode_request(request, policy)?;
141 Ok(TranslationOutput {
142 body: encoded.body,
143 diagnostics: encoded.diagnostics,
144 })
145 }
146
147 pub fn translate_request(
149 &self,
150 source: impl Into<FormatId>,
151 target: impl Into<FormatId>,
152 body: &Value,
153 policy: &TranslationPolicy,
154 ) -> Result<TranslationOutput> {
155 let source = source.into();
156 let target = target.into();
157 let decoded = self
158 .registry
159 .codec(source.clone())?
160 .decode_request(body, policy)?;
161 let encoded = self
162 .registry
163 .codec(target.clone())?
164 .encode_request(&decoded.request, policy)?;
165 Ok(TranslationOutput {
166 body: encoded.body,
167 diagnostics: with_formats(decoded.diagnostics, encoded.diagnostics, source, target),
168 })
169 }
170
171 pub fn decode_response(
173 &self,
174 source: impl Into<FormatId>,
175 body: &Value,
176 policy: &TranslationPolicy,
177 ) -> Result<ResponseIrOutput> {
178 let source = source.into();
179 let decoded = self.registry.codec(source)?.decode_response(body, policy)?;
180 Ok(ResponseIrOutput {
181 response: decoded.response,
182 diagnostics: decoded.diagnostics,
183 })
184 }
185
186 pub fn encode_response(
188 &self,
189 target: impl Into<FormatId>,
190 response: &AggLlmResponse,
191 policy: &TranslationPolicy,
192 ) -> Result<TranslationOutput> {
193 let target = target.into();
194 let encoded = self
195 .registry
196 .codec(target)?
197 .encode_response(response, policy)?;
198 Ok(TranslationOutput {
199 body: encoded.body,
200 diagnostics: encoded.diagnostics,
201 })
202 }
203
204 pub fn translate_response(
206 &self,
207 source: impl Into<FormatId>,
208 target: impl Into<FormatId>,
209 body: &Value,
210 policy: &TranslationPolicy,
211 ) -> Result<TranslationOutput> {
212 let source = source.into();
213 let target = target.into();
214 let decoded = self
215 .registry
216 .codec(source.clone())?
217 .decode_response(body, policy)?;
218 let encoded = self
219 .registry
220 .codec(target.clone())?
221 .encode_response(&decoded.response, policy)?;
222 Ok(TranslationOutput {
223 body: encoded.body,
224 diagnostics: with_formats(decoded.diagnostics, encoded.diagnostics, source, target),
225 })
226 }
227
228 pub fn translate_event(
230 &self,
231 state: &mut StreamTranslationState,
232 source: impl Into<FormatId>,
233 target: impl Into<FormatId>,
234 event: &Value,
235 ) -> Result<Vec<Value>> {
236 let source = source.into();
237 let target = target.into();
238 let source_codec = self.stream_registry.codec(source.clone())?;
239 let target_codec = self.stream_registry.codec(target.clone())?;
240 let canonical = source_codec.decode_event(state, event);
241 state.source = Some(source);
242 state.target = Some(target);
243 Ok(canonical
244 .into_iter()
245 .flat_map(|event| target_codec.encode_event(state, event))
246 .collect())
247 }
248
249 pub fn decode_stream_event(
254 &self,
255 state: &mut StreamTranslationState,
256 source: impl Into<FormatId>,
257 event: Value,
258 ) -> Result<LlmResponseStreamEvent> {
259 let source = source.into();
260 let source_codec = self.stream_registry.codec(source.clone())?;
261 state.source = Some(source.clone());
262 let normalized = source_codec.decode_event(state, &event);
263 Ok(LlmResponseStreamEvent::preserved(source, event, normalized))
264 }
265
266 pub fn encode_stream_event(
272 &self,
273 state: &mut StreamTranslationState,
274 target: impl Into<FormatId>,
275 event: LlmResponseStreamEvent,
276 ) -> Result<Vec<Value>> {
277 let target = target.into();
278 let target_codec = self.stream_registry.codec(target.clone())?;
279 state.target = Some(target.clone());
280 Ok(encode_response_stream_event(
281 state,
282 target_codec.as_ref(),
283 &target,
284 event,
285 ))
286 }
287
288 pub fn finish_stream(
290 &self,
291 state: &mut StreamTranslationState,
292 target: impl Into<FormatId>,
293 ) -> Result<Vec<Value>> {
294 let target = target.into();
295 let target_codec = self.stream_registry.codec(target)?;
296 Ok(target_codec.finish(state))
297 }
298}
299
300fn with_formats(
302 decoded: Vec<TranslationDiagnostic>,
303 encoded: Vec<TranslationDiagnostic>,
304 source: FormatId,
305 target: FormatId,
306) -> Vec<TranslationDiagnostic> {
307 decoded
308 .into_iter()
309 .chain(encoded)
310 .map(|diagnostic| diagnostic.with_formats(source.clone(), target.clone()))
311 .collect()
312}