mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
fix(provider): 修复 Windsurf PR CI 失败
This commit is contained in:
@@ -934,7 +934,11 @@ async fn resolve_openai_chat_to_openai_image_payload_parts(
|
|||||||
let upstream_url = if is_chatgpt_web {
|
let upstream_url = if is_chatgpt_web {
|
||||||
chatgpt_web_image_internal_url(&transport.endpoint.base_url)
|
chatgpt_web_image_internal_url(&transport.endpoint.base_url)
|
||||||
} else {
|
} else {
|
||||||
build_openai_image_upstream_url(transport, parts.uri.query())
|
build_openai_image_upstream_url(
|
||||||
|
transport,
|
||||||
|
Some("/v1/images/generations"),
|
||||||
|
parts.uri.query(),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
let Some(mut provider_request_headers) =
|
let Some(mut provider_request_headers) =
|
||||||
build_openai_image_headers(ProviderOpenAiImageHeadersInput {
|
build_openai_image_headers(ProviderOpenAiImageHeadersInput {
|
||||||
|
|||||||
@@ -1727,7 +1727,7 @@ async fn execute_execution_runtime_sync_impl(
|
|||||||
}
|
}
|
||||||
let (mut result_error_type, mut result_error_message) =
|
let (mut result_error_type, mut result_error_message) =
|
||||||
execution_error_details(result.error.as_ref(), body_json.as_ref());
|
execution_error_details(result.error.as_ref(), body_json.as_ref());
|
||||||
if result.status_code < 400 {
|
if result.status_code < 400 && body_json.is_none() {
|
||||||
if let Some(error_body_json) =
|
if let Some(error_body_json) =
|
||||||
extract_provider_private_stream_error_body(report_context.as_ref(), &body_bytes)
|
extract_provider_private_stream_error_body(report_context.as_ref(), &body_bytes)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ use serde_json::json;
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::ai_serving::api::extract_provider_private_stream_error_body;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use crate::execution_runtime::remote_compat::execute_sync_plan_via_remote_execution_runtime;
|
use crate::execution_runtime::remote_compat::execute_sync_plan_via_remote_execution_runtime;
|
||||||
use crate::execution_runtime::windsurf::maybe_execute_windsurf_sync;
|
use crate::execution_runtime::windsurf::maybe_execute_windsurf_sync;
|
||||||
@@ -1466,11 +1467,8 @@ pub(crate) fn build_execution_response_body(
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(body_json) =
|
if let Some(body_json) = extract_provider_private_stream_error_body(None, decoded_body_bytes)
|
||||||
aether_ai_formats::api::extract_provider_private_stream_error_body(None, decoded_body_bytes)
|
.or_else(|| extract_provider_private_stream_error_body(None, body_bytes))
|
||||||
.or_else(|| {
|
|
||||||
aether_ai_formats::api::extract_provider_private_stream_error_body(None, body_bytes)
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
return Ok(Some(ResponseBody {
|
return Ok(Some(ResponseBody {
|
||||||
json_body: Some(body_json),
|
json_body: Some(body_json),
|
||||||
|
|||||||
@@ -345,7 +345,6 @@ async fn prepare_windsurf_cascade(
|
|||||||
native_allowlist: native_bridge
|
native_allowlist: native_bridge
|
||||||
.map(|bridge| bridge.native_allowlist.clone())
|
.map(|bridge| bridge.native_allowlist.clone())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
..SendCascadeMessageOptions::default()
|
|
||||||
};
|
};
|
||||||
let send_payload = build_send_cascade_message_request_with_options(
|
let send_payload = build_send_cascade_message_request_with_options(
|
||||||
&input.api_key,
|
&input.api_key,
|
||||||
@@ -414,7 +413,7 @@ async fn read_windsurf_key_upstream_metadata(
|
|||||||
}
|
}
|
||||||
|
|
||||||
match state
|
match state
|
||||||
.read_provider_catalog_keys_by_ids(&[plan.key_id.clone()])
|
.read_provider_catalog_keys_by_ids(std::slice::from_ref(&plan.key_id))
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(keys) => keys
|
Ok(keys) => keys
|
||||||
@@ -1693,10 +1692,8 @@ fn build_cascade_message_with_options(
|
|||||||
};
|
};
|
||||||
let text = text.trim();
|
let text = text.trim();
|
||||||
match role {
|
match role {
|
||||||
"system" => {
|
"system" if !text.is_empty() => {
|
||||||
if !text.is_empty() {
|
system_text.push(text.to_string());
|
||||||
system_text.push(text.to_string());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"assistant" => {
|
"assistant" => {
|
||||||
let assistant_text = assistant_message_text_for_cascade(message, text, dialect);
|
let assistant_text = assistant_message_text_for_cascade(message, text, dialect);
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ use crate::handlers::admin::provider::oauth::runtime::{
|
|||||||
use crate::handlers::admin::provider::oauth::state::{
|
use crate::handlers::admin::provider::oauth::state::{
|
||||||
admin_provider_oauth_template, exchange_admin_provider_oauth_refresh_token,
|
admin_provider_oauth_template, exchange_admin_provider_oauth_refresh_token,
|
||||||
};
|
};
|
||||||
|
use crate::handlers::admin::provider::shared::support::ADMIN_PROVIDER_OAUTH_DATA_UNAVAILABLE_DETAIL;
|
||||||
use crate::handlers::admin::request::{AdminAppState, AdminProviderOAuthTemplate};
|
use crate::handlers::admin::request::{AdminAppState, AdminProviderOAuthTemplate};
|
||||||
use crate::GatewayError;
|
use crate::GatewayError;
|
||||||
use aether_admin::provider::oauth::parse_admin_provider_oauth_kiro_batch_import_entries;
|
use aether_admin::provider::oauth::parse_admin_provider_oauth_kiro_batch_import_entries;
|
||||||
@@ -167,10 +168,6 @@ async fn resolve_admin_provider_oauth_batch_import_tokens(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(template) = template else {
|
|
||||||
return Err(ADMIN_PROVIDER_OAUTH_DATA_UNAVAILABLE_DETAIL.to_string());
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(refresh_token) = refresh_token {
|
if let Some(refresh_token) = refresh_token {
|
||||||
let Some(template) = template else {
|
let Some(template) = template else {
|
||||||
if provider_type_supports_access_token_import(provider_type) {
|
if provider_type_supports_access_token_import(provider_type) {
|
||||||
@@ -301,7 +298,10 @@ pub(super) async fn execute_admin_provider_oauth_batch_import(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let template = admin_provider_oauth_template(provider_type);
|
let template = admin_provider_oauth_template(provider_type);
|
||||||
if template.is_none() && !provider_type.eq_ignore_ascii_case("windsurf") {
|
if template.is_none()
|
||||||
|
&& !provider_type.eq_ignore_ascii_case("windsurf")
|
||||||
|
&& !provider_type_supports_access_token_import(provider_type)
|
||||||
|
{
|
||||||
return Ok(AdminProviderOAuthBatchImportOutcome {
|
return Ok(AdminProviderOAuthBatchImportOutcome {
|
||||||
total: entries.len(),
|
total: entries.len(),
|
||||||
success: 0,
|
success: 0,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use super::parse::{
|
|||||||
};
|
};
|
||||||
use crate::handlers::admin::provider::oauth::errors::build_internal_control_error_response;
|
use crate::handlers::admin::provider::oauth::errors::build_internal_control_error_response;
|
||||||
use crate::handlers::admin::provider::oauth::state::{
|
use crate::handlers::admin::provider::oauth::state::{
|
||||||
build_admin_provider_oauth_backend_unavailable_response,
|
admin_provider_oauth_template, build_admin_provider_oauth_backend_unavailable_response,
|
||||||
is_fixed_provider_type_for_provider_oauth,
|
is_fixed_provider_type_for_provider_oauth,
|
||||||
};
|
};
|
||||||
use crate::handlers::admin::provider::shared::paths::admin_provider_oauth_batch_import_provider_id;
|
use crate::handlers::admin::provider::shared::paths::admin_provider_oauth_batch_import_provider_id;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use super::progress::{
|
|||||||
};
|
};
|
||||||
use crate::handlers::admin::provider::oauth::errors::build_internal_control_error_response;
|
use crate::handlers::admin::provider::oauth::errors::build_internal_control_error_response;
|
||||||
use crate::handlers::admin::provider::oauth::state::{
|
use crate::handlers::admin::provider::oauth::state::{
|
||||||
build_admin_provider_oauth_backend_unavailable_response,
|
admin_provider_oauth_template, build_admin_provider_oauth_backend_unavailable_response,
|
||||||
is_fixed_provider_type_for_provider_oauth,
|
is_fixed_provider_type_for_provider_oauth,
|
||||||
};
|
};
|
||||||
use crate::handlers::admin::provider::shared::paths::admin_provider_oauth_batch_import_task_provider_id;
|
use crate::handlers::admin::provider::shared::paths::admin_provider_oauth_batch_import_task_provider_id;
|
||||||
|
|||||||
@@ -508,9 +508,6 @@ pub(super) async fn handle_admin_provider_oauth_import_refresh_token(
|
|||||||
"Refresh Token 或 Access Token 不能为空",
|
"Refresh Token 或 Access Token 不能为空",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let Some(template) = admin_provider_oauth_template(&provider_type) else {
|
|
||||||
return Ok(build_admin_provider_oauth_backend_unavailable_response());
|
|
||||||
};
|
|
||||||
match resolve_admin_provider_oauth_single_import_tokens(
|
match resolve_admin_provider_oauth_single_import_tokens(
|
||||||
state,
|
state,
|
||||||
template,
|
template,
|
||||||
|
|||||||
@@ -2880,6 +2880,7 @@ async fn gateway_completes_admin_provider_oauth_key_locally_with_trusted_admin_p
|
|||||||
.expect("account_state_recheck_error should be string when recheck is attempted");
|
.expect("account_state_recheck_error should be string when recheck is attempted");
|
||||||
assert!(
|
assert!(
|
||||||
account_state_recheck_error == "wham/usage API 返回状态码 401"
|
account_state_recheck_error == "wham/usage API 返回状态码 401"
|
||||||
|
|| account_state_recheck_error == "wham/usage API 返回状态码 403"
|
||||||
|| account_state_recheck_error.starts_with("wham/usage 请求执行失败:"),
|
|| account_state_recheck_error.starts_with("wham/usage 请求执行失败:"),
|
||||||
"unexpected account_state_recheck_error: {account_state_recheck_error}"
|
"unexpected account_state_recheck_error: {account_state_recheck_error}"
|
||||||
);
|
);
|
||||||
@@ -5351,6 +5352,7 @@ async fn gateway_refreshes_admin_provider_oauth_key_locally_with_trusted_admin_p
|
|||||||
.expect("account_state_recheck_error should be string when attempted");
|
.expect("account_state_recheck_error should be string when attempted");
|
||||||
assert!(
|
assert!(
|
||||||
account_state_recheck_error == "wham/usage API 返回状态码 401"
|
account_state_recheck_error == "wham/usage API 返回状态码 401"
|
||||||
|
|| account_state_recheck_error == "wham/usage API 返回状态码 403"
|
||||||
|| account_state_recheck_error.starts_with("wham/usage 请求执行失败:"),
|
|| account_state_recheck_error.starts_with("wham/usage 请求执行失败:"),
|
||||||
"unexpected account_state_recheck_error: {account_state_recheck_error}"
|
"unexpected account_state_recheck_error: {account_state_recheck_error}"
|
||||||
);
|
);
|
||||||
@@ -5400,6 +5402,14 @@ async fn gateway_refreshes_admin_provider_oauth_key_locally_with_trusted_admin_p
|
|||||||
stored_key.oauth_invalid_reason.as_deref(),
|
stored_key.oauth_invalid_reason.as_deref(),
|
||||||
Some("[OAUTH_EXPIRED] Codex Token 无效或已过期 (401)")
|
Some("[OAUTH_EXPIRED] Codex Token 无效或已过期 (401)")
|
||||||
);
|
);
|
||||||
|
} else if account_state_recheck_attempted
|
||||||
|
&& payload["account_state_recheck_error"] == "wham/usage API 返回状态码 403"
|
||||||
|
{
|
||||||
|
assert!(stored_key.oauth_invalid_at_unix_secs.is_some());
|
||||||
|
assert!(stored_key
|
||||||
|
.oauth_invalid_reason
|
||||||
|
.as_deref()
|
||||||
|
.is_some_and(|reason| reason.contains("(403)")));
|
||||||
} else {
|
} else {
|
||||||
assert_eq!(stored_key.oauth_invalid_at_unix_secs, None);
|
assert_eq!(stored_key.oauth_invalid_at_unix_secs, None);
|
||||||
assert_eq!(stored_key.oauth_invalid_reason, None);
|
assert_eq!(stored_key.oauth_invalid_reason, None);
|
||||||
|
|||||||
@@ -1705,6 +1705,7 @@ mod tests {
|
|||||||
"modelUid": "claude-sonnet-4-6"
|
"modelUid": "claude-sonnet-4-6"
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
status_code: 200,
|
||||||
};
|
};
|
||||||
let outcome = fetch_models_from_transports(&runtime, &[sample_windsurf_transport()])
|
let outcome = fetch_models_from_transports(&runtime, &[sample_windsurf_transport()])
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -315,7 +315,7 @@ mod tests {
|
|||||||
let model = resolve_windsurf_model("gpt-5-5-low").expect("model should resolve");
|
let model = resolve_windsurf_model("gpt-5-5-low").expect("model should resolve");
|
||||||
|
|
||||||
assert_eq!(model.canonical_name, "gpt-5.5-low");
|
assert_eq!(model.canonical_name, "gpt-5.5-low");
|
||||||
assert_eq!(model.model_uid.as_deref(), Some("gpt-5-5-low"));
|
assert_eq!(model.model_uid, Some("gpt-5-5-low"));
|
||||||
assert_eq!(model.enum_value, 0);
|
assert_eq!(model.enum_value, 0);
|
||||||
assert_eq!(model.credit_multiplier, 1.0);
|
assert_eq!(model.credit_multiplier, 1.0);
|
||||||
}
|
}
|
||||||
@@ -325,7 +325,7 @@ mod tests {
|
|||||||
let model = resolve_windsurf_model("claude-opus-4.7").expect("model should resolve");
|
let model = resolve_windsurf_model("claude-opus-4.7").expect("model should resolve");
|
||||||
|
|
||||||
assert_eq!(model.canonical_name, "claude-opus-4-7-medium");
|
assert_eq!(model.canonical_name, "claude-opus-4-7-medium");
|
||||||
assert_eq!(model.model_uid.as_deref(), Some("claude-opus-4-7-medium"));
|
assert_eq!(model.model_uid, Some("claude-opus-4-7-medium"));
|
||||||
assert_eq!(model.enum_value, 0);
|
assert_eq!(model.enum_value, 0);
|
||||||
assert_eq!(model.credit_multiplier, 8.0);
|
assert_eq!(model.credit_multiplier, 8.0);
|
||||||
}
|
}
|
||||||
@@ -335,7 +335,7 @@ mod tests {
|
|||||||
let model = resolve_windsurf_model("gpt-5-5-low-priority").expect("model should resolve");
|
let model = resolve_windsurf_model("gpt-5-5-low-priority").expect("model should resolve");
|
||||||
|
|
||||||
assert_eq!(model.canonical_name, "gpt-5.5-low-fast");
|
assert_eq!(model.canonical_name, "gpt-5.5-low-fast");
|
||||||
assert_eq!(model.model_uid.as_deref(), Some("gpt-5-5-low-priority"));
|
assert_eq!(model.model_uid, Some("gpt-5-5-low-priority"));
|
||||||
assert_eq!(model.credit_multiplier, 2.0);
|
assert_eq!(model.credit_multiplier, 2.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,21 +343,18 @@ mod tests {
|
|||||||
fn resolves_full_gpt55_effort_ladder_and_priority_aliases() {
|
fn resolves_full_gpt55_effort_ladder_and_priority_aliases() {
|
||||||
let none = resolve_windsurf_model("gpt-5-5-none").expect("none should resolve");
|
let none = resolve_windsurf_model("gpt-5-5-none").expect("none should resolve");
|
||||||
assert_eq!(none.canonical_name, "gpt-5.5-none");
|
assert_eq!(none.canonical_name, "gpt-5.5-none");
|
||||||
assert_eq!(none.model_uid.as_deref(), Some("gpt-5-5-none"));
|
assert_eq!(none.model_uid, Some("gpt-5-5-none"));
|
||||||
assert_eq!(none.credit_multiplier, 1.0);
|
assert_eq!(none.credit_multiplier, 1.0);
|
||||||
|
|
||||||
let high = resolve_windsurf_model("gpt-5.5-high").expect("high should resolve");
|
let high = resolve_windsurf_model("gpt-5.5-high").expect("high should resolve");
|
||||||
assert_eq!(high.canonical_name, "gpt-5.5-high");
|
assert_eq!(high.canonical_name, "gpt-5.5-high");
|
||||||
assert_eq!(high.model_uid.as_deref(), Some("gpt-5-5-high"));
|
assert_eq!(high.model_uid, Some("gpt-5-5-high"));
|
||||||
assert_eq!(high.credit_multiplier, 4.0);
|
assert_eq!(high.credit_multiplier, 4.0);
|
||||||
|
|
||||||
let xhigh_fast = resolve_windsurf_model("gpt-5-5-xhigh-priority")
|
let xhigh_fast = resolve_windsurf_model("gpt-5-5-xhigh-priority")
|
||||||
.expect("xhigh priority should resolve");
|
.expect("xhigh priority should resolve");
|
||||||
assert_eq!(xhigh_fast.canonical_name, "gpt-5.5-xhigh-fast");
|
assert_eq!(xhigh_fast.canonical_name, "gpt-5.5-xhigh-fast");
|
||||||
assert_eq!(
|
assert_eq!(xhigh_fast.model_uid, Some("gpt-5-5-xhigh-priority"));
|
||||||
xhigh_fast.model_uid.as_deref(),
|
|
||||||
Some("gpt-5-5-xhigh-priority")
|
|
||||||
);
|
|
||||||
assert_eq!(xhigh_fast.credit_multiplier, 16.0);
|
assert_eq!(xhigh_fast.credit_multiplier, 16.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,14 +362,11 @@ mod tests {
|
|||||||
fn resolves_windsurfapi_catalog_aliases_beyond_gpt55() {
|
fn resolves_windsurfapi_catalog_aliases_beyond_gpt55() {
|
||||||
let gpt52_medium = resolve_windsurf_model("gpt-5.2-medium").expect("gpt-5.2 medium alias");
|
let gpt52_medium = resolve_windsurf_model("gpt-5.2-medium").expect("gpt-5.2 medium alias");
|
||||||
assert_eq!(gpt52_medium.canonical_name, "gpt-5.2");
|
assert_eq!(gpt52_medium.canonical_name, "gpt-5.2");
|
||||||
assert_eq!(
|
assert_eq!(gpt52_medium.model_uid, Some("MODEL_GPT_5_2_MEDIUM"));
|
||||||
gpt52_medium.model_uid.as_deref(),
|
|
||||||
Some("MODEL_GPT_5_2_MEDIUM")
|
|
||||||
);
|
|
||||||
|
|
||||||
let haiku = resolve_windsurf_model("claude-haiku-4-5-20251001").expect("dated haiku alias");
|
let haiku = resolve_windsurf_model("claude-haiku-4-5-20251001").expect("dated haiku alias");
|
||||||
assert_eq!(haiku.canonical_name, "claude-4.5-haiku");
|
assert_eq!(haiku.canonical_name, "claude-4.5-haiku");
|
||||||
assert_eq!(haiku.model_uid.as_deref(), Some("MODEL_PRIVATE_11"));
|
assert_eq!(haiku.model_uid, Some("MODEL_PRIVATE_11"));
|
||||||
|
|
||||||
let uid = resolve_windsurf_model("MODEL_GPT_5_2_LOW").expect("model uid alias");
|
let uid = resolve_windsurf_model("MODEL_GPT_5_2_LOW").expect("model uid alias");
|
||||||
assert_eq!(uid.canonical_name, "gpt-5.2-low");
|
assert_eq!(uid.canonical_name, "gpt-5.2-low");
|
||||||
|
|||||||
Reference in New Issue
Block a user