mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
fix(provider): 接入 Windsurf 模型测试链路
This commit is contained in:
@@ -2659,6 +2659,22 @@ async fn provider_query_execute_standard_test_candidate(
|
|||||||
route_path,
|
route_path,
|
||||||
client_api_format,
|
client_api_format,
|
||||||
);
|
);
|
||||||
|
if crate::provider_transport::is_windsurf_provider_transport(&transport)
|
||||||
|
&& provider_query_normalize_api_format_alias(candidate.endpoint.api_format.as_str())
|
||||||
|
== "openai:chat"
|
||||||
|
{
|
||||||
|
return provider_query_execute_windsurf_test_candidate(
|
||||||
|
state,
|
||||||
|
provider,
|
||||||
|
candidate,
|
||||||
|
payload,
|
||||||
|
route_path,
|
||||||
|
trace_id,
|
||||||
|
transport,
|
||||||
|
original_request_body,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
if !provider_query_transport_supports_model_test_execution(
|
if !provider_query_transport_supports_model_test_execution(
|
||||||
state,
|
state,
|
||||||
&transport,
|
&transport,
|
||||||
@@ -3066,6 +3082,165 @@ async fn provider_query_execute_standard_test_candidate(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
async fn provider_query_execute_windsurf_test_candidate(
|
||||||
|
state: &AdminAppState<'_>,
|
||||||
|
provider: &StoredProviderCatalogProvider,
|
||||||
|
candidate: &ProviderQueryTestCandidate,
|
||||||
|
payload: &Value,
|
||||||
|
route_path: &str,
|
||||||
|
trace_id: &str,
|
||||||
|
transport: AdminGatewayProviderTransportSnapshot,
|
||||||
|
original_request_body: Value,
|
||||||
|
) -> Result<ProviderQueryExecutionOutcome, GatewayError> {
|
||||||
|
if let Some(_reason) =
|
||||||
|
crate::provider_transport::local_windsurf_request_transport_unsupported_reason_with_network(
|
||||||
|
&transport,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return Ok(provider_query_skipped_execution_outcome(
|
||||||
|
original_request_body,
|
||||||
|
provider_query_standard_test_unsupported_reason(
|
||||||
|
&transport,
|
||||||
|
candidate.endpoint.api_format.as_str(),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let incoming_request_headers = provider_query_extract_request_headers(payload);
|
||||||
|
let mut request_body = original_request_body.clone();
|
||||||
|
if let Some(object) = request_body.as_object_mut() {
|
||||||
|
object.insert("stream".to_string(), Value::Bool(false));
|
||||||
|
}
|
||||||
|
let request_model =
|
||||||
|
provider_query_request_body_model(&request_body, &candidate.effective_model);
|
||||||
|
let upstream_is_stream = provider_query_resolve_standard_test_upstream_is_stream(
|
||||||
|
transport.endpoint.config.as_ref(),
|
||||||
|
transport.provider.provider_type.as_str(),
|
||||||
|
candidate.endpoint.api_format.as_str(),
|
||||||
|
);
|
||||||
|
let Some((auth_header, auth_value)) =
|
||||||
|
crate::provider_transport::windsurf::resolve_windsurf_cascade_auth(&transport).or_else(
|
||||||
|
|| crate::provider_transport::auth::resolve_local_openai_bearer_auth(&transport),
|
||||||
|
)
|
||||||
|
else {
|
||||||
|
return Ok(provider_query_skipped_execution_outcome(
|
||||||
|
request_body,
|
||||||
|
"Provider auth is unavailable for windsurf".to_string(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut synthetic_request = http::Request::builder()
|
||||||
|
.uri(route_path)
|
||||||
|
.body(())
|
||||||
|
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||||
|
*synthetic_request.headers_mut() = incoming_request_headers;
|
||||||
|
let (parts, _) = synthetic_request.into_parts();
|
||||||
|
|
||||||
|
let Some(provider_request_body) =
|
||||||
|
crate::provider_transport::build_windsurf_cascade_request_body(
|
||||||
|
&request_body,
|
||||||
|
request_model,
|
||||||
|
&auth_value,
|
||||||
|
transport.endpoint.body_rules.as_ref(),
|
||||||
|
Some(&parts.headers),
|
||||||
|
upstream_is_stream,
|
||||||
|
)
|
||||||
|
else {
|
||||||
|
return Ok(provider_query_skipped_execution_outcome(
|
||||||
|
request_body,
|
||||||
|
"Provider request body could not be built for windsurf".to_string(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let Some(request_url) = crate::provider_transport::build_windsurf_cascade_upstream_url(
|
||||||
|
transport.endpoint.base_url.as_str(),
|
||||||
|
parts.uri.query(),
|
||||||
|
) else {
|
||||||
|
return Ok(provider_query_skipped_execution_outcome(
|
||||||
|
provider_request_body,
|
||||||
|
"Provider request URL is unavailable for windsurf".to_string(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let Some(request_headers) = crate::provider_transport::build_windsurf_cascade_headers(
|
||||||
|
&parts.headers,
|
||||||
|
&provider_request_body,
|
||||||
|
&request_body,
|
||||||
|
transport.endpoint.header_rules.as_ref(),
|
||||||
|
&auth_header,
|
||||||
|
&auth_value,
|
||||||
|
upstream_is_stream,
|
||||||
|
) else {
|
||||||
|
return Ok(ProviderQueryExecutionOutcome {
|
||||||
|
status: "failed",
|
||||||
|
skip_reason: None,
|
||||||
|
error_message: Some("provider request headers build failed".to_string()),
|
||||||
|
status_code: None,
|
||||||
|
latency_ms: None,
|
||||||
|
request_url,
|
||||||
|
request_headers: BTreeMap::new(),
|
||||||
|
request_body: provider_request_body,
|
||||||
|
response_headers: BTreeMap::new(),
|
||||||
|
response_body: None,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let plan = ExecutionPlan {
|
||||||
|
request_id: trace_id.to_string(),
|
||||||
|
candidate_id: Some(format!("provider-query-{}", candidate.key.id)),
|
||||||
|
provider_name: Some(provider.name.clone()),
|
||||||
|
provider_id: provider.id.clone(),
|
||||||
|
endpoint_id: candidate.endpoint.id.clone(),
|
||||||
|
key_id: candidate.key.id.clone(),
|
||||||
|
method: "POST".to_string(),
|
||||||
|
url: request_url.clone(),
|
||||||
|
headers: request_headers.clone(),
|
||||||
|
content_type: Some("application/json".to_string()),
|
||||||
|
content_encoding: None,
|
||||||
|
body: RequestBody::from_json(provider_request_body.clone()),
|
||||||
|
stream: upstream_is_stream,
|
||||||
|
client_api_format: "openai:chat".to_string(),
|
||||||
|
provider_api_format: candidate.endpoint.api_format.clone(),
|
||||||
|
model_name: Some(request_model.to_string()),
|
||||||
|
proxy: state
|
||||||
|
.resolve_transport_proxy_snapshot_with_tunnel_affinity(&transport)
|
||||||
|
.await,
|
||||||
|
transport_profile: state.resolve_transport_profile(&transport),
|
||||||
|
timeouts: state.resolve_transport_execution_timeouts(&transport),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = state
|
||||||
|
.execute_execution_runtime_sync_plan(Some(trace_id), &plan)
|
||||||
|
.await?;
|
||||||
|
let response_body = result.body.as_ref().and_then(|body| body.json_body.clone());
|
||||||
|
let missing_success_body = result.status_code < 400 && response_body.is_none();
|
||||||
|
let did_fail = result.status_code >= 400 || missing_success_body;
|
||||||
|
let error_message = if did_fail {
|
||||||
|
provider_query_extract_error_message(&result).or_else(|| {
|
||||||
|
missing_success_body.then(|| {
|
||||||
|
format!(
|
||||||
|
"Provider returned HTTP {} without a model-test response body",
|
||||||
|
result.status_code
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ProviderQueryExecutionOutcome {
|
||||||
|
status: if did_fail { "failed" } else { "success" },
|
||||||
|
skip_reason: None,
|
||||||
|
error_message,
|
||||||
|
status_code: Some(result.status_code),
|
||||||
|
latency_ms: result.telemetry.as_ref().and_then(|value| value.elapsed_ms),
|
||||||
|
request_url,
|
||||||
|
request_headers,
|
||||||
|
request_body: provider_request_body,
|
||||||
|
response_headers: result.headers,
|
||||||
|
response_body,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async fn build_admin_provider_query_kiro_failover_response(
|
async fn build_admin_provider_query_kiro_failover_response(
|
||||||
state: &AdminAppState<'_>,
|
state: &AdminAppState<'_>,
|
||||||
payload: &Value,
|
payload: &Value,
|
||||||
|
|||||||
@@ -46,6 +46,22 @@ pub(super) fn provider_query_standard_test_unsupported_reason(
|
|||||||
api_format: &str,
|
api_format: &str,
|
||||||
) -> String {
|
) -> String {
|
||||||
let normalized_api_format = crate::ai_serving::normalize_api_format_alias(api_format);
|
let normalized_api_format = crate::ai_serving::normalize_api_format_alias(api_format);
|
||||||
|
if crate::provider_transport::is_windsurf_provider_transport(transport)
|
||||||
|
&& normalized_api_format == "openai:chat"
|
||||||
|
{
|
||||||
|
let reason =
|
||||||
|
crate::provider_transport::local_windsurf_request_transport_unsupported_reason_with_network(
|
||||||
|
transport,
|
||||||
|
);
|
||||||
|
return match reason {
|
||||||
|
Some(reason) => format!(
|
||||||
|
"{} ({reason})",
|
||||||
|
provider_query_unsupported_test_api_format_message(api_format)
|
||||||
|
),
|
||||||
|
None => provider_query_unsupported_test_api_format_message(api_format),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let reason = match normalized_api_format.as_str() {
|
let reason = match normalized_api_format.as_str() {
|
||||||
"openai:chat" => {
|
"openai:chat" => {
|
||||||
crate::provider_transport::policy::local_openai_chat_transport_unsupported_reason(
|
crate::provider_transport::policy::local_openai_chat_transport_unsupported_reason(
|
||||||
@@ -294,6 +310,15 @@ pub(super) fn provider_query_transport_supports_model_test_execution(
|
|||||||
transport: &AdminGatewayProviderTransportSnapshot,
|
transport: &AdminGatewayProviderTransportSnapshot,
|
||||||
api_format: &str,
|
api_format: &str,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
if crate::provider_transport::is_windsurf_provider_transport(transport)
|
||||||
|
&& provider_query_normalize_api_format_alias(api_format) == "openai:chat"
|
||||||
|
{
|
||||||
|
return crate::provider_transport::local_windsurf_request_transport_unsupported_reason_with_network(
|
||||||
|
transport,
|
||||||
|
)
|
||||||
|
.is_none();
|
||||||
|
}
|
||||||
|
|
||||||
match provider_query_test_adapter_for_provider_api_format(
|
match provider_query_test_adapter_for_provider_api_format(
|
||||||
transport.provider.provider_type.as_str(),
|
transport.provider.provider_type.as_str(),
|
||||||
api_format,
|
api_format,
|
||||||
|
|||||||
Reference in New Issue
Block a user