mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-11 11:49:50 +08:00
refactor(workspace): enforce layered crate boundaries
This commit is contained in:
@@ -0,0 +1,582 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde_json::{json, Value};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::rules::{
|
||||
apply_local_body_rules_with_request_headers, apply_local_header_rules_with_request_headers,
|
||||
body_rules_are_locally_supported, header_rules_are_locally_supported,
|
||||
};
|
||||
use crate::snapshot::GatewayProviderTransportSnapshot;
|
||||
use crate::url::build_passthrough_path_url;
|
||||
use crate::{
|
||||
resolve_transport_profile, should_skip_upstream_passthrough_header,
|
||||
supports_local_oauth_request_auth_resolution, transport_profile_is_configured,
|
||||
transport_proxy_is_locally_supported,
|
||||
};
|
||||
|
||||
pub mod cascade;
|
||||
pub mod models;
|
||||
pub mod proto;
|
||||
|
||||
pub const PROVIDER_TYPE: &str = "windsurf";
|
||||
pub const WINDSURF_ENVELOPE_NAME: &str = "windsurf:GetChatMessage";
|
||||
pub const GET_CHAT_MESSAGE_PATH: &str = "/exa.api_server_pb.ApiServerService/GetChatMessage";
|
||||
const DEFAULT_IDE_VERSION: &str = "1.9600.41";
|
||||
const PLACEHOLDER_API_KEY: &str = "__placeholder__";
|
||||
|
||||
pub fn is_windsurf_provider_transport(transport: &GatewayProviderTransportSnapshot) -> bool {
|
||||
transport
|
||||
.provider
|
||||
.provider_type
|
||||
.trim()
|
||||
.eq_ignore_ascii_case(PROVIDER_TYPE)
|
||||
}
|
||||
|
||||
pub fn local_windsurf_request_transport_unsupported_reason_with_network(
|
||||
transport: &GatewayProviderTransportSnapshot,
|
||||
) -> Option<&'static str> {
|
||||
if !transport.provider.is_active {
|
||||
return Some("provider_inactive");
|
||||
}
|
||||
if !transport.endpoint.is_active {
|
||||
return Some("endpoint_inactive");
|
||||
}
|
||||
if !transport.key.is_active {
|
||||
return Some("key_inactive");
|
||||
}
|
||||
if !is_windsurf_provider_transport(transport) {
|
||||
return Some("transport_provider_type_unsupported");
|
||||
}
|
||||
if !transport
|
||||
.endpoint
|
||||
.api_format
|
||||
.trim()
|
||||
.eq_ignore_ascii_case("openai:chat")
|
||||
{
|
||||
return Some("transport_api_format_mismatch");
|
||||
}
|
||||
if !header_rules_are_locally_supported(transport.endpoint.header_rules.as_ref()) {
|
||||
return Some("transport_header_rules_unsupported");
|
||||
}
|
||||
if !body_rules_are_locally_supported(transport.endpoint.body_rules.as_ref()) {
|
||||
return Some("transport_body_rules_unsupported");
|
||||
}
|
||||
if transport.key.decrypted_auth_config.is_some()
|
||||
&& !supports_local_oauth_request_auth_resolution(transport)
|
||||
&& !supports_local_windsurf_request_auth_resolution(transport)
|
||||
{
|
||||
return Some("transport_oauth_resolution_unsupported");
|
||||
}
|
||||
if !transport_proxy_is_locally_supported(transport) {
|
||||
return Some("transport_proxy_unsupported");
|
||||
}
|
||||
if transport_profile_is_configured(transport) && resolve_transport_profile(transport).is_none()
|
||||
{
|
||||
return Some("transport_profile_unsupported");
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn supports_local_windsurf_request_auth_resolution(
|
||||
transport: &GatewayProviderTransportSnapshot,
|
||||
) -> bool {
|
||||
resolve_windsurf_cascade_auth(transport).is_some()
|
||||
}
|
||||
|
||||
pub fn resolve_windsurf_cascade_auth(
|
||||
transport: &GatewayProviderTransportSnapshot,
|
||||
) -> Option<(String, String)> {
|
||||
if !is_windsurf_provider_transport(transport) {
|
||||
return None;
|
||||
}
|
||||
let auth_type = transport.key.auth_type.trim().to_ascii_lowercase();
|
||||
if !matches!(auth_type.as_str(), "oauth" | "api_key" | "bearer") {
|
||||
return None;
|
||||
}
|
||||
let secret = transport.key.decrypted_api_key.trim();
|
||||
if secret.is_empty() || secret == PLACEHOLDER_API_KEY {
|
||||
return None;
|
||||
}
|
||||
Some(("authorization".to_string(), format!("Bearer {secret}")))
|
||||
}
|
||||
|
||||
pub fn build_windsurf_cascade_upstream_url(
|
||||
upstream_base_url: &str,
|
||||
query: Option<&str>,
|
||||
) -> Option<String> {
|
||||
build_passthrough_path_url(upstream_base_url, GET_CHAT_MESSAGE_PATH, query, &[])
|
||||
}
|
||||
|
||||
pub fn build_windsurf_cascade_request_body(
|
||||
body_json: &Value,
|
||||
mapped_model: &str,
|
||||
auth_value: &str,
|
||||
body_rules: Option<&Value>,
|
||||
request_headers: Option<&http::HeaderMap>,
|
||||
upstream_is_stream: bool,
|
||||
) -> Option<Value> {
|
||||
let mapped_model = mapped_model.trim();
|
||||
if mapped_model.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let messages = body_json.get("messages")?.as_array()?.clone();
|
||||
if messages.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let conversation_id =
|
||||
extract_conversation_id(body_json).unwrap_or_else(|| Uuid::new_v4().to_string());
|
||||
let message_text =
|
||||
latest_message_snapshot_text(&messages).unwrap_or_else(|| "Continue.".to_string());
|
||||
let mut provider_request_body = json!({
|
||||
"metadata": windsurf_metadata_from_auth(auth_value),
|
||||
"model": mapped_model,
|
||||
"modelName": mapped_model,
|
||||
"stream": upstream_is_stream,
|
||||
"conversationId": conversation_id,
|
||||
"message": message_text,
|
||||
"messages": messages,
|
||||
});
|
||||
|
||||
if let Some(max_tokens) = body_json
|
||||
.get("max_tokens")
|
||||
.or_else(|| body_json.get("maxTokens"))
|
||||
{
|
||||
provider_request_body
|
||||
.as_object_mut()?
|
||||
.insert("maxTokens".to_string(), max_tokens.clone());
|
||||
}
|
||||
if let Some(temperature) = body_json.get("temperature") {
|
||||
provider_request_body
|
||||
.as_object_mut()?
|
||||
.insert("temperature".to_string(), temperature.clone());
|
||||
}
|
||||
if let Some(top_p) = body_json.get("top_p").or_else(|| body_json.get("topP")) {
|
||||
provider_request_body
|
||||
.as_object_mut()?
|
||||
.insert("topP".to_string(), top_p.clone());
|
||||
}
|
||||
for field in [
|
||||
"tools",
|
||||
"tool_choice",
|
||||
"toolChoice",
|
||||
"parallel_tool_calls",
|
||||
"response_format",
|
||||
] {
|
||||
if let Some(value) = body_json.get(field) {
|
||||
provider_request_body
|
||||
.as_object_mut()?
|
||||
.insert(field.to_string(), value.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if !apply_local_body_rules_with_request_headers(
|
||||
&mut provider_request_body,
|
||||
body_rules,
|
||||
Some(body_json),
|
||||
request_headers,
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
Some(provider_request_body)
|
||||
}
|
||||
|
||||
pub fn build_windsurf_cascade_headers(
|
||||
headers: &http::HeaderMap,
|
||||
provider_request_body: &Value,
|
||||
original_request_body: &Value,
|
||||
header_rules: Option<&Value>,
|
||||
auth_header: &str,
|
||||
auth_value: &str,
|
||||
_upstream_is_stream: bool,
|
||||
) -> Option<BTreeMap<String, String>> {
|
||||
let mut out = BTreeMap::new();
|
||||
for (name, value) in headers {
|
||||
let Ok(value) = value.to_str() else {
|
||||
continue;
|
||||
};
|
||||
let key = name.as_str().to_ascii_lowercase();
|
||||
if should_skip_upstream_passthrough_header(&key) {
|
||||
continue;
|
||||
}
|
||||
let value = value.trim();
|
||||
if !value.is_empty() {
|
||||
out.insert(key, value.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
let auth_header = auth_header.trim().to_ascii_lowercase();
|
||||
if !apply_local_header_rules_with_request_headers(
|
||||
&mut out,
|
||||
header_rules,
|
||||
&[
|
||||
auth_header.as_str(),
|
||||
"content-type",
|
||||
"connect-protocol-version",
|
||||
],
|
||||
provider_request_body,
|
||||
Some(original_request_body),
|
||||
Some(headers),
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
|
||||
out.insert(
|
||||
"content-type".to_string(),
|
||||
"application/connect+json".to_string(),
|
||||
);
|
||||
out.insert("connect-protocol-version".to_string(), "1".to_string());
|
||||
out.insert(
|
||||
"user-agent".to_string(),
|
||||
format!("windsurf/{DEFAULT_IDE_VERSION}"),
|
||||
);
|
||||
out.insert("accept".to_string(), "application/connect+json".to_string());
|
||||
if !auth_header.is_empty() {
|
||||
out.insert(auth_header, auth_value.trim().to_string());
|
||||
}
|
||||
out.remove("content-length");
|
||||
Some(out)
|
||||
}
|
||||
|
||||
fn windsurf_metadata_from_auth(auth_value: &str) -> Value {
|
||||
json!({
|
||||
"apiKey": auth_secret_from_header_value(auth_value),
|
||||
"ideName": "windsurf",
|
||||
"ideVersion": DEFAULT_IDE_VERSION,
|
||||
"extensionName": "windsurf",
|
||||
"extensionVersion": DEFAULT_IDE_VERSION,
|
||||
"locale": "en",
|
||||
})
|
||||
}
|
||||
|
||||
fn auth_secret_from_header_value(auth_value: &str) -> String {
|
||||
let value = auth_value.trim();
|
||||
value
|
||||
.strip_prefix("Bearer ")
|
||||
.or_else(|| value.strip_prefix("bearer "))
|
||||
.unwrap_or(value)
|
||||
.trim()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn extract_conversation_id(body_json: &Value) -> Option<String> {
|
||||
let object = body_json.as_object()?;
|
||||
string_value(object.get("conversation_id"))
|
||||
.or_else(|| string_value(object.get("conversationId")))
|
||||
.or_else(|| string_value(object.get("session_id")))
|
||||
.or_else(|| string_value(object.get("sessionId")))
|
||||
.or_else(|| {
|
||||
object
|
||||
.get("metadata")
|
||||
.and_then(Value::as_object)
|
||||
.and_then(|metadata| {
|
||||
string_value(metadata.get("conversation_id"))
|
||||
.or_else(|| string_value(metadata.get("conversationId")))
|
||||
.or_else(|| string_value(metadata.get("session_id")))
|
||||
.or_else(|| string_value(metadata.get("sessionId")))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn string_value(value: Option<&Value>) -> Option<String> {
|
||||
value
|
||||
.and_then(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
fn latest_message_snapshot_text(messages: &[Value]) -> Option<String> {
|
||||
messages
|
||||
.iter()
|
||||
.rev()
|
||||
.filter_map(Value::as_object)
|
||||
.find_map(|message| {
|
||||
let role = message.get("role").and_then(Value::as_str)?;
|
||||
match role {
|
||||
"user" => openai_content_to_text(message.get("content"))
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty()),
|
||||
"tool" => {
|
||||
let content = openai_content_to_text(message.get("content"))
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())?;
|
||||
let tool_call_id = message
|
||||
.get("tool_call_id")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("unknown");
|
||||
Some(format!(
|
||||
"<tool_result tool_call_id=\"{}\">\n{content}\n</tool_result>",
|
||||
escape_xml_attr(tool_call_id)
|
||||
))
|
||||
}
|
||||
"assistant" => openai_content_to_text(message.get("content"))
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty()),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn openai_content_to_text(value: Option<&Value>) -> Option<String> {
|
||||
match value? {
|
||||
Value::String(text) => Some(text.clone()),
|
||||
Value::Array(items) => {
|
||||
let parts = items
|
||||
.iter()
|
||||
.filter_map(|item| {
|
||||
item.as_object()
|
||||
.and_then(|object| object.get("text"))
|
||||
.and_then(Value::as_str)
|
||||
})
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
(!parts.is_empty()).then(|| parts.join("\n"))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn escape_xml_attr(value: &str) -> String {
|
||||
value
|
||||
.replace('&', "&")
|
||||
.replace('"', """)
|
||||
.replace('<', "<")
|
||||
.replace('>', ">")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use http::HeaderMap;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::snapshot::{
|
||||
GatewayProviderTransportEndpoint, GatewayProviderTransportKey,
|
||||
GatewayProviderTransportProvider, GatewayProviderTransportSnapshot,
|
||||
};
|
||||
|
||||
use super::{
|
||||
build_windsurf_cascade_headers, build_windsurf_cascade_request_body,
|
||||
build_windsurf_cascade_upstream_url,
|
||||
local_windsurf_request_transport_unsupported_reason_with_network,
|
||||
resolve_windsurf_cascade_auth, GET_CHAT_MESSAGE_PATH,
|
||||
};
|
||||
|
||||
fn sample_windsurf_transport(auth_type: &str) -> GatewayProviderTransportSnapshot {
|
||||
GatewayProviderTransportSnapshot {
|
||||
provider: GatewayProviderTransportProvider {
|
||||
id: "provider-windsurf".to_string(),
|
||||
name: "Windsurf".to_string(),
|
||||
provider_type: "windsurf".to_string(),
|
||||
website: None,
|
||||
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-windsurf-chat".to_string(),
|
||||
provider_id: "provider-windsurf".to_string(),
|
||||
api_format: "openai:chat".to_string(),
|
||||
api_family: Some("openai".to_string()),
|
||||
endpoint_kind: Some("chat".to_string()),
|
||||
is_active: true,
|
||||
base_url: "https://server.codeium.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-windsurf".to_string(),
|
||||
provider_id: "provider-windsurf".to_string(),
|
||||
name: "[email protected]".to_string(),
|
||||
auth_type: auth_type.to_string(),
|
||||
is_active: true,
|
||||
api_formats: None,
|
||||
auth_type_by_format: None,
|
||||
allow_auth_channel_mismatch_formats: None,
|
||||
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: "devin-session-token$abc".to_string(),
|
||||
decrypted_auth_config: Some(r#"{"provider_type":"windsurf"}"#.to_string()),
|
||||
upstream_metadata: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_windsurf_cascade_url() {
|
||||
assert_eq!(
|
||||
build_windsurf_cascade_upstream_url("https://server.codeium.com", Some("debug=1"))
|
||||
.as_deref(),
|
||||
Some(
|
||||
"https://server.codeium.com/exa.api_server_pb.ApiServerService/GetChatMessage?debug=1"
|
||||
)
|
||||
);
|
||||
assert!(GET_CHAT_MESSAGE_PATH.ends_with("/GetChatMessage"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_cascade_request_body_with_metadata_and_messages() {
|
||||
let body = build_windsurf_cascade_request_body(
|
||||
&json!({
|
||||
"model": "gpt-5",
|
||||
"conversation_id": "conv-1",
|
||||
"messages": [
|
||||
{"role": "system", "content": "brief"},
|
||||
{"role": "user", "content": [{"type": "text", "text": "hello"}]}
|
||||
],
|
||||
"max_tokens": 128
|
||||
}),
|
||||
"windsurf-model",
|
||||
"Bearer devin-session-token$abc",
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.expect("body should build");
|
||||
|
||||
assert_eq!(body["metadata"]["apiKey"], json!("devin-session-token$abc"));
|
||||
assert_eq!(body["modelName"], json!("windsurf-model"));
|
||||
assert_eq!(body["stream"], json!(true));
|
||||
assert_eq!(body["conversationId"], json!("conv-1"));
|
||||
assert_eq!(body["message"], json!("hello"));
|
||||
assert_eq!(body["maxTokens"], json!(128));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_openai_tool_fields_for_native_windsurf_runtime() {
|
||||
let body = build_windsurf_cascade_request_body(
|
||||
&json!({
|
||||
"model": "gpt-5-5-low",
|
||||
"messages": [
|
||||
{"role": "user", "content": "read Cargo.toml"}
|
||||
],
|
||||
"tools": [{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "Read",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {"file_path": {"type": "string"}}
|
||||
}
|
||||
}
|
||||
}],
|
||||
"tool_choice": "required",
|
||||
"parallel_tool_calls": false,
|
||||
"response_format": {"type": "json_object"}
|
||||
}),
|
||||
"gpt-5-5-low",
|
||||
"Bearer devin-session-token$abc",
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.expect("body should build");
|
||||
|
||||
assert_eq!(body["tools"][0]["function"]["name"], json!("Read"));
|
||||
assert_eq!(body["tool_choice"], json!("required"));
|
||||
assert_eq!(body["parallel_tool_calls"], json!(false));
|
||||
assert_eq!(body["response_format"]["type"], json!("json_object"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_cascade_request_body_message_snapshot_from_latest_tool_result() {
|
||||
let body = build_windsurf_cascade_request_body(
|
||||
&json!({
|
||||
"model": "gpt-5-5-low",
|
||||
"messages": [
|
||||
{"role": "user", "content": "read Cargo.toml"},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": null,
|
||||
"tool_calls": [{
|
||||
"id": "call_1",
|
||||
"type": "function",
|
||||
"function": {"name": "Read", "arguments": "{\"file_path\":\"Cargo.toml\"}"}
|
||||
}]
|
||||
},
|
||||
{"role": "tool", "tool_call_id": "call_1", "content": "workspace Cargo.toml content"}
|
||||
]
|
||||
}),
|
||||
"gpt-5-5-low",
|
||||
"Bearer devin-session-token$abc",
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.expect("body should build");
|
||||
|
||||
assert!(body["message"]
|
||||
.as_str()
|
||||
.expect("message should be a string")
|
||||
.contains(r#"<tool_result tool_call_id="call_1">"#));
|
||||
assert!(body["message"]
|
||||
.as_str()
|
||||
.expect("message should be a string")
|
||||
.contains("workspace Cargo.toml content"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_cascade_headers_with_connect_protocol_and_auth() {
|
||||
let headers = build_windsurf_cascade_headers(
|
||||
&HeaderMap::new(),
|
||||
&json!({"metadata": {"apiKey": "secret"}}),
|
||||
&json!({"messages": []}),
|
||||
None,
|
||||
"authorization",
|
||||
"Bearer secret",
|
||||
false,
|
||||
)
|
||||
.expect("headers should build");
|
||||
|
||||
assert_eq!(
|
||||
headers.get("connect-protocol-version").map(String::as_str),
|
||||
Some("1")
|
||||
);
|
||||
assert_eq!(
|
||||
headers.get("authorization").map(String::as_str),
|
||||
Some("Bearer secret")
|
||||
);
|
||||
assert_eq!(
|
||||
headers.get("content-type").map(String::as_str),
|
||||
Some("application/connect+json")
|
||||
);
|
||||
assert_eq!(
|
||||
headers.get("accept").map(String::as_str),
|
||||
Some("application/connect+json")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oauth_windsurf_transport_resolves_direct_bearer_auth() {
|
||||
let transport = sample_windsurf_transport("oauth");
|
||||
|
||||
assert_eq!(
|
||||
local_windsurf_request_transport_unsupported_reason_with_network(&transport),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_windsurf_cascade_auth(&transport),
|
||||
Some((
|
||||
"authorization".to_string(),
|
||||
"Bearer devin-session-token$abc".to_string()
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user