mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
feat(transport): 暴露同格式 provider 的细分失败原因并在时间线展示
- claude_code/kiro/vertex policy 新增 transport_unsupported_reason_with_network 变体,返回具体失败码 - planner candidate_metadata 在 request_pair 写入同格式 provider 的细分 transport 原因 - 前端请求时间线优先展示 transport_diagnostics 的细分原因而非通用 transport_unsupported
This commit is contained in:
@@ -3,6 +3,7 @@ use aether_scheduler_core::SchedulerMinimalCandidateSelectionCandidate;
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
use crate::ai_pipeline::planner::candidate_eligibility::EligibleLocalExecutionCandidate;
|
||||
use crate::ai_pipeline::planner::passthrough::resolve_same_format_provider_transport_unsupported_reason_for_trace;
|
||||
use crate::ai_pipeline::transport::{
|
||||
body_rules_are_locally_supported, header_rules_are_locally_supported,
|
||||
local_gemini_transport_unsupported_reason_with_network,
|
||||
@@ -343,6 +344,14 @@ fn resolve_request_transport_unsupported_reason(
|
||||
let client_api_format = client_api_format.trim().to_ascii_lowercase();
|
||||
let provider_api_format = provider_api_format.trim().to_ascii_lowercase();
|
||||
if client_api_format == provider_api_format {
|
||||
if let Some(skip_reason) =
|
||||
resolve_same_format_provider_transport_unsupported_reason_for_trace(
|
||||
transport,
|
||||
provider_api_format.as_str(),
|
||||
)
|
||||
{
|
||||
return Some(skip_reason);
|
||||
}
|
||||
return match provider_api_format.as_str() {
|
||||
"openai:chat" => local_openai_chat_transport_unsupported_reason(transport),
|
||||
"gemini:chat" | "gemini:cli" => local_gemini_transport_unsupported_reason_with_network(
|
||||
@@ -456,6 +465,59 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_claude_code_transport_without_auth() -> GatewayProviderTransportSnapshot {
|
||||
GatewayProviderTransportSnapshot {
|
||||
provider: GatewayProviderTransportProvider {
|
||||
id: "provider-cc-1".to_string(),
|
||||
name: "NekoCode".to_string(),
|
||||
provider_type: "claude_code".to_string(),
|
||||
website: Some("https://nekocode.ai".to_string()),
|
||||
is_active: true,
|
||||
keep_priority_on_conversion: false,
|
||||
enable_format_conversion: true,
|
||||
concurrent_limit: None,
|
||||
max_retries: None,
|
||||
proxy: None,
|
||||
request_timeout_secs: None,
|
||||
stream_first_byte_timeout_secs: None,
|
||||
config: None,
|
||||
},
|
||||
endpoint: GatewayProviderTransportEndpoint {
|
||||
id: "endpoint-cc-1".to_string(),
|
||||
provider_id: "provider-cc-1".to_string(),
|
||||
api_format: "claude:cli".to_string(),
|
||||
api_family: Some("claude".to_string()),
|
||||
endpoint_kind: Some("cli".to_string()),
|
||||
is_active: true,
|
||||
base_url: "https://api.anthropic.com".to_string(),
|
||||
header_rules: None,
|
||||
body_rules: None,
|
||||
max_retries: None,
|
||||
custom_path: None,
|
||||
config: None,
|
||||
format_acceptance_config: None,
|
||||
proxy: None,
|
||||
},
|
||||
key: GatewayProviderTransportKey {
|
||||
id: "key-cc-1".to_string(),
|
||||
provider_id: "provider-cc-1".to_string(),
|
||||
name: "CC-特价-0.4".to_string(),
|
||||
auth_type: "api_key".to_string(),
|
||||
is_active: true,
|
||||
api_formats: Some(vec!["claude:cli".to_string()]),
|
||||
allowed_models: None,
|
||||
capabilities: None,
|
||||
rate_multipliers: None,
|
||||
global_priority_by_format: None,
|
||||
expires_at_unix_secs: None,
|
||||
proxy: None,
|
||||
fingerprint: None,
|
||||
decrypted_api_key: "__placeholder__".to_string(),
|
||||
decrypted_auth_config: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn candidate_contract_metadata_includes_transport_diagnostics() {
|
||||
let metadata = build_local_execution_candidate_contract_metadata_for_candidate(
|
||||
@@ -503,4 +565,20 @@ mod tests {
|
||||
Value::Bool(false)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn candidate_metadata_uses_same_format_provider_specific_transport_reason() {
|
||||
let metadata = build_local_execution_candidate_metadata_for_candidate(
|
||||
&sample_candidate(),
|
||||
Some(&sample_claude_code_transport_without_auth()),
|
||||
"claude:cli",
|
||||
"claude:cli",
|
||||
serde_json::Map::new(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
metadata["transport_diagnostics"]["request_pair"]["transport_unsupported_reason"],
|
||||
Value::String("transport_auth_unavailable".to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,5 +7,6 @@ pub(crate) use self::provider::{
|
||||
build_local_sync_plan_and_reports as build_local_same_format_sync_plan_and_reports,
|
||||
maybe_build_stream_local_same_format_provider_decision_payload,
|
||||
maybe_build_sync_local_same_format_provider_decision_payload,
|
||||
resolve_same_format_provider_transport_unsupported_reason_for_trace,
|
||||
};
|
||||
pub(crate) use crate::ai_pipeline::transport::provider_types::provider_type_supports_local_same_format_transport;
|
||||
|
||||
@@ -65,11 +65,12 @@ mod family;
|
||||
mod plans;
|
||||
mod request;
|
||||
|
||||
pub(super) use self::family::{
|
||||
pub(crate) use self::family::{
|
||||
materialize_local_same_format_provider_candidate_attempts,
|
||||
maybe_build_local_same_format_provider_decision_payload_for_candidate,
|
||||
resolve_local_same_format_provider_decision_input, LocalSameFormatProviderFamily,
|
||||
LocalSameFormatProviderSpec,
|
||||
resolve_local_same_format_provider_decision_input,
|
||||
resolve_same_format_provider_transport_unsupported_reason_for_trace,
|
||||
LocalSameFormatProviderFamily, LocalSameFormatProviderSpec,
|
||||
};
|
||||
pub(crate) use self::family::{
|
||||
maybe_build_stream_local_same_format_provider_decision_payload,
|
||||
|
||||
@@ -12,6 +12,7 @@ pub(crate) use self::candidates::{
|
||||
resolve_local_same_format_provider_decision_input,
|
||||
};
|
||||
pub(crate) use self::payload::maybe_build_local_same_format_provider_decision_payload_for_candidate;
|
||||
pub(crate) use self::request::resolve_same_format_provider_transport_unsupported_reason_for_trace;
|
||||
pub(crate) use crate::ai_pipeline::planner::candidate_materialization::LocalExecutionCandidateAttempt as LocalSameFormatProviderCandidateAttempt;
|
||||
pub(crate) use crate::ai_pipeline::planner::decision_input::LocalRequestedModelDecisionInput as LocalSameFormatProviderDecisionInput;
|
||||
pub(crate) use crate::ai_pipeline::{LocalSameFormatProviderFamily, LocalSameFormatProviderSpec};
|
||||
|
||||
@@ -26,6 +26,51 @@ use super::{
|
||||
LocalSameFormatProviderSpec,
|
||||
};
|
||||
|
||||
pub(crate) fn resolve_same_format_provider_transport_unsupported_reason_for_trace(
|
||||
transport: &GatewayProviderTransportSnapshot,
|
||||
provider_api_format: &str,
|
||||
) -> Option<&'static str> {
|
||||
let provider_api_format = match provider_api_format.trim().to_ascii_lowercase().as_str() {
|
||||
"openai:chat" => "openai:chat",
|
||||
"openai:cli" => "openai:cli",
|
||||
"openai:compact" => "openai:compact",
|
||||
"claude:chat" => "claude:chat",
|
||||
"claude:cli" => "claude:cli",
|
||||
"gemini:chat" => "gemini:chat",
|
||||
"gemini:cli" => "gemini:cli",
|
||||
_ => return Some("transport_api_format_unsupported"),
|
||||
};
|
||||
let behavior = policy::classify_same_format_provider_request_behavior(
|
||||
transport,
|
||||
crate::ai_pipeline::planner::spec_metadata::LocalExecutionSurfaceSpecMetadata {
|
||||
api_format: provider_api_format,
|
||||
require_streaming: false,
|
||||
requested_model_family: None,
|
||||
decision_kind: "trace_candidate_metadata",
|
||||
report_kind: Some("trace_candidate_metadata"),
|
||||
},
|
||||
);
|
||||
if !behavior.is_antigravity
|
||||
&& !behavior.is_claude_code
|
||||
&& !behavior.is_vertex
|
||||
&& !behavior.is_kiro
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let family = if provider_api_format.starts_with("gemini:") {
|
||||
crate::ai_pipeline::LocalSameFormatProviderFamily::Gemini
|
||||
} else {
|
||||
crate::ai_pipeline::LocalSameFormatProviderFamily::Standard
|
||||
};
|
||||
policy::same_format_provider_transport_unsupported_reason(
|
||||
&behavior,
|
||||
transport,
|
||||
family,
|
||||
provider_api_format,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) struct LocalSameFormatProviderCandidatePayloadParts {
|
||||
pub(super) transport: GatewayProviderTransportSnapshot,
|
||||
pub(super) is_antigravity: bool,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use crate::ai_pipeline::planner::spec_metadata::LocalExecutionSurfaceSpecMetadata;
|
||||
use crate::ai_pipeline::transport::auth::{resolve_local_gemini_auth, resolve_local_standard_auth};
|
||||
use crate::ai_pipeline::transport::claude_code::supports_local_claude_code_transport_with_network;
|
||||
use crate::ai_pipeline::transport::kiro::supports_local_kiro_request_transport_with_network;
|
||||
use crate::ai_pipeline::transport::claude_code::local_claude_code_transport_unsupported_reason_with_network;
|
||||
use crate::ai_pipeline::transport::kiro::local_kiro_request_transport_unsupported_reason_with_network;
|
||||
use crate::ai_pipeline::transport::policy::{
|
||||
supports_local_gemini_transport_with_network, supports_local_standard_transport_with_network,
|
||||
local_gemini_transport_unsupported_reason_with_network,
|
||||
local_standard_transport_unsupported_reason_with_network,
|
||||
};
|
||||
use crate::ai_pipeline::transport::vertex::supports_local_vertex_api_key_gemini_transport_with_network;
|
||||
use crate::ai_pipeline::transport::vertex::local_vertex_api_key_gemini_transport_unsupported_reason_with_network;
|
||||
use crate::ai_pipeline::GatewayProviderTransportSnapshot;
|
||||
|
||||
use super::super::LocalSameFormatProviderFamily;
|
||||
@@ -75,21 +76,31 @@ pub(super) fn same_format_provider_transport_supported(
|
||||
family: LocalSameFormatProviderFamily,
|
||||
api_format: &str,
|
||||
) -> bool {
|
||||
same_format_provider_transport_unsupported_reason(behavior, transport, family, api_format)
|
||||
.is_none()
|
||||
}
|
||||
|
||||
pub(super) fn same_format_provider_transport_unsupported_reason(
|
||||
behavior: &SameFormatProviderRequestBehavior,
|
||||
transport: &GatewayProviderTransportSnapshot,
|
||||
family: LocalSameFormatProviderFamily,
|
||||
api_format: &str,
|
||||
) -> Option<&'static str> {
|
||||
if behavior.is_kiro {
|
||||
supports_local_kiro_request_transport_with_network(transport)
|
||||
local_kiro_request_transport_unsupported_reason_with_network(transport)
|
||||
} else if behavior.is_antigravity {
|
||||
true
|
||||
None
|
||||
} else if behavior.is_claude_code {
|
||||
supports_local_claude_code_transport_with_network(transport, api_format)
|
||||
local_claude_code_transport_unsupported_reason_with_network(transport, api_format)
|
||||
} else if behavior.is_vertex {
|
||||
supports_local_vertex_api_key_gemini_transport_with_network(transport)
|
||||
local_vertex_api_key_gemini_transport_unsupported_reason_with_network(transport)
|
||||
} else {
|
||||
match family {
|
||||
LocalSameFormatProviderFamily::Standard => {
|
||||
supports_local_standard_transport_with_network(transport, api_format)
|
||||
local_standard_transport_unsupported_reason_with_network(transport, api_format)
|
||||
}
|
||||
LocalSameFormatProviderFamily::Gemini => {
|
||||
supports_local_gemini_transport_with_network(transport, api_format)
|
||||
local_gemini_transport_unsupported_reason_with_network(transport, api_format)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ use super::super::LocalSameFormatProviderDecisionInput;
|
||||
use super::super::LocalSameFormatProviderSpec;
|
||||
use super::policy::{
|
||||
classify_same_format_provider_request_behavior, resolve_same_format_provider_direct_auth,
|
||||
same_format_provider_transport_supported, should_try_same_format_provider_oauth_auth,
|
||||
same_format_provider_transport_supported, same_format_provider_transport_unsupported_reason,
|
||||
should_try_same_format_provider_oauth_auth,
|
||||
};
|
||||
|
||||
pub(super) struct PreparedSameFormatProviderCandidate {
|
||||
@@ -52,6 +53,13 @@ pub(super) async fn prepare_local_same_format_provider_candidate(
|
||||
spec.family,
|
||||
spec_metadata.api_format,
|
||||
) {
|
||||
let skip_reason = same_format_provider_transport_unsupported_reason(
|
||||
&behavior,
|
||||
&transport,
|
||||
spec.family,
|
||||
spec_metadata.api_format,
|
||||
)
|
||||
.unwrap_or("transport_unsupported");
|
||||
super::super::payload::mark_skipped_local_same_format_provider_candidate(
|
||||
state,
|
||||
input,
|
||||
@@ -59,7 +67,7 @@ pub(super) async fn prepare_local_same_format_provider_candidate(
|
||||
candidate,
|
||||
candidate_index,
|
||||
candidate_id,
|
||||
"transport_unsupported",
|
||||
skip_reason,
|
||||
)
|
||||
.await;
|
||||
return None;
|
||||
|
||||
Reference in New Issue
Block a user