mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
refactor ai formats registry adapters
This commit is contained in:
77
crates/aether-ai-formats/src/context.rs
Normal file
77
crates/aether-ai-formats/src/context.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use std::{error::Error, fmt};
|
||||
|
||||
use serde_json::{json, Value};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FormatContext {
|
||||
pub mapped_model: Option<String>,
|
||||
pub request_path: Option<String>,
|
||||
pub upstream_is_stream: bool,
|
||||
pub report_context: Option<Value>,
|
||||
}
|
||||
|
||||
impl FormatContext {
|
||||
pub fn with_mapped_model(mut self, mapped_model: impl Into<String>) -> Self {
|
||||
self.mapped_model = Some(mapped_model.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_request_path(mut self, request_path: impl Into<String>) -> Self {
|
||||
self.request_path = Some(request_path.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_upstream_stream(mut self, upstream_is_stream: bool) -> Self {
|
||||
self.upstream_is_stream = upstream_is_stream;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_report_context(mut self, report_context: Value) -> Self {
|
||||
self.report_context = Some(report_context);
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn mapped_model_or<'a>(&'a self, fallback: &'a str) -> &'a str {
|
||||
self.mapped_model
|
||||
.as_deref()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
.unwrap_or(fallback)
|
||||
}
|
||||
|
||||
pub(crate) fn report_context_value(&self) -> Value {
|
||||
self.report_context.clone().unwrap_or_else(|| {
|
||||
json!({
|
||||
"mapped_model": self.mapped_model,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum FormatError {
|
||||
UnsupportedFormat(String),
|
||||
RequestParseFailed { format: String },
|
||||
RequestEmitFailed { format: String },
|
||||
ResponseParseFailed { format: String },
|
||||
ResponseEmitFailed { format: String },
|
||||
}
|
||||
|
||||
impl fmt::Display for FormatError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::UnsupportedFormat(format) => write!(f, "unsupported AI format: {format}"),
|
||||
Self::RequestParseFailed { format } => {
|
||||
write!(f, "failed to parse {format} request")
|
||||
}
|
||||
Self::RequestEmitFailed { format } => write!(f, "failed to emit {format} request"),
|
||||
Self::ResponseParseFailed { format } => {
|
||||
write!(f, "failed to parse {format} response")
|
||||
}
|
||||
Self::ResponseEmitFailed { format } => {
|
||||
write!(f, "failed to emit {format} response")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for FormatError {}
|
||||
@@ -1,2 +1,9 @@
|
||||
//! Legacy pairwise conversion exports.
|
||||
//!
|
||||
//! Primary routing lives under `crate::formats::<wire_format>` and must pass
|
||||
//! through canonical IR. This module remains so older pipeline/gateway call
|
||||
//! sites and focused golden tests can keep their existing function names while
|
||||
//! the cleanup proceeds.
|
||||
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
//! Format identity and per-wire-format adapters.
|
||||
//!
|
||||
//! Each child module owns the boundary between one external wire shape and
|
||||
//! the canonical IR. Registry conversion is intentionally constrained to:
|
||||
//! source format -> canonical -> target format.
|
||||
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
pub mod claude_messages;
|
||||
pub mod gemini_generate_content;
|
||||
pub mod openai_chat;
|
||||
pub mod openai_responses;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum FormatFamily {
|
||||
OpenAi,
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
@@ -0,0 +1,18 @@
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
canonical::{canonical_to_claude_request, from_claude_to_canonical_request, CanonicalRequest},
|
||||
context::FormatContext,
|
||||
};
|
||||
|
||||
pub fn from(body: &Value, _ctx: &FormatContext) -> Option<CanonicalRequest> {
|
||||
from_claude_to_canonical_request(body)
|
||||
}
|
||||
|
||||
pub fn to(request: &CanonicalRequest, ctx: &FormatContext) -> Option<Value> {
|
||||
canonical_to_claude_request(
|
||||
request,
|
||||
ctx.mapped_model_or(request.model.as_str()),
|
||||
ctx.upstream_is_stream,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
canonical::{
|
||||
canonical_to_claude_response, from_claude_to_canonical_response, CanonicalResponse,
|
||||
},
|
||||
context::FormatContext,
|
||||
};
|
||||
|
||||
pub fn from(body: &Value, _ctx: &FormatContext) -> Option<CanonicalResponse> {
|
||||
from_claude_to_canonical_response(body)
|
||||
}
|
||||
|
||||
pub fn to(response: &CanonicalResponse, _ctx: &FormatContext) -> Option<Value> {
|
||||
Some(canonical_to_claude_response(response))
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
@@ -0,0 +1,18 @@
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
canonical::{canonical_to_gemini_request, from_gemini_to_canonical_request, CanonicalRequest},
|
||||
context::FormatContext,
|
||||
};
|
||||
|
||||
pub fn from(body: &Value, ctx: &FormatContext) -> Option<CanonicalRequest> {
|
||||
from_gemini_to_canonical_request(body, ctx.request_path.as_deref().unwrap_or_default())
|
||||
}
|
||||
|
||||
pub fn to(request: &CanonicalRequest, ctx: &FormatContext) -> Option<Value> {
|
||||
canonical_to_gemini_request(
|
||||
request,
|
||||
ctx.mapped_model_or(request.model.as_str()),
|
||||
ctx.upstream_is_stream,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
canonical::{
|
||||
canonical_to_gemini_response, from_gemini_to_canonical_response, CanonicalResponse,
|
||||
},
|
||||
context::FormatContext,
|
||||
};
|
||||
|
||||
pub fn from(body: &Value, _ctx: &FormatContext) -> Option<CanonicalResponse> {
|
||||
from_gemini_to_canonical_response(body)
|
||||
}
|
||||
|
||||
pub fn to(response: &CanonicalResponse, ctx: &FormatContext) -> Option<Value> {
|
||||
canonical_to_gemini_response(response, &ctx.report_context_value())
|
||||
}
|
||||
2
crates/aether-ai-formats/src/formats/openai_chat/mod.rs
Normal file
2
crates/aether-ai-formats/src/formats/openai_chat/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
41
crates/aether-ai-formats/src/formats/openai_chat/request.rs
Normal file
41
crates/aether-ai-formats/src/formats/openai_chat/request.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::{
|
||||
canonical::{
|
||||
canonical_to_openai_chat_request, from_openai_chat_to_canonical_request, CanonicalRequest,
|
||||
},
|
||||
context::FormatContext,
|
||||
};
|
||||
|
||||
pub fn from(body: &Value, _ctx: &FormatContext) -> Option<CanonicalRequest> {
|
||||
from_openai_chat_to_canonical_request(body)
|
||||
}
|
||||
|
||||
pub fn to(request: &CanonicalRequest, ctx: &FormatContext) -> Option<Value> {
|
||||
let mut body = canonical_to_openai_chat_request(request);
|
||||
force_stream_options(&mut body, ctx.upstream_is_stream);
|
||||
Some(body)
|
||||
}
|
||||
|
||||
fn force_stream_options(body: &mut Value, upstream_is_stream: bool) {
|
||||
if !upstream_is_stream {
|
||||
return;
|
||||
}
|
||||
let Some(object) = body.as_object_mut() else {
|
||||
return;
|
||||
};
|
||||
object.insert("stream".to_string(), Value::Bool(true));
|
||||
match object.get_mut("stream_options") {
|
||||
Some(Value::Object(stream_options)) => {
|
||||
stream_options.insert("include_usage".to_string(), Value::Bool(true));
|
||||
}
|
||||
_ => {
|
||||
object.insert(
|
||||
"stream_options".to_string(),
|
||||
json!({
|
||||
"include_usage": true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
crates/aether-ai-formats/src/formats/openai_chat/response.rs
Normal file
29
crates/aether-ai-formats/src/formats/openai_chat/response.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
canonical::{
|
||||
canonical_to_openai_chat_response, from_openai_chat_to_canonical_response,
|
||||
CanonicalResponse,
|
||||
},
|
||||
context::FormatContext,
|
||||
};
|
||||
|
||||
pub fn from(body: &Value, _ctx: &FormatContext) -> Option<CanonicalResponse> {
|
||||
from_openai_chat_to_canonical_response(body)
|
||||
}
|
||||
|
||||
pub fn to(response: &CanonicalResponse, ctx: &FormatContext) -> Option<Value> {
|
||||
let mut body = canonical_to_openai_chat_response(response);
|
||||
if body.get("service_tier").is_none() {
|
||||
if let Some(service_tier) = ctx
|
||||
.report_context_value()
|
||||
.get("original_request_body")
|
||||
.and_then(Value::as_object)
|
||||
.and_then(|request| request.get("service_tier"))
|
||||
.cloned()
|
||||
{
|
||||
body["service_tier"] = service_tier;
|
||||
}
|
||||
}
|
||||
Some(body)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
@@ -0,0 +1,28 @@
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
canonical::{
|
||||
canonical_to_openai_responses_compact_request, canonical_to_openai_responses_request,
|
||||
from_openai_responses_to_canonical_request, CanonicalRequest,
|
||||
},
|
||||
context::FormatContext,
|
||||
};
|
||||
|
||||
pub fn from(body: &Value, _ctx: &FormatContext) -> Option<CanonicalRequest> {
|
||||
from_openai_responses_to_canonical_request(body)
|
||||
}
|
||||
|
||||
pub fn to(request: &CanonicalRequest, ctx: &FormatContext) -> Option<Value> {
|
||||
canonical_to_openai_responses_request(
|
||||
request,
|
||||
ctx.mapped_model_or(request.model.as_str()),
|
||||
ctx.upstream_is_stream,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_compact(request: &CanonicalRequest, ctx: &FormatContext) -> Option<Value> {
|
||||
canonical_to_openai_responses_compact_request(
|
||||
request,
|
||||
ctx.mapped_model_or(request.model.as_str()),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
canonical::{
|
||||
canonical_to_openai_responses_compact_response, canonical_to_openai_responses_response,
|
||||
from_openai_responses_to_canonical_response, CanonicalResponse,
|
||||
},
|
||||
context::FormatContext,
|
||||
};
|
||||
|
||||
pub fn from(body: &Value, _ctx: &FormatContext) -> Option<CanonicalResponse> {
|
||||
from_openai_responses_to_canonical_response(body)
|
||||
}
|
||||
|
||||
pub fn to(response: &CanonicalResponse, ctx: &FormatContext) -> Option<Value> {
|
||||
Some(canonical_to_openai_responses_response(
|
||||
response,
|
||||
&ctx.report_context_value(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn to_compact(response: &CanonicalResponse, ctx: &FormatContext) -> Option<Value> {
|
||||
Some(canonical_to_openai_responses_compact_response(
|
||||
response,
|
||||
&ctx.report_context_value(),
|
||||
))
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod canonical;
|
||||
pub mod context;
|
||||
pub mod conversion;
|
||||
pub mod formats;
|
||||
pub mod planner;
|
||||
@@ -22,12 +23,11 @@ pub use canonical::{
|
||||
CanonicalRole, CanonicalStopReason, CanonicalStreamEvent, CanonicalStreamFrame,
|
||||
CanonicalThinkingConfig, CanonicalToolChoice, CanonicalToolDefinition, CanonicalUsage,
|
||||
};
|
||||
pub use context::{FormatContext, FormatError};
|
||||
pub use formats::{
|
||||
is_openai_responses_compact_format, is_openai_responses_family_format,
|
||||
is_openai_responses_format, legacy_openai_format_alias_matches,
|
||||
normalize_legacy_openai_format_alias, openai_format_storage_aliases, FormatFamily, FormatId,
|
||||
FormatProfile,
|
||||
};
|
||||
pub use registry::{
|
||||
build_stream_transcoder, convert_request, convert_response, FormatContext, FormatError,
|
||||
};
|
||||
pub use registry::{build_stream_transcoder, convert_request, convert_response};
|
||||
|
||||
@@ -1,93 +1,11 @@
|
||||
use std::{error::Error, fmt};
|
||||
|
||||
use serde_json::{json, Value};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
canonical::{
|
||||
canonical_to_claude_request, canonical_to_claude_response, canonical_to_gemini_request,
|
||||
canonical_to_gemini_response, canonical_to_openai_chat_request,
|
||||
canonical_to_openai_chat_response, canonical_to_openai_responses_compact_request,
|
||||
canonical_to_openai_responses_compact_response, canonical_to_openai_responses_request,
|
||||
canonical_to_openai_responses_response, from_claude_to_canonical_request,
|
||||
from_claude_to_canonical_response, from_gemini_to_canonical_request,
|
||||
from_gemini_to_canonical_response, from_openai_chat_to_canonical_request,
|
||||
from_openai_chat_to_canonical_response, from_openai_responses_to_canonical_request,
|
||||
from_openai_responses_to_canonical_response, CanonicalRequest, CanonicalResponse,
|
||||
},
|
||||
formats::FormatId,
|
||||
canonical::{CanonicalRequest, CanonicalResponse},
|
||||
formats::{claude_messages, gemini_generate_content, openai_chat, openai_responses, FormatId},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FormatContext {
|
||||
pub mapped_model: Option<String>,
|
||||
pub request_path: Option<String>,
|
||||
pub upstream_is_stream: bool,
|
||||
pub report_context: Option<Value>,
|
||||
}
|
||||
|
||||
impl FormatContext {
|
||||
pub fn with_mapped_model(mut self, mapped_model: impl Into<String>) -> Self {
|
||||
self.mapped_model = Some(mapped_model.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_request_path(mut self, request_path: impl Into<String>) -> Self {
|
||||
self.request_path = Some(request_path.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_upstream_stream(mut self, upstream_is_stream: bool) -> Self {
|
||||
self.upstream_is_stream = upstream_is_stream;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_report_context(mut self, report_context: Value) -> Self {
|
||||
self.report_context = Some(report_context);
|
||||
self
|
||||
}
|
||||
|
||||
fn mapped_model_or<'a>(&'a self, fallback: &'a str) -> &'a str {
|
||||
self.mapped_model
|
||||
.as_deref()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
.unwrap_or(fallback)
|
||||
}
|
||||
|
||||
fn report_context_value(&self) -> Value {
|
||||
self.report_context.clone().unwrap_or_else(|| {
|
||||
json!({
|
||||
"mapped_model": self.mapped_model,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum FormatError {
|
||||
UnsupportedFormat(String),
|
||||
RequestParseFailed { format: String },
|
||||
RequestEmitFailed { format: String },
|
||||
ResponseParseFailed { format: String },
|
||||
ResponseEmitFailed { format: String },
|
||||
}
|
||||
|
||||
impl fmt::Display for FormatError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::UnsupportedFormat(format) => write!(f, "unsupported AI format: {format}"),
|
||||
Self::RequestParseFailed { format } => {
|
||||
write!(f, "failed to parse {format} request")
|
||||
}
|
||||
Self::RequestEmitFailed { format } => write!(f, "failed to emit {format} request"),
|
||||
Self::ResponseParseFailed { format } => {
|
||||
write!(f, "failed to parse {format} response")
|
||||
}
|
||||
Self::ResponseEmitFailed { format } => write!(f, "failed to emit {format} response"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for FormatError {}
|
||||
pub use crate::context::{FormatContext, FormatError};
|
||||
|
||||
pub fn parse_request(
|
||||
source_format: &str,
|
||||
@@ -96,14 +14,12 @@ pub fn parse_request(
|
||||
) -> Result<CanonicalRequest, FormatError> {
|
||||
let source = parse_format(source_format)?;
|
||||
match source {
|
||||
FormatId::OpenAiChat => from_openai_chat_to_canonical_request(body),
|
||||
FormatId::OpenAiChat => openai_chat::request::from(body, ctx),
|
||||
FormatId::OpenAiResponses | FormatId::OpenAiResponsesCompact => {
|
||||
from_openai_responses_to_canonical_request(body)
|
||||
}
|
||||
FormatId::ClaudeMessages => from_claude_to_canonical_request(body),
|
||||
FormatId::GeminiGenerateContent => {
|
||||
from_gemini_to_canonical_request(body, ctx.request_path.as_deref().unwrap_or_default())
|
||||
openai_responses::request::from(body, ctx)
|
||||
}
|
||||
FormatId::ClaudeMessages => claude_messages::request::from(body, ctx),
|
||||
FormatId::GeminiGenerateContent => gemini_generate_content::request::from(body, ctx),
|
||||
}
|
||||
.ok_or_else(|| FormatError::RequestParseFailed {
|
||||
format: source.as_str().to_string(),
|
||||
@@ -124,25 +40,12 @@ pub fn emit_request(
|
||||
{
|
||||
request.model = mapped_model.to_string();
|
||||
}
|
||||
let mapped_model = ctx.mapped_model_or(request.model.as_str());
|
||||
match target {
|
||||
FormatId::OpenAiChat => {
|
||||
let mut body = canonical_to_openai_chat_request(&request);
|
||||
force_openai_chat_stream_options(&mut body, ctx.upstream_is_stream);
|
||||
Some(body)
|
||||
}
|
||||
FormatId::OpenAiResponses => {
|
||||
canonical_to_openai_responses_request(&request, mapped_model, ctx.upstream_is_stream)
|
||||
}
|
||||
FormatId::OpenAiResponsesCompact => {
|
||||
canonical_to_openai_responses_compact_request(&request, mapped_model)
|
||||
}
|
||||
FormatId::ClaudeMessages => {
|
||||
canonical_to_claude_request(&request, mapped_model, ctx.upstream_is_stream)
|
||||
}
|
||||
FormatId::GeminiGenerateContent => {
|
||||
canonical_to_gemini_request(&request, mapped_model, ctx.upstream_is_stream)
|
||||
}
|
||||
FormatId::OpenAiChat => openai_chat::request::to(&request, ctx),
|
||||
FormatId::OpenAiResponses => openai_responses::request::to(&request, ctx),
|
||||
FormatId::OpenAiResponsesCompact => openai_responses::request::to_compact(&request, ctx),
|
||||
FormatId::ClaudeMessages => claude_messages::request::to(&request, ctx),
|
||||
FormatId::GeminiGenerateContent => gemini_generate_content::request::to(&request, ctx),
|
||||
}
|
||||
.ok_or_else(|| FormatError::RequestEmitFailed {
|
||||
format: target.as_str().to_string(),
|
||||
@@ -162,16 +65,16 @@ pub fn convert_request(
|
||||
pub fn parse_response(
|
||||
source_format: &str,
|
||||
body: &Value,
|
||||
_ctx: &FormatContext,
|
||||
ctx: &FormatContext,
|
||||
) -> Result<CanonicalResponse, FormatError> {
|
||||
let source = parse_format(source_format)?;
|
||||
match source {
|
||||
FormatId::OpenAiChat => from_openai_chat_to_canonical_response(body),
|
||||
FormatId::OpenAiChat => openai_chat::response::from(body, ctx),
|
||||
FormatId::OpenAiResponses | FormatId::OpenAiResponsesCompact => {
|
||||
from_openai_responses_to_canonical_response(body)
|
||||
openai_responses::response::from(body, ctx)
|
||||
}
|
||||
FormatId::ClaudeMessages => from_claude_to_canonical_response(body),
|
||||
FormatId::GeminiGenerateContent => from_gemini_to_canonical_response(body),
|
||||
FormatId::ClaudeMessages => claude_messages::response::from(body, ctx),
|
||||
FormatId::GeminiGenerateContent => gemini_generate_content::response::from(body, ctx),
|
||||
}
|
||||
.ok_or_else(|| FormatError::ResponseParseFailed {
|
||||
format: source.as_str().to_string(),
|
||||
@@ -184,32 +87,12 @@ pub fn emit_response(
|
||||
ctx: &FormatContext,
|
||||
) -> Result<Value, FormatError> {
|
||||
let target = parse_format(target_format)?;
|
||||
let report_context = ctx.report_context_value();
|
||||
match target {
|
||||
FormatId::OpenAiChat => {
|
||||
let mut response = canonical_to_openai_chat_response(response);
|
||||
if response.get("service_tier").is_none() {
|
||||
if let Some(service_tier) = report_context
|
||||
.get("original_request_body")
|
||||
.and_then(Value::as_object)
|
||||
.and_then(|request| request.get("service_tier"))
|
||||
.cloned()
|
||||
{
|
||||
response["service_tier"] = service_tier;
|
||||
}
|
||||
}
|
||||
Some(response)
|
||||
}
|
||||
FormatId::OpenAiResponses => Some(canonical_to_openai_responses_response(
|
||||
response,
|
||||
&report_context,
|
||||
)),
|
||||
FormatId::OpenAiResponsesCompact => Some(canonical_to_openai_responses_compact_response(
|
||||
response,
|
||||
&report_context,
|
||||
)),
|
||||
FormatId::ClaudeMessages => Some(canonical_to_claude_response(response)),
|
||||
FormatId::GeminiGenerateContent => canonical_to_gemini_response(response, &report_context),
|
||||
FormatId::OpenAiChat => openai_chat::response::to(response, ctx),
|
||||
FormatId::OpenAiResponses => openai_responses::response::to(response, ctx),
|
||||
FormatId::OpenAiResponsesCompact => openai_responses::response::to_compact(response, ctx),
|
||||
FormatId::ClaudeMessages => claude_messages::response::to(response, ctx),
|
||||
FormatId::GeminiGenerateContent => gemini_generate_content::response::to(response, ctx),
|
||||
}
|
||||
.ok_or_else(|| FormatError::ResponseEmitFailed {
|
||||
format: target.as_str().to_string(),
|
||||
@@ -256,29 +139,6 @@ fn parse_format(format: &str) -> Result<FormatId, FormatError> {
|
||||
FormatId::parse(format).ok_or_else(|| FormatError::UnsupportedFormat(format.to_string()))
|
||||
}
|
||||
|
||||
fn force_openai_chat_stream_options(body: &mut Value, upstream_is_stream: bool) {
|
||||
if !upstream_is_stream {
|
||||
return;
|
||||
}
|
||||
let Some(object) = body.as_object_mut() else {
|
||||
return;
|
||||
};
|
||||
object.insert("stream".to_string(), Value::Bool(true));
|
||||
match object.get_mut("stream_options") {
|
||||
Some(Value::Object(stream_options)) => {
|
||||
stream_options.insert("include_usage".to_string(), Value::Bool(true));
|
||||
}
|
||||
_ => {
|
||||
object.insert(
|
||||
"stream_options".to_string(),
|
||||
json!({
|
||||
"include_usage": true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
@@ -309,4 +169,26 @@ mod tests {
|
||||
assert_eq!(converted["input"][0]["type"], "message");
|
||||
assert_eq!(converted["input"][0]["content"][0]["type"], "input_text");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registry_does_not_call_wire_specific_canonical_functions_directly() {
|
||||
let implementation = include_str!("registry.rs")
|
||||
.split("#[cfg(test)]")
|
||||
.next()
|
||||
.expect("registry implementation should be readable");
|
||||
for forbidden in [
|
||||
"canonical_to_openai",
|
||||
"canonical_to_claude",
|
||||
"canonical_to_gemini",
|
||||
"from_openai_chat_to_canonical",
|
||||
"from_openai_responses_to_canonical",
|
||||
"from_claude_to_canonical",
|
||||
"from_gemini_to_canonical",
|
||||
] {
|
||||
assert!(
|
||||
!implementation.contains(forbidden),
|
||||
"registry should dispatch through formats::<format> adapters, found {forbidden}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user