Isolate standalone key wallet handling

This commit is contained in:
fawney19
2026-04-27 12:54:59 +08:00
parent 3b542434a2
commit 1d23bf4ecb
12 changed files with 240 additions and 16 deletions

View File

@@ -69,6 +69,7 @@ fn copy_allowed_metadata_fields(source: &Map<String, Value>, target: &mut Map<St
copy_non_empty_string(source, target, "trace_id");
copy_bool(source, target, "client_requested_stream");
copy_bool(source, target, "upstream_is_stream");
copy_bool(source, target, "api_key_is_standalone");
copy_number(source, target, "provider_request_body_base64_bytes");
copy_number(source, target, "provider_response_body_base64_bytes");
copy_number(source, target, "client_response_body_base64_bytes");
@@ -97,6 +98,7 @@ fn move_allowed_metadata_fields(mut source: Map<String, Value>, target: &mut Map
remove_non_empty_string(&mut source, target, "trace_id");
remove_bool(&mut source, target, "client_requested_stream");
remove_bool(&mut source, target, "upstream_is_stream");
remove_bool(&mut source, target, "api_key_is_standalone");
remove_number(&mut source, target, "provider_request_body_base64_bytes");
remove_number(&mut source, target, "provider_response_body_base64_bytes");
remove_number(&mut source, target, "client_response_body_base64_bytes");
@@ -384,6 +386,7 @@ mod tests {
"trace_id": "trace-1",
"client_requested_stream": false,
"upstream_is_stream": true,
"api_key_is_standalone": true,
"provider_request_body_base64_bytes": 512,
"provider_response_body_base64_bytes": 1024,
"client_response_body_base64_bytes": 2048,
@@ -414,6 +417,7 @@ mod tests {
"trace_id": "trace-1",
"client_requested_stream": false,
"upstream_is_stream": true,
"api_key_is_standalone": true,
"provider_request_body_base64_bytes": 512,
"provider_response_body_base64_bytes": 1024,
"client_response_body_base64_bytes": 2048,
@@ -472,6 +476,7 @@ mod tests {
"candidate_index": 0,
"client_requested_stream": false,
"upstream_is_stream": true,
"api_key_is_standalone": true,
"provider_id": "provider-1",
"model_id": "model-1",
"global_model_id": "global-model-1",
@@ -489,6 +494,7 @@ mod tests {
json!({
"client_requested_stream": false,
"upstream_is_stream": true,
"api_key_is_standalone": true,
"model_id": "model-1",
"global_model_id": "global-model-1",
"global_model_name": "gpt-5",

View File

@@ -31,6 +31,7 @@ pub async fn settle_usage_if_needed(
request_id: usage.request_id.clone(),
user_id: usage.user_id.clone(),
api_key_id: usage.api_key_id.clone(),
api_key_is_standalone: usage_api_key_is_standalone(usage),
provider_id: usage.provider_id.clone(),
status: usage.status.clone(),
billing_status: usage.billing_status.clone(),
@@ -42,6 +43,15 @@ pub async fn settle_usage_if_needed(
Ok(())
}
fn usage_api_key_is_standalone(usage: &StoredRequestUsageAudit) -> bool {
usage
.request_metadata
.as_ref()
.and_then(|metadata| metadata.get("api_key_is_standalone"))
.and_then(serde_json::Value::as_bool)
.unwrap_or(false)
}
fn finite_cost(value: f64) -> Result<f64, DataLayerError> {
if value.is_finite() {
Ok(value)
@@ -60,6 +70,7 @@ mod tests {
use aether_data_contracts::repository::settlement::UsageSettlementInput;
use aether_data_contracts::repository::usage::StoredRequestUsageAudit;
use async_trait::async_trait;
use serde_json::json;
#[derive(Default)]
struct TestSettlementWriter {
@@ -150,6 +161,25 @@ mod tests {
assert_eq!(inputs[0].finalized_at_unix_secs, Some(200));
assert_eq!(inputs[0].total_cost_usd, 1.25);
assert_eq!(inputs[0].actual_total_cost_usd, 0.75);
assert!(!inputs[0].api_key_is_standalone);
}
#[tokio::test]
async fn propagates_standalone_key_flag_from_usage_metadata() {
let writer = TestSettlementWriter {
has_writer: true,
..Default::default()
};
let mut usage = sample_usage();
usage.request_metadata = Some(json!({ "api_key_is_standalone": true }));
settle_usage_if_needed(&writer, &usage)
.await
.expect("settlement should succeed");
let inputs = writer.inputs.lock().expect("settlement inputs lock");
assert_eq!(inputs.len(), 1);
assert!(inputs[0].api_key_is_standalone);
}
#[tokio::test]

View File

@@ -1543,6 +1543,12 @@ fn build_runtime_request_metadata_seed_from_parts(
Value::Bool(upstream_is_stream),
);
}
if let Some(api_key_is_standalone) = context_bool(context, "api_key_is_standalone") {
metadata.insert(
"api_key_is_standalone".to_string(),
Value::Bool(api_key_is_standalone),
);
}
let provider_source_bytes = provider_request_body_base64.and_then(decoded_base64_len_hint);
append_runtime_body_capture_metadata(
&mut metadata,
@@ -2498,6 +2504,47 @@ mod tests {
assert_eq!(record.request_metadata, None);
}
#[test]
fn pending_usage_record_preserves_standalone_key_metadata() {
let plan = ExecutionPlan {
request_id: "req-pending-standalone-1".to_string(),
candidate_id: None,
provider_name: Some("OpenAI".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://example.com/v1/responses".to_string(),
headers: BTreeMap::new(),
content_type: Some("application/json".to_string()),
content_encoding: None,
body: RequestBody::from_json(json!({"model": "gpt-5.4"})),
stream: false,
client_api_format: "openai:responses".to_string(),
provider_api_format: "openai:responses".to_string(),
model_name: Some("gpt-5.4".to_string()),
proxy: None,
tls_profile: None,
timeouts: None,
};
let record = build_pending_usage_record(
&plan,
Some(&json!({
"api_key_is_standalone": true
})),
1_700_000_000,
)
.expect("pending usage should build");
assert_eq!(
record.request_metadata,
Some(json!({
"api_key_is_standalone": true
}))
);
}
#[test]
fn streaming_usage_records_stay_lightweight_by_default() {
let plan = ExecutionPlan {