mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-08 18:37:46 +08:00
feat(gateway): harden provider request execution
Preserve exact request payloads and model client surface and API operation explicitly. Add Anthropic compatibility profiles, bounded stream commitment, and scoped OAuth retry behavior across provider transports.
This commit is contained in:
@@ -511,7 +511,13 @@ async fn assert_ai_route_locally_denied_after_execution_runtime_miss_with_reques
|
||||
None
|
||||
);
|
||||
let payload: serde_json::Value = response.json().await.expect("body should parse");
|
||||
assert_eq!(payload["error"]["type"], "http_error");
|
||||
if request_path.trim_end_matches('/') == "/v1/messages" {
|
||||
assert_eq!(payload["type"], "error");
|
||||
assert_eq!(payload["error"]["type"], "overloaded_error");
|
||||
} else {
|
||||
assert!(payload.get("type").is_none());
|
||||
assert_eq!(payload["error"]["type"], "http_error");
|
||||
}
|
||||
assert_eq!(payload["error"]["message"], expected_message);
|
||||
assert_eq!(*control_execute_hits.lock().expect("mutex should lock"), 0);
|
||||
assert_eq!(*public_hits.lock().expect("mutex should lock"), 0);
|
||||
|
||||
@@ -992,6 +992,7 @@ async fn gateway_executes_kiro_claude_cli_sync_upstream_stream_via_local_finaliz
|
||||
let response = reqwest::Client::new()
|
||||
.post(format!("{gateway_url}/v1/messages"))
|
||||
.header(http::header::CONTENT_TYPE, "application/json")
|
||||
.header(http::header::USER_AGENT, "Claude-Code/2.1.0")
|
||||
.header(
|
||||
http::header::AUTHORIZATION,
|
||||
"Bearer sk-client-kiro-cli-finalize-local",
|
||||
|
||||
@@ -1326,6 +1326,7 @@ async fn gateway_executes_claude_cli_sync_upstream_stream_via_local_finalize_res
|
||||
let response = reqwest::Client::new()
|
||||
.post(format!("{gateway_url}/v1/messages"))
|
||||
.header(http::header::CONTENT_TYPE, "application/json")
|
||||
.header(http::header::USER_AGENT, "Claude-Code/2.1.0")
|
||||
.header(
|
||||
http::header::AUTHORIZATION,
|
||||
"Bearer sk-client-claude-cli-stream-sync-local",
|
||||
|
||||
@@ -516,6 +516,7 @@ async fn gateway_executes_kiro_claude_cli_stream_via_local_provider_catalog_cand
|
||||
let response = reqwest::Client::new()
|
||||
.post(format!("{gateway_url}/v1/messages"))
|
||||
.header(http::header::CONTENT_TYPE, "application/json")
|
||||
.header(http::header::USER_AGENT, "Claude-Code/2.1.0")
|
||||
.header(
|
||||
http::header::AUTHORIZATION,
|
||||
"Bearer sk-client-kiro-cli-local-stream",
|
||||
@@ -927,6 +928,9 @@ async fn gateway_executes_claude_cli_stream_via_local_decision_gate_without_wait
|
||||
b"{\"type\":\"data\",\"payload\":{\"kind\":\"data\",\"text\":\"event: message_start\\ndata: {\\\"type\\\":\\\"message_start\\\"}\\n\\n\"}}\n"
|
||||
));
|
||||
tokio::time::sleep(std::time::Duration::from_millis(250)).await;
|
||||
yield Ok::<Bytes, std::convert::Infallible>(Bytes::from_static(
|
||||
b"{\"type\":\"data\",\"payload\":{\"kind\":\"data\",\"text\":\"event: message_stop\\ndata: {\\\"type\\\":\\\"message_stop\\\"}\\n\\n\"}}\n"
|
||||
));
|
||||
yield Ok::<Bytes, std::convert::Infallible>(Bytes::from_static(
|
||||
b"{\"type\":\"telemetry\",\"payload\":{\"kind\":\"telemetry\",\"telemetry\":{\"elapsed_ms\":31,\"ttfb_ms\":11,\"upstream_bytes\":37}}}\n"
|
||||
));
|
||||
@@ -980,6 +984,7 @@ async fn gateway_executes_claude_cli_stream_via_local_decision_gate_without_wait
|
||||
let mut response = reqwest::Client::new()
|
||||
.post(format!("{gateway_url}/v1/messages"))
|
||||
.header(http::header::CONTENT_TYPE, "application/json")
|
||||
.header(http::header::USER_AGENT, "Claude-Code/2.1.0")
|
||||
.header(
|
||||
http::header::AUTHORIZATION,
|
||||
"Bearer sk-client-claude-cli-local",
|
||||
@@ -1004,7 +1009,7 @@ async fn gateway_executes_claude_cli_stream_via_local_decision_gate_without_wait
|
||||
);
|
||||
assert_eq!(
|
||||
response.text().await.expect("remaining body should read"),
|
||||
""
|
||||
"event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
|
||||
);
|
||||
|
||||
let seen_execution_runtime_request = seen_execution_runtime
|
||||
@@ -1438,6 +1443,7 @@ async fn gateway_executes_claude_code_cli_stream_via_local_decision_gate_with_lo
|
||||
let frames = concat!(
|
||||
"{\"type\":\"headers\",\"payload\":{\"kind\":\"headers\",\"status_code\":200,\"headers\":{\"content-type\":\"text/event-stream\"}}}\n",
|
||||
"{\"type\":\"data\",\"payload\":{\"kind\":\"data\",\"text\":\"event: message_start\\ndata: {\\\"type\\\":\\\"message_start\\\"}\\n\\n\"}}\n",
|
||||
"{\"type\":\"data\",\"payload\":{\"kind\":\"data\",\"text\":\"event: message_stop\\ndata: {\\\"type\\\":\\\"message_stop\\\"}\\n\\n\"}}\n",
|
||||
"{\"type\":\"telemetry\",\"payload\":{\"kind\":\"telemetry\",\"telemetry\":{\"elapsed_ms\":31,\"ttfb_ms\":11,\"upstream_bytes\":37}}}\n",
|
||||
"{\"type\":\"eof\",\"payload\":{\"kind\":\"eof\"}}\n"
|
||||
);
|
||||
@@ -1490,6 +1496,7 @@ async fn gateway_executes_claude_code_cli_stream_via_local_decision_gate_with_lo
|
||||
let response = reqwest::Client::new()
|
||||
.post(format!("{gateway_url}/v1/messages"))
|
||||
.header(http::header::CONTENT_TYPE, "application/json")
|
||||
.header(http::header::USER_AGENT, "Claude-Code/2.1.0")
|
||||
.header(
|
||||
http::header::AUTHORIZATION,
|
||||
"Bearer sk-client-claude-code-cli-local",
|
||||
@@ -1522,7 +1529,10 @@ async fn gateway_executes_claude_code_cli_stream_via_local_decision_gate_with_lo
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
strip_sse_keepalive_comments(&response.text().await.expect("body should read")),
|
||||
"event: message_start\ndata: {\"type\":\"message_start\"}\n\n"
|
||||
concat!(
|
||||
"event: message_start\ndata: {\"type\":\"message_start\"}\n\n",
|
||||
"event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n",
|
||||
)
|
||||
);
|
||||
|
||||
let seen_execution_runtime_request = seen_execution_runtime
|
||||
@@ -1551,14 +1561,17 @@ async fn gateway_executes_claude_code_cli_stream_via_local_decision_gate_with_lo
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.anthropic_beta,
|
||||
"claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,custom-beta"
|
||||
"claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,prompt-caching-scope-2026-01-05,effort-2025-11-24,context-management-2025-06-27,extended-cache-ttl-2025-04-11,context-1m-2025-08-07,custom-beta"
|
||||
);
|
||||
assert_eq!(seen_execution_runtime_request.x_app, "cli");
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.x_stainless_helper_method,
|
||||
"stream"
|
||||
);
|
||||
assert_eq!(seen_execution_runtime_request.user_agent, "Claude-Code/9.9");
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.user_agent,
|
||||
"claude-cli/2.1.161 (external, cli)"
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.endpoint_tag,
|
||||
"claude-code-cli-local"
|
||||
@@ -1921,6 +1934,7 @@ async fn gateway_executes_claude_chat_stream_via_local_decision_gate_with_local_
|
||||
let frames = concat!(
|
||||
"{\"type\":\"headers\",\"payload\":{\"kind\":\"headers\",\"status_code\":200,\"headers\":{\"content-type\":\"text/event-stream\"}}}\n",
|
||||
"{\"type\":\"data\",\"payload\":{\"kind\":\"data\",\"text\":\"event: message_start\\ndata: {\\\"type\\\":\\\"message_start\\\"}\\n\\n\"}}\n",
|
||||
"{\"type\":\"data\",\"payload\":{\"kind\":\"data\",\"text\":\"event: message_stop\\ndata: {\\\"type\\\":\\\"message_stop\\\"}\\n\\n\"}}\n",
|
||||
"{\"type\":\"telemetry\",\"payload\":{\"kind\":\"telemetry\",\"telemetry\":{\"elapsed_ms\":31,\"ttfb_ms\":11,\"upstream_bytes\":37}}}\n",
|
||||
"{\"type\":\"eof\",\"payload\":{\"kind\":\"eof\"}}\n"
|
||||
);
|
||||
@@ -1985,7 +1999,10 @@ async fn gateway_executes_claude_chat_stream_via_local_decision_gate_with_local_
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
strip_sse_keepalive_comments(&response.text().await.expect("body should read")),
|
||||
"event: message_start\ndata: {\"type\":\"message_start\"}\n\n"
|
||||
concat!(
|
||||
"event: message_start\ndata: {\"type\":\"message_start\"}\n\n",
|
||||
"event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n",
|
||||
)
|
||||
);
|
||||
|
||||
let seen_execution_runtime_request = seen_execution_runtime
|
||||
|
||||
@@ -469,6 +469,7 @@ async fn gateway_executes_claude_code_cli_sync_via_local_decision_gate_with_loca
|
||||
let response = reqwest::Client::new()
|
||||
.post(format!("{gateway_url}/v1/messages"))
|
||||
.header(http::header::CONTENT_TYPE, "application/json")
|
||||
.header(http::header::USER_AGENT, "Claude-Code/2.1.0")
|
||||
.header(
|
||||
http::header::AUTHORIZATION,
|
||||
"Bearer sk-client-claude-code-cli-local",
|
||||
@@ -535,15 +536,18 @@ async fn gateway_executes_claude_code_cli_sync_via_local_decision_gate_with_loca
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.anthropic_beta,
|
||||
"claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,custom-beta"
|
||||
"claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,prompt-caching-scope-2026-01-05,effort-2025-11-24,context-management-2025-06-27,extended-cache-ttl-2025-04-11,context-1m-2025-08-07,custom-beta"
|
||||
);
|
||||
assert_eq!(seen_execution_runtime_request.x_app, "cli");
|
||||
assert_eq!(seen_execution_runtime_request.x_stainless_helper_method, "");
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.x_stainless_package_version,
|
||||
"1.0.5"
|
||||
"0.94.0"
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.user_agent,
|
||||
"claude-cli/2.1.161 (external, cli)"
|
||||
);
|
||||
assert_eq!(seen_execution_runtime_request.user_agent, "Claude-Code/9.9");
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.endpoint_tag,
|
||||
"claude-code-cli-local"
|
||||
|
||||
@@ -545,6 +545,7 @@ async fn gateway_executes_kiro_claude_cli_sync_via_local_provider_catalog_candid
|
||||
let response = reqwest::Client::new()
|
||||
.post(format!("{gateway_url}/v1/messages"))
|
||||
.header(http::header::CONTENT_TYPE, "application/json")
|
||||
.header(http::header::USER_AGENT, "Claude-Code/2.1.0")
|
||||
.header(
|
||||
http::header::AUTHORIZATION,
|
||||
"Bearer sk-client-kiro-cli-local-sync",
|
||||
@@ -1153,6 +1154,7 @@ async fn gateway_executes_kiro_claude_cli_sync_via_local_provider_catalog_candid
|
||||
let response = reqwest::Client::new()
|
||||
.post(format!("{gateway_url}/v1/messages"))
|
||||
.header(http::header::CONTENT_TYPE, "application/json")
|
||||
.header(http::header::USER_AGENT, "Claude-Code/2.1.0")
|
||||
.header(
|
||||
http::header::AUTHORIZATION,
|
||||
"Bearer sk-client-kiro-cli-local-refresh",
|
||||
|
||||
@@ -49,7 +49,9 @@ async fn gateway_executes_claude_chat_sync_via_local_decision_gate_with_local_sy
|
||||
trace_id: String,
|
||||
url: String,
|
||||
model: String,
|
||||
stream: Option<bool>,
|
||||
auth_header_value: String,
|
||||
accept: String,
|
||||
anthropic_version: String,
|
||||
anthropic_beta: String,
|
||||
endpoint_tag: String,
|
||||
@@ -270,6 +272,12 @@ async fn gateway_executes_claude_chat_sync_via_local_decision_gate_with_local_sy
|
||||
let raw_body = to_bytes(body, usize::MAX).await.expect("body should read");
|
||||
let payload: serde_json::Value = serde_json::from_slice(&raw_body)
|
||||
.expect("execution runtime payload should parse");
|
||||
let upstream_url = payload
|
||||
.get("url")
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
let is_count_tokens = upstream_url.ends_with("/v1/messages/count_tokens");
|
||||
*seen_execution_runtime_inner
|
||||
.lock()
|
||||
.expect("mutex should lock") = Some(SeenExecutionRuntimeSyncRequest {
|
||||
@@ -279,11 +287,7 @@ async fn gateway_executes_claude_chat_sync_via_local_decision_gate_with_local_sy
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
url: payload
|
||||
.get("url")
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
url: upstream_url,
|
||||
model: payload
|
||||
.get("body")
|
||||
.and_then(|value| value.get("json_body"))
|
||||
@@ -291,12 +295,23 @@ async fn gateway_executes_claude_chat_sync_via_local_decision_gate_with_local_sy
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
stream: payload
|
||||
.get("body")
|
||||
.and_then(|value| value.get("json_body"))
|
||||
.and_then(|value| value.get("stream"))
|
||||
.and_then(|value| value.as_bool()),
|
||||
auth_header_value: payload
|
||||
.get("headers")
|
||||
.and_then(|value| value.get("x-api-key"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
accept: payload
|
||||
.get("headers")
|
||||
.and_then(|value| value.get("accept"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
anthropic_version: payload
|
||||
.get("headers")
|
||||
.and_then(|value| value.get("anthropic-version"))
|
||||
@@ -344,29 +359,39 @@ async fn gateway_executes_claude_chat_sync_via_local_decision_gate_with_local_sy
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
});
|
||||
Json(json!({
|
||||
"request_id": "trace-claude-chat-local-123",
|
||||
"status_code": 200,
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"json_body": {
|
||||
"id": "msg-local-claude-123",
|
||||
"type": "message",
|
||||
"model": "claude-sonnet-4-5-upstream",
|
||||
"role": "assistant",
|
||||
"content": [],
|
||||
"usage": {
|
||||
"input_tokens": 2,
|
||||
"output_tokens": 3
|
||||
if is_count_tokens {
|
||||
Json(json!({
|
||||
"request_id": "trace-claude-count-tokens-local-123",
|
||||
"status_code": 200,
|
||||
"headers": {"content-type": "application/json"},
|
||||
"body": {"json_body": {"input_tokens": 17}},
|
||||
"telemetry": {"elapsed_ms": 11}
|
||||
}))
|
||||
} else {
|
||||
Json(json!({
|
||||
"request_id": "trace-claude-chat-local-123",
|
||||
"status_code": 200,
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"json_body": {
|
||||
"id": "msg-local-claude-123",
|
||||
"type": "message",
|
||||
"model": "claude-sonnet-4-5-upstream",
|
||||
"role": "assistant",
|
||||
"content": [],
|
||||
"usage": {
|
||||
"input_tokens": 2,
|
||||
"output_tokens": 3
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry": {
|
||||
"elapsed_ms": 29
|
||||
}
|
||||
},
|
||||
"telemetry": {
|
||||
"elapsed_ms": 29
|
||||
}
|
||||
}))
|
||||
}))
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
@@ -484,6 +509,125 @@ async fn gateway_executes_claude_chat_sync_via_local_decision_gate_with_local_sy
|
||||
assert_eq!(*plan_hits.lock().expect("mutex should lock"), 0);
|
||||
assert_eq!(*public_hits.lock().expect("mutex should lock"), 0);
|
||||
|
||||
for (case, body, expected_message) in [
|
||||
("missing-body", None, "Request body is required"),
|
||||
("invalid-json", Some("{"), "Invalid JSON body"),
|
||||
(
|
||||
"missing-model",
|
||||
Some(r#"{"messages":[]}"#),
|
||||
"model: Field required",
|
||||
),
|
||||
(
|
||||
"missing-messages",
|
||||
Some(r#"{"model":"claude-sonnet-4-5"}"#),
|
||||
"messages: Field required",
|
||||
),
|
||||
] {
|
||||
let mut request = reqwest::Client::new()
|
||||
.post(format!("{gateway_url}/v1/messages/count_tokens"))
|
||||
.header(http::header::CONTENT_TYPE, "application/json")
|
||||
.header("x-api-key", "sk-client-claude-chat-local")
|
||||
.header("anthropic-version", "2023-06-01")
|
||||
.header(TRACE_ID_HEADER, format!("trace-claude-count-tokens-{case}"));
|
||||
if let Some(body) = body {
|
||||
request = request.body(body);
|
||||
}
|
||||
let invalid_response = request
|
||||
.send()
|
||||
.await
|
||||
.expect("invalid count_tokens request should complete locally");
|
||||
assert_eq!(invalid_response.status(), StatusCode::BAD_REQUEST);
|
||||
let invalid_json: serde_json::Value = invalid_response
|
||||
.json()
|
||||
.await
|
||||
.expect("Anthropic error body should parse");
|
||||
assert_eq!(invalid_json["type"], "error");
|
||||
assert_eq!(invalid_json["error"]["type"], "invalid_request_error");
|
||||
assert_eq!(invalid_json["error"]["message"], expected_message);
|
||||
assert_eq!(
|
||||
seen_execution_runtime
|
||||
.lock()
|
||||
.expect("mutex should lock")
|
||||
.as_ref()
|
||||
.map(|request| request.url.as_str()),
|
||||
Some("https://api.anthropic.example/custom/v1/messages"),
|
||||
"invalid count_tokens request must not reach the execution runtime"
|
||||
);
|
||||
}
|
||||
|
||||
let count_tokens_response = reqwest::Client::new()
|
||||
.post(format!("{gateway_url}/v1/messages/count_tokens"))
|
||||
.header(http::header::CONTENT_TYPE, "application/json")
|
||||
.header("x-api-key", "sk-client-claude-chat-local")
|
||||
.header("anthropic-version", "2023-06-01")
|
||||
.header(TRACE_ID_HEADER, "trace-claude-count-tokens-local-123")
|
||||
.body(
|
||||
"{\"model\":\"claude-sonnet-4-5\",\"messages\":[{\"role\":\"user\",\"content\":\"hello\"}],\"stream\":true}",
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.expect("count_tokens request should succeed");
|
||||
|
||||
assert_eq!(count_tokens_response.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
count_tokens_response
|
||||
.headers()
|
||||
.get(EXECUTION_PATH_HEADER)
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
Some(EXECUTION_PATH_EXECUTION_RUNTIME_SYNC)
|
||||
);
|
||||
let seen_count_tokens = seen_execution_runtime
|
||||
.lock()
|
||||
.expect("mutex should lock")
|
||||
.clone()
|
||||
.expect("count_tokens execution request should be captured");
|
||||
assert_eq!(
|
||||
seen_count_tokens.url,
|
||||
"https://api.anthropic.example/custom/v1/messages/count_tokens"
|
||||
);
|
||||
assert_eq!(seen_count_tokens.model, "claude-sonnet-4-5-upstream");
|
||||
assert_eq!(seen_count_tokens.stream, None);
|
||||
assert_eq!(seen_count_tokens.accept, "application/json");
|
||||
assert_eq!(
|
||||
seen_count_tokens.auth_header_value,
|
||||
"sk-upstream-claude-chat"
|
||||
);
|
||||
|
||||
let count_tokens_json: serde_json::Value = count_tokens_response
|
||||
.json()
|
||||
.await
|
||||
.expect("count_tokens response should parse");
|
||||
assert_eq!(
|
||||
count_tokens_json["input_tokens"], 17,
|
||||
"unexpected count_tokens response: {count_tokens_json}"
|
||||
);
|
||||
|
||||
use std::io::Write as _;
|
||||
let mut encoder = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default());
|
||||
encoder
|
||||
.write_all(
|
||||
br#"{"model":"claude-sonnet-4-5","messages":[{"role":"user","content":"hello"}]}"#,
|
||||
)
|
||||
.expect("gzip request body should encode");
|
||||
let gzip_body = encoder.finish().expect("gzip request body should finish");
|
||||
let gzip_count_tokens_response = reqwest::Client::new()
|
||||
.post(format!("{gateway_url}/v1/messages/count_tokens"))
|
||||
.header(http::header::CONTENT_TYPE, "application/json")
|
||||
.header(http::header::CONTENT_ENCODING, "gzip")
|
||||
.header("x-api-key", "sk-client-claude-chat-local")
|
||||
.header("anthropic-version", "2023-06-01")
|
||||
.header(TRACE_ID_HEADER, "trace-claude-count-tokens-gzip-123")
|
||||
.body(gzip_body)
|
||||
.send()
|
||||
.await
|
||||
.expect("gzip count_tokens request should succeed");
|
||||
assert_eq!(gzip_count_tokens_response.status(), StatusCode::OK);
|
||||
let gzip_count_tokens_json: serde_json::Value = gzip_count_tokens_response
|
||||
.json()
|
||||
.await
|
||||
.expect("gzip count_tokens response should parse");
|
||||
assert_eq!(gzip_count_tokens_json["input_tokens"], 17);
|
||||
|
||||
gateway_handle.abort();
|
||||
execution_runtime_handle.abort();
|
||||
upstream_handle.abort();
|
||||
@@ -603,7 +747,8 @@ async fn gateway_surfaces_candidate_list_empty_reason_for_claude_chat_runtime_mi
|
||||
Some("candidate_list_empty")
|
||||
);
|
||||
let payload: serde_json::Value = response.json().await.expect("body should parse");
|
||||
assert_eq!(payload["error"]["type"], "http_error");
|
||||
assert_eq!(payload["type"], "error");
|
||||
assert_eq!(payload["error"]["type"], "overloaded_error");
|
||||
assert_eq!(
|
||||
payload["error"]["message"],
|
||||
"没有可用提供商支持模型 claude-sonnet-4-5 的同步请求"
|
||||
|
||||
@@ -404,6 +404,7 @@ async fn gateway_executes_claude_cli_sync_via_local_decision_gate_with_local_syn
|
||||
let response = reqwest::Client::new()
|
||||
.post(format!("{gateway_url}/v1/messages"))
|
||||
.header(http::header::CONTENT_TYPE, "application/json")
|
||||
.header(http::header::USER_AGENT, "Claude-Code/2.1.0")
|
||||
.header(
|
||||
http::header::AUTHORIZATION,
|
||||
"Bearer sk-client-claude-cli-local",
|
||||
@@ -730,6 +731,7 @@ async fn gateway_returns_claude_cli_error_for_local_sync_failure_impl() {
|
||||
let response = reqwest::Client::new()
|
||||
.post(format!("{gateway_url}/v1/messages"))
|
||||
.header(http::header::CONTENT_TYPE, "application/json")
|
||||
.header(http::header::USER_AGENT, "Claude-Code/2.1.0")
|
||||
.header(
|
||||
http::header::AUTHORIZATION,
|
||||
"Bearer sk-client-claude-cli-local-error",
|
||||
@@ -985,6 +987,7 @@ async fn gateway_marks_claude_cli_cross_format_runtime_miss_when_format_conversi
|
||||
let response = reqwest::Client::new()
|
||||
.post(format!("{gateway_url}/v1/messages?beta=true"))
|
||||
.header(http::header::CONTENT_TYPE, "application/json")
|
||||
.header(http::header::USER_AGENT, "Claude-Code/2.1.0")
|
||||
.header(
|
||||
http::header::AUTHORIZATION,
|
||||
"Bearer sk-client-claude-cli-openai-local-miss",
|
||||
@@ -1011,7 +1014,8 @@ async fn gateway_marks_claude_cli_cross_format_runtime_miss_when_format_conversi
|
||||
Some("all_candidates_skipped")
|
||||
);
|
||||
let response_json: serde_json::Value = response.json().await.expect("body should parse");
|
||||
assert_eq!(response_json["error"]["type"], "http_error");
|
||||
assert_eq!(response_json["type"], "error");
|
||||
assert_eq!(response_json["error"]["type"], "overloaded_error");
|
||||
assert_eq!(
|
||||
response_json["error"]["message"],
|
||||
"没有可用提供商支持模型 gpt-5.4 的同步请求"
|
||||
|
||||
Reference in New Issue
Block a user