use crate::api::llm::LlmRequest;
use crate::error::Result;
use crate::json::Json;
use super::request::AnnotatedLlmRequest;
use super::response::AnnotatedLlmResponse;
use super::{anthropic, openai_chat, openai_responses};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProviderSurface {
OpenAIChat,
OpenAIResponses,
AnthropicMessages,
}
type RequestSurfaceDetector = fn(&serde_json::Map<String, Json>, Option<&str>) -> bool;
type ResponseSurfaceDetector = fn(&serde_json::Map<String, Json>) -> bool;
pub(crate) struct ProviderSurfaceDescriptor {
pub(crate) surface: ProviderSurface,
pub(crate) detect_request: RequestSurfaceDetector,
pub(crate) detect_response: ResponseSurfaceDetector,
pub(crate) decode_request: fn(&LlmRequest) -> Result<AnnotatedLlmRequest>,
pub(crate) decode_response: fn(&Json) -> Result<AnnotatedLlmResponse>,
}
pub(crate) static BUILTIN_PROVIDER_SURFACES: &[ProviderSurfaceDescriptor] = &[
openai_responses::PROVIDER_SURFACE,
anthropic::PROVIDER_SURFACE,
openai_chat::PROVIDER_SURFACE,
];
#[must_use]
pub fn detect_request_surface(body: &Json) -> Option<ProviderSurface> {
detect_request_surface_with_hint(body, None)
}
#[must_use]
pub fn detect_request_surface_with_hint(
body: &Json,
provider_hint: Option<&str>,
) -> Option<ProviderSurface> {
request_descriptor(body, provider_hint).map(|descriptor| descriptor.surface)
}
#[must_use]
pub fn detect_response_surface(raw: &Json) -> Option<ProviderSurface> {
response_descriptor(raw).map(|descriptor| descriptor.surface)
}
fn request_descriptor(
body: &Json,
provider_hint: Option<&str>,
) -> Option<&'static ProviderSurfaceDescriptor> {
let obj = body.as_object()?;
BUILTIN_PROVIDER_SURFACES
.iter()
.find(|descriptor| (descriptor.detect_request)(obj, provider_hint))
}
fn response_descriptor(raw: &Json) -> Option<&'static ProviderSurfaceDescriptor> {
let obj = raw.as_object()?;
let mut matches = BUILTIN_PROVIDER_SURFACES
.iter()
.filter(|descriptor| (descriptor.detect_response)(obj));
match (matches.next(), matches.next()) {
(Some(descriptor), None) => Some(descriptor),
_ => None,
}
}
#[must_use]
pub fn normalize_request(request: &LlmRequest) -> Option<AnnotatedLlmRequest> {
normalize_request_with_hint(request, None)
}
#[must_use]
pub fn normalize_request_with_hint(
request: &LlmRequest,
provider_hint: Option<&str>,
) -> Option<AnnotatedLlmRequest> {
let descriptor = request_descriptor(&request.content, provider_hint)?;
(descriptor.decode_request)(request).ok()
}
#[must_use]
pub fn normalize_response(raw: &Json) -> Option<AnnotatedLlmResponse> {
let descriptor = response_descriptor(raw)?;
(descriptor.decode_response)(raw).ok()
}
#[cfg(test)]
#[path = "../../tests/unit/codec/resolve_tests.rs"]
mod tests;