feat(gateway): route OpenAI image streams through chat bridge

This commit is contained in:
ZheFox
2026-05-17 23:09:50 +08:00
parent d6c8c14de7
commit 680b617b00
12 changed files with 1699 additions and 105 deletions

View File

@@ -814,10 +814,10 @@ async fn resolve_openai_chat_to_openai_image_payload_parts(
}
let (execution_strategy, conversion_mode) =
ai_local_execution_contract_for_formats("openai:image", provider_api_format);
ai_local_execution_contract_for_formats("openai:chat", provider_api_format);
Ok(Some(LocalOpenAiChatCandidatePayloadParts {
client_api_format: "openai:image".to_string(),
client_api_format: "openai:chat".to_string(),
auth_header: prepared_candidate.auth_header,
auth_value: prepared_candidate.auth_value,
mapped_model: prepared_candidate.mapped_model,
@@ -827,7 +827,7 @@ async fn resolve_openai_chat_to_openai_image_payload_parts(
upstream_url,
execution_strategy,
conversion_mode,
report_kind: "openai_image_stream_success".to_string(),
report_kind: "openai_chat_stream_success".to_string(),
envelope_name: None,
transport: Arc::clone(transport),
request_redacted: false,

View File

@@ -386,7 +386,7 @@ pub(crate) async fn build_local_openai_chat_image_candidate_attempt_source<'a>(
Ok(build_local_execution_candidate_attempt_source_with_serving(
planner_state,
trace_id,
"openai:image",
"openai:chat",
Some(&input.requested_model),
Some(&input.auth_snapshot),
input.client_session_affinity.as_ref(),
@@ -400,12 +400,12 @@ pub(crate) async fn build_local_openai_chat_image_candidate_attempt_source<'a>(
|eligible| {
let provider_api_format = eligible.provider_api_format.clone();
let (execution_strategy, conversion_mode) =
ai_local_execution_contract_for_formats("openai:image", &provider_api_format);
ai_local_execution_contract_for_formats("openai:chat", &provider_api_format);
Some(build_local_execution_candidate_contract_metadata(
LocalExecutionCandidateMetadataParts {
eligible,
provider_api_format: provider_api_format.as_str(),
client_api_format: "openai:image",
client_api_format: "openai:chat",
extra_fields: serde_json::Map::new(),
},
execution_strategy,
@@ -426,13 +426,13 @@ pub(crate) async fn build_local_openai_chat_image_candidate_attempt_source<'a>(
.to_ascii_lowercase()
});
let (execution_strategy, conversion_mode) =
ai_local_execution_contract_for_formats("openai:image", &provider_api_format);
ai_local_execution_contract_for_formats("openai:chat", &provider_api_format);
skipped_candidate.extra_data = Some(
build_local_execution_candidate_contract_metadata_for_candidate(
&skipped_candidate.candidate,
skipped_candidate.transport_ref(),
provider_api_format.as_str(),
"openai:image",
"openai:chat",
serde_json::Map::new(),
execution_strategy,
conversion_mode,

View File

@@ -1104,8 +1104,10 @@ async fn gateway_routes_openai_chat_stream_image_intent_to_openai_image_plan_wit
StatusCode::OK,
"{response_text}\n{stored_candidates:#?}"
);
assert!(response_text.contains("image_generation.completed"));
assert!(response_text.contains("aGVsbG8="));
assert!(response_text.contains("\"object\":\"chat.completion.chunk\""));
assert!(response_text.contains("![generated image](data:image/png;base64,aGVsbG8=)"));
assert!(response_text.contains("data: [DONE]"));
assert!(!response_text.contains("image_generation.completed"));
let seen_plan = seen_execution_plan
.lock()
@@ -1113,7 +1115,7 @@ async fn gateway_routes_openai_chat_stream_image_intent_to_openai_image_plan_wit
.clone()
.expect("execution plan should be captured");
assert_eq!(seen_plan.trace_id, "trace-chat-stream-image-bridge-123");
assert_eq!(seen_plan.client_api_format, "openai:image");
assert_eq!(seen_plan.client_api_format, "openai:chat");
assert_eq!(seen_plan.provider_api_format, "openai:image");
assert_eq!(seen_plan.url, "https://images.example.com/v1/responses");
assert!(seen_plan.plan_stream);