fix(provider): 接入 Windsurf 模型测试链路

This commit is contained in:
Entropy.Xu
2026-05-18 20:23:36 +08:00
parent 9466d92a7a
commit 931c577345
2 changed files with 200 additions and 0 deletions

View File

@@ -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,

View File

@@ -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,