mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
refactor(ai-formats): group formats by provider
Move protocol/request/response format modules under provider-oriented formats modules and update registry, transport, and architecture paths.
This commit is contained in:
@@ -701,13 +701,13 @@ fn gateway_ai_serving_api_module_delegates_pure_ownership_to_format_crate() {
|
||||
"pub use crate::provider_compat::kiro_stream::{",
|
||||
"pub use crate::provider_compat::private_envelope::{",
|
||||
"pub use crate::provider_compat::surfaces::{",
|
||||
"pub use crate::request::common::{",
|
||||
"pub use crate::request::route::{",
|
||||
"pub use crate::response::common::{",
|
||||
"pub use crate::response::error_body::{",
|
||||
"pub use crate::formats::shared::request::{",
|
||||
"pub use crate::formats::shared::routing::{",
|
||||
"pub use crate::formats::shared::response::{",
|
||||
"pub use crate::formats::shared::error_body::{",
|
||||
"pub use aether_ai_formats::{",
|
||||
"pub use aether_ai_formats::protocol::conversion::request::{",
|
||||
"pub use aether_ai_formats::protocol::conversion::response::{",
|
||||
"pub use aether_ai_formats::formats::conversion::request::{",
|
||||
"pub use aether_ai_formats::formats::conversion::response::{",
|
||||
] {
|
||||
assert!(
|
||||
format_crate_api.contains(pattern),
|
||||
|
||||
@@ -2153,7 +2153,7 @@ fn ai_serving_image_routes_split_surface_normalization_and_transport_policy() {
|
||||
}
|
||||
|
||||
let surface_image =
|
||||
read_workspace_file("crates/aether-ai-formats/src/request/specialized/image.rs");
|
||||
read_workspace_file("crates/aether-ai-formats/src/formats/openai/image/request.rs");
|
||||
for pattern in [
|
||||
"pub enum OpenAiImageOperation",
|
||||
"pub fn is_openai_image_stream_request(",
|
||||
@@ -3220,7 +3220,7 @@ fn ai_serving_leaf_planner_owners_route_contract_specs_through_gateway_seams() {
|
||||
);
|
||||
assert!(
|
||||
!specialized_files_decision
|
||||
.contains("aether_ai_formats::request::specialized::files::LocalGeminiFilesSpec"),
|
||||
.contains("aether_ai_formats::formats::gemini::files::spec::LocalGeminiFilesSpec"),
|
||||
"planner/specialized/files/decision.rs should consume LocalGeminiFilesSpec through the local specialized seam"
|
||||
);
|
||||
assert!(
|
||||
@@ -3243,7 +3243,7 @@ fn ai_serving_m5_moves_contracts_and_route_logic_into_format_crate() {
|
||||
"crates/aether-ai-formats/src/contracts/actions.rs",
|
||||
"crates/aether-ai-formats/src/contracts/plan_kinds.rs",
|
||||
"crates/aether-ai-formats/src/contracts/report_kinds.rs",
|
||||
"crates/aether-ai-formats/src/request/route.rs",
|
||||
"crates/aether-ai-formats/src/formats/shared/routing.rs",
|
||||
] {
|
||||
assert!(
|
||||
workspace_file_exists(path),
|
||||
@@ -3304,7 +3304,8 @@ fn ai_serving_m5_moves_contracts_and_route_logic_into_format_crate() {
|
||||
);
|
||||
}
|
||||
|
||||
let surface_route = read_workspace_file("crates/aether-ai-formats/src/request/route.rs");
|
||||
let surface_route =
|
||||
read_workspace_file("crates/aether-ai-formats/src/formats/shared/routing.rs");
|
||||
for pattern in [
|
||||
"pub fn is_matching_stream_http_request(",
|
||||
"is_openai_image_stream_request(parts, body_json, body_base64)",
|
||||
@@ -3553,11 +3554,11 @@ fn ai_serving_error_body_is_owned_by_format_finalize_module() {
|
||||
"ai_serving/conversion/error.rs should stay removed"
|
||||
);
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/response/error_body.rs"),
|
||||
workspace_file_exists("crates/aether-ai-formats/src/formats/shared/error_body.rs"),
|
||||
"format error response-body helpers should live under finalize/error_body.rs"
|
||||
);
|
||||
assert!(
|
||||
!workspace_file_exists("crates/aether-ai-formats/src/protocol/conversion/error.rs"),
|
||||
!workspace_file_exists("crates/aether-ai-formats/src/formats/conversion/error.rs"),
|
||||
"aether-ai-formats should not keep error response-body helpers under conversion"
|
||||
);
|
||||
|
||||
@@ -3582,7 +3583,7 @@ fn ai_serving_error_body_is_owned_by_format_finalize_module() {
|
||||
#[test]
|
||||
fn ai_serving_conversion_request_is_owned_by_format_crate() {
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/protocol/conversion/request/mod.rs"),
|
||||
workspace_file_exists("crates/aether-ai-formats/src/formats/conversion/request.rs"),
|
||||
"request conversion should live in aether-ai-formats"
|
||||
);
|
||||
assert!(
|
||||
@@ -3609,7 +3610,7 @@ fn ai_serving_conversion_request_is_owned_by_format_crate() {
|
||||
|
||||
let surface_api = read_workspace_file("crates/aether-ai-formats/src/api.rs");
|
||||
assert!(
|
||||
surface_api.contains("pub use aether_ai_formats::protocol::conversion::request::{"),
|
||||
surface_api.contains("pub use aether_ai_formats::formats::conversion::request::{"),
|
||||
"format API facade should re-export request conversion directly from aether-ai-formats"
|
||||
);
|
||||
}
|
||||
@@ -3617,7 +3618,7 @@ fn ai_serving_conversion_request_is_owned_by_format_crate() {
|
||||
#[test]
|
||||
fn ai_serving_conversion_response_is_owned_by_format_crate() {
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/protocol/conversion/response/mod.rs"),
|
||||
workspace_file_exists("crates/aether-ai-formats/src/formats/conversion/response.rs"),
|
||||
"response conversion should live in aether-ai-formats"
|
||||
);
|
||||
assert!(
|
||||
@@ -3644,7 +3645,7 @@ fn ai_serving_conversion_response_is_owned_by_format_crate() {
|
||||
|
||||
let surface_api = read_workspace_file("crates/aether-ai-formats/src/api.rs");
|
||||
assert!(
|
||||
surface_api.contains("pub use aether_ai_formats::protocol::conversion::response::{"),
|
||||
surface_api.contains("pub use aether_ai_formats::formats::conversion::response::{"),
|
||||
"format API facade should re-export response conversion directly from aether-ai-formats"
|
||||
);
|
||||
}
|
||||
@@ -3652,7 +3653,7 @@ fn ai_serving_conversion_response_is_owned_by_format_crate() {
|
||||
#[test]
|
||||
fn ai_format_crate_owns_conversion_and_surface_facade() {
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/protocol/conversion"),
|
||||
workspace_file_exists("crates/aether-ai-formats/src/formats/conversion"),
|
||||
"aether-ai-formats should own the conversion directory"
|
||||
);
|
||||
|
||||
@@ -3665,9 +3666,9 @@ fn ai_format_crate_owns_conversion_and_surface_facade() {
|
||||
let surface_api = read_workspace_file("crates/aether-ai-formats/src/api.rs");
|
||||
for pattern in [
|
||||
"pub use aether_ai_formats::{",
|
||||
"pub use aether_ai_formats::protocol::conversion::request::{",
|
||||
"pub use aether_ai_formats::protocol::conversion::response::{",
|
||||
"pub use crate::response::error_body::{",
|
||||
"pub use aether_ai_formats::formats::conversion::request::{",
|
||||
"pub use aether_ai_formats::formats::conversion::response::{",
|
||||
"pub use crate::formats::shared::error_body::{",
|
||||
] {
|
||||
assert!(
|
||||
surface_api.contains(pattern),
|
||||
@@ -3746,12 +3747,12 @@ fn ai_serving_finalize_standard_sync_response_converters_are_owned_by_format_cra
|
||||
#[test]
|
||||
fn ai_serving_finalize_stream_engine_is_owned_by_format_crate() {
|
||||
for path in [
|
||||
"crates/aether-ai-formats/src/response/sse.rs",
|
||||
"crates/aether-ai-formats/src/response/standard/stream_core/common.rs",
|
||||
"crates/aether-ai-formats/src/response/standard/stream_core/format_matrix.rs",
|
||||
"crates/aether-ai-formats/src/response/standard/openai/stream.rs",
|
||||
"crates/aether-ai-formats/src/response/standard/claude/stream.rs",
|
||||
"crates/aether-ai-formats/src/response/standard/gemini/stream.rs",
|
||||
"crates/aether-ai-formats/src/formats/shared/sse.rs",
|
||||
"crates/aether-ai-formats/src/formats/shared/stream_core/common.rs",
|
||||
"crates/aether-ai-formats/src/formats/shared/stream_core/format_matrix.rs",
|
||||
"crates/aether-ai-formats/src/formats/openai/chat/stream.rs",
|
||||
"crates/aether-ai-formats/src/formats/claude/messages/stream.rs",
|
||||
"crates/aether-ai-formats/src/formats/gemini/generate_content/stream.rs",
|
||||
] {
|
||||
assert!(
|
||||
workspace_file_exists(path),
|
||||
@@ -3787,7 +3788,7 @@ fn ai_serving_finalize_stream_engine_is_owned_by_format_crate() {
|
||||
}
|
||||
|
||||
let surface_format_matrix = read_workspace_file(
|
||||
"crates/aether-ai-formats/src/response/standard/stream_core/format_matrix.rs",
|
||||
"crates/aether-ai-formats/src/formats/shared/stream_core/format_matrix.rs",
|
||||
);
|
||||
for pattern in [
|
||||
"pub struct StreamingStandardFormatMatrix",
|
||||
@@ -3811,16 +3812,16 @@ fn ai_serving_finalize_stream_engine_is_owned_by_format_crate() {
|
||||
#[test]
|
||||
fn ai_serving_finalize_standard_sync_products_are_owned_by_format_crate() {
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/response/sync_products.rs"),
|
||||
workspace_file_exists("crates/aether-ai-formats/src/formats/shared/sync_products.rs"),
|
||||
"finalize sync_products should live in aether-ai-formats"
|
||||
);
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/response/sync_to_stream.rs"),
|
||||
workspace_file_exists("crates/aether-ai-formats/src/formats/shared/sync_to_stream.rs"),
|
||||
"finalize sync-to-stream bridge should live in aether-ai-formats"
|
||||
);
|
||||
|
||||
let surface_sync_products =
|
||||
read_workspace_file("crates/aether-ai-formats/src/response/sync_products.rs");
|
||||
read_workspace_file("crates/aether-ai-formats/src/formats/shared/sync_products.rs");
|
||||
for expected in [
|
||||
"pub fn maybe_build_standard_cross_format_sync_product_from_normalized_payload(",
|
||||
"pub fn maybe_build_standard_same_format_sync_body_from_normalized_payload(",
|
||||
@@ -3947,7 +3948,7 @@ fn ai_serving_finalize_standard_sync_products_are_owned_by_format_crate() {
|
||||
}
|
||||
|
||||
let surface_openai_image_stream =
|
||||
read_workspace_file("crates/aether-ai-formats/src/response/openai_image_stream.rs");
|
||||
read_workspace_file("crates/aether-ai-formats/src/formats/openai/image/stream.rs");
|
||||
for expected in [
|
||||
"pub fn maybe_build_openai_image_sync_finalize_product(",
|
||||
"pub struct OpenAiImageSyncFinalizeProduct",
|
||||
@@ -3962,7 +3963,7 @@ fn ai_serving_finalize_standard_sync_products_are_owned_by_format_crate() {
|
||||
}
|
||||
|
||||
let surface_sync_to_stream =
|
||||
read_workspace_file("crates/aether-ai-formats/src/response/sync_to_stream.rs");
|
||||
read_workspace_file("crates/aether-ai-formats/src/formats/shared/sync_to_stream.rs");
|
||||
for expected in [
|
||||
"pub fn maybe_bridge_standard_sync_json_to_stream(",
|
||||
"pub struct SyncToStreamBridgeOutcome",
|
||||
@@ -3999,11 +4000,11 @@ fn ai_serving_finalize_standard_sync_products_are_owned_by_format_crate() {
|
||||
#[test]
|
||||
fn ai_serving_finalize_stream_rewrite_matrix_is_owned_by_format_crate() {
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/response/stream_rewrite.rs"),
|
||||
workspace_file_exists("crates/aether-ai-formats/src/formats/shared/stream_rewrite.rs"),
|
||||
"finalize stream rewrite matrix should live in aether-ai-formats"
|
||||
);
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/response/openai_image_stream.rs"),
|
||||
workspace_file_exists("crates/aether-ai-formats/src/formats/openai/image/stream.rs"),
|
||||
"OpenAI image stream rewrite state should live in aether-ai-formats"
|
||||
);
|
||||
|
||||
@@ -4052,7 +4053,7 @@ fn ai_serving_finalize_stream_rewrite_matrix_is_owned_by_format_crate() {
|
||||
#[test]
|
||||
fn ai_serving_planner_common_parser_is_owned_by_format_crate() {
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/request/common.rs"),
|
||||
workspace_file_exists("crates/aether-ai-formats/src/formats/shared/request.rs"),
|
||||
"planner/common pure parser should exist in aether-ai-formats"
|
||||
);
|
||||
|
||||
@@ -4195,7 +4196,7 @@ fn ai_serving_root_owns_shared_gemini_request_path_parser() {
|
||||
#[test]
|
||||
fn ai_serving_planner_standard_normalize_is_owned_by_format_crate() {
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/request/standard/normalize.rs"),
|
||||
workspace_file_exists("crates/aether-ai-formats/src/formats/shared/standard_normalize.rs"),
|
||||
"planner/standard/normalize should live in aether-ai-formats"
|
||||
);
|
||||
|
||||
@@ -4258,7 +4259,7 @@ fn ai_serving_planner_standard_normalize_is_owned_by_format_crate() {
|
||||
#[test]
|
||||
fn ai_serving_openai_helpers_are_owned_by_format_crate() {
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/request/openai.rs"),
|
||||
workspace_file_exists("crates/aether-ai-formats/src/formats/openai/shared.rs"),
|
||||
"planner/openai helper owner should exist in aether-ai-formats"
|
||||
);
|
||||
|
||||
@@ -4291,17 +4292,17 @@ fn ai_serving_openai_helpers_are_owned_by_format_crate() {
|
||||
#[test]
|
||||
fn ai_serving_standard_matrix_delegates_format_conversion_to_format_crate() {
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/request/matrix.rs"),
|
||||
workspace_file_exists("crates/aether-ai-formats/src/formats/shared/request_matrix.rs"),
|
||||
"planner/matrix facade should live in aether-ai-formats"
|
||||
);
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/request/standard/matrix.rs"),
|
||||
workspace_file_exists("crates/aether-ai-formats/src/formats/shared/standard_matrix.rs"),
|
||||
"format standard request-body planner should live in aether-ai-formats"
|
||||
);
|
||||
for path in [
|
||||
"crates/aether-ai-formats/src/protocol/canonical.rs",
|
||||
"crates/aether-ai-formats/src/protocol/matrix.rs",
|
||||
"crates/aether-ai-formats/src/protocol/registry.rs",
|
||||
"crates/aether-ai-formats/src/formats/matrix.rs",
|
||||
"crates/aether-ai-formats/src/formats/registry.rs",
|
||||
] {
|
||||
assert!(
|
||||
workspace_file_exists(path),
|
||||
@@ -4309,12 +4310,12 @@ fn ai_serving_standard_matrix_delegates_format_conversion_to_format_crate() {
|
||||
);
|
||||
}
|
||||
let surface_matrix =
|
||||
read_workspace_file("crates/aether-ai-formats/src/request/standard/matrix.rs");
|
||||
read_workspace_file("crates/aether-ai-formats/src/formats/shared/standard_matrix.rs");
|
||||
assert!(
|
||||
surface_matrix.contains("use aether_ai_formats::protocol::registry::{")
|
||||
surface_matrix.contains("use aether_ai_formats::formats::registry::{")
|
||||
&& surface_matrix.contains("convert_request")
|
||||
&& surface_matrix.contains("FormatContext")
|
||||
&& surface_matrix.contains("aether_ai_formats::protocol::conversion::request::{"),
|
||||
&& surface_matrix.contains("aether_ai_formats::formats::conversion::request::{"),
|
||||
"format standard matrix should delegate format conversion to aether-ai-formats"
|
||||
);
|
||||
for forbidden in [
|
||||
@@ -4361,23 +4362,27 @@ fn ai_serving_standard_matrix_delegates_format_conversion_to_format_crate() {
|
||||
#[test]
|
||||
fn ai_serving_standard_family_specs_are_owned_by_format_crate() {
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/request/standard/family.rs"),
|
||||
workspace_file_exists("crates/aether-ai-formats/src/formats/shared/family.rs"),
|
||||
"planner/standard/family pure spec owner should live in aether-ai-formats"
|
||||
);
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/request/standard/claude/chat.rs"),
|
||||
workspace_file_exists("crates/aether-ai-formats/src/formats/claude/messages/chat_spec.rs"),
|
||||
"planner/standard/claude/chat pure spec resolver should live in aether-ai-formats"
|
||||
);
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/request/standard/claude/cli.rs"),
|
||||
workspace_file_exists("crates/aether-ai-formats/src/formats/claude/messages/cli_spec.rs"),
|
||||
"planner/standard/claude/cli pure spec resolver should live in aether-ai-formats"
|
||||
);
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/request/standard/gemini/chat.rs"),
|
||||
workspace_file_exists(
|
||||
"crates/aether-ai-formats/src/formats/gemini/generate_content/chat_spec.rs"
|
||||
),
|
||||
"planner/standard/gemini/chat pure spec resolver should live in aether-ai-formats"
|
||||
);
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/request/standard/gemini/cli.rs"),
|
||||
workspace_file_exists(
|
||||
"crates/aether-ai-formats/src/formats/gemini/generate_content/cli_spec.rs"
|
||||
),
|
||||
"planner/standard/gemini/cli pure spec resolver should live in aether-ai-formats"
|
||||
);
|
||||
|
||||
@@ -4444,7 +4449,7 @@ fn ai_serving_standard_family_specs_are_owned_by_format_crate() {
|
||||
#[test]
|
||||
fn ai_serving_same_format_provider_specs_are_owned_by_format_crate() {
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/request/passthrough/provider.rs"),
|
||||
workspace_file_exists("crates/aether-ai-formats/src/formats/shared/passthrough.rs"),
|
||||
"planner/passthrough/provider pure spec owner should live in aether-ai-formats"
|
||||
);
|
||||
|
||||
@@ -4495,7 +4500,7 @@ fn ai_serving_same_format_provider_specs_are_owned_by_format_crate() {
|
||||
#[test]
|
||||
fn ai_serving_passthrough_provider_specs_are_owned_by_format_crate() {
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/request/passthrough/provider.rs"),
|
||||
workspace_file_exists("crates/aether-ai-formats/src/formats/shared/passthrough.rs"),
|
||||
"planner/passthrough/provider pure spec owner should live in aether-ai-formats"
|
||||
);
|
||||
|
||||
@@ -4540,7 +4545,7 @@ fn ai_serving_passthrough_provider_specs_are_owned_by_format_crate() {
|
||||
#[test]
|
||||
fn ai_serving_specialized_files_specs_are_owned_by_format_crate() {
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/request/specialized/files.rs"),
|
||||
workspace_file_exists("crates/aether-ai-formats/src/formats/gemini/files/spec.rs"),
|
||||
"planner/specialized/files pure spec owner should live in aether-ai-formats"
|
||||
);
|
||||
|
||||
@@ -4570,9 +4575,18 @@ fn ai_serving_specialized_files_specs_are_owned_by_format_crate() {
|
||||
#[test]
|
||||
fn ai_serving_specialized_video_specs_are_owned_by_format_crate() {
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/request/specialized/video.rs"),
|
||||
"planner/specialized/video pure spec owner should live in aether-ai-formats"
|
||||
workspace_file_exists("crates/aether-ai-formats/src/formats/shared/video.rs"),
|
||||
"planner/specialized/video shared spec seam should live in aether-ai-formats"
|
||||
);
|
||||
for path in [
|
||||
"crates/aether-ai-formats/src/formats/openai/video/spec.rs",
|
||||
"crates/aether-ai-formats/src/formats/gemini/video/spec.rs",
|
||||
] {
|
||||
assert!(
|
||||
workspace_file_exists(path),
|
||||
"{path} should own provider-specific video create spec resolution"
|
||||
);
|
||||
}
|
||||
|
||||
let video =
|
||||
read_workspace_file("apps/aether-gateway/src/ai_serving/planner/specialized/video.rs");
|
||||
@@ -4598,7 +4612,7 @@ fn ai_serving_specialized_video_specs_are_owned_by_format_crate() {
|
||||
#[test]
|
||||
fn ai_serving_openai_responses_specs_are_owned_by_format_crate() {
|
||||
assert!(
|
||||
workspace_file_exists("crates/aether-ai-formats/src/request/standard/openai_responses.rs"),
|
||||
workspace_file_exists("crates/aether-ai-formats/src/formats/openai/responses/spec.rs"),
|
||||
"planner/standard/openai_responses pure spec owner should live in aether-ai-formats"
|
||||
);
|
||||
|
||||
@@ -4639,8 +4653,8 @@ fn ai_serving_openai_responses_specs_are_owned_by_format_crate() {
|
||||
fn ai_serving_legacy_api_format_names_stay_out_of_primary_paths() {
|
||||
for path in [
|
||||
"crates/aether-ai-formats/src/contracts/plan_kinds.rs",
|
||||
"crates/aether-ai-formats/src/request/route.rs",
|
||||
"crates/aether-ai-formats/src/request/standard/openai_responses.rs",
|
||||
"crates/aether-ai-formats/src/formats/shared/routing.rs",
|
||||
"crates/aether-ai-formats/src/formats/openai/responses/spec.rs",
|
||||
"apps/aether-gateway/src/ai_serving/planner/decision/control_plan.rs",
|
||||
"apps/aether-gateway/src/execution_runtime/fallback.rs",
|
||||
] {
|
||||
@@ -4664,7 +4678,7 @@ fn ai_serving_legacy_api_format_names_stay_out_of_primary_paths() {
|
||||
}
|
||||
}
|
||||
|
||||
let registry = read_workspace_file("crates/aether-ai-formats/src/protocol/registry.rs");
|
||||
let registry = read_workspace_file("crates/aether-ai-formats/src/formats/registry.rs");
|
||||
let implementation = registry
|
||||
.split("#[cfg(test)]")
|
||||
.next()
|
||||
@@ -4697,9 +4711,9 @@ fn retired_api_format_occurrences_are_whitelisted() {
|
||||
|
||||
let allowed_paths = [
|
||||
"apps/aether-gateway/src/handlers/admin/provider/write/normalize.rs",
|
||||
"crates/aether-ai-formats/src/protocol/formats.rs",
|
||||
"crates/aether-ai-formats/src/protocol/matrix.rs",
|
||||
"crates/aether-ai-formats/src/protocol/registry.rs",
|
||||
"crates/aether-ai-formats/src/formats/id.rs",
|
||||
"crates/aether-ai-formats/src/formats/matrix.rs",
|
||||
"crates/aether-ai-formats/src/formats/registry.rs",
|
||||
"crates/aether-data/src/migrate.rs",
|
||||
"crates/aether-data/src/lifecycle/migrate/tests.rs",
|
||||
"crates/aether-usage-runtime/src/report.rs",
|
||||
|
||||
@@ -38,6 +38,146 @@ pub use crate::contracts::{
|
||||
OPENAI_VIDEO_CREATE_SYNC_PLAN_KIND, OPENAI_VIDEO_DELETE_SYNC_PLAN_KIND,
|
||||
OPENAI_VIDEO_REMIX_SYNC_PLAN_KIND,
|
||||
};
|
||||
pub use crate::formats::claude::messages::stream::{ClaudeClientEmitter, ClaudeProviderState};
|
||||
pub use crate::formats::gemini::generate_content::stream::{
|
||||
GeminiClientEmitter, GeminiProviderState,
|
||||
};
|
||||
pub use crate::formats::openai::chat::stream::{
|
||||
OpenAIChatClientEmitter, OpenAIChatProviderState, OpenAIResponsesClientEmitter,
|
||||
OpenAIResponsesProviderState,
|
||||
};
|
||||
pub use crate::formats::openai::image::stream::{
|
||||
maybe_build_openai_image_sync_finalize_product, OpenAiImageStreamState,
|
||||
OpenAiImageSyncFinalizeProduct,
|
||||
};
|
||||
pub use crate::formats::openai::shared::{
|
||||
copy_request_number_field, copy_request_number_field_as,
|
||||
map_openai_reasoning_effort_to_claude_output, map_openai_reasoning_effort_to_gemini_budget,
|
||||
parse_openai_stop_sequences, resolve_openai_chat_max_tokens, value_as_u64,
|
||||
};
|
||||
pub use crate::formats::shared::error_body::{
|
||||
build_core_error_body_for_client_format, is_core_error_finalize_kind, LocalCoreSyncErrorKind,
|
||||
};
|
||||
pub use crate::formats::shared::model_directives::{
|
||||
apply_model_directive_mapping_patch, apply_model_directive_overrides_from_model,
|
||||
apply_model_directive_overrides_from_request, claude_model_uses_adaptive_effort,
|
||||
extract_gemini_model_from_path, gemini_model_uses_thinking_level, model_directive_base_model,
|
||||
normalize_model_directive_model, parse_model_directive, ModelDirective, ModelOverride,
|
||||
ReasoningEffort,
|
||||
};
|
||||
pub use crate::formats::shared::passthrough::{
|
||||
resolve_stream_spec as resolve_local_same_format_stream_spec,
|
||||
resolve_sync_spec as resolve_local_same_format_sync_spec, LocalSameFormatProviderFamily,
|
||||
LocalSameFormatProviderSpec,
|
||||
};
|
||||
pub use crate::formats::shared::request::{
|
||||
force_upstream_streaming_for_provider, parse_direct_request_body,
|
||||
};
|
||||
pub use crate::formats::shared::request_matrix::{
|
||||
build_standard_request_body_from_canonical,
|
||||
build_standard_request_body_from_canonical_with_model_directives,
|
||||
};
|
||||
pub use crate::formats::shared::response::{
|
||||
build_generated_tool_call_id, build_local_success_background_report,
|
||||
build_local_success_conversion_background_report, canonicalize_tool_arguments,
|
||||
prepare_local_success_response_parts, prepare_local_success_response_parts_owned,
|
||||
LocalSyncReportParts,
|
||||
};
|
||||
pub use crate::formats::shared::routing::{
|
||||
is_matching_stream_http_request, is_matching_stream_request,
|
||||
resolve_execution_runtime_stream_plan_kind, resolve_execution_runtime_sync_plan_kind,
|
||||
supports_stream_execution_decision_kind, supports_sync_execution_decision_kind,
|
||||
};
|
||||
pub use crate::formats::shared::sse::{encode_done_sse, encode_json_sse, map_claude_stop_reason};
|
||||
pub use crate::formats::shared::standard_matrix::normalize_standard_request_to_openai_chat_request;
|
||||
pub use crate::formats::shared::stream_core::common::*;
|
||||
pub use crate::formats::shared::stream_core::{
|
||||
CanonicalStreamFrame, StreamingStandardFormatMatrix, StreamingStandardTerminalObserver,
|
||||
};
|
||||
pub use crate::formats::shared::sync_products::{
|
||||
aggregate_claude_stream_sync_response, aggregate_gemini_stream_sync_response,
|
||||
aggregate_openai_chat_stream_sync_response, aggregate_openai_responses_stream_sync_response,
|
||||
aggregate_standard_chat_stream_sync_response, aggregate_standard_cli_stream_sync_response,
|
||||
convert_standard_chat_response, convert_standard_cli_response,
|
||||
maybe_build_openai_chat_cross_format_sync_product_from_normalized_payload,
|
||||
maybe_build_openai_responses_cross_format_sync_product_from_normalized_payload,
|
||||
maybe_build_openai_responses_same_family_sync_body_from_normalized_payload,
|
||||
maybe_build_standard_cross_format_sync_product,
|
||||
maybe_build_standard_cross_format_sync_product_from_normalized_payload,
|
||||
maybe_build_standard_same_format_sync_body_from_normalized_payload,
|
||||
maybe_build_standard_sync_finalize_product_from_normalized_payload,
|
||||
StandardCrossFormatSyncProduct, StandardSyncFinalizeNormalizedProduct,
|
||||
};
|
||||
pub use crate::formats::shared::sync_to_stream::{
|
||||
maybe_bridge_standard_sync_json_to_stream, SyncToStreamBridgeOutcome,
|
||||
};
|
||||
pub use crate::formats::shared::{
|
||||
maybe_build_ai_surface_stream_rewriter, resolve_finalize_stream_rewrite_mode,
|
||||
AiSurfaceFinalizeError, AiSurfaceStreamRewriter, FinalizeStreamRewriteMode,
|
||||
};
|
||||
pub use crate::formats::{
|
||||
claude::messages::{
|
||||
resolve_stream_spec as resolve_claude_stream_spec,
|
||||
resolve_sync_spec as resolve_claude_sync_spec,
|
||||
},
|
||||
gemini::generate_content::{
|
||||
resolve_stream_spec as resolve_gemini_stream_spec,
|
||||
resolve_sync_spec as resolve_gemini_sync_spec,
|
||||
},
|
||||
openai::responses::{
|
||||
codex::{
|
||||
apply_codex_openai_responses_special_body_edits,
|
||||
apply_codex_openai_responses_special_headers,
|
||||
apply_openai_responses_compact_special_body_edits, CODEX_OPENAI_IMAGE_DEFAULT_MODEL,
|
||||
CODEX_OPENAI_IMAGE_DEFAULT_OUTPUT_FORMAT, CODEX_OPENAI_IMAGE_DEFAULT_VARIATION_MODEL,
|
||||
CODEX_OPENAI_IMAGE_DEFAULT_VARIATION_PROMPT, CODEX_OPENAI_IMAGE_INTERNAL_MODEL,
|
||||
},
|
||||
spec::{
|
||||
resolve_stream_spec as resolve_openai_responses_stream_spec,
|
||||
resolve_sync_spec as resolve_openai_responses_sync_spec, LocalOpenAiResponsesSpec,
|
||||
},
|
||||
},
|
||||
shared::{
|
||||
family::{LocalStandardSourceFamily, LocalStandardSourceMode, LocalStandardSpec},
|
||||
standard_matrix::{
|
||||
build_standard_request_body, build_standard_request_body_with_model_directives,
|
||||
build_standard_request_body_with_model_directives_and_request_headers,
|
||||
},
|
||||
standard_normalize::{
|
||||
build_cross_format_openai_chat_request_body,
|
||||
build_cross_format_openai_chat_request_body_with_model_directives,
|
||||
build_cross_format_openai_responses_request_body,
|
||||
build_cross_format_openai_responses_request_body_with_model_directives,
|
||||
build_local_openai_chat_request_body,
|
||||
build_local_openai_chat_request_body_with_model_directives,
|
||||
build_local_openai_responses_request_body,
|
||||
build_local_openai_responses_request_body_with_model_directives,
|
||||
},
|
||||
},
|
||||
};
|
||||
pub use crate::formats::{
|
||||
gemini::files::spec::{
|
||||
resolve_stream_spec as resolve_gemini_files_stream_spec,
|
||||
resolve_sync_spec as resolve_gemini_files_sync_spec, LocalGeminiFilesSpec,
|
||||
},
|
||||
openai::image::{
|
||||
request::{
|
||||
build_chatgpt_web_image_request_body, build_openai_image_provider_request_body,
|
||||
default_model_for_openai_image_operation, is_openai_image_stream_request,
|
||||
normalize_openai_image_request, openai_image_operation_from_path,
|
||||
resolve_requested_openai_image_model_for_request, ChatGptWebImageRequestError,
|
||||
NormalizedOpenAiImageRequest, OpenAiImageOperation, OpenAiImageResponseFormat,
|
||||
},
|
||||
spec::{
|
||||
resolve_stream_spec as resolve_local_image_stream_spec,
|
||||
resolve_sync_spec as resolve_local_image_sync_spec, LocalOpenAiImageSpec,
|
||||
},
|
||||
},
|
||||
shared::video::{
|
||||
resolve_sync_spec as resolve_local_video_sync_spec, LocalVideoCreateFamily,
|
||||
LocalVideoCreateSpec,
|
||||
},
|
||||
};
|
||||
pub use crate::provider_compat::kiro_stream::{
|
||||
build_kiro_final_message_sse_events, build_kiro_initial_sse_events,
|
||||
build_kiro_stream_error_sse_events, calculate_kiro_context_input_tokens,
|
||||
@@ -59,138 +199,14 @@ pub use crate::provider_compat::surfaces::{
|
||||
ProviderAdaptationSurface, ANTIGRAVITY_V1INTERNAL_ENVELOPE_NAME,
|
||||
GEMINI_CLI_V1INTERNAL_ENVELOPE_NAME, KIRO_ENVELOPE_NAME,
|
||||
};
|
||||
pub use crate::request::common::{
|
||||
force_upstream_streaming_for_provider, parse_direct_request_body,
|
||||
};
|
||||
pub use crate::request::matrix::{
|
||||
build_standard_request_body_from_canonical,
|
||||
build_standard_request_body_from_canonical_with_model_directives,
|
||||
};
|
||||
pub use crate::request::model_directives::{
|
||||
apply_model_directive_mapping_patch, apply_model_directive_overrides_from_model,
|
||||
apply_model_directive_overrides_from_request, claude_model_uses_adaptive_effort,
|
||||
extract_gemini_model_from_path, gemini_model_uses_thinking_level, model_directive_base_model,
|
||||
normalize_model_directive_model, parse_model_directive, ModelDirective, ModelOverride,
|
||||
ReasoningEffort,
|
||||
};
|
||||
pub use crate::request::openai::{
|
||||
copy_request_number_field, copy_request_number_field_as,
|
||||
map_openai_reasoning_effort_to_claude_output, map_openai_reasoning_effort_to_gemini_budget,
|
||||
parse_openai_stop_sequences, resolve_openai_chat_max_tokens, value_as_u64,
|
||||
};
|
||||
pub use crate::request::passthrough::provider::{
|
||||
resolve_stream_spec as resolve_local_same_format_stream_spec,
|
||||
resolve_sync_spec as resolve_local_same_format_sync_spec, LocalSameFormatProviderFamily,
|
||||
LocalSameFormatProviderSpec,
|
||||
};
|
||||
pub use crate::request::route::{
|
||||
is_matching_stream_http_request, is_matching_stream_request,
|
||||
resolve_execution_runtime_stream_plan_kind, resolve_execution_runtime_sync_plan_kind,
|
||||
supports_stream_execution_decision_kind, supports_sync_execution_decision_kind,
|
||||
};
|
||||
pub use crate::request::specialized::{
|
||||
files::{
|
||||
resolve_stream_spec as resolve_gemini_files_stream_spec,
|
||||
resolve_sync_spec as resolve_gemini_files_sync_spec, LocalGeminiFilesSpec,
|
||||
},
|
||||
image::{
|
||||
build_chatgpt_web_image_request_body, build_openai_image_provider_request_body,
|
||||
default_model_for_openai_image_operation, is_openai_image_stream_request,
|
||||
normalize_openai_image_request, openai_image_operation_from_path,
|
||||
resolve_requested_openai_image_model_for_request,
|
||||
resolve_stream_spec as resolve_local_image_stream_spec,
|
||||
resolve_sync_spec as resolve_local_image_sync_spec, ChatGptWebImageRequestError,
|
||||
LocalOpenAiImageSpec, NormalizedOpenAiImageRequest, OpenAiImageOperation,
|
||||
OpenAiImageResponseFormat,
|
||||
},
|
||||
video::{
|
||||
resolve_sync_spec as resolve_local_video_sync_spec, LocalVideoCreateFamily,
|
||||
LocalVideoCreateSpec,
|
||||
},
|
||||
};
|
||||
pub use crate::request::standard::{
|
||||
apply_codex_openai_responses_special_body_edits, apply_codex_openai_responses_special_headers,
|
||||
apply_openai_responses_compact_special_body_edits, build_cross_format_openai_chat_request_body,
|
||||
build_cross_format_openai_chat_request_body_with_model_directives,
|
||||
build_cross_format_openai_responses_request_body,
|
||||
build_cross_format_openai_responses_request_body_with_model_directives,
|
||||
build_local_openai_chat_request_body,
|
||||
build_local_openai_chat_request_body_with_model_directives,
|
||||
build_local_openai_responses_request_body,
|
||||
build_local_openai_responses_request_body_with_model_directives, build_standard_request_body,
|
||||
build_standard_request_body_with_model_directives,
|
||||
build_standard_request_body_with_model_directives_and_request_headers,
|
||||
claude::{
|
||||
resolve_stream_spec as resolve_claude_stream_spec,
|
||||
resolve_sync_spec as resolve_claude_sync_spec,
|
||||
},
|
||||
gemini::{
|
||||
resolve_stream_spec as resolve_gemini_stream_spec,
|
||||
resolve_sync_spec as resolve_gemini_sync_spec,
|
||||
},
|
||||
normalize_standard_request_to_openai_chat_request,
|
||||
openai_responses::{
|
||||
resolve_stream_spec as resolve_openai_responses_stream_spec,
|
||||
resolve_sync_spec as resolve_openai_responses_sync_spec, LocalOpenAiResponsesSpec,
|
||||
},
|
||||
LocalStandardSourceFamily, LocalStandardSourceMode, LocalStandardSpec,
|
||||
CODEX_OPENAI_IMAGE_DEFAULT_MODEL, CODEX_OPENAI_IMAGE_DEFAULT_OUTPUT_FORMAT,
|
||||
CODEX_OPENAI_IMAGE_DEFAULT_VARIATION_MODEL, CODEX_OPENAI_IMAGE_DEFAULT_VARIATION_PROMPT,
|
||||
CODEX_OPENAI_IMAGE_INTERNAL_MODEL,
|
||||
};
|
||||
pub use crate::response::common::{
|
||||
build_generated_tool_call_id, build_local_success_background_report,
|
||||
build_local_success_conversion_background_report, canonicalize_tool_arguments,
|
||||
prepare_local_success_response_parts, prepare_local_success_response_parts_owned,
|
||||
LocalSyncReportParts,
|
||||
};
|
||||
pub use crate::response::error_body::{
|
||||
build_core_error_body_for_client_format, is_core_error_finalize_kind, LocalCoreSyncErrorKind,
|
||||
};
|
||||
pub use crate::response::openai_image_stream::{
|
||||
maybe_build_openai_image_sync_finalize_product, OpenAiImageStreamState,
|
||||
OpenAiImageSyncFinalizeProduct,
|
||||
};
|
||||
pub use crate::response::sse::{encode_done_sse, encode_json_sse, map_claude_stop_reason};
|
||||
pub use crate::response::standard::claude::stream::{ClaudeClientEmitter, ClaudeProviderState};
|
||||
pub use crate::response::standard::gemini::stream::{GeminiClientEmitter, GeminiProviderState};
|
||||
pub use crate::response::standard::openai::stream::{
|
||||
OpenAIChatClientEmitter, OpenAIChatProviderState, OpenAIResponsesClientEmitter,
|
||||
OpenAIResponsesProviderState,
|
||||
};
|
||||
pub use crate::response::standard::stream_core::common::*;
|
||||
pub use crate::response::standard::stream_core::{
|
||||
CanonicalStreamFrame, StreamingStandardFormatMatrix, StreamingStandardTerminalObserver,
|
||||
};
|
||||
pub use crate::response::sync_products::{
|
||||
aggregate_claude_stream_sync_response, aggregate_gemini_stream_sync_response,
|
||||
aggregate_openai_chat_stream_sync_response, aggregate_openai_responses_stream_sync_response,
|
||||
aggregate_standard_chat_stream_sync_response, aggregate_standard_cli_stream_sync_response,
|
||||
convert_standard_chat_response, convert_standard_cli_response,
|
||||
maybe_build_openai_chat_cross_format_sync_product_from_normalized_payload,
|
||||
maybe_build_openai_responses_cross_format_sync_product_from_normalized_payload,
|
||||
maybe_build_openai_responses_same_family_sync_body_from_normalized_payload,
|
||||
maybe_build_standard_cross_format_sync_product,
|
||||
maybe_build_standard_cross_format_sync_product_from_normalized_payload,
|
||||
maybe_build_standard_same_format_sync_body_from_normalized_payload,
|
||||
maybe_build_standard_sync_finalize_product_from_normalized_payload,
|
||||
StandardCrossFormatSyncProduct, StandardSyncFinalizeNormalizedProduct,
|
||||
};
|
||||
pub use crate::response::sync_to_stream::{
|
||||
maybe_bridge_standard_sync_json_to_stream, SyncToStreamBridgeOutcome,
|
||||
};
|
||||
pub use crate::response::{
|
||||
maybe_build_ai_surface_stream_rewriter, resolve_finalize_stream_rewrite_mode,
|
||||
AiSurfaceFinalizeError, AiSurfaceStreamRewriter, FinalizeStreamRewriteMode,
|
||||
};
|
||||
pub use aether_ai_formats::protocol::conversion::request::{
|
||||
pub use aether_ai_formats::formats::conversion::request::{
|
||||
convert_openai_chat_request_to_claude_request, convert_openai_chat_request_to_gemini_request,
|
||||
convert_openai_chat_request_to_openai_responses_request, extract_openai_text_content,
|
||||
normalize_claude_request_to_openai_chat_request,
|
||||
normalize_gemini_request_to_openai_chat_request,
|
||||
normalize_openai_responses_request_to_openai_chat_request, parse_openai_tool_result_content,
|
||||
};
|
||||
pub use aether_ai_formats::protocol::conversion::response::{
|
||||
pub use aether_ai_formats::formats::conversion::response::{
|
||||
build_openai_responses_response, build_openai_responses_response_with_content,
|
||||
build_openai_responses_response_with_reasoning, convert_claude_chat_response_to_openai_chat,
|
||||
convert_claude_response_to_openai_responses, convert_gemini_chat_response_to_openai_chat,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::contracts::{CLAUDE_CHAT_STREAM_PLAN_KIND, CLAUDE_CHAT_SYNC_PLAN_KIND};
|
||||
use crate::request::standard::family::{
|
||||
use crate::formats::shared::family::{
|
||||
LocalStandardSourceFamily, LocalStandardSourceMode, LocalStandardSpec,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::contracts::{CLAUDE_CLI_STREAM_PLAN_KIND, CLAUDE_CLI_SYNC_PLAN_KIND};
|
||||
use crate::request::standard::family::{
|
||||
use crate::formats::shared::family::{
|
||||
LocalStandardSourceFamily, LocalStandardSourceMode, LocalStandardSpec,
|
||||
};
|
||||
|
||||
16
crates/aether-ai-formats/src/formats/claude/messages/mod.rs
Normal file
16
crates/aether-ai-formats/src/formats/claude/messages/mod.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
pub mod chat_spec;
|
||||
pub mod cli_spec;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
pub mod spec;
|
||||
pub mod stream;
|
||||
|
||||
use crate::formats::shared::family::LocalStandardSpec;
|
||||
|
||||
pub fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
|
||||
chat_spec::resolve_sync_spec(plan_kind).or_else(|| cli_spec::resolve_sync_spec(plan_kind))
|
||||
}
|
||||
|
||||
pub fn resolve_stream_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
|
||||
chat_spec::resolve_stream_spec(plan_kind).or_else(|| cli_spec::resolve_stream_spec(plan_kind))
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
use crate::{
|
||||
formats::{
|
||||
context::FormatContext,
|
||||
openai::shared::{
|
||||
map_openai_reasoning_effort_to_claude_output,
|
||||
map_openai_reasoning_effort_to_thinking_budget,
|
||||
},
|
||||
shared::model_directives::claude_model_uses_adaptive_effort,
|
||||
},
|
||||
protocol::canonical::{
|
||||
canonical_extension_object_mut, canonical_instructions_to_claude_system,
|
||||
canonical_messages_to_claude, canonical_openai_reasoning_effort,
|
||||
@@ -11,14 +19,6 @@ use crate::{
|
||||
compact_canonical_claude_messages, insert_f64, namespace_extension_object,
|
||||
CanonicalRequest,
|
||||
},
|
||||
protocol::context::FormatContext,
|
||||
request::{
|
||||
model_directives::claude_model_uses_adaptive_effort,
|
||||
openai::{
|
||||
map_openai_reasoning_effort_to_claude_output,
|
||||
map_openai_reasoning_effort_to_thinking_budget,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pub fn from(body: &Value, _ctx: &FormatContext) -> Option<CanonicalRequest> {
|
||||
@@ -3,13 +3,13 @@ use std::collections::BTreeMap;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::{
|
||||
formats::context::FormatContext,
|
||||
protocol::canonical::{
|
||||
canonical_blocks_to_claude, canonical_stop_reason_to_claude, canonical_usage_to_claude,
|
||||
claude_content_to_canonical_blocks, claude_extensions, claude_stop_reason_to_canonical,
|
||||
claude_usage_to_canonical, namespace_extension_object, CanonicalResponse,
|
||||
CanonicalResponseOutput, CanonicalRole,
|
||||
},
|
||||
protocol::context::FormatContext,
|
||||
};
|
||||
|
||||
pub fn from(body: &Value, _ctx: &FormatContext) -> Option<CanonicalResponse> {
|
||||
@@ -0,0 +1 @@
|
||||
pub use super::{chat_spec, cli_spec, resolve_stream_spec, resolve_sync_spec};
|
||||
@@ -2,13 +2,13 @@ use std::collections::BTreeMap;
|
||||
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
use crate::response::common::{
|
||||
use crate::formats::shared::response::{
|
||||
build_generated_tool_call_id, canonicalize_tool_arguments,
|
||||
remove_empty_pages_from_tool_arguments,
|
||||
};
|
||||
use crate::response::sse::{encode_json_sse, map_claude_stop_reason};
|
||||
use crate::response::standard::stream_core::common::*;
|
||||
use crate::response::AiSurfaceFinalizeError;
|
||||
use crate::formats::shared::sse::{encode_json_sse, map_claude_stop_reason};
|
||||
use crate::formats::shared::stream_core::common::*;
|
||||
use crate::formats::shared::AiSurfaceFinalizeError;
|
||||
|
||||
#[derive(Default)]
|
||||
struct ClaudeProviderToolState {
|
||||
1
crates/aether-ai-formats/src/formats/claude/mod.rs
Normal file
1
crates/aether-ai-formats/src/formats/claude/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod messages;
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::protocol::{context::FormatContext, registry};
|
||||
use crate::formats::{context::FormatContext, registry};
|
||||
|
||||
pub fn convert_openai_chat_request_to_claude_request(
|
||||
body_json: &Value,
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::protocol::{context::FormatContext, registry};
|
||||
use crate::formats::{context::FormatContext, registry};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct OpenAiResponsesResponseUsage {
|
||||
@@ -0,0 +1 @@
|
||||
pub mod request;
|
||||
@@ -0,0 +1,40 @@
|
||||
use serde_json::Value;
|
||||
use serde_json::{json, Map};
|
||||
|
||||
use crate::formats::context::FormatContext;
|
||||
use crate::formats::openai::embedding::request::mapped_embedding_model;
|
||||
use crate::protocol::canonical::{namespace_extension_object, CanonicalRequest};
|
||||
|
||||
pub fn to(request: &CanonicalRequest, ctx: &FormatContext) -> Option<Value> {
|
||||
let embedding = request.embedding.as_ref()?;
|
||||
let items = embedding.input.as_string_items()?;
|
||||
if items.is_empty() || items.iter().any(|value| value.trim().is_empty()) {
|
||||
return None;
|
||||
}
|
||||
let mut output = Map::new();
|
||||
output.insert(
|
||||
"model".to_string(),
|
||||
Value::String(mapped_embedding_model(
|
||||
request,
|
||||
ctx.mapped_model_or(request.model.as_str()),
|
||||
)),
|
||||
);
|
||||
output.insert(
|
||||
"input".to_string(),
|
||||
Value::Array(
|
||||
items
|
||||
.into_iter()
|
||||
.map(|text| json!({"type": "text", "text": text}))
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
if let Some(dimensions) = embedding.dimensions {
|
||||
output.insert("dimensions".to_string(), Value::from(dimensions));
|
||||
}
|
||||
output.extend(namespace_extension_object(
|
||||
&embedding.extensions,
|
||||
"doubao",
|
||||
&output,
|
||||
));
|
||||
Some(Value::Object(output))
|
||||
}
|
||||
1
crates/aether-ai-formats/src/formats/doubao/mod.rs
Normal file
1
crates/aether-ai-formats/src/formats/doubao/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod embedding;
|
||||
@@ -0,0 +1 @@
|
||||
pub mod request;
|
||||
@@ -0,0 +1,34 @@
|
||||
use serde_json::json;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::formats::context::FormatContext;
|
||||
use crate::formats::openai::embedding::request::mapped_embedding_model;
|
||||
use crate::protocol::canonical::CanonicalRequest;
|
||||
|
||||
pub fn to(request: &CanonicalRequest, ctx: &FormatContext) -> Option<Value> {
|
||||
let embedding = request.embedding.as_ref()?;
|
||||
let items = embedding.input.as_string_items()?;
|
||||
if items.is_empty() || items.iter().any(|value| value.trim().is_empty()) {
|
||||
return None;
|
||||
}
|
||||
let model = mapped_embedding_model(request, ctx.mapped_model_or(request.model.as_str()));
|
||||
if items.len() == 1 {
|
||||
return Some(json!({
|
||||
"model": model,
|
||||
"content": {
|
||||
"parts": [{"text": items[0]}]
|
||||
}
|
||||
}));
|
||||
}
|
||||
Some(json!({
|
||||
"model": model,
|
||||
"requests": items.into_iter().map(|text| {
|
||||
json!({
|
||||
"model": model,
|
||||
"content": {
|
||||
"parts": [{"text": text}]
|
||||
}
|
||||
})
|
||||
}).collect::<Vec<_>>()
|
||||
}))
|
||||
}
|
||||
1
crates/aether-ai-formats/src/formats/gemini/files/mod.rs
Normal file
1
crates/aether-ai-formats/src/formats/gemini/files/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod spec;
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::contracts::{GEMINI_CHAT_STREAM_PLAN_KIND, GEMINI_CHAT_SYNC_PLAN_KIND};
|
||||
use crate::request::standard::family::{
|
||||
use crate::formats::shared::family::{
|
||||
LocalStandardSourceFamily, LocalStandardSourceMode, LocalStandardSpec,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::contracts::{GEMINI_CLI_STREAM_PLAN_KIND, GEMINI_CLI_SYNC_PLAN_KIND};
|
||||
use crate::request::standard::family::{
|
||||
use crate::formats::shared::family::{
|
||||
LocalStandardSourceFamily, LocalStandardSourceMode, LocalStandardSpec,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
pub mod chat_spec;
|
||||
pub mod cli_spec;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
pub mod spec;
|
||||
pub mod stream;
|
||||
|
||||
use crate::formats::shared::family::LocalStandardSpec;
|
||||
|
||||
pub fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
|
||||
chat_spec::resolve_sync_spec(plan_kind).or_else(|| cli_spec::resolve_sync_spec(plan_kind))
|
||||
}
|
||||
|
||||
pub fn resolve_stream_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
|
||||
chat_spec::resolve_stream_spec(plan_kind).or_else(|| cli_spec::resolve_stream_spec(plan_kind))
|
||||
}
|
||||
@@ -3,6 +3,14 @@ use std::collections::BTreeMap;
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
use crate::{
|
||||
formats::{
|
||||
context::FormatContext,
|
||||
openai::shared::{
|
||||
map_openai_reasoning_effort_to_gemini_budget,
|
||||
map_thinking_budget_to_openai_reasoning_effort,
|
||||
},
|
||||
shared::model_directives::{gemini_model_uses_thinking_level, ReasoningEffort},
|
||||
},
|
||||
protocol::canonical::{
|
||||
apply_gemini_request_extensions, canonical_extension_object_mut,
|
||||
canonical_openai_reasoning_effort, extract_gemini_model_from_path,
|
||||
@@ -14,14 +22,6 @@ use crate::{
|
||||
CanonicalResponseFormat, CanonicalRole, CanonicalToolChoice, CanonicalToolDefinition,
|
||||
OPENAI_RESPONSES_LEGACY_EXTENSION_NAMESPACE,
|
||||
},
|
||||
protocol::context::FormatContext,
|
||||
request::{
|
||||
model_directives::{gemini_model_uses_thinking_level, ReasoningEffort},
|
||||
openai::{
|
||||
map_openai_reasoning_effort_to_gemini_budget,
|
||||
map_thinking_budget_to_openai_reasoning_effort,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pub fn from(body: &Value, ctx: &FormatContext) -> Option<CanonicalRequest> {
|
||||
@@ -1,13 +1,13 @@
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
use crate::{
|
||||
formats::context::FormatContext,
|
||||
protocol::canonical::{
|
||||
canonical_extension_object_mut, gemini_extensions, gemini_part_to_canonical_block,
|
||||
gemini_stop_reason_to_canonical, gemini_usage_to_canonical, CanonicalContentBlock,
|
||||
CanonicalResponse, CanonicalResponseOutput, CanonicalRole, CanonicalStopReason,
|
||||
CanonicalUsage,
|
||||
},
|
||||
protocol::context::FormatContext,
|
||||
};
|
||||
|
||||
pub fn from(body: &Value, _ctx: &FormatContext) -> Option<CanonicalResponse> {
|
||||
@@ -0,0 +1 @@
|
||||
pub use super::{chat_spec, cli_spec, resolve_stream_spec, resolve_sync_spec};
|
||||
@@ -2,10 +2,10 @@ use std::collections::BTreeMap;
|
||||
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
use crate::response::common::{build_generated_tool_call_id, canonicalize_tool_arguments};
|
||||
use crate::response::sse::encode_json_sse;
|
||||
use crate::response::standard::stream_core::common::*;
|
||||
use crate::response::AiSurfaceFinalizeError;
|
||||
use crate::formats::shared::response::{build_generated_tool_call_id, canonicalize_tool_arguments};
|
||||
use crate::formats::shared::sse::encode_json_sse;
|
||||
use crate::formats::shared::stream_core::common::*;
|
||||
use crate::formats::shared::AiSurfaceFinalizeError;
|
||||
|
||||
#[derive(Default)]
|
||||
struct GeminiProviderToolState {
|
||||
4
crates/aether-ai-formats/src/formats/gemini/mod.rs
Normal file
4
crates/aether-ai-formats/src/formats/gemini/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod embedding;
|
||||
pub mod files;
|
||||
pub mod generate_content;
|
||||
pub mod video;
|
||||
1
crates/aether-ai-formats/src/formats/gemini/video/mod.rs
Normal file
1
crates/aether-ai-formats/src/formats/gemini/video/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod spec;
|
||||
27
crates/aether-ai-formats/src/formats/gemini/video/spec.rs
Normal file
27
crates/aether-ai-formats/src/formats/gemini/video/spec.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use crate::contracts::GEMINI_VIDEO_CREATE_SYNC_PLAN_KIND;
|
||||
use crate::formats::shared::video::{LocalVideoCreateFamily, LocalVideoCreateSpec};
|
||||
|
||||
pub fn resolve_sync_spec(plan_kind: &str) -> Option<LocalVideoCreateSpec> {
|
||||
match plan_kind {
|
||||
GEMINI_VIDEO_CREATE_SYNC_PLAN_KIND => Some(LocalVideoCreateSpec {
|
||||
api_format: "gemini:video",
|
||||
decision_kind: GEMINI_VIDEO_CREATE_SYNC_PLAN_KIND,
|
||||
report_kind: "gemini_video_create_sync_finalize",
|
||||
family: LocalVideoCreateFamily::Gemini,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{resolve_sync_spec, LocalVideoCreateFamily};
|
||||
|
||||
#[test]
|
||||
fn resolves_gemini_video_create_spec() {
|
||||
let spec = resolve_sync_spec("gemini_video_create_sync").expect("spec");
|
||||
assert_eq!(spec.api_format, "gemini:video");
|
||||
assert_eq!(spec.family, LocalVideoCreateFamily::Gemini);
|
||||
assert_eq!(spec.report_kind, "gemini_video_create_sync_finalize");
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,7 @@
|
||||
//! 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.
|
||||
//! Format identity and aliases.
|
||||
|
||||
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,17 @@
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::formats::context::FormatContext;
|
||||
use crate::protocol::canonical::CanonicalRequest;
|
||||
|
||||
pub fn from(body: &Value, _ctx: &FormatContext) -> Option<CanonicalRequest> {
|
||||
crate::formats::openai::embedding::request::from_namespace(body, "jina")
|
||||
}
|
||||
|
||||
pub fn to(request: &CanonicalRequest, ctx: &FormatContext) -> Option<Value> {
|
||||
crate::formats::openai::embedding::request::to_openai_like(
|
||||
request,
|
||||
ctx.mapped_model_or(request.model.as_str()),
|
||||
"jina",
|
||||
true,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::protocol::canonical::CanonicalEmbeddingResponse;
|
||||
|
||||
pub fn from(body: &Value) -> Option<CanonicalEmbeddingResponse> {
|
||||
crate::formats::openai::embedding::response::from_namespace(body, "jina")
|
||||
}
|
||||
|
||||
pub fn to(response: &CanonicalEmbeddingResponse) -> Option<Value> {
|
||||
Some(crate::formats::openai::embedding::response::to_openai_like(
|
||||
response, "jina",
|
||||
))
|
||||
}
|
||||
2
crates/aether-ai-formats/src/formats/jina/mod.rs
Normal file
2
crates/aether-ai-formats/src/formats/jina/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod embedding;
|
||||
pub mod rerank;
|
||||
1
crates/aether-ai-formats/src/formats/jina/rerank/mod.rs
Normal file
1
crates/aether-ai-formats/src/formats/jina/rerank/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod request;
|
||||
16
crates/aether-ai-formats/src/formats/jina/rerank/request.rs
Normal file
16
crates/aether-ai-formats/src/formats/jina/rerank/request.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::formats::context::FormatContext;
|
||||
use crate::protocol::canonical::CanonicalRequest;
|
||||
|
||||
pub fn from(body: &Value, _ctx: &FormatContext) -> Option<CanonicalRequest> {
|
||||
crate::formats::openai::rerank::request::from_namespace(body, "jina")
|
||||
}
|
||||
|
||||
pub fn to(request: &CanonicalRequest, ctx: &FormatContext) -> Option<Value> {
|
||||
crate::formats::openai::rerank::request::to_openai_like(
|
||||
request,
|
||||
ctx.mapped_model_or(request.model.as_str()),
|
||||
"jina",
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
api_format_alias_matches,
|
||||
protocol::formats::{is_openai_responses_compact_format, normalize_api_format_alias},
|
||||
formats::id::{is_openai_responses_compact_format, normalize_api_format_alias},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
18
crates/aether-ai-formats/src/formats/mod.rs
Normal file
18
crates/aether-ai-formats/src/formats/mod.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
pub mod claude;
|
||||
pub mod context;
|
||||
pub mod conversion;
|
||||
pub mod doubao;
|
||||
pub mod gemini;
|
||||
pub mod id;
|
||||
pub mod jina;
|
||||
pub mod matrix;
|
||||
pub mod openai;
|
||||
pub mod registry;
|
||||
pub mod shared;
|
||||
|
||||
pub use context::{FormatContext, FormatError};
|
||||
pub use id::{
|
||||
api_format_alias_matches, api_format_storage_aliases, is_openai_responses_compact_format,
|
||||
is_openai_responses_family_format, is_openai_responses_format, normalize_api_format_alias,
|
||||
FormatFamily, FormatId, FormatProfile,
|
||||
};
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
pub mod stream;
|
||||
@@ -1,6 +1,7 @@
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::{
|
||||
formats::context::FormatContext,
|
||||
protocol::canonical::{
|
||||
canonical_extension_object_mut, canonical_message_to_openai_chat,
|
||||
canonical_response_format_to_openai, canonical_tool_choice_to_openai,
|
||||
@@ -11,7 +12,6 @@ use crate::{
|
||||
CanonicalInstruction, CanonicalRequest, CanonicalRole, CanonicalThinkingConfig,
|
||||
OPENAI_RESPONSES_EXTENSION_NAMESPACE, OPENAI_RESPONSES_LEGACY_EXTENSION_NAMESPACE,
|
||||
},
|
||||
protocol::context::FormatContext,
|
||||
};
|
||||
|
||||
pub fn from(body: &Value, _ctx: &FormatContext) -> Option<CanonicalRequest> {
|
||||
@@ -3,6 +3,7 @@ use std::collections::BTreeMap;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::{
|
||||
formats::context::FormatContext,
|
||||
protocol::canonical::{
|
||||
canonical_blocks_to_openai_chat_message, canonical_stop_reason_to_openai,
|
||||
canonical_usage_to_openai, openai_extensions, openai_finish_reason_to_canonical,
|
||||
@@ -10,7 +11,6 @@ use crate::{
|
||||
CanonicalResponse, CanonicalResponseOutput, CanonicalRole,
|
||||
OPENAI_RESPONSES_EXTENSION_NAMESPACE, OPENAI_RESPONSES_LEGACY_EXTENSION_NAMESPACE,
|
||||
},
|
||||
protocol::context::FormatContext,
|
||||
};
|
||||
|
||||
pub fn from(body: &Value, _ctx: &FormatContext) -> Option<CanonicalResponse> {
|
||||
@@ -2,10 +2,10 @@ use std::collections::BTreeMap;
|
||||
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
use crate::response::common::build_generated_tool_call_id;
|
||||
use crate::response::sse::{encode_done_sse, encode_json_sse};
|
||||
use crate::response::standard::stream_core::common::*;
|
||||
use crate::response::AiSurfaceFinalizeError;
|
||||
use crate::formats::shared::response::build_generated_tool_call_id;
|
||||
use crate::formats::shared::sse::{encode_done_sse, encode_json_sse};
|
||||
use crate::formats::shared::stream_core::common::*;
|
||||
use crate::formats::shared::AiSurfaceFinalizeError;
|
||||
|
||||
#[derive(Default)]
|
||||
struct OpenAIChatProviderToolState {
|
||||
150
crates/aether-ai-formats/src/formats/openai/embedding/request.rs
Normal file
150
crates/aether-ai-formats/src/formats/openai/embedding/request.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
use serde_json::Map;
|
||||
use serde_json::Value;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::formats::context::FormatContext;
|
||||
use crate::protocol::canonical::{
|
||||
namespace_extension_object, CanonicalEmbeddingInput, CanonicalEmbeddingRequest,
|
||||
CanonicalRequest,
|
||||
};
|
||||
|
||||
pub fn from(body: &Value, _ctx: &FormatContext) -> Option<CanonicalRequest> {
|
||||
from_namespace(body, "openai")
|
||||
}
|
||||
|
||||
pub fn to(request: &CanonicalRequest, ctx: &FormatContext) -> Option<Value> {
|
||||
to_openai_like(
|
||||
request,
|
||||
ctx.mapped_model_or(request.model.as_str()),
|
||||
"openai",
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn from_namespace(body_json: &Value, namespace: &str) -> Option<CanonicalRequest> {
|
||||
let request = body_json.as_object()?;
|
||||
let model = request
|
||||
.get("model")
|
||||
.and_then(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())?
|
||||
.to_string();
|
||||
let input =
|
||||
serde_json::from_value::<CanonicalEmbeddingInput>(request.get("input")?.clone()).ok()?;
|
||||
if input.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let embedding = CanonicalEmbeddingRequest {
|
||||
input,
|
||||
encoding_format: request
|
||||
.get("encoding_format")
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned),
|
||||
dimensions: request.get("dimensions").and_then(Value::as_u64),
|
||||
task: request
|
||||
.get("task")
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned),
|
||||
user: request
|
||||
.get("user")
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned),
|
||||
extensions: namespace_extensions(
|
||||
namespace,
|
||||
request,
|
||||
&[
|
||||
"model",
|
||||
"input",
|
||||
"encoding_format",
|
||||
"dimensions",
|
||||
"task",
|
||||
"user",
|
||||
],
|
||||
),
|
||||
};
|
||||
|
||||
Some(CanonicalRequest {
|
||||
model,
|
||||
embedding: Some(embedding),
|
||||
..CanonicalRequest::default()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn to_openai_like(
|
||||
canonical: &CanonicalRequest,
|
||||
mapped_model: &str,
|
||||
namespace: &str,
|
||||
default_task: bool,
|
||||
) -> Option<Value> {
|
||||
let embedding = canonical.embedding.as_ref()?;
|
||||
if embedding.input.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut output = Map::new();
|
||||
output.insert(
|
||||
"model".to_string(),
|
||||
Value::String(mapped_embedding_model(canonical, mapped_model)),
|
||||
);
|
||||
output.insert(
|
||||
"input".to_string(),
|
||||
serde_json::to_value(&embedding.input).ok()?,
|
||||
);
|
||||
if let Some(value) = &embedding.encoding_format {
|
||||
output.insert("encoding_format".to_string(), Value::String(value.clone()));
|
||||
}
|
||||
if let Some(value) = embedding.dimensions {
|
||||
output.insert("dimensions".to_string(), Value::from(value));
|
||||
}
|
||||
if let Some(value) = &embedding.user {
|
||||
output.insert("user".to_string(), Value::String(value.clone()));
|
||||
}
|
||||
if let Some(task) = embedding
|
||||
.task
|
||||
.as_ref()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
{
|
||||
output.insert("task".to_string(), Value::String(task.clone()));
|
||||
} else if default_task {
|
||||
output.insert(
|
||||
"task".to_string(),
|
||||
Value::String("text-matching".to_string()),
|
||||
);
|
||||
}
|
||||
output.extend(namespace_extension_object(
|
||||
&embedding.extensions,
|
||||
namespace,
|
||||
&output,
|
||||
));
|
||||
Some(Value::Object(output))
|
||||
}
|
||||
|
||||
pub(crate) fn mapped_embedding_model(canonical: &CanonicalRequest, mapped_model: &str) -> String {
|
||||
let mapped_model = mapped_model.trim();
|
||||
if mapped_model.is_empty() {
|
||||
canonical.model.clone()
|
||||
} else {
|
||||
mapped_model.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn namespace_extensions(
|
||||
namespace: &str,
|
||||
object: &Map<String, Value>,
|
||||
handled_keys: &[&str],
|
||||
) -> BTreeMap<String, Value> {
|
||||
let handled = handled_keys
|
||||
.iter()
|
||||
.copied()
|
||||
.collect::<std::collections::BTreeSet<_>>();
|
||||
let raw = object
|
||||
.iter()
|
||||
.filter(|(key, _)| !handled.contains(key.as_str()))
|
||||
.map(|(key, value)| (key.clone(), value.clone()))
|
||||
.collect::<Map<String, Value>>();
|
||||
if raw.is_empty() {
|
||||
BTreeMap::new()
|
||||
} else {
|
||||
BTreeMap::from([(namespace.to_string(), Value::Object(raw))])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
use serde_json::Value;
|
||||
use serde_json::{json, Map};
|
||||
|
||||
use crate::formats::openai::embedding::request::namespace_extensions;
|
||||
use crate::protocol::canonical::{
|
||||
canonical_usage_to_openai, namespace_extension_object, openai_usage_to_canonical,
|
||||
CanonicalEmbedding, CanonicalEmbeddingResponse,
|
||||
};
|
||||
|
||||
pub fn from(body: &Value) -> Option<CanonicalEmbeddingResponse> {
|
||||
from_namespace(body, "openai")
|
||||
}
|
||||
|
||||
pub fn to(response: &CanonicalEmbeddingResponse) -> Option<Value> {
|
||||
Some(to_openai_like(response, "openai"))
|
||||
}
|
||||
|
||||
pub(crate) fn from_namespace(
|
||||
body_json: &Value,
|
||||
namespace: &str,
|
||||
) -> Option<CanonicalEmbeddingResponse> {
|
||||
let body = body_json.as_object()?;
|
||||
if body.contains_key("error") {
|
||||
return None;
|
||||
}
|
||||
let data = body.get("data")?.as_array()?;
|
||||
let mut embeddings = Vec::new();
|
||||
for (fallback_index, item) in data.iter().enumerate() {
|
||||
let item_object = item.as_object()?;
|
||||
let values = item_object.get("embedding")?.as_array()?;
|
||||
let embedding = values
|
||||
.iter()
|
||||
.map(Value::as_f64)
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
embeddings.push(CanonicalEmbedding {
|
||||
index: item_object
|
||||
.get("index")
|
||||
.and_then(Value::as_u64)
|
||||
.and_then(|value| usize::try_from(value).ok())
|
||||
.unwrap_or(fallback_index),
|
||||
embedding,
|
||||
extensions: namespace_extensions(
|
||||
namespace,
|
||||
item_object,
|
||||
&["object", "index", "embedding"],
|
||||
),
|
||||
});
|
||||
}
|
||||
Some(CanonicalEmbeddingResponse {
|
||||
id: body
|
||||
.get("id")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("embd-unknown")
|
||||
.to_string(),
|
||||
model: body
|
||||
.get("model")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("unknown")
|
||||
.to_string(),
|
||||
embeddings,
|
||||
usage: openai_usage_to_canonical(body.get("usage")),
|
||||
extensions: namespace_extensions(
|
||||
namespace,
|
||||
body,
|
||||
&["id", "object", "model", "data", "usage"],
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn to_openai_like(canonical: &CanonicalEmbeddingResponse, namespace: &str) -> Value {
|
||||
let mut response = Map::new();
|
||||
response.insert("object".to_string(), Value::String("list".to_string()));
|
||||
if !canonical.model.trim().is_empty() && canonical.model != "unknown" {
|
||||
response.insert("model".to_string(), Value::String(canonical.model.clone()));
|
||||
}
|
||||
response.insert(
|
||||
"data".to_string(),
|
||||
Value::Array(
|
||||
canonical
|
||||
.embeddings
|
||||
.iter()
|
||||
.map(|embedding| {
|
||||
let mut item = Map::new();
|
||||
item.insert("object".to_string(), Value::String("embedding".to_string()));
|
||||
item.insert("index".to_string(), Value::from(embedding.index as u64));
|
||||
item.insert("embedding".to_string(), json!(embedding.embedding));
|
||||
item.extend(namespace_extension_object(
|
||||
&embedding.extensions,
|
||||
namespace,
|
||||
&item,
|
||||
));
|
||||
Value::Object(item)
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
if let Some(usage) = &canonical.usage {
|
||||
response.insert("usage".to_string(), canonical_usage_to_openai(usage));
|
||||
}
|
||||
response.extend(namespace_extension_object(
|
||||
&canonical.extensions,
|
||||
namespace,
|
||||
&response,
|
||||
));
|
||||
Value::Object(response)
|
||||
}
|
||||
3
crates/aether-ai-formats/src/formats/openai/image/mod.rs
Normal file
3
crates/aether-ai-formats/src/formats/openai/image/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod request;
|
||||
pub mod spec;
|
||||
pub mod stream;
|
||||
@@ -3,43 +3,10 @@ use std::collections::BTreeMap;
|
||||
use base64::Engine as _;
|
||||
use serde_json::{json, Map, Number, Value};
|
||||
|
||||
use crate::contracts::{OPENAI_IMAGE_STREAM_PLAN_KIND, OPENAI_IMAGE_SYNC_PLAN_KIND};
|
||||
use crate::request::standard::{
|
||||
use crate::formats::openai::responses::codex::{
|
||||
CODEX_OPENAI_IMAGE_DEFAULT_MODEL, CODEX_OPENAI_IMAGE_DEFAULT_VARIATION_MODEL,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LocalOpenAiImageSpec {
|
||||
pub api_format: &'static str,
|
||||
pub decision_kind: &'static str,
|
||||
pub report_kind: &'static str,
|
||||
pub require_streaming: bool,
|
||||
}
|
||||
|
||||
pub fn resolve_sync_spec(plan_kind: &str) -> Option<LocalOpenAiImageSpec> {
|
||||
match plan_kind {
|
||||
OPENAI_IMAGE_SYNC_PLAN_KIND => Some(LocalOpenAiImageSpec {
|
||||
api_format: "openai:image",
|
||||
decision_kind: OPENAI_IMAGE_SYNC_PLAN_KIND,
|
||||
report_kind: "openai_image_sync_finalize",
|
||||
require_streaming: false,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_stream_spec(plan_kind: &str) -> Option<LocalOpenAiImageSpec> {
|
||||
match plan_kind {
|
||||
OPENAI_IMAGE_STREAM_PLAN_KIND => Some(LocalOpenAiImageSpec {
|
||||
api_format: "openai:image",
|
||||
decision_kind: OPENAI_IMAGE_STREAM_PLAN_KIND,
|
||||
report_kind: "openai_image_stream_success",
|
||||
require_streaming: true,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum OpenAiImageOperation {
|
||||
Generate,
|
||||
@@ -1134,10 +1101,10 @@ mod tests {
|
||||
|
||||
use super::{
|
||||
build_chatgpt_web_image_request_body, build_openai_image_provider_request_body,
|
||||
is_openai_image_stream_request, normalize_openai_image_request, resolve_stream_spec,
|
||||
resolve_sync_spec, OpenAiImageOperation,
|
||||
is_openai_image_stream_request, normalize_openai_image_request, OpenAiImageOperation,
|
||||
};
|
||||
use crate::request::standard::{
|
||||
use crate::formats::openai::image::spec::{resolve_stream_spec, resolve_sync_spec};
|
||||
use crate::formats::openai::responses::codex::{
|
||||
apply_codex_openai_responses_special_body_edits, CODEX_OPENAI_IMAGE_INTERNAL_MODEL,
|
||||
};
|
||||
|
||||
54
crates/aether-ai-formats/src/formats/openai/image/spec.rs
Normal file
54
crates/aether-ai-formats/src/formats/openai/image/spec.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use crate::contracts::{OPENAI_IMAGE_STREAM_PLAN_KIND, OPENAI_IMAGE_SYNC_PLAN_KIND};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LocalOpenAiImageSpec {
|
||||
pub api_format: &'static str,
|
||||
pub decision_kind: &'static str,
|
||||
pub report_kind: &'static str,
|
||||
pub require_streaming: bool,
|
||||
}
|
||||
|
||||
pub fn resolve_sync_spec(plan_kind: &str) -> Option<LocalOpenAiImageSpec> {
|
||||
match plan_kind {
|
||||
OPENAI_IMAGE_SYNC_PLAN_KIND => Some(LocalOpenAiImageSpec {
|
||||
api_format: "openai:image",
|
||||
decision_kind: OPENAI_IMAGE_SYNC_PLAN_KIND,
|
||||
report_kind: "openai_image_sync_finalize",
|
||||
require_streaming: false,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_stream_spec(plan_kind: &str) -> Option<LocalOpenAiImageSpec> {
|
||||
match plan_kind {
|
||||
OPENAI_IMAGE_STREAM_PLAN_KIND => Some(LocalOpenAiImageSpec {
|
||||
api_format: "openai:image",
|
||||
decision_kind: OPENAI_IMAGE_STREAM_PLAN_KIND,
|
||||
report_kind: "openai_image_stream_success",
|
||||
require_streaming: true,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{resolve_stream_spec, resolve_sync_spec};
|
||||
|
||||
#[test]
|
||||
fn resolves_openai_image_sync_spec() {
|
||||
let spec = resolve_sync_spec("openai_image_sync").expect("spec");
|
||||
assert_eq!(spec.api_format, "openai:image");
|
||||
assert_eq!(spec.report_kind, "openai_image_sync_finalize");
|
||||
assert!(!spec.require_streaming);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolves_openai_image_stream_spec() {
|
||||
let spec = resolve_stream_spec("openai_image_stream").expect("spec");
|
||||
assert_eq!(spec.api_format, "openai:image");
|
||||
assert_eq!(spec.report_kind, "openai_image_stream_success");
|
||||
assert!(spec.require_streaming);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@ use base64::Engine as _;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::contracts::OPENAI_IMAGE_SYNC_FINALIZE_REPORT_KIND;
|
||||
use crate::request::standard::CODEX_OPENAI_IMAGE_DEFAULT_OUTPUT_FORMAT;
|
||||
use crate::response::sse::encode_json_sse;
|
||||
use crate::response::AiSurfaceFinalizeError;
|
||||
use crate::formats::openai::responses::codex::CODEX_OPENAI_IMAGE_DEFAULT_OUTPUT_FORMAT;
|
||||
use crate::formats::shared::sse::encode_json_sse;
|
||||
use crate::formats::shared::AiSurfaceFinalizeError;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct OpenAiImageStreamState {
|
||||
7
crates/aether-ai-formats/src/formats/openai/mod.rs
Normal file
7
crates/aether-ai-formats/src/formats/openai/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
pub mod chat;
|
||||
pub mod embedding;
|
||||
pub mod image;
|
||||
pub mod rerank;
|
||||
pub mod responses;
|
||||
pub mod shared;
|
||||
pub mod video;
|
||||
@@ -0,0 +1 @@
|
||||
pub mod request;
|
||||
113
crates/aether-ai-formats/src/formats/openai/rerank/request.rs
Normal file
113
crates/aether-ai-formats/src/formats/openai/rerank/request.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use serde_json::Map;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::formats::context::FormatContext;
|
||||
use crate::formats::openai::embedding::request::namespace_extensions;
|
||||
use crate::protocol::canonical::{
|
||||
namespace_extension_object, CanonicalRequest, CanonicalRerankRequest,
|
||||
};
|
||||
|
||||
pub fn from(body: &Value, _ctx: &FormatContext) -> Option<CanonicalRequest> {
|
||||
from_namespace(body, "openai")
|
||||
}
|
||||
|
||||
pub fn to(request: &CanonicalRequest, ctx: &FormatContext) -> Option<Value> {
|
||||
to_openai_like(
|
||||
request,
|
||||
ctx.mapped_model_or(request.model.as_str()),
|
||||
"openai",
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn from_namespace(body_json: &Value, namespace: &str) -> Option<CanonicalRequest> {
|
||||
let request = body_json.as_object()?;
|
||||
let model = request
|
||||
.get("model")
|
||||
.and_then(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())?
|
||||
.to_string();
|
||||
let query = request
|
||||
.get("query")
|
||||
.and_then(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())?
|
||||
.to_string();
|
||||
let documents = request.get("documents").and_then(Value::as_array)?.to_vec();
|
||||
let rerank = CanonicalRerankRequest {
|
||||
query,
|
||||
documents,
|
||||
top_n: request
|
||||
.get("top_n")
|
||||
.or_else(|| request.get("topN"))
|
||||
.and_then(Value::as_u64),
|
||||
return_documents: request
|
||||
.get("return_documents")
|
||||
.or_else(|| request.get("returnDocuments"))
|
||||
.and_then(Value::as_bool),
|
||||
extensions: namespace_extensions(
|
||||
namespace,
|
||||
request,
|
||||
&[
|
||||
"model",
|
||||
"query",
|
||||
"documents",
|
||||
"top_n",
|
||||
"topN",
|
||||
"return_documents",
|
||||
"returnDocuments",
|
||||
],
|
||||
),
|
||||
};
|
||||
if rerank.is_empty() || rerank.top_n == Some(0) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(CanonicalRequest {
|
||||
model,
|
||||
rerank: Some(rerank),
|
||||
..CanonicalRequest::default()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn to_openai_like(
|
||||
canonical: &CanonicalRequest,
|
||||
mapped_model: &str,
|
||||
namespace: &str,
|
||||
) -> Option<Value> {
|
||||
let rerank = canonical.rerank.as_ref()?;
|
||||
if rerank.is_empty() || rerank.top_n == Some(0) {
|
||||
return None;
|
||||
}
|
||||
let mut output = Map::new();
|
||||
output.insert(
|
||||
"model".to_string(),
|
||||
Value::String(mapped_rerank_model(canonical, mapped_model)),
|
||||
);
|
||||
output.insert("query".to_string(), Value::String(rerank.query.clone()));
|
||||
output.insert(
|
||||
"documents".to_string(),
|
||||
Value::Array(rerank.documents.clone()),
|
||||
);
|
||||
if let Some(value) = rerank.top_n {
|
||||
output.insert("top_n".to_string(), Value::from(value));
|
||||
}
|
||||
if let Some(value) = rerank.return_documents {
|
||||
output.insert("return_documents".to_string(), Value::Bool(value));
|
||||
}
|
||||
output.extend(namespace_extension_object(
|
||||
&rerank.extensions,
|
||||
namespace,
|
||||
&output,
|
||||
));
|
||||
Some(Value::Object(output))
|
||||
}
|
||||
|
||||
fn mapped_rerank_model(canonical: &CanonicalRequest, mapped_model: &str) -> String {
|
||||
let mapped_model = mapped_model.trim();
|
||||
if mapped_model.is_empty() {
|
||||
canonical.model.clone()
|
||||
} else {
|
||||
mapped_model.to_string()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod codex;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
pub mod spec;
|
||||
pub mod stream;
|
||||
@@ -1,6 +1,8 @@
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
use crate::{
|
||||
formats::context::FormatContext,
|
||||
formats::openai::shared::map_thinking_budget_to_openai_reasoning_effort,
|
||||
protocol::canonical::{
|
||||
canonical_response_format_to_openai, canonicalize_tool_arguments, media_data_or_url,
|
||||
namespace_extension_object, openai_content_text, openai_extensions,
|
||||
@@ -11,8 +13,6 @@ use crate::{
|
||||
CanonicalThinkingConfig, CanonicalToolChoice, CanonicalToolDefinition,
|
||||
OPENAI_RESPONSES_EXTENSION_NAMESPACE, OPENAI_RESPONSES_LEGACY_EXTENSION_NAMESPACE,
|
||||
},
|
||||
protocol::context::FormatContext,
|
||||
request::openai::map_thinking_budget_to_openai_reasoning_effort,
|
||||
};
|
||||
|
||||
pub fn from(body: &Value, _ctx: &FormatContext) -> Option<CanonicalRequest> {
|
||||
@@ -3,6 +3,7 @@ use std::collections::BTreeMap;
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
use crate::{
|
||||
formats::context::FormatContext,
|
||||
protocol::canonical::{
|
||||
canonical_content_block_to_openai_responses_part,
|
||||
canonical_usage_to_openai_responses_usage, canonicalize_tool_arguments,
|
||||
@@ -12,7 +13,6 @@ use crate::{
|
||||
CanonicalResponseOutput, CanonicalRole, CanonicalStopReason,
|
||||
OPENAI_RESPONSES_EXTENSION_NAMESPACE, OPENAI_RESPONSES_LEGACY_EXTENSION_NAMESPACE,
|
||||
},
|
||||
protocol::context::FormatContext,
|
||||
};
|
||||
|
||||
pub fn from(body: &Value, _ctx: &FormatContext) -> Option<CanonicalResponse> {
|
||||
@@ -0,0 +1,3 @@
|
||||
pub use crate::formats::openai::chat::stream::{
|
||||
OpenAIResponsesClientEmitter, OpenAIResponsesProviderState,
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
use super::model_directives::ReasoningEffort;
|
||||
use crate::formats::shared::model_directives::ReasoningEffort;
|
||||
|
||||
pub fn parse_openai_stop_sequences(stop: Option<&Value>) -> Option<Vec<Value>> {
|
||||
match stop {
|
||||
1
crates/aether-ai-formats/src/formats/openai/video/mod.rs
Normal file
1
crates/aether-ai-formats/src/formats/openai/video/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod spec;
|
||||
27
crates/aether-ai-formats/src/formats/openai/video/spec.rs
Normal file
27
crates/aether-ai-formats/src/formats/openai/video/spec.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use crate::contracts::OPENAI_VIDEO_CREATE_SYNC_PLAN_KIND;
|
||||
use crate::formats::shared::video::{LocalVideoCreateFamily, LocalVideoCreateSpec};
|
||||
|
||||
pub fn resolve_sync_spec(plan_kind: &str) -> Option<LocalVideoCreateSpec> {
|
||||
match plan_kind {
|
||||
OPENAI_VIDEO_CREATE_SYNC_PLAN_KIND => Some(LocalVideoCreateSpec {
|
||||
api_format: "openai:video",
|
||||
decision_kind: OPENAI_VIDEO_CREATE_SYNC_PLAN_KIND,
|
||||
report_kind: "openai_video_create_sync_finalize",
|
||||
family: LocalVideoCreateFamily::OpenAi,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{resolve_sync_spec, LocalVideoCreateFamily};
|
||||
|
||||
#[test]
|
||||
fn resolves_openai_video_create_spec() {
|
||||
let spec = resolve_sync_spec("openai_video_create_sync").expect("spec");
|
||||
assert_eq!(spec.api_format, "openai:video");
|
||||
assert_eq!(spec.family, LocalVideoCreateFamily::OpenAi);
|
||||
assert_eq!(spec.report_kind, "openai_video_create_sync_finalize");
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
protocol::canonical::{
|
||||
canonical_to_embedding_request, canonical_to_rerank_request,
|
||||
from_embedding_to_canonical_request, from_rerank_to_canonical_request, CanonicalRequest,
|
||||
CanonicalResponse,
|
||||
},
|
||||
protocol::formats::{
|
||||
claude_messages, gemini_generate_content, openai_chat, openai_responses, FormatId,
|
||||
},
|
||||
use crate::formats::{
|
||||
claude::messages as claude_messages,
|
||||
doubao,
|
||||
gemini::{self, generate_content as gemini_generate_content},
|
||||
id::FormatId,
|
||||
jina,
|
||||
openai::{self, chat as openai_chat, responses as openai_responses},
|
||||
};
|
||||
use crate::protocol::canonical::{CanonicalRequest, CanonicalResponse};
|
||||
|
||||
pub use crate::protocol::context::{FormatContext, FormatError};
|
||||
pub use crate::formats::context::{FormatContext, FormatError};
|
||||
|
||||
pub fn parse_request(
|
||||
source_format: &str,
|
||||
@@ -26,10 +25,10 @@ pub fn parse_request(
|
||||
}
|
||||
FormatId::ClaudeMessages => claude_messages::request::from(body, ctx),
|
||||
FormatId::GeminiGenerateContent => gemini_generate_content::request::from(body, ctx),
|
||||
FormatId::OpenAiEmbedding => from_embedding_to_canonical_request(body, "openai"),
|
||||
FormatId::JinaEmbedding => from_embedding_to_canonical_request(body, "jina"),
|
||||
FormatId::OpenAiRerank => from_rerank_to_canonical_request(body, "openai"),
|
||||
FormatId::JinaRerank => from_rerank_to_canonical_request(body, "jina"),
|
||||
FormatId::OpenAiEmbedding => openai::embedding::request::from(body, ctx),
|
||||
FormatId::JinaEmbedding => jina::embedding::request::from(body, ctx),
|
||||
FormatId::OpenAiRerank => openai::rerank::request::from(body, ctx),
|
||||
FormatId::JinaRerank => jina::rerank::request::from(body, ctx),
|
||||
FormatId::GeminiEmbedding | FormatId::DoubaoEmbedding => None,
|
||||
}
|
||||
.ok_or_else(|| FormatError::RequestParseFailed {
|
||||
@@ -57,36 +56,12 @@ pub fn emit_request(
|
||||
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),
|
||||
FormatId::OpenAiEmbedding => canonical_to_embedding_request(
|
||||
&request,
|
||||
ctx.mapped_model_or(request.model.as_str()),
|
||||
"openai",
|
||||
),
|
||||
FormatId::JinaEmbedding => canonical_to_embedding_request(
|
||||
&request,
|
||||
ctx.mapped_model_or(request.model.as_str()),
|
||||
"jina",
|
||||
),
|
||||
FormatId::OpenAiRerank => canonical_to_rerank_request(
|
||||
&request,
|
||||
ctx.mapped_model_or(request.model.as_str()),
|
||||
"openai",
|
||||
),
|
||||
FormatId::JinaRerank => canonical_to_rerank_request(
|
||||
&request,
|
||||
ctx.mapped_model_or(request.model.as_str()),
|
||||
"jina",
|
||||
),
|
||||
FormatId::GeminiEmbedding => canonical_to_embedding_request(
|
||||
&request,
|
||||
ctx.mapped_model_or(request.model.as_str()),
|
||||
"gemini",
|
||||
),
|
||||
FormatId::DoubaoEmbedding => canonical_to_embedding_request(
|
||||
&request,
|
||||
ctx.mapped_model_or(request.model.as_str()),
|
||||
"doubao",
|
||||
),
|
||||
FormatId::OpenAiEmbedding => openai::embedding::request::to(&request, ctx),
|
||||
FormatId::JinaEmbedding => jina::embedding::request::to(&request, ctx),
|
||||
FormatId::OpenAiRerank => openai::rerank::request::to(&request, ctx),
|
||||
FormatId::JinaRerank => jina::rerank::request::to(&request, ctx),
|
||||
FormatId::GeminiEmbedding => gemini::embedding::request::to(&request, ctx),
|
||||
FormatId::DoubaoEmbedding => doubao::embedding::request::to(&request, ctx),
|
||||
}
|
||||
.ok_or_else(|| FormatError::RequestEmitFailed {
|
||||
format: target.as_str().to_string(),
|
||||
@@ -197,7 +172,7 @@ mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
use super::{convert_request, FormatContext};
|
||||
use crate::protocol::formats::FormatId;
|
||||
use crate::formats::id::FormatId;
|
||||
|
||||
#[test]
|
||||
fn openai_cli_alias_is_not_a_primary_format() {
|
||||
@@ -347,7 +322,7 @@ mod tests {
|
||||
] {
|
||||
assert!(
|
||||
!implementation.contains(forbidden),
|
||||
"registry should dispatch through protocol::formats::<format> adapters, found {forbidden}"
|
||||
"registry should dispatch through formats::<provider>::<surface> adapters, found {forbidden}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,29 @@
|
||||
use std::fmt;
|
||||
|
||||
pub mod error_body;
|
||||
pub mod family;
|
||||
pub mod model_directives;
|
||||
pub mod passthrough;
|
||||
pub mod request;
|
||||
pub mod request_matrix;
|
||||
pub mod response;
|
||||
pub mod routing;
|
||||
pub mod sse;
|
||||
pub mod standard_matrix;
|
||||
pub mod standard_normalize;
|
||||
pub mod stream_core;
|
||||
pub mod stream_rewrite;
|
||||
pub mod sync_products;
|
||||
pub mod sync_to_stream;
|
||||
pub mod video;
|
||||
|
||||
pub use self::sse::{encode_done_sse, encode_json_sse, map_claude_stop_reason};
|
||||
pub use self::standard::stream_core::CanonicalStreamEvent;
|
||||
pub use self::standard::stream_core::CanonicalStreamFrame;
|
||||
pub use self::stream_core::{CanonicalStreamEvent, CanonicalStreamFrame};
|
||||
pub use self::stream_rewrite::{
|
||||
maybe_build_ai_surface_stream_rewriter, resolve_finalize_stream_rewrite_mode,
|
||||
AiSurfaceStreamRewriter, FinalizeStreamRewriteMode,
|
||||
};
|
||||
|
||||
pub mod common;
|
||||
pub mod error_body;
|
||||
pub mod openai_image_stream;
|
||||
pub mod sse;
|
||||
pub mod standard;
|
||||
pub mod stream_rewrite;
|
||||
pub mod sync_products;
|
||||
pub mod sync_to_stream;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AiSurfaceFinalizeError(pub String);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
pub use crate::request::standard::matrix::{
|
||||
pub use crate::formats::shared::standard_matrix::{
|
||||
build_standard_request_body_from_canonical,
|
||||
build_standard_request_body_from_canonical_with_model_directives,
|
||||
};
|
||||
@@ -14,7 +14,7 @@ use crate::contracts::{
|
||||
OPENAI_VIDEO_CONTENT_PLAN_KIND, OPENAI_VIDEO_CREATE_SYNC_PLAN_KIND,
|
||||
OPENAI_VIDEO_DELETE_SYNC_PLAN_KIND, OPENAI_VIDEO_REMIX_SYNC_PLAN_KIND,
|
||||
};
|
||||
use crate::request::specialized::image::is_openai_image_stream_request;
|
||||
use crate::formats::openai::image::request::is_openai_image_stream_request;
|
||||
|
||||
pub fn resolve_execution_runtime_stream_plan_kind(
|
||||
route_class: Option<&str>,
|
||||
@@ -1,6 +1,6 @@
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::response::AiSurfaceFinalizeError;
|
||||
use crate::formats::shared::AiSurfaceFinalizeError;
|
||||
|
||||
pub fn map_claude_stop_reason(
|
||||
stop_reason: Option<&str>,
|
||||
@@ -1,23 +1,23 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use aether_ai_formats::protocol::conversion::request::{
|
||||
use aether_ai_formats::formats::conversion::request::{
|
||||
convert_openai_chat_request_to_claude_request, convert_openai_chat_request_to_gemini_request,
|
||||
convert_openai_chat_request_to_openai_responses_request,
|
||||
normalize_claude_request_to_openai_chat_request,
|
||||
normalize_gemini_request_to_openai_chat_request,
|
||||
normalize_openai_responses_request_to_openai_chat_request,
|
||||
};
|
||||
use aether_ai_formats::protocol::registry::{convert_request, FormatContext};
|
||||
use aether_ai_formats::formats::registry::{convert_request, FormatContext};
|
||||
use aether_ai_formats::provider_compat::proxy::rules::apply_local_body_rules_with_request_headers;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::request::model_directives::apply_model_directive_overrides_from_request;
|
||||
use crate::formats::shared::model_directives::apply_model_directive_overrides_from_request;
|
||||
|
||||
use super::{
|
||||
use crate::formats::openai::responses::codex::{
|
||||
apply_codex_openai_responses_special_body_edits,
|
||||
apply_openai_responses_compact_special_body_edits,
|
||||
codex::apply_codex_openai_responses_special_body_edits,
|
||||
normalize::build_local_openai_chat_request_body_with_model_directives,
|
||||
};
|
||||
use crate::formats::shared::standard_normalize::build_local_openai_chat_request_body_with_model_directives;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn build_standard_request_body(
|
||||
@@ -1,4 +1,4 @@
|
||||
use aether_ai_formats::protocol::conversion::request::{
|
||||
use aether_ai_formats::formats::conversion::request::{
|
||||
convert_openai_chat_request_to_claude_request, convert_openai_chat_request_to_gemini_request,
|
||||
convert_openai_chat_request_to_openai_responses_request,
|
||||
normalize_openai_responses_request_to_openai_chat_request,
|
||||
@@ -6,7 +6,7 @@ use aether_ai_formats::protocol::conversion::request::{
|
||||
use aether_ai_formats::{request_conversion_kind, RequestConversionKind};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::request::model_directives::apply_model_directive_overrides_from_request;
|
||||
use crate::formats::shared::model_directives::apply_model_directive_overrides_from_request;
|
||||
|
||||
pub fn build_local_openai_chat_request_body(
|
||||
body_json: &Value,
|
||||
@@ -2,20 +2,20 @@ use aether_ai_formats::FormatId;
|
||||
use aether_contracts::{ExecutionStreamTerminalSummary, StandardizedUsage};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::response::error_body::{
|
||||
build_core_error_body_for_client_format, LocalCoreSyncErrorKind,
|
||||
};
|
||||
use crate::response::sse::encode_json_sse;
|
||||
use crate::response::standard::claude::stream::{ClaudeClientEmitter, ClaudeProviderState};
|
||||
use crate::response::standard::gemini::stream::{GeminiClientEmitter, GeminiProviderState};
|
||||
use crate::response::standard::openai::stream::{
|
||||
use crate::formats::claude::messages::stream::{ClaudeClientEmitter, ClaudeProviderState};
|
||||
use crate::formats::gemini::generate_content::stream::{GeminiClientEmitter, GeminiProviderState};
|
||||
use crate::formats::openai::chat::stream::{
|
||||
OpenAIChatClientEmitter, OpenAIChatProviderState, OpenAIResponsesClientEmitter,
|
||||
OpenAIResponsesProviderState,
|
||||
};
|
||||
use crate::response::standard::stream_core::common::{
|
||||
use crate::formats::shared::error_body::{
|
||||
build_core_error_body_for_client_format, LocalCoreSyncErrorKind,
|
||||
};
|
||||
use crate::formats::shared::sse::encode_json_sse;
|
||||
use crate::formats::shared::stream_core::common::{
|
||||
decode_json_data_line, CanonicalStreamEvent, CanonicalStreamFrame, CanonicalUsage,
|
||||
};
|
||||
use crate::response::AiSurfaceFinalizeError;
|
||||
use crate::formats::shared::AiSurfaceFinalizeError;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct StreamingStandardFormatMatrix {
|
||||
@@ -1,13 +1,13 @@
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::formats::openai::image::stream::OpenAiImageStreamState;
|
||||
use crate::formats::shared::stream_core::StreamingStandardFormatMatrix;
|
||||
use crate::formats::shared::AiSurfaceFinalizeError;
|
||||
use crate::provider_compat::kiro_stream::KiroToClaudeCliStreamState;
|
||||
use crate::provider_compat::private_envelope::transform_provider_private_stream_line;
|
||||
use crate::provider_compat::surfaces::{
|
||||
provider_adaptation_should_unwrap_stream_envelope, KIRO_ENVELOPE_NAME,
|
||||
};
|
||||
use crate::response::openai_image_stream::OpenAiImageStreamState;
|
||||
use crate::response::standard::stream_core::StreamingStandardFormatMatrix;
|
||||
use crate::response::AiSurfaceFinalizeError;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FinalizeStreamRewriteMode {
|
||||
@@ -1,13 +1,13 @@
|
||||
use base64::Engine as _;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use aether_ai_formats::protocol::conversion::response::{
|
||||
use aether_ai_formats::formats::conversion::response::{
|
||||
convert_claude_response_to_openai_responses, convert_gemini_chat_response_to_openai_chat,
|
||||
convert_openai_chat_response_to_claude_chat, convert_openai_chat_response_to_gemini_chat,
|
||||
convert_openai_chat_response_to_openai_responses,
|
||||
convert_openai_responses_response_to_openai_chat,
|
||||
};
|
||||
use aether_ai_formats::protocol::registry::{convert_response, FormatContext};
|
||||
use aether_ai_formats::formats::registry::{convert_response, FormatContext};
|
||||
use aether_ai_formats::{
|
||||
canonical_to_claude_response, canonical_to_gemini_response, canonical_to_openai_chat_response,
|
||||
canonical_to_openai_responses_compact_response, canonical_to_openai_responses_response,
|
||||
@@ -18,9 +18,9 @@ use aether_ai_formats::{
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
use super::AiSurfaceFinalizeError;
|
||||
use crate::response::common::remove_empty_pages_from_tool_arguments;
|
||||
use crate::response::standard::gemini::stream::GeminiProviderState;
|
||||
use crate::response::standard::stream_core::common::{
|
||||
use crate::formats::gemini::generate_content::stream::GeminiProviderState;
|
||||
use crate::formats::shared::response::remove_empty_pages_from_tool_arguments;
|
||||
use crate::formats::shared::stream_core::common::{
|
||||
map_openai_finish_reason_to_gemini, parse_json_arguments_value, CanonicalContentPart,
|
||||
CanonicalStreamEvent, CanonicalUsage,
|
||||
};
|
||||
@@ -2875,7 +2875,7 @@ mod tests {
|
||||
maybe_build_standard_sync_finalize_product_from_normalized_payload,
|
||||
StandardSyncFinalizeNormalizedProduct,
|
||||
};
|
||||
use aether_ai_formats::protocol::conversion::response::{
|
||||
use aether_ai_formats::formats::conversion::response::{
|
||||
convert_claude_chat_response_to_openai_chat, convert_gemini_chat_response_to_openai_chat,
|
||||
convert_openai_chat_response_to_claude_chat, convert_openai_chat_response_to_gemini_chat,
|
||||
convert_openai_chat_response_to_openai_responses,
|
||||
@@ -1,18 +1,18 @@
|
||||
use aether_ai_formats::protocol::conversion::response::{
|
||||
use aether_ai_formats::formats::conversion::response::{
|
||||
convert_claude_response_to_openai_responses, convert_gemini_response_to_openai_responses,
|
||||
convert_openai_chat_response_to_openai_responses,
|
||||
};
|
||||
use aether_contracts::{ExecutionStreamTerminalSummary, StandardizedUsage};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::response::sse::encode_json_sse;
|
||||
use crate::response::standard::claude::stream::ClaudeClientEmitter;
|
||||
use crate::response::standard::gemini::stream::GeminiClientEmitter;
|
||||
use crate::response::standard::openai::stream::{
|
||||
use crate::formats::claude::messages::stream::ClaudeClientEmitter;
|
||||
use crate::formats::gemini::generate_content::stream::GeminiClientEmitter;
|
||||
use crate::formats::openai::chat::stream::{
|
||||
OpenAIChatClientEmitter, OpenAIResponsesClientEmitter, OpenAIResponsesProviderState,
|
||||
};
|
||||
use crate::response::standard::stream_core::CanonicalStreamFrame;
|
||||
use crate::response::AiSurfaceFinalizeError;
|
||||
use crate::formats::shared::sse::encode_json_sse;
|
||||
use crate::formats::shared::stream_core::CanonicalStreamFrame;
|
||||
use crate::formats::shared::AiSurfaceFinalizeError;
|
||||
|
||||
pub struct SyncToStreamBridgeOutcome {
|
||||
pub sse_body: Vec<u8>,
|
||||
34
crates/aether-ai-formats/src/formats/shared/video.rs
Normal file
34
crates/aether-ai-formats/src/formats/shared/video.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LocalVideoCreateFamily {
|
||||
OpenAi,
|
||||
Gemini,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LocalVideoCreateSpec {
|
||||
pub api_format: &'static str,
|
||||
pub decision_kind: &'static str,
|
||||
pub report_kind: &'static str,
|
||||
pub family: LocalVideoCreateFamily,
|
||||
}
|
||||
|
||||
pub fn resolve_sync_spec(plan_kind: &str) -> Option<LocalVideoCreateSpec> {
|
||||
crate::formats::openai::video::spec::resolve_sync_spec(plan_kind)
|
||||
.or_else(|| crate::formats::gemini::video::spec::resolve_sync_spec(plan_kind))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{resolve_sync_spec, LocalVideoCreateFamily};
|
||||
|
||||
#[test]
|
||||
fn resolves_openai_and_gemini_video_create_specs() {
|
||||
let openai = resolve_sync_spec("openai_video_create_sync").expect("openai spec");
|
||||
assert_eq!(openai.api_format, "openai:video");
|
||||
assert_eq!(openai.family, LocalVideoCreateFamily::OpenAi);
|
||||
|
||||
let gemini = resolve_sync_spec("gemini_video_create_sync").expect("gemini spec");
|
||||
assert_eq!(gemini.api_format, "gemini:video");
|
||||
assert_eq!(gemini.family, LocalVideoCreateFamily::Gemini);
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,31 @@ extern crate self as aether_ai_formats;
|
||||
|
||||
pub mod api;
|
||||
pub mod contracts;
|
||||
pub mod formats;
|
||||
pub mod protocol;
|
||||
pub mod provider_compat;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
|
||||
pub use formats::context::{FormatContext, FormatError};
|
||||
pub use formats::id::{
|
||||
api_format_alias_matches, api_format_storage_aliases, is_openai_responses_compact_format,
|
||||
is_openai_responses_family_format, is_openai_responses_format, normalize_api_format_alias,
|
||||
FormatFamily, FormatId, FormatProfile,
|
||||
};
|
||||
pub use formats::matrix::{
|
||||
is_embedding_api_format, is_rerank_api_format, request_candidate_api_format_preference,
|
||||
request_candidate_api_formats, request_conversion_kind,
|
||||
request_conversion_requires_enable_flag, sync_chat_response_conversion_kind,
|
||||
sync_cli_response_conversion_kind, RequestConversionKind, SyncChatResponseConversionKind,
|
||||
SyncCliResponseConversionKind,
|
||||
};
|
||||
pub use formats::registry::{build_stream_transcoder, convert_request, convert_response};
|
||||
pub use formats::shared::model_directives::{
|
||||
apply_model_directive_mapping_patch, apply_model_directive_overrides_from_model,
|
||||
apply_model_directive_overrides_from_request, claude_model_uses_adaptive_effort,
|
||||
extract_gemini_model_from_path, gemini_model_uses_thinking_level, model_directive_base_model,
|
||||
normalize_model_directive_model, parse_model_directive, ModelDirective, ModelOverride,
|
||||
ReasoningEffort,
|
||||
};
|
||||
pub use protocol::canonical::{
|
||||
canonical_request_unknown_block_count, canonical_response_unknown_block_count,
|
||||
canonical_to_claude_request, canonical_to_claude_response, canonical_to_embedding_response,
|
||||
@@ -25,24 +45,3 @@ pub use protocol::canonical::{
|
||||
CanonicalStopReason, CanonicalStreamEvent, CanonicalStreamFrame, CanonicalThinkingConfig,
|
||||
CanonicalToolChoice, CanonicalToolDefinition, CanonicalUsage,
|
||||
};
|
||||
pub use protocol::context::{FormatContext, FormatError};
|
||||
pub use protocol::formats::{
|
||||
api_format_alias_matches, api_format_storage_aliases, is_openai_responses_compact_format,
|
||||
is_openai_responses_family_format, is_openai_responses_format, normalize_api_format_alias,
|
||||
FormatFamily, FormatId, FormatProfile,
|
||||
};
|
||||
pub use protocol::matrix::{
|
||||
is_embedding_api_format, is_rerank_api_format, request_candidate_api_format_preference,
|
||||
request_candidate_api_formats, request_conversion_kind,
|
||||
request_conversion_requires_enable_flag, sync_chat_response_conversion_kind,
|
||||
sync_cli_response_conversion_kind, RequestConversionKind, SyncChatResponseConversionKind,
|
||||
SyncCliResponseConversionKind,
|
||||
};
|
||||
pub use protocol::registry::{build_stream_transcoder, convert_request, convert_response};
|
||||
pub use request::model_directives::{
|
||||
apply_model_directive_mapping_patch, apply_model_directive_overrides_from_model,
|
||||
apply_model_directive_overrides_from_request, claude_model_uses_adaptive_effort,
|
||||
extract_gemini_model_from_path, gemini_model_uses_thinking_level, model_directive_base_model,
|
||||
normalize_model_directive_model, parse_model_directive, ModelDirective, ModelOverride,
|
||||
ReasoningEffort,
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::collections::BTreeMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
use crate::request::openai::map_thinking_budget_to_openai_reasoning_effort;
|
||||
use crate::formats::openai::shared::map_thinking_budget_to_openai_reasoning_effort;
|
||||
|
||||
pub use crate::protocol::stream::{CanonicalStreamEvent, CanonicalStreamFrame};
|
||||
|
||||
@@ -232,7 +232,7 @@ pub enum CanonicalEmbeddingInput {
|
||||
}
|
||||
|
||||
impl CanonicalEmbeddingInput {
|
||||
fn is_empty(&self) -> bool {
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
Self::String(value) => value.trim().is_empty(),
|
||||
Self::StringArray(values) => {
|
||||
@@ -243,7 +243,7 @@ impl CanonicalEmbeddingInput {
|
||||
}
|
||||
}
|
||||
|
||||
fn as_string_items(&self) -> Option<Vec<&str>> {
|
||||
pub(crate) fn as_string_items(&self) -> Option<Vec<&str>> {
|
||||
match self {
|
||||
Self::String(value) => Some(vec![value.as_str()]),
|
||||
Self::StringArray(values) => Some(values.iter().map(String::as_str).collect()),
|
||||
@@ -281,7 +281,7 @@ pub struct CanonicalRerankRequest {
|
||||
}
|
||||
|
||||
impl CanonicalRerankRequest {
|
||||
fn is_empty(&self) -> bool {
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.query.trim().is_empty()
|
||||
|| self.documents.is_empty()
|
||||
|| self.documents.iter().any(rerank_document_is_empty)
|
||||
@@ -385,15 +385,15 @@ pub struct CanonicalResponse {
|
||||
}
|
||||
|
||||
pub fn from_openai_chat_to_canonical_request(body_json: &Value) -> Option<CanonicalRequest> {
|
||||
crate::protocol::formats::openai_chat::request::from_raw(body_json)
|
||||
crate::formats::openai::chat::request::from_raw(body_json)
|
||||
}
|
||||
|
||||
pub fn canonical_to_openai_chat_request(canonical: &CanonicalRequest) -> Value {
|
||||
crate::protocol::formats::openai_chat::request::to_raw(canonical)
|
||||
crate::formats::openai::chat::request::to_raw(canonical)
|
||||
}
|
||||
|
||||
pub fn from_openai_responses_to_canonical_request(body_json: &Value) -> Option<CanonicalRequest> {
|
||||
crate::protocol::formats::openai_responses::request::from_raw(body_json)
|
||||
crate::formats::openai::responses::request::from_raw(body_json)
|
||||
}
|
||||
|
||||
pub(crate) fn canonical_to_openai_responses_request_with_profile(
|
||||
@@ -402,7 +402,7 @@ pub(crate) fn canonical_to_openai_responses_request_with_profile(
|
||||
upstream_is_stream: bool,
|
||||
compact: bool,
|
||||
) -> Option<Value> {
|
||||
crate::protocol::formats::openai_responses::request::to_raw(
|
||||
crate::formats::openai::responses::request::to_raw(
|
||||
canonical,
|
||||
mapped_model,
|
||||
upstream_is_stream,
|
||||
@@ -431,7 +431,7 @@ pub fn canonical_to_openai_responses_compact_request(
|
||||
}
|
||||
|
||||
pub fn from_claude_to_canonical_request(body_json: &Value) -> Option<CanonicalRequest> {
|
||||
crate::protocol::formats::claude_messages::request::from_raw(body_json)
|
||||
crate::formats::claude::messages::request::from_raw(body_json)
|
||||
}
|
||||
|
||||
pub fn canonical_to_claude_request(
|
||||
@@ -439,18 +439,14 @@ pub fn canonical_to_claude_request(
|
||||
mapped_model: &str,
|
||||
upstream_is_stream: bool,
|
||||
) -> Option<Value> {
|
||||
crate::protocol::formats::claude_messages::request::to_raw(
|
||||
canonical,
|
||||
mapped_model,
|
||||
upstream_is_stream,
|
||||
)
|
||||
crate::formats::claude::messages::request::to_raw(canonical, mapped_model, upstream_is_stream)
|
||||
}
|
||||
|
||||
pub fn from_gemini_to_canonical_request(
|
||||
body_json: &Value,
|
||||
request_path: &str,
|
||||
) -> Option<CanonicalRequest> {
|
||||
crate::protocol::formats::gemini_generate_content::request::from_raw(body_json, request_path)
|
||||
crate::formats::gemini::generate_content::request::from_raw(body_json, request_path)
|
||||
}
|
||||
|
||||
pub fn canonical_to_gemini_request(
|
||||
@@ -458,72 +454,59 @@ pub fn canonical_to_gemini_request(
|
||||
mapped_model: &str,
|
||||
upstream_is_stream: bool,
|
||||
) -> Option<Value> {
|
||||
crate::protocol::formats::gemini_generate_content::request::to_raw(
|
||||
crate::formats::gemini::generate_content::request::to_raw(
|
||||
canonical,
|
||||
mapped_model,
|
||||
upstream_is_stream,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn from_embedding_to_canonical_request(
|
||||
body_json: &Value,
|
||||
namespace: &str,
|
||||
) -> Option<CanonicalRequest> {
|
||||
embedding_request_from_raw(body_json, namespace)
|
||||
match namespace {
|
||||
"openai" => crate::formats::openai::embedding::request::from_namespace(body_json, "openai"),
|
||||
"jina" => crate::formats::openai::embedding::request::from_namespace(body_json, "jina"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn canonical_to_embedding_request(
|
||||
canonical: &CanonicalRequest,
|
||||
mapped_model: &str,
|
||||
namespace: &str,
|
||||
) -> Option<Value> {
|
||||
let ctx = crate::formats::context::FormatContext::default().with_mapped_model(mapped_model);
|
||||
match namespace {
|
||||
"openai" => canonical_to_openai_embedding_request(canonical, mapped_model),
|
||||
"jina" => canonical_to_jina_embedding_request(canonical, mapped_model),
|
||||
"gemini" => canonical_to_gemini_embedding_request(canonical, mapped_model),
|
||||
"doubao" => canonical_to_doubao_embedding_request(canonical, mapped_model),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_rerank_to_canonical_request(
|
||||
body_json: &Value,
|
||||
namespace: &str,
|
||||
) -> Option<CanonicalRequest> {
|
||||
rerank_request_from_raw(body_json, namespace)
|
||||
}
|
||||
|
||||
pub(crate) fn canonical_to_rerank_request(
|
||||
canonical: &CanonicalRequest,
|
||||
mapped_model: &str,
|
||||
namespace: &str,
|
||||
) -> Option<Value> {
|
||||
match namespace {
|
||||
"openai" | "jina" => {
|
||||
canonical_to_openai_like_rerank_request(canonical, mapped_model, namespace)
|
||||
}
|
||||
"openai" => crate::formats::openai::embedding::request::to(canonical, &ctx),
|
||||
"jina" => crate::formats::jina::embedding::request::to(canonical, &ctx),
|
||||
"gemini" => crate::formats::gemini::embedding::request::to(canonical, &ctx),
|
||||
"doubao" => crate::formats::doubao::embedding::request::to(canonical, &ctx),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_openai_chat_to_canonical_response(body_json: &Value) -> Option<CanonicalResponse> {
|
||||
crate::protocol::formats::openai_chat::response::from_raw(body_json)
|
||||
crate::formats::openai::chat::response::from_raw(body_json)
|
||||
}
|
||||
|
||||
pub fn from_openai_responses_to_canonical_response(body_json: &Value) -> Option<CanonicalResponse> {
|
||||
crate::protocol::formats::openai_responses::response::from_raw(body_json)
|
||||
crate::formats::openai::responses::response::from_raw(body_json)
|
||||
}
|
||||
|
||||
pub fn from_claude_to_canonical_response(body_json: &Value) -> Option<CanonicalResponse> {
|
||||
crate::protocol::formats::claude_messages::response::from_raw(body_json)
|
||||
crate::formats::claude::messages::response::from_raw(body_json)
|
||||
}
|
||||
|
||||
pub fn from_gemini_to_canonical_response(body_json: &Value) -> Option<CanonicalResponse> {
|
||||
crate::protocol::formats::gemini_generate_content::response::from_raw(body_json)
|
||||
crate::formats::gemini::generate_content::response::from_raw(body_json)
|
||||
}
|
||||
|
||||
pub fn canonical_to_openai_chat_response(canonical: &CanonicalResponse) -> Value {
|
||||
crate::protocol::formats::openai_chat::response::to_raw(canonical)
|
||||
crate::formats::openai::chat::response::to_raw(canonical)
|
||||
}
|
||||
|
||||
pub(crate) fn canonical_blocks_to_openai_chat_message(content: &[CanonicalContentBlock]) -> Value {
|
||||
@@ -661,7 +644,7 @@ pub(crate) fn canonical_to_openai_responses_response_with_profile(
|
||||
report_context: &Value,
|
||||
compact: bool,
|
||||
) -> Value {
|
||||
crate::protocol::formats::openai_responses::response::to_raw(canonical, report_context, compact)
|
||||
crate::formats::openai::responses::response::to_raw(canonical, report_context, compact)
|
||||
}
|
||||
|
||||
pub fn canonical_to_openai_responses_response(
|
||||
@@ -679,21 +662,27 @@ pub fn canonical_to_openai_responses_compact_response(
|
||||
}
|
||||
|
||||
pub fn canonical_to_claude_response(canonical: &CanonicalResponse) -> Value {
|
||||
crate::protocol::formats::claude_messages::response::to_raw(canonical)
|
||||
crate::formats::claude::messages::response::to_raw(canonical)
|
||||
}
|
||||
|
||||
pub fn canonical_to_gemini_response(
|
||||
canonical: &CanonicalResponse,
|
||||
report_context: &Value,
|
||||
) -> Option<Value> {
|
||||
crate::protocol::formats::gemini_generate_content::response::to_raw(canonical, report_context)
|
||||
crate::formats::gemini::generate_content::response::to_raw(canonical, report_context)
|
||||
}
|
||||
|
||||
pub fn from_embedding_to_canonical_response(
|
||||
body_json: &Value,
|
||||
namespace: &str,
|
||||
) -> Option<CanonicalEmbeddingResponse> {
|
||||
embedding_response_from_raw(body_json, namespace)
|
||||
match namespace {
|
||||
"openai" => {
|
||||
crate::formats::openai::embedding::response::from_namespace(body_json, "openai")
|
||||
}
|
||||
"jina" => crate::formats::openai::embedding::response::from_namespace(body_json, "jina"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn canonical_to_embedding_response(
|
||||
@@ -701,7 +690,8 @@ pub fn canonical_to_embedding_response(
|
||||
namespace: &str,
|
||||
) -> Option<Value> {
|
||||
match namespace {
|
||||
"openai" | "jina" => Some(canonical_to_openai_embedding_response(canonical, namespace)),
|
||||
"openai" => crate::formats::openai::embedding::response::to(canonical),
|
||||
"jina" => crate::formats::jina::embedding::response::to(canonical),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -4273,140 +4263,6 @@ pub(crate) fn strip_claude_billing_header(text: &str) -> String {
|
||||
remainder.trim_start_matches('\n').trim().to_string()
|
||||
}
|
||||
|
||||
fn embedding_request_from_raw(body_json: &Value, namespace: &str) -> Option<CanonicalRequest> {
|
||||
let request = body_json.as_object()?;
|
||||
let model = request
|
||||
.get("model")
|
||||
.and_then(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())?
|
||||
.to_string();
|
||||
let input =
|
||||
serde_json::from_value::<CanonicalEmbeddingInput>(request.get("input")?.clone()).ok()?;
|
||||
if input.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let embedding = CanonicalEmbeddingRequest {
|
||||
input,
|
||||
encoding_format: request
|
||||
.get("encoding_format")
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned),
|
||||
dimensions: request.get("dimensions").and_then(Value::as_u64),
|
||||
task: request
|
||||
.get("task")
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned),
|
||||
user: request
|
||||
.get("user")
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned),
|
||||
extensions: namespace_extensions(
|
||||
namespace,
|
||||
request,
|
||||
&[
|
||||
"model",
|
||||
"input",
|
||||
"encoding_format",
|
||||
"dimensions",
|
||||
"task",
|
||||
"user",
|
||||
],
|
||||
),
|
||||
};
|
||||
|
||||
Some(CanonicalRequest {
|
||||
model,
|
||||
embedding: Some(embedding),
|
||||
..CanonicalRequest::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn rerank_request_from_raw(body_json: &Value, namespace: &str) -> Option<CanonicalRequest> {
|
||||
let request = body_json.as_object()?;
|
||||
let model = request
|
||||
.get("model")
|
||||
.and_then(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())?
|
||||
.to_string();
|
||||
let query = request
|
||||
.get("query")
|
||||
.and_then(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())?
|
||||
.to_string();
|
||||
let documents = request.get("documents").and_then(Value::as_array)?.to_vec();
|
||||
let rerank = CanonicalRerankRequest {
|
||||
query,
|
||||
documents,
|
||||
top_n: request
|
||||
.get("top_n")
|
||||
.or_else(|| request.get("topN"))
|
||||
.and_then(Value::as_u64),
|
||||
return_documents: request
|
||||
.get("return_documents")
|
||||
.or_else(|| request.get("returnDocuments"))
|
||||
.and_then(Value::as_bool),
|
||||
extensions: namespace_extensions(
|
||||
namespace,
|
||||
request,
|
||||
&[
|
||||
"model",
|
||||
"query",
|
||||
"documents",
|
||||
"top_n",
|
||||
"topN",
|
||||
"return_documents",
|
||||
"returnDocuments",
|
||||
],
|
||||
),
|
||||
};
|
||||
if rerank.is_empty() || rerank.top_n == Some(0) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(CanonicalRequest {
|
||||
model,
|
||||
rerank: Some(rerank),
|
||||
..CanonicalRequest::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn canonical_to_openai_like_rerank_request(
|
||||
canonical: &CanonicalRequest,
|
||||
mapped_model: &str,
|
||||
namespace: &str,
|
||||
) -> Option<Value> {
|
||||
let rerank = canonical.rerank.as_ref()?;
|
||||
if rerank.is_empty() || rerank.top_n == Some(0) {
|
||||
return None;
|
||||
}
|
||||
let mut output = Map::new();
|
||||
output.insert(
|
||||
"model".to_string(),
|
||||
Value::String(mapped_rerank_model(canonical, mapped_model)),
|
||||
);
|
||||
output.insert("query".to_string(), Value::String(rerank.query.clone()));
|
||||
output.insert(
|
||||
"documents".to_string(),
|
||||
Value::Array(rerank.documents.clone()),
|
||||
);
|
||||
if let Some(value) = rerank.top_n {
|
||||
output.insert("top_n".to_string(), Value::from(value));
|
||||
}
|
||||
if let Some(value) = rerank.return_documents {
|
||||
output.insert("return_documents".to_string(), Value::Bool(value));
|
||||
}
|
||||
output.extend(namespace_extension_object(
|
||||
&rerank.extensions,
|
||||
namespace,
|
||||
&output,
|
||||
));
|
||||
Some(Value::Object(output))
|
||||
}
|
||||
|
||||
fn rerank_document_is_empty(value: &Value) -> bool {
|
||||
match value {
|
||||
Value::String(text) => text.trim().is_empty(),
|
||||
@@ -4419,265 +4275,6 @@ fn rerank_document_is_empty(value: &Value) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn mapped_rerank_model(canonical: &CanonicalRequest, mapped_model: &str) -> String {
|
||||
mapped_model
|
||||
.trim()
|
||||
.chars()
|
||||
.next()
|
||||
.map(|_| mapped_model.trim().to_string())
|
||||
.unwrap_or_else(|| canonical.model.clone())
|
||||
}
|
||||
|
||||
fn canonical_to_openai_embedding_request(
|
||||
canonical: &CanonicalRequest,
|
||||
mapped_model: &str,
|
||||
) -> Option<Value> {
|
||||
canonical_to_openai_like_embedding_request(canonical, mapped_model, "openai", false)
|
||||
}
|
||||
|
||||
fn canonical_to_jina_embedding_request(
|
||||
canonical: &CanonicalRequest,
|
||||
mapped_model: &str,
|
||||
) -> Option<Value> {
|
||||
canonical_to_openai_like_embedding_request(canonical, mapped_model, "jina", true)
|
||||
}
|
||||
|
||||
fn canonical_to_openai_like_embedding_request(
|
||||
canonical: &CanonicalRequest,
|
||||
mapped_model: &str,
|
||||
namespace: &str,
|
||||
default_task: bool,
|
||||
) -> Option<Value> {
|
||||
let embedding = canonical.embedding.as_ref()?;
|
||||
if embedding.input.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut output = Map::new();
|
||||
output.insert(
|
||||
"model".to_string(),
|
||||
Value::String(mapped_embedding_model(canonical, mapped_model)),
|
||||
);
|
||||
output.insert(
|
||||
"input".to_string(),
|
||||
serde_json::to_value(&embedding.input).ok()?,
|
||||
);
|
||||
if let Some(value) = &embedding.encoding_format {
|
||||
output.insert("encoding_format".to_string(), Value::String(value.clone()));
|
||||
}
|
||||
if let Some(value) = embedding.dimensions {
|
||||
output.insert("dimensions".to_string(), Value::from(value));
|
||||
}
|
||||
if let Some(value) = &embedding.user {
|
||||
output.insert("user".to_string(), Value::String(value.clone()));
|
||||
}
|
||||
if let Some(task) = embedding
|
||||
.task
|
||||
.as_ref()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
{
|
||||
output.insert("task".to_string(), Value::String(task.clone()));
|
||||
} else if default_task {
|
||||
output.insert(
|
||||
"task".to_string(),
|
||||
Value::String("text-matching".to_string()),
|
||||
);
|
||||
}
|
||||
output.extend(namespace_extension_object(
|
||||
&embedding.extensions,
|
||||
namespace,
|
||||
&output,
|
||||
));
|
||||
Some(Value::Object(output))
|
||||
}
|
||||
|
||||
fn canonical_to_gemini_embedding_request(
|
||||
canonical: &CanonicalRequest,
|
||||
mapped_model: &str,
|
||||
) -> Option<Value> {
|
||||
let embedding = canonical.embedding.as_ref()?;
|
||||
let items = embedding.input.as_string_items()?;
|
||||
if items.is_empty() || items.iter().any(|value| value.trim().is_empty()) {
|
||||
return None;
|
||||
}
|
||||
let model = mapped_embedding_model(canonical, mapped_model);
|
||||
if items.len() == 1 {
|
||||
return Some(json!({
|
||||
"model": model,
|
||||
"content": {
|
||||
"parts": [{"text": items[0]}]
|
||||
}
|
||||
}));
|
||||
}
|
||||
Some(json!({
|
||||
"model": model,
|
||||
"requests": items.into_iter().map(|text| {
|
||||
json!({
|
||||
"model": model,
|
||||
"content": {
|
||||
"parts": [{"text": text}]
|
||||
}
|
||||
})
|
||||
}).collect::<Vec<_>>()
|
||||
}))
|
||||
}
|
||||
|
||||
fn canonical_to_doubao_embedding_request(
|
||||
canonical: &CanonicalRequest,
|
||||
mapped_model: &str,
|
||||
) -> Option<Value> {
|
||||
let embedding = canonical.embedding.as_ref()?;
|
||||
let items = embedding.input.as_string_items()?;
|
||||
if items.is_empty() || items.iter().any(|value| value.trim().is_empty()) {
|
||||
return None;
|
||||
}
|
||||
let mut output = Map::new();
|
||||
output.insert(
|
||||
"model".to_string(),
|
||||
Value::String(mapped_embedding_model(canonical, mapped_model)),
|
||||
);
|
||||
output.insert(
|
||||
"input".to_string(),
|
||||
Value::Array(
|
||||
items
|
||||
.into_iter()
|
||||
.map(|text| json!({"type": "text", "text": text}))
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
if let Some(dimensions) = embedding.dimensions {
|
||||
output.insert("dimensions".to_string(), Value::from(dimensions));
|
||||
}
|
||||
output.extend(namespace_extension_object(
|
||||
&embedding.extensions,
|
||||
"doubao",
|
||||
&output,
|
||||
));
|
||||
Some(Value::Object(output))
|
||||
}
|
||||
|
||||
fn embedding_response_from_raw(
|
||||
body_json: &Value,
|
||||
namespace: &str,
|
||||
) -> Option<CanonicalEmbeddingResponse> {
|
||||
let body = body_json.as_object()?;
|
||||
if body.contains_key("error") {
|
||||
return None;
|
||||
}
|
||||
let data = body.get("data")?.as_array()?;
|
||||
let mut embeddings = Vec::new();
|
||||
for (fallback_index, item) in data.iter().enumerate() {
|
||||
let item_object = item.as_object()?;
|
||||
let values = item_object.get("embedding")?.as_array()?;
|
||||
let embedding = values
|
||||
.iter()
|
||||
.map(Value::as_f64)
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
embeddings.push(CanonicalEmbedding {
|
||||
index: item_object
|
||||
.get("index")
|
||||
.and_then(Value::as_u64)
|
||||
.and_then(|value| usize::try_from(value).ok())
|
||||
.unwrap_or(fallback_index),
|
||||
embedding,
|
||||
extensions: namespace_extensions(
|
||||
namespace,
|
||||
item_object,
|
||||
&["object", "index", "embedding"],
|
||||
),
|
||||
});
|
||||
}
|
||||
Some(CanonicalEmbeddingResponse {
|
||||
id: body
|
||||
.get("id")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("embd-unknown")
|
||||
.to_string(),
|
||||
model: body
|
||||
.get("model")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("unknown")
|
||||
.to_string(),
|
||||
embeddings,
|
||||
usage: openai_usage_to_canonical(body.get("usage")),
|
||||
extensions: namespace_extensions(
|
||||
namespace,
|
||||
body,
|
||||
&["id", "object", "model", "data", "usage"],
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
fn canonical_to_openai_embedding_response(
|
||||
canonical: &CanonicalEmbeddingResponse,
|
||||
namespace: &str,
|
||||
) -> Value {
|
||||
let mut response = Map::new();
|
||||
response.insert("object".to_string(), Value::String("list".to_string()));
|
||||
if !canonical.model.trim().is_empty() && canonical.model != "unknown" {
|
||||
response.insert("model".to_string(), Value::String(canonical.model.clone()));
|
||||
}
|
||||
response.insert(
|
||||
"data".to_string(),
|
||||
Value::Array(
|
||||
canonical
|
||||
.embeddings
|
||||
.iter()
|
||||
.map(|embedding| {
|
||||
let mut item = Map::new();
|
||||
item.insert("object".to_string(), Value::String("embedding".to_string()));
|
||||
item.insert("index".to_string(), Value::from(embedding.index as u64));
|
||||
item.insert("embedding".to_string(), json!(embedding.embedding));
|
||||
item.extend(namespace_extension_object(
|
||||
&embedding.extensions,
|
||||
namespace,
|
||||
&item,
|
||||
));
|
||||
Value::Object(item)
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
if let Some(usage) = &canonical.usage {
|
||||
response.insert("usage".to_string(), canonical_usage_to_openai(usage));
|
||||
}
|
||||
response.extend(namespace_extension_object(
|
||||
&canonical.extensions,
|
||||
namespace,
|
||||
&response,
|
||||
));
|
||||
Value::Object(response)
|
||||
}
|
||||
|
||||
fn mapped_embedding_model(canonical: &CanonicalRequest, mapped_model: &str) -> String {
|
||||
let mapped_model = mapped_model.trim();
|
||||
if mapped_model.is_empty() {
|
||||
canonical.model.clone()
|
||||
} else {
|
||||
mapped_model.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn namespace_extensions(
|
||||
namespace: &str,
|
||||
object: &Map<String, Value>,
|
||||
handled_keys: &[&str],
|
||||
) -> BTreeMap<String, Value> {
|
||||
let handled = handled_keys
|
||||
.iter()
|
||||
.copied()
|
||||
.collect::<std::collections::BTreeSet<_>>();
|
||||
let raw = object
|
||||
.iter()
|
||||
.filter(|(key, _)| !handled.contains(key.as_str()))
|
||||
.map(|(key, value)| (key.clone(), value.clone()))
|
||||
.collect::<Map<String, Value>>();
|
||||
if raw.is_empty() {
|
||||
BTreeMap::new()
|
||||
} else {
|
||||
BTreeMap::from([(namespace.to_string(), Value::Object(raw))])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
//! Pairwise format conversion entry points.
|
||||
//!
|
||||
//! The public helpers in this module route through the registry, so request and
|
||||
//! response conversion still pass through the typed canonical IR before a target
|
||||
//! wire format is emitted.
|
||||
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
@@ -1,7 +1,2 @@
|
||||
pub mod canonical;
|
||||
pub mod context;
|
||||
pub mod conversion;
|
||||
pub mod formats;
|
||||
pub mod matrix;
|
||||
pub mod registry;
|
||||
pub mod stream;
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::provider_compat::kiro_stream::{
|
||||
find_kiro_real_thinking_start_tag, KIRO_MAX_THINKING_BUFFER,
|
||||
};
|
||||
|
||||
use crate::response::AiSurfaceFinalizeError;
|
||||
use crate::formats::shared::AiSurfaceFinalizeError;
|
||||
|
||||
use super::super::AwsEventFrame;
|
||||
use super::super::KiroClaudeStreamState;
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::provider_compat::kiro_stream::{
|
||||
find_kiro_real_thinking_end_tag_at_buffer_end,
|
||||
};
|
||||
|
||||
use crate::response::AiSurfaceFinalizeError;
|
||||
use crate::formats::shared::AiSurfaceFinalizeError;
|
||||
|
||||
use super::super::KiroClaudeStreamState;
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use serde_json::Value;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::formats::shared::AiSurfaceFinalizeError;
|
||||
use crate::provider_compat::kiro_stream::{
|
||||
build_kiro_initial_sse_events, build_kiro_stream_error_sse_events, encode_kiro_sse_events,
|
||||
};
|
||||
use crate::response::AiSurfaceFinalizeError;
|
||||
|
||||
use super::super::{EventStreamDecoder, KiroClaudeStreamState, KiroToClaudeCliStreamState};
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ use std::collections::BTreeMap;
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::formats::shared::AiSurfaceFinalizeError;
|
||||
use crate::provider_compat::kiro_stream::KiroToClaudeCliStreamState;
|
||||
use crate::response::AiSurfaceFinalizeError;
|
||||
|
||||
use super::surfaces::{
|
||||
provider_adaptation_allows_sync_finalize_envelope, provider_adaptation_descriptor_for_envelope,
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
pub mod common;
|
||||
pub mod matrix;
|
||||
pub mod model_directives;
|
||||
pub mod openai;
|
||||
pub mod passthrough;
|
||||
pub mod route;
|
||||
pub mod specialized;
|
||||
pub mod standard;
|
||||
@@ -1 +0,0 @@
|
||||
pub mod provider;
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod files;
|
||||
pub mod image;
|
||||
pub mod video;
|
||||
@@ -1,54 +0,0 @@
|
||||
use crate::contracts::{GEMINI_VIDEO_CREATE_SYNC_PLAN_KIND, OPENAI_VIDEO_CREATE_SYNC_PLAN_KIND};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LocalVideoCreateFamily {
|
||||
OpenAi,
|
||||
Gemini,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LocalVideoCreateSpec {
|
||||
pub api_format: &'static str,
|
||||
pub decision_kind: &'static str,
|
||||
pub report_kind: &'static str,
|
||||
pub family: LocalVideoCreateFamily,
|
||||
}
|
||||
|
||||
pub fn resolve_sync_spec(plan_kind: &str) -> Option<LocalVideoCreateSpec> {
|
||||
match plan_kind {
|
||||
OPENAI_VIDEO_CREATE_SYNC_PLAN_KIND => Some(LocalVideoCreateSpec {
|
||||
api_format: "openai:video",
|
||||
decision_kind: OPENAI_VIDEO_CREATE_SYNC_PLAN_KIND,
|
||||
report_kind: "openai_video_create_sync_finalize",
|
||||
family: LocalVideoCreateFamily::OpenAi,
|
||||
}),
|
||||
GEMINI_VIDEO_CREATE_SYNC_PLAN_KIND => Some(LocalVideoCreateSpec {
|
||||
api_format: "gemini:video",
|
||||
decision_kind: GEMINI_VIDEO_CREATE_SYNC_PLAN_KIND,
|
||||
report_kind: "gemini_video_create_sync_finalize",
|
||||
family: LocalVideoCreateFamily::Gemini,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{resolve_sync_spec, LocalVideoCreateFamily};
|
||||
|
||||
#[test]
|
||||
fn resolves_openai_video_create_spec() {
|
||||
let spec = resolve_sync_spec("openai_video_create_sync").expect("spec");
|
||||
assert_eq!(spec.api_format, "openai:video");
|
||||
assert_eq!(spec.family, LocalVideoCreateFamily::OpenAi);
|
||||
assert_eq!(spec.report_kind, "openai_video_create_sync_finalize");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolves_gemini_video_create_spec() {
|
||||
let spec = resolve_sync_spec("gemini_video_create_sync").expect("spec");
|
||||
assert_eq!(spec.api_format, "gemini:video");
|
||||
assert_eq!(spec.family, LocalVideoCreateFamily::Gemini);
|
||||
assert_eq!(spec.report_kind, "gemini_video_create_sync_finalize");
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
pub mod chat;
|
||||
pub mod cli;
|
||||
|
||||
use crate::request::standard::LocalStandardSpec;
|
||||
|
||||
pub fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
|
||||
chat::resolve_sync_spec(plan_kind).or_else(|| cli::resolve_sync_spec(plan_kind))
|
||||
}
|
||||
|
||||
pub fn resolve_stream_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
|
||||
chat::resolve_stream_spec(plan_kind).or_else(|| cli::resolve_stream_spec(plan_kind))
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
pub mod chat;
|
||||
pub mod cli;
|
||||
|
||||
use crate::request::standard::LocalStandardSpec;
|
||||
|
||||
pub fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
|
||||
chat::resolve_sync_spec(plan_kind).or_else(|| cli::resolve_sync_spec(plan_kind))
|
||||
}
|
||||
|
||||
pub fn resolve_stream_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
|
||||
chat::resolve_stream_spec(plan_kind).or_else(|| cli::resolve_stream_spec(plan_kind))
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
pub mod claude;
|
||||
pub mod codex;
|
||||
pub mod family;
|
||||
pub mod gemini;
|
||||
pub mod matrix;
|
||||
pub mod normalize;
|
||||
pub mod openai_responses;
|
||||
|
||||
pub use codex::{
|
||||
apply_codex_openai_responses_special_body_edits, apply_codex_openai_responses_special_headers,
|
||||
apply_openai_responses_compact_special_body_edits, CODEX_OPENAI_IMAGE_DEFAULT_MODEL,
|
||||
CODEX_OPENAI_IMAGE_DEFAULT_OUTPUT_FORMAT, CODEX_OPENAI_IMAGE_DEFAULT_VARIATION_MODEL,
|
||||
CODEX_OPENAI_IMAGE_DEFAULT_VARIATION_PROMPT, CODEX_OPENAI_IMAGE_INTERNAL_MODEL,
|
||||
};
|
||||
pub use family::{LocalStandardSourceFamily, LocalStandardSourceMode, LocalStandardSpec};
|
||||
pub use matrix::{
|
||||
build_standard_request_body, build_standard_request_body_from_canonical_with_model_directives,
|
||||
build_standard_request_body_with_model_directives,
|
||||
build_standard_request_body_with_model_directives_and_request_headers,
|
||||
normalize_standard_request_to_openai_chat_request,
|
||||
};
|
||||
pub use normalize::{
|
||||
build_cross_format_openai_chat_request_body,
|
||||
build_cross_format_openai_chat_request_body_with_model_directives,
|
||||
build_cross_format_openai_responses_request_body,
|
||||
build_cross_format_openai_responses_request_body_with_model_directives,
|
||||
build_local_openai_chat_request_body,
|
||||
build_local_openai_chat_request_body_with_model_directives,
|
||||
build_local_openai_responses_request_body,
|
||||
build_local_openai_responses_request_body_with_model_directives,
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
pub mod stream;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user