Files
Aether/crates/aether-ai/serving/src/attempt_plan.rs
T
elky 579f2c7cc1 feat(security): harden gateway boundaries and usage policies
Consolidate subscription usage policy enforcement, privacy-safe persistence, and gateway security hardening into one reviewable change.

Includes bounded HTTP and execution envelopes, header and protocol guards, DNS and relay validation, authentication and secret projection hardening, secure backup/install paths, and regression coverage.
2026-09-04 03:45:52 +08:00

912 lines
32 KiB
Rust

use std::collections::BTreeMap;
use std::fmt;
use aether_ai_formats::api::ExecutionRuntimeAuthContext;
use aether_contracts::{redact_url_for_debug, ExecutionPlan, RequestBody};
use url::Url;
use crate::dto::{AiExecutionDecision, AiRequestGzipPolicy};
const DEFAULT_REQUEST_GZIP_MIN_JSON_BYTES: usize = 64 * 1024;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AiDecisionPlanCore {
pub request_id: String,
pub provider_id: String,
pub endpoint_id: String,
pub key_id: String,
pub provider_api_format: String,
pub client_api_format: String,
}
#[derive(Clone, PartialEq, Eq)]
pub struct AiUpstreamAuthPair {
pub header: String,
pub value: String,
}
impl fmt::Debug for AiUpstreamAuthPair {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("AiUpstreamAuthPair")
.field("header", &self.header)
.field("has_value", &(!self.value.is_empty()))
.field("value_len", &self.value.len())
.finish()
}
}
pub struct AiExecutionPlanFromDecisionParts {
pub core: AiDecisionPlanCore,
pub method: String,
pub url: String,
pub headers: BTreeMap<String, String>,
pub content_type: Option<String>,
pub body: RequestBody,
pub stream: bool,
}
impl fmt::Debug for AiExecutionPlanFromDecisionParts {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("AiExecutionPlanFromDecisionParts")
.field("core", &self.core)
.field("method", &self.method)
.field("url", &redact_url_for_debug(&self.url))
.field("header_names", &self.headers.keys().collect::<Vec<_>>())
.field("content_type", &self.content_type)
.field("body", &self.body)
.field("stream", &self.stream)
.finish()
}
}
pub struct AiExecutionDecisionFromPlanParts {
pub action: String,
pub decision_kind: Option<String>,
pub request_id: Option<String>,
pub upstream_base_url: Option<String>,
pub include_auth_pair: bool,
pub plan: ExecutionPlan,
pub report_kind: Option<String>,
pub report_context: Option<serde_json::Value>,
pub auth_context: Option<ExecutionRuntimeAuthContext>,
}
impl fmt::Debug for AiExecutionDecisionFromPlanParts {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("AiExecutionDecisionFromPlanParts")
.field("action", &self.action)
.field("decision_kind", &self.decision_kind)
.field("request_id", &self.request_id)
.field(
"upstream_base_url",
&self.upstream_base_url.as_deref().map(redact_url_for_debug),
)
.field("include_auth_pair", &self.include_auth_pair)
.field("plan", &self.plan)
.field("report_kind", &self.report_kind)
.field("has_report_context", &self.report_context.is_some())
.field(
"report_context_bytes",
&self
.report_context
.as_ref()
.and_then(|value| serde_json::to_vec(value).ok().map(|bytes| bytes.len())),
)
.field("has_auth_context", &self.auth_context.is_some())
.finish()
}
}
pub fn take_ai_non_empty_string(value: &mut Option<String>) -> Option<String> {
value.take().filter(|value| !value.trim().is_empty())
}
pub fn trim_ai_owned_non_empty_string(value: String) -> Option<String> {
let trimmed = value.trim();
if trimmed.is_empty() {
return None;
}
if trimmed.len() == value.len() {
return Some(value);
}
Some(trimmed.to_owned())
}
pub fn take_ai_decision_plan_core(payload: &mut AiExecutionDecision) -> Option<AiDecisionPlanCore> {
Some(AiDecisionPlanCore {
request_id: take_ai_non_empty_string(&mut payload.request_id)?,
provider_id: take_ai_non_empty_string(&mut payload.provider_id)?,
endpoint_id: take_ai_non_empty_string(&mut payload.endpoint_id)?,
key_id: take_ai_non_empty_string(&mut payload.key_id)?,
provider_api_format: take_ai_non_empty_string(&mut payload.provider_api_format)?,
client_api_format: take_ai_non_empty_string(&mut payload.client_api_format)?,
})
}
pub fn take_ai_upstream_auth_pair(
payload: &mut AiExecutionDecision,
) -> Option<Option<AiUpstreamAuthPair>> {
let header = take_ai_non_empty_string(&mut payload.auth_header);
let value = take_ai_non_empty_string(&mut payload.auth_value);
match (header, value) {
(Some(header), Some(value)) => Some(Some(AiUpstreamAuthPair { header, value })),
(None, None) => Some(None),
_ => None,
}
}
pub fn resolve_ai_passthrough_sync_request_body(
provider_request_body: Option<serde_json::Value>,
provider_request_body_base64: Option<String>,
) -> RequestBody {
if let Some(body_bytes_b64) =
provider_request_body_base64.and_then(trim_ai_owned_non_empty_string)
{
return RequestBody {
json_body: None,
body_bytes_b64: Some(body_bytes_b64),
body_ref: None,
};
}
match provider_request_body.unwrap_or(serde_json::Value::Null) {
serde_json::Value::Null => RequestBody {
json_body: None,
body_bytes_b64: None,
body_ref: None,
},
other => RequestBody::from_json(other),
}
}
pub fn build_ai_execution_plan_from_decision(
payload: &mut AiExecutionDecision,
parts: AiExecutionPlanFromDecisionParts,
) -> ExecutionPlan {
let explicit_content_encoding = take_ai_non_empty_string(&mut payload.content_encoding);
let request_gzip = payload.request_gzip.take();
let content_encoding = explicit_content_encoding
.or_else(|| infer_ai_execution_plan_content_encoding(&parts, request_gzip.as_ref()));
ExecutionPlan {
request_id: parts.core.request_id,
candidate_id: payload.candidate_id.take(),
provider_name: payload.provider_name.take(),
provider_id: parts.core.provider_id,
endpoint_id: parts.core.endpoint_id,
key_id: parts.core.key_id,
method: parts.method,
url: parts.url,
headers: parts.headers,
content_type: parts.content_type,
content_encoding,
body: parts.body,
stream: parts.stream,
client_api_format: parts.core.client_api_format,
provider_api_format: parts.core.provider_api_format,
model_name: payload.model_name.take(),
proxy: payload.proxy.take(),
transport_profile: payload.transport_profile.take(),
timeouts: payload.timeouts.take(),
}
}
fn infer_ai_execution_plan_content_encoding(
parts: &AiExecutionPlanFromDecisionParts,
request_gzip: Option<&AiRequestGzipPolicy>,
) -> Option<String> {
if let Some(should_gzip) = should_gzip_explicit_json_request(parts, request_gzip) {
return should_gzip.then(|| "gzip".to_string());
}
None
}
fn should_gzip_explicit_json_request(
parts: &AiExecutionPlanFromDecisionParts,
request_gzip: Option<&AiRequestGzipPolicy>,
) -> Option<bool> {
let request_gzip = request_gzip?;
let enabled = request_gzip
.enabled
.unwrap_or(request_gzip.min_bytes.is_some());
if !enabled {
return Some(false);
}
Some(json_request_body_len_at_least(
parts,
request_gzip
.min_bytes
.unwrap_or(DEFAULT_REQUEST_GZIP_MIN_JSON_BYTES),
))
}
fn json_request_body_len_at_least(
parts: &AiExecutionPlanFromDecisionParts,
min_bytes: usize,
) -> bool {
if parts.body.body_bytes_b64.is_some() || parts.body.body_ref.is_some() {
return false;
}
let Some(json_body) = parts.body.json_body.as_ref() else {
return false;
};
serde_json::to_vec(json_body)
.map(|body| body.len() >= min_bytes)
.unwrap_or(false)
}
pub fn build_ai_execution_decision_from_plan(
parts: AiExecutionDecisionFromPlanParts,
) -> AiExecutionDecision {
let ExecutionPlan {
request_id,
candidate_id,
provider_name,
provider_id,
endpoint_id,
key_id,
method,
url,
headers,
content_type,
content_encoding,
body,
stream,
client_api_format,
provider_api_format,
model_name,
proxy,
transport_profile,
timeouts,
} = parts.plan;
let auth_pair = parts
.include_auth_pair
.then(|| extract_ai_auth_header_pair(&headers))
.flatten();
let provider_contract = provider_api_format.clone();
let client_contract = client_api_format.clone();
let request_id = parts.request_id.unwrap_or(request_id);
let auth_header = auth_pair.map(|(name, _)| name.to_string());
let auth_value = auth_pair.map(|(_, value)| value.to_string());
let RequestBody {
json_body,
body_bytes_b64,
body_ref: _body_ref,
} = body;
AiExecutionDecision {
action: parts.action,
decision_kind: parts.decision_kind,
execution_strategy: Some(ai_execution_strategy_for_formats(
provider_api_format.as_str(),
client_api_format.as_str(),
)),
conversion_mode: Some(ai_conversion_mode_for_formats(
provider_api_format.as_str(),
client_api_format.as_str(),
)),
request_id: Some(request_id),
candidate_id,
provider_name,
provider_type: None,
provider_id: Some(provider_id),
endpoint_id: Some(endpoint_id),
key_id: Some(key_id),
upstream_base_url: parts.upstream_base_url,
upstream_url: Some(url),
provider_request_method: Some(method),
auth_header,
auth_value,
provider_api_format: Some(provider_api_format),
client_api_format: Some(client_api_format),
provider_contract: Some(provider_contract),
client_contract: Some(client_contract),
model_name,
mapped_model: None,
prompt_cache_key: None,
extra_headers: BTreeMap::new(),
provider_request_headers: headers,
provider_request_body: json_body,
provider_request_body_base64: body_bytes_b64,
content_type,
content_encoding,
request_gzip: None,
proxy,
transport_profile,
timeouts,
upstream_is_stream: stream,
report_kind: parts.report_kind,
report_context: parts.report_context,
auth_context: parts.auth_context,
}
}
pub fn extract_ai_auth_header_pair(headers: &BTreeMap<String, String>) -> Option<(&str, &str)> {
[
"authorization",
"x-api-key",
"api-key",
"x-goog-api-key",
"proxy-authorization",
]
.into_iter()
.find_map(|name| {
headers
.iter()
.find(|(header_name, _)| header_name.eq_ignore_ascii_case(name))
.map(|(header_name, value)| (header_name.as_str(), value.as_str()))
})
}
pub fn infer_ai_upstream_base_url(upstream_url: &str) -> Option<String> {
let parsed = Url::parse(upstream_url).ok()?;
let host = parsed.host_str()?;
let mut base = format!("{}://{}", parsed.scheme(), host);
if let Some(port) = parsed.port() {
base.push(':');
base.push_str(port.to_string().as_str());
}
let base_path = infer_ai_upstream_base_path(parsed.path());
if !base_path.is_empty() {
base.push_str(base_path);
}
Some(base)
}
fn infer_ai_upstream_base_path(path: &str) -> &str {
let trimmed = path.trim_end_matches('/');
if trimmed.is_empty() || trimmed == "/" {
return "";
}
for suffix in [
"/responses/compact",
"/responses",
"/chat/completions",
"/messages",
] {
if let Some(prefix) = trimmed.strip_suffix(suffix) {
return normalize_inferred_ai_base_path(prefix);
}
}
for marker in ["/v1/videos", "/v1beta/"] {
if let Some((prefix, _)) = trimmed.split_once(marker) {
return normalize_inferred_ai_base_path(prefix);
}
}
normalize_inferred_ai_base_path(trimmed)
}
fn normalize_inferred_ai_base_path(path: &str) -> &str {
let trimmed = path.trim_end_matches('/');
if trimmed.is_empty() || trimmed == "/" {
""
} else {
trimmed
}
}
fn ai_execution_strategy_for_formats(provider_api_format: &str, client_api_format: &str) -> String {
if provider_api_format == client_api_format {
"local_same_format"
} else {
"local_cross_format"
}
.to_string()
}
fn ai_conversion_mode_for_formats(provider_api_format: &str, client_api_format: &str) -> String {
if provider_api_format == client_api_format {
"none"
} else {
"bidirectional"
}
.to_string()
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use serde_json::json;
use super::*;
#[test]
fn take_ai_decision_plan_core_consumes_required_non_empty_fields() {
let mut payload = test_decision();
let core =
take_ai_decision_plan_core(&mut payload).expect("core fields should be available");
assert_eq!(core.request_id, "req_1");
assert_eq!(core.provider_id, "provider_1");
assert_eq!(core.endpoint_id, "endpoint_1");
assert_eq!(core.key_id, "key_1");
assert_eq!(core.provider_api_format, "openai:chat");
assert_eq!(core.client_api_format, "openai:chat");
assert!(payload.request_id.is_none());
assert!(payload.provider_api_format.is_none());
}
#[test]
fn take_ai_decision_plan_core_rejects_blank_required_fields() {
let mut payload = test_decision();
payload.endpoint_id = Some(" ".to_string());
assert!(take_ai_decision_plan_core(&mut payload).is_none());
}
#[test]
fn take_ai_upstream_auth_pair_rejects_incomplete_auth() {
let mut payload = test_decision();
payload.auth_header = Some("authorization".to_string());
payload.auth_value = Some(" ".to_string());
assert!(take_ai_upstream_auth_pair(&mut payload).is_none());
}
#[test]
fn resolve_ai_passthrough_sync_request_body_prefers_trimmed_base64() {
let body = resolve_ai_passthrough_sync_request_body(
Some(json!({"ignored": true})),
Some(" YWJj ".to_string()),
);
assert_eq!(body.body_bytes_b64.as_deref(), Some("YWJj"));
assert!(body.json_body.is_none());
}
#[test]
fn resolve_ai_passthrough_sync_request_body_uses_json_when_no_base64() {
let body = resolve_ai_passthrough_sync_request_body(Some(json!({"ok": true})), None);
assert_eq!(body.json_body, Some(json!({"ok": true})));
assert!(body.body_bytes_b64.is_none());
}
#[test]
fn build_ai_execution_plan_from_decision_merges_core_and_remaining_payload_fields() {
let mut payload = test_decision();
let core =
take_ai_decision_plan_core(&mut payload).expect("core fields should be available");
let plan = build_ai_execution_plan_from_decision(
&mut payload,
AiExecutionPlanFromDecisionParts {
core,
method: "POST".to_string(),
url: "https://example.com/v1/chat/completions".to_string(),
headers: BTreeMap::from([(
"content-type".to_string(),
"application/json".to_string(),
)]),
content_type: Some("application/json".to_string()),
body: RequestBody::from_json(json!({"model": "gpt-test"})),
stream: true,
},
);
assert_eq!(plan.request_id, "req_1");
assert_eq!(plan.candidate_id.as_deref(), Some("candidate_1"));
assert_eq!(plan.provider_id, "provider_1");
assert_eq!(plan.endpoint_id, "endpoint_1");
assert_eq!(plan.key_id, "key_1");
assert!(plan.stream);
assert_eq!(plan.provider_api_format, "openai:chat");
assert_eq!(plan.client_api_format, "openai:chat");
assert_eq!(plan.model_name.as_deref(), Some("gpt-test"));
assert!(payload.candidate_id.is_none());
assert!(payload.model_name.is_none());
}
#[test]
fn build_ai_execution_plan_without_request_gzip_policy_leaves_json_uncompressed() {
let large_codex_url = test_plan_for_url_and_body(
"https://chatgpt.com/backend-api/codex/responses",
RequestBody::from_json(json!({
"model": "gpt-5.5",
"input": "x".repeat(DEFAULT_REQUEST_GZIP_MIN_JSON_BYTES),
})),
);
let large_openai = test_plan_for_url_and_body(
"https://api.openai.com/v1/responses",
RequestBody::from_json(json!({
"model": "gpt-5.5",
"input": "x".repeat(DEFAULT_REQUEST_GZIP_MIN_JSON_BYTES),
})),
);
assert_eq!(large_codex_url.content_encoding, None);
assert_eq!(large_openai.content_encoding, None);
}
#[test]
fn build_ai_execution_plan_does_not_gzip_raw_body_even_when_explicit() {
let mut payload = test_decision();
payload.request_gzip = Some(AiRequestGzipPolicy {
enabled: Some(true),
min_bytes: Some(0),
});
let core =
take_ai_decision_plan_core(&mut payload).expect("core fields should be available");
let plan = build_ai_execution_plan_from_decision(
&mut payload,
AiExecutionPlanFromDecisionParts {
core,
method: "POST".to_string(),
url: "https://api.example.com/v1/chat/completions".to_string(),
headers: BTreeMap::new(),
content_type: Some("application/json".to_string()),
body: RequestBody {
json_body: None,
body_bytes_b64: Some("dGVzdA==".to_string()),
body_ref: None,
},
stream: false,
},
);
assert_eq!(plan.content_encoding, None);
}
#[test]
fn build_ai_execution_plan_preserves_explicit_content_encoding_for_raw_body() {
let mut payload = test_decision();
payload.content_encoding = Some("gzip".to_string());
payload.request_gzip = Some(AiRequestGzipPolicy {
enabled: Some(false),
min_bytes: None,
});
let core =
take_ai_decision_plan_core(&mut payload).expect("core fields should be available");
let plan = build_ai_execution_plan_from_decision(
&mut payload,
AiExecutionPlanFromDecisionParts {
core,
method: "POST".to_string(),
url: "https://api.example.com/v1/chat/completions".to_string(),
headers: BTreeMap::new(),
content_type: Some("application/octet-stream".to_string()),
body: RequestBody {
json_body: None,
body_bytes_b64: Some("dGVzdA==".to_string()),
body_ref: None,
},
stream: false,
},
);
assert_eq!(plan.content_encoding.as_deref(), Some("gzip"));
}
#[test]
fn build_ai_execution_plan_gzips_explicit_json_request_for_non_codex() {
let mut payload = test_decision();
payload.request_gzip = Some(AiRequestGzipPolicy {
enabled: Some(true),
min_bytes: Some(1),
});
let core =
take_ai_decision_plan_core(&mut payload).expect("core fields should be available");
let plan = build_ai_execution_plan_from_decision(
&mut payload,
AiExecutionPlanFromDecisionParts {
core,
method: "POST".to_string(),
url: "https://api.example.com/v1/chat/completions".to_string(),
headers: BTreeMap::new(),
content_type: Some("application/json".to_string()),
body: RequestBody::from_json(json!({"model": "gpt-test"})),
stream: false,
},
);
assert_eq!(plan.content_encoding.as_deref(), Some("gzip"));
}
#[test]
fn build_ai_execution_plan_respects_explicit_request_gzip_threshold() {
let mut payload = test_decision();
payload.request_gzip = Some(AiRequestGzipPolicy {
enabled: Some(true),
min_bytes: Some(1024),
});
let core =
take_ai_decision_plan_core(&mut payload).expect("core fields should be available");
let plan = build_ai_execution_plan_from_decision(
&mut payload,
AiExecutionPlanFromDecisionParts {
core,
method: "POST".to_string(),
url: "https://api.example.com/v1/chat/completions".to_string(),
headers: BTreeMap::new(),
content_type: Some("application/json".to_string()),
body: RequestBody::from_json(json!({"model": "gpt-test"})),
stream: false,
},
);
assert_eq!(plan.content_encoding, None);
}
#[test]
fn build_ai_execution_plan_explicit_request_gzip_false_disables_gzip() {
let mut payload = test_decision();
payload.provider_api_format = Some("openai:responses".to_string());
payload.client_api_format = Some("openai:responses".to_string());
payload.request_gzip = Some(AiRequestGzipPolicy {
enabled: Some(false),
min_bytes: None,
});
let core =
take_ai_decision_plan_core(&mut payload).expect("core fields should be available");
let plan = build_ai_execution_plan_from_decision(
&mut payload,
AiExecutionPlanFromDecisionParts {
core,
method: "POST".to_string(),
url: "https://chatgpt.com/backend-api/codex/responses".to_string(),
headers: BTreeMap::new(),
content_type: Some("application/json".to_string()),
body: RequestBody::from_json(json!({
"model": "gpt-5.5",
"input": "x".repeat(DEFAULT_REQUEST_GZIP_MIN_JSON_BYTES),
})),
stream: true,
},
);
assert_eq!(plan.content_encoding, None);
}
#[test]
fn infer_ai_upstream_base_url_preserves_codex_base_path() {
assert_eq!(
infer_ai_upstream_base_url("https://tiger.bookapi.cc/codex/responses").as_deref(),
Some("https://tiger.bookapi.cc/codex")
);
assert_eq!(
infer_ai_upstream_base_url("https://chatgpt.com/backend-api/codex/responses")
.as_deref(),
Some("https://chatgpt.com/backend-api/codex")
);
}
#[test]
fn infer_ai_upstream_base_url_preserves_nested_v1_prefix() {
assert_eq!(
infer_ai_upstream_base_url(
"https://api.openai.example/custom/v1/chat/completions?mode=1"
)
.as_deref(),
Some("https://api.openai.example/custom/v1")
);
}
#[test]
fn infer_ai_upstream_base_url_strips_video_operation_path() {
assert_eq!(
infer_ai_upstream_base_url("https://video.example/nested/v1/videos/task-123/content")
.as_deref(),
Some("https://video.example/nested")
);
}
#[test]
fn build_ai_execution_decision_from_plan_maps_plan_fields() {
let plan = ExecutionPlan {
request_id: "plan-request".to_string(),
candidate_id: Some("candidate-1".to_string()),
provider_name: Some("provider".to_string()),
provider_id: "provider-1".to_string(),
endpoint_id: "endpoint-1".to_string(),
key_id: "key-1".to_string(),
method: "POST".to_string(),
url: "https://api.example.com/v1/chat/completions".to_string(),
headers: BTreeMap::from([("Authorization".to_string(), "Bearer secret".to_string())]),
content_type: Some("application/json".to_string()),
content_encoding: None,
body: RequestBody::from_json(json!({"model": "mapped"})),
stream: false,
client_api_format: "openai:chat".to_string(),
provider_api_format: "claude:messages".to_string(),
model_name: Some("mapped".to_string()),
proxy: None,
transport_profile: None,
timeouts: None,
};
let decision = build_ai_execution_decision_from_plan(AiExecutionDecisionFromPlanParts {
action: "execution_runtime.sync_decision".to_string(),
decision_kind: Some("openai_chat_sync".to_string()),
request_id: Some("trace-1".to_string()),
upstream_base_url: Some("https://api.example.com".to_string()),
include_auth_pair: true,
plan,
report_kind: Some("report".to_string()),
report_context: Some(json!({"candidate_index": 0})),
auth_context: None,
});
assert_eq!(decision.request_id.as_deref(), Some("trace-1"));
assert_eq!(
decision.execution_strategy.as_deref(),
Some("local_cross_format")
);
assert_eq!(decision.conversion_mode.as_deref(), Some("bidirectional"));
assert_eq!(decision.auth_header.as_deref(), Some("Authorization"));
assert_eq!(decision.auth_value.as_deref(), Some("Bearer secret"));
assert_eq!(
decision.provider_request_body,
Some(json!({"model": "mapped"}))
);
assert_eq!(decision.report_kind.as_deref(), Some("report"));
}
#[test]
fn plan_decision_round_trip_preserves_raw_body_content_encoding() {
let original = ExecutionPlan {
request_id: "plan-request".to_string(),
candidate_id: Some("candidate-1".to_string()),
provider_name: Some("provider".to_string()),
provider_id: "provider-1".to_string(),
endpoint_id: "endpoint-1".to_string(),
key_id: "key-1".to_string(),
method: "POST".to_string(),
url: "https://api.example.com/v1/upload".to_string(),
headers: BTreeMap::from([(
"content-type".to_string(),
"application/octet-stream".to_string(),
)]),
content_type: Some("application/octet-stream".to_string()),
content_encoding: Some("gzip".to_string()),
body: RequestBody {
json_body: None,
body_bytes_b64: Some("dGVzdA==".to_string()),
body_ref: None,
},
stream: false,
client_api_format: "openai:chat".to_string(),
provider_api_format: "openai:chat".to_string(),
model_name: Some("gpt-test".to_string()),
proxy: None,
transport_profile: None,
timeouts: None,
};
let mut decision =
build_ai_execution_decision_from_plan(AiExecutionDecisionFromPlanParts {
action: "execution_runtime.sync_decision".to_string(),
decision_kind: Some("raw_upload_sync".to_string()),
request_id: None,
upstream_base_url: Some("https://api.example.com".to_string()),
include_auth_pair: false,
plan: original,
report_kind: None,
report_context: None,
auth_context: None,
});
assert_eq!(decision.content_encoding.as_deref(), Some("gzip"));
assert!(decision.request_gzip.is_none());
let core =
take_ai_decision_plan_core(&mut decision).expect("core fields should be available");
let method = take_ai_non_empty_string(&mut decision.provider_request_method)
.expect("method should round-trip");
let url =
take_ai_non_empty_string(&mut decision.upstream_url).expect("url should round-trip");
let headers = std::mem::take(&mut decision.provider_request_headers);
let content_type = decision.content_type.take();
let body = resolve_ai_passthrough_sync_request_body(
decision.provider_request_body.take(),
decision.provider_request_body_base64.take(),
);
let stream = decision.upstream_is_stream;
let round_tripped = build_ai_execution_plan_from_decision(
&mut decision,
AiExecutionPlanFromDecisionParts {
core,
method,
url,
headers,
content_type,
body,
stream,
},
);
assert_eq!(round_tripped.content_encoding.as_deref(), Some("gzip"));
assert_eq!(
round_tripped.body.body_bytes_b64.as_deref(),
Some("dGVzdA==")
);
assert!(round_tripped.body.json_body.is_none());
}
fn test_decision() -> AiExecutionDecision {
AiExecutionDecision {
action: "sync".to_string(),
decision_kind: Some("test".to_string()),
execution_strategy: None,
conversion_mode: None,
request_id: Some("req_1".to_string()),
candidate_id: Some("candidate_1".to_string()),
provider_name: Some("provider".to_string()),
provider_type: None,
provider_id: Some("provider_1".to_string()),
endpoint_id: Some("endpoint_1".to_string()),
key_id: Some("key_1".to_string()),
upstream_base_url: Some("https://example.com".to_string()),
upstream_url: Some("https://example.com/v1/chat/completions".to_string()),
provider_request_method: None,
auth_header: Some("authorization".to_string()),
auth_value: Some("Bearer token".to_string()),
provider_api_format: Some("openai:chat".to_string()),
client_api_format: Some("openai:chat".to_string()),
provider_contract: Some("openai:chat".to_string()),
client_contract: Some("openai:chat".to_string()),
model_name: Some("gpt-test".to_string()),
mapped_model: Some("gpt-test".to_string()),
prompt_cache_key: None,
extra_headers: BTreeMap::new(),
provider_request_headers: BTreeMap::new(),
provider_request_body: None,
provider_request_body_base64: None,
content_type: None,
content_encoding: None,
request_gzip: None,
proxy: None,
transport_profile: None,
timeouts: None,
upstream_is_stream: false,
report_kind: None,
report_context: None,
auth_context: None,
}
}
fn test_plan_for_url_and_body(url: &str, body: RequestBody) -> ExecutionPlan {
test_plan_for_url_body_and_format(url, body, "openai:responses")
}
fn test_plan_for_url_body_and_format(
url: &str,
body: RequestBody,
provider_api_format: &str,
) -> ExecutionPlan {
let mut payload = test_decision();
payload.provider_api_format = Some(provider_api_format.to_string());
payload.client_api_format = Some(provider_api_format.to_string());
let core =
take_ai_decision_plan_core(&mut payload).expect("core fields should be available");
build_ai_execution_plan_from_decision(
&mut payload,
AiExecutionPlanFromDecisionParts {
core,
method: "POST".to_string(),
url: url.to_string(),
headers: BTreeMap::from([(
"content-type".to_string(),
"application/json".to_string(),
)]),
content_type: Some("application/json".to_string()),
body,
stream: true,
},
)
}
}