rig_core/providers/openai/wire/
route.rs1use serde::{Deserialize, Serialize};
13
14use crate::catalog::ModelFacts;
15use crate::completion::CompletionRequest;
16use crate::error::{EncodeError, ProviderError};
17use crate::operation::Completion;
18use crate::providers::openai::responses_api::streaming::{ResponsesDecoder, ResponsesEvent};
19use crate::providers::openai::responses_api::wire::Responses;
20use crate::providers::openai::responses_api::{
21 ResponsesToolDefinition, SystemInstructionsPlacement,
22};
23use crate::wire::document::Reassemble;
24use crate::wire::{Decoder, Descriptor, Encoded, Flow, Mode, Out, Wire, WireEvent, WireFrame};
25
26use super::OpenAIConfig;
27use super::chat::{Chat, ChatDecoder, ChatEvent};
28
29macro_rules! on_route {
31 ($chosen:expr, $wire:ident => $ask:expr) => {
32 match $chosen {
33 Self::Chat($wire) => $ask,
34 Self::Responses($wire) => $ask,
35 }
36 };
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
41pub enum Route {
42 Chat,
44 Responses,
46}
47
48fn catalog_route(spec: &crate::catalog::ModelSpec) -> Option<Route> {
51 let provider = spec.details.provider.as_ref()?;
52 match (provider.npm.as_deref(), provider.shape.as_deref()) {
53 (_, Some("responses")) | (Some("@ai-sdk/openai"), None) => Some(Route::Responses),
54 (_, Some("completions")) | (Some("@ai-sdk/openai-compatible"), None) => Some(Route::Chat),
55 _ => None,
56 }
57}
58
59#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
63pub enum OpenAiWire {
64 Chat(Chat),
66 Responses(Responses),
68}
69
70impl OpenAiWire {
71 pub fn new(provider: OpenAIConfig, model: impl Into<String>) -> Self {
78 Self::routed(provider, model, ModelFacts::builtin())
79 }
80
81 pub(crate) fn routed(
83 provider: OpenAIConfig,
84 model: impl Into<String>,
85 facts: &ModelFacts,
86 ) -> Self {
87 let model = model.into();
88 let hook = provider
89 .dialect
90 .quirks
91 .hooks
92 .and_then(|hooks| hooks.model_route);
93 let route = provider
94 .route
95 .or_else(|| hook.map(|route| route(&model)))
96 .or_else(|| match provider.dialect.quirks.routes_by_catalog {
97 true => catalog_route(facts.for_model(provider.dialect.name, &model)?),
98 false => None,
99 })
100 .unwrap_or_else(|| provider.completion_route());
101 match route {
102 Route::Chat => Self::Chat(Chat::new(provider, model)),
103 Route::Responses => Self::Responses(Responses::new(provider, model)),
104 }
105 }
106
107 pub fn provider(&self) -> &OpenAIConfig {
109 on_route!(self, wire => &wire.provider)
110 }
111
112 pub fn with_strict_tools(self) -> Self {
115 self.on_chat(Chat::with_strict_tools)
116 .on_responses(Responses::with_strict_tools)
117 }
118
119 pub fn with_tool_result_array_content(self) -> Self {
123 self.on_chat(Chat::with_tool_result_array_content)
124 }
125
126 pub fn with_tool(self, tool: impl Into<ResponsesToolDefinition>) -> Self {
129 self.on_responses(|wire| wire.with_tool(tool))
130 }
131
132 pub fn with_tools<I, Tool>(self, tools: I) -> Self
135 where
136 I: IntoIterator<Item = Tool>,
137 Tool: Into<ResponsesToolDefinition>,
138 {
139 self.on_responses(|wire| wire.with_tools(tools))
140 }
141
142 pub fn with_system_instructions_placement(
146 self,
147 placement: SystemInstructionsPlacement,
148 ) -> Self {
149 self.on_responses(|wire| wire.with_system_instructions_placement(placement))
150 }
151
152 pub fn with_system_instructions_as_messages(self) -> Self {
155 self.on_responses(Responses::with_system_instructions_as_messages)
156 }
157
158 pub fn with_facts(self, facts: ModelFacts) -> Self {
161 match self {
162 Self::Chat(wire) => Self::Chat(wire.with_facts(facts)),
163 Self::Responses(wire) => Self::Responses(wire.with_facts(facts)),
164 }
165 }
166
167 fn on_chat(self, option: impl FnOnce(Chat) -> Chat) -> Self {
169 match self {
170 Self::Chat(wire) => Self::Chat(option(wire)),
171 responses => responses,
172 }
173 }
174
175 fn on_responses(self, option: impl FnOnce(Responses) -> Responses) -> Self {
177 match self {
178 Self::Responses(wire) => Self::Responses(option(wire)),
179 chat => chat,
180 }
181 }
182}
183
184impl From<Chat> for OpenAiWire {
185 fn from(wire: Chat) -> Self {
186 Self::Chat(wire)
187 }
188}
189
190impl From<Responses> for OpenAiWire {
191 fn from(wire: Responses) -> Self {
192 Self::Responses(wire)
193 }
194}
195
196impl Wire for OpenAiWire {
197 type Op = Completion;
198 type Payload = crate::wire::Encoded;
199 type Frame = crate::wire::WireFrame;
200 type Event = OpenAiEvent;
201 type Decoder<'id> = OpenAiDecoder;
202 type Reassembler = OpenAiReassembler;
203
204 fn describe(&self) -> Descriptor<'_> {
205 on_route!(self, wire => wire.describe())
206 }
207
208 fn encode(&self, request: CompletionRequest, mode: Mode) -> Result<Encoded, EncodeError> {
209 on_route!(self, wire => wire.encode(request, mode))
210 }
211
212 fn decoder<'id>(&self) -> Self::Decoder<'id> {
213 match self {
214 Self::Chat(wire) => OpenAiDecoder::Chat(wire.decoder()),
215 Self::Responses(wire) => OpenAiDecoder::Responses(wire.decoder()),
216 }
217 }
218
219 fn reassembler(&self) -> Self::Reassembler {
220 match self {
221 Self::Chat(wire) => OpenAiReassembler::Chat(wire.reassembler()),
222 Self::Responses(wire) => OpenAiReassembler::Responses(wire.reassembler()),
223 }
224 }
225}
226
227pub enum OpenAiReassembler {
229 Chat(<Chat as Wire>::Reassembler),
231 Responses(<Responses as Wire>::Reassembler),
233}
234
235impl Default for OpenAiReassembler {
237 fn default() -> Self {
238 Self::Chat(Default::default())
239 }
240}
241
242impl crate::wire::document::Serves<crate::operation::Completion> for OpenAiReassembler {}
243
244impl Reassemble<OpenAiEvent> for OpenAiReassembler {
245 fn absorb(&mut self, event: &OpenAiEvent) {
246 match (self, event) {
247 (Self::Chat(document), OpenAiEvent::Chat(event)) => document.absorb(event),
248 (Self::Responses(document), OpenAiEvent::Responses(event)) => document.absorb(event),
249 _ => {}
251 }
252 }
253
254 fn finish(self) -> serde_json::Value {
255 on_route!(self, document => document.finish())
256 }
257}
258
259pub enum OpenAiEvent {
261 Chat(ChatEvent),
263 Responses(ResponsesEvent),
265}
266
267pub enum OpenAiDecoder {
269 Chat(ChatDecoder),
271 Responses(ResponsesDecoder),
273}
274
275impl<'id> Decoder<'id, Completion> for OpenAiDecoder {
276 type Event = OpenAiEvent;
277
278 fn classify(&self, frame: WireFrame) -> WireEvent<OpenAiEvent> {
279 match self {
280 Self::Chat(decoder) => decoder.classify(frame).map(OpenAiEvent::Chat),
281 Self::Responses(decoder) => decoder.classify(frame).map(OpenAiEvent::Responses),
282 }
283 }
284
285 fn decode(
286 &mut self,
287 event: OpenAiEvent,
288 out: Out<'id, Completion>,
289 ) -> Result<Flow, ProviderError> {
290 match (self, event) {
291 (Self::Chat(decoder), OpenAiEvent::Chat(event)) => decoder.decode(event, out),
292 (Self::Responses(decoder), OpenAiEvent::Responses(event)) => decoder.decode(event, out),
293 (Self::Chat(_), OpenAiEvent::Responses(_))
296 | (Self::Responses(_), OpenAiEvent::Chat(_)) => Ok(Flow::More),
297 }
298 }
299
300 fn eof(&mut self, out: Out<'id, Completion>) -> Result<Flow, ProviderError> {
301 on_route!(self, decoder => decoder.eof(out))
302 }
303}
304
305#[cfg(test)]
306mod tests;