From 4830ef093bfd7c97c8cc2fb25a07f563e2963436 Mon Sep 17 00:00:00 2001 From: Zyy Date: Sat, 29 Aug 2026 22:04:20 +0800 Subject: [PATCH] refactor(architecture): remove obsolete compatibility scaffolding Remove orphaned fixtures, unused capability and resource branches, and stale credential APIs. Retire legacy Usage metadata fallbacks and record the completed cleanup. Co-authored-by: Codex --- backend/Cargo.lock | 8 - backend/crates/gateway-api/src/admin/auth.rs | 5 - .../src/openai/responses/request.rs | 6 - .../crates/gateway-api/tests/architecture.rs | 8 - .../golden/reasoning_replay_request.json | 26 --- .../responses/fixtures/http_sse/after_401.sse | 5 - .../responses/fixtures/http_sse/after_402.sse | 5 - .../responses/fixtures/http_sse/after_403.sse | 5 - .../fixtures/http_sse/after_5xx_retry.sse | 5 - .../fixtures/http_sse/after_cloudflare.sse | 5 - .../http_sse/after_model_unsupported.sse | 5 - .../http_sse/chat_delta_completed_usage.sse | 6 - .../chat_delta_completed_usage_with_id.sse | 6 - .../http_sse/chat_delta_incomplete_usage.sse | 5 - .../chat_reasoning_text_completed.sse | 9 - .../fixtures/http_sse/completed_empty.sse | 3 - .../http_sse/completed_empty_done_crlf.sse | 5 - .../http_sse/completed_image_tool_usage.sse | 3 - .../http_sse/completed_image_usage.sse | 5 - .../http_sse/completed_order_usage.sse | 3 - .../fixtures/http_sse/completed_usage.sse | 5 - .../http_sse/completed_usage_basic.sse | 3 - .../http_sse/created_completed_usage.sse | 6 - .../http_sse/data_type_text_delta.sse | 2 - .../fixtures/http_sse/done_item_completed.sse | 5 - .../responses/fixtures/http_sse/done_only.sse | 2 - .../fixtures/http_sse/empty_completed.sse | 2 - .../fixtures/http_sse/empty_text_delta.sse | 3 - .../fixtures/http_sse/failed_auth.sse | 2 - .../fixtures/http_sse/failed_cyber_policy.sse | 3 - .../http_sse/failed_model_unsupported.sse | 2 - .../fixtures/http_sse/failed_quota.sse | 2 - .../http_sse/failed_server_overloaded.sse | 2 - .../function_call_arguments_delta.sse | 3 - .../http_sse/incomplete_text_delta.sse | 2 - .../http_sse/live_stream_completed.sse | 3 - .../http_sse/live_stream_failed_auth.sse | 2 - .../http_sse/live_stream_failed_quota.sse | 2 - .../http_sse/live_stream_hello_delta.sse | 3 - .../metadata_rate_limits_no_first_output.sse | 9 - .../http_sse/output_then_cyber_policy.sse | 8 - ...utput_then_previous_response_not_found.sse | 8 - .../fixtures/http_sse/partial_clean_close.sse | 6 - .../http_sse/partial_logged_disconnect.sse | 3 - .../http_sse/partial_transport_failure.sse | 3 - .../fixtures/http_sse/reasoning_delta.sse | 3 - .../fixtures/http_sse/response_created.sse | 3 - .../fixtures/http_sse/stream_after_401.sse | 5 - .../fixtures/http_sse/stream_after_402.sse | 5 - .../fixtures/http_sse/stream_after_403.sse | 5 - .../fixtures/http_sse/stream_after_429.sse | 5 - .../http_sse/stream_after_5xx_retry.sse | 5 - .../http_sse/stream_after_cloudflare.sse | 5 - .../stream_after_model_unsupported.sse | 5 - .../fixtures/http_sse/stream_usage.sse | 5 - ...tural_then_previous_response_not_found.sse | 8 - .../responses/fixtures/http_sse/success.sse | 5 - .../http_sse/text_delta_completed_usage.sse | 6 - .../fixtures/http_sse/text_delta_crlf.sse | 3 - .../fixtures/http_sse/text_delta_hello.sse | 3 - .../http_sse/text_deltas_completed.sse | 8 - .../fixtures/http_sse/tuple_object.sse | 5 - .../sse/multiline_data_with_metadata.sse | 6 - .../completed_with_reasoning_replay.json | 32 ---- .../websocket/first_account_limited.json | 1 - .../websocket/history_rate_limited.json | 1 - .../websocket/invalid_encrypted_content.json | 1 - .../previous_response_not_found.json | 7 - .../fixtures/websocket/rate_limited.json | 1 - .../websocket/second_account_limited.json | 1 - .../fixtures/websocket/token_revoked.json | 1 - .../websocket/unanswered_function_call.json | 7 - .../gateway-core/src/engine/coordinator.rs | 86 +++------ .../gateway-core/src/engine/provider.rs | 116 ++---------- backend/crates/gateway-core/src/error.rs | 20 -- .../crates/gateway-core/src/operation/mod.rs | 175 +----------------- .../gateway-core/tests/engine/coordinator.rs | 132 ++++++------- .../gateway-core/tests/engine/execution.rs | 9 +- .../gateway-core/tests/engine/provider.rs | 40 +--- .../gateway-core/tests/operation/mod.rs | 35 +--- .../crates/gateway-core/tests/routing/mod.rs | 2 +- backend/crates/gateway-host/Cargo.toml | 1 - .../src/postgres/execution_buffer.rs | 1 - backend/crates/providers/openai/Cargo.toml | 1 - .../providers/openai/src/credential/mod.rs | 2 +- .../openai/src/credential/repository.rs | 62 +------ .../openai/src/credential/selector.rs | 3 +- .../openai/src/credential/token_client.rs | 8 - .../providers/openai/src/credential/types.rs | 31 ---- .../crates/providers/openai/src/provider.rs | 14 +- .../openai/tests/transport/canonical.rs | 4 +- .../fixtures}/metadata_only_prefix.sse | 0 backend/crates/providers/xai/src/provider.rs | 2 +- .../providers/xai/src/transport/session.rs | 10 - .../providers/xai/tests/provider/contract.rs | 4 +- frontend/src/api/modules/accounts.ts | 8 - .../components/AccountGroupMultiSelect.vue | 164 ---------------- .../settings/composables/useBackupRecords.ts | 2 - .../settings/composables/useBackupSettings.ts | 2 - frontend/src/views/usage/utils/format.ts | 6 - frontend/src/views/usage/utils/records.ts | 108 +++-------- 101 files changed, 160 insertions(+), 1249 deletions(-) delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/golden/reasoning_replay_request.json delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_401.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_402.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_403.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_5xx_retry.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_cloudflare.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_model_unsupported.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/chat_delta_completed_usage.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/chat_delta_completed_usage_with_id.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/chat_delta_incomplete_usage.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/chat_reasoning_text_completed.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_empty.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_empty_done_crlf.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_image_tool_usage.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_image_usage.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_order_usage.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_usage.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_usage_basic.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/created_completed_usage.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/data_type_text_delta.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/done_item_completed.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/done_only.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/empty_completed.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/empty_text_delta.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/failed_auth.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/failed_cyber_policy.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/failed_model_unsupported.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/failed_quota.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/failed_server_overloaded.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/function_call_arguments_delta.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/incomplete_text_delta.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/live_stream_completed.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/live_stream_failed_auth.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/live_stream_failed_quota.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/live_stream_hello_delta.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/metadata_rate_limits_no_first_output.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/output_then_cyber_policy.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/output_then_previous_response_not_found.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/partial_clean_close.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/partial_logged_disconnect.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/partial_transport_failure.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/reasoning_delta.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/response_created.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_401.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_402.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_403.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_429.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_5xx_retry.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_cloudflare.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_model_unsupported.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_usage.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/structural_then_previous_response_not_found.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/success.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/text_delta_completed_usage.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/text_delta_crlf.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/text_delta_hello.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/text_deltas_completed.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/tuple_object.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/sse/multiline_data_with_metadata.sse delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/completed_with_reasoning_replay.json delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/first_account_limited.json delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/history_rate_limited.json delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/invalid_encrypted_content.json delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/previous_response_not_found.json delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/rate_limited.json delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/second_account_limited.json delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/token_revoked.json delete mode 100644 backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/unanswered_function_call.json rename backend/crates/{gateway-api/tests/openai/responses/fixtures/http_sse => providers/openai/tests/transport/fixtures}/metadata_only_prefix.sse (100%) delete mode 100644 frontend/src/components/AccountGroupMultiSelect.vue diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 5d18d8c79..aaa1745ba 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -1446,7 +1446,6 @@ dependencies = [ "tempfile", "thiserror", "tokio", - "tower", "tracing", "tracing-appender", "tracing-subscriber", @@ -2449,7 +2448,6 @@ dependencies = [ "tracing-subscriber", "tungstenite 0.27.0", "url", - "urlencoding", "uuid", "wiremock", "zstd", @@ -3901,12 +3899,6 @@ dependencies = [ "serde", ] -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - [[package]] name = "utf-8" version = "0.7.6" diff --git a/backend/crates/gateway-api/src/admin/auth.rs b/backend/crates/gateway-api/src/admin/auth.rs index 675778472..b0b67a473 100644 --- a/backend/crates/gateway-api/src/admin/auth.rs +++ b/backend/crates/gateway-api/src/admin/auth.rs @@ -37,11 +37,6 @@ impl AdminAuth { pub const fn context(&self) -> &AdminRequestContext { &self.context } - - #[must_use] - pub fn into_context(self) -> AdminRequestContext { - self.context - } } impl FromRequestParts for AdminAuth diff --git a/backend/crates/gateway-api/src/openai/responses/request.rs b/backend/crates/gateway-api/src/openai/responses/request.rs index 00161af10..8fba6ed0a 100644 --- a/backend/crates/gateway-api/src/openai/responses/request.rs +++ b/backend/crates/gateway-api/src/openai/responses/request.rs @@ -136,12 +136,6 @@ pub enum ContinuationIntent { } impl ContinuationIntent { - /// 返回是否请求了对话延续。 - #[must_use] - pub const fn is_continuation(&self) -> bool { - matches!(self, Self::PreviousResponseId(_)) - } - /// 返回待 history owner 解析的 response ID。 #[must_use] pub fn previous_response_id(&self) -> Option<&str> { diff --git a/backend/crates/gateway-api/tests/architecture.rs b/backend/crates/gateway-api/tests/architecture.rs index ec27c5bcf..aadd1eb4d 100644 --- a/backend/crates/gateway-api/tests/architecture.rs +++ b/backend/crates/gateway-api/tests/architecture.rs @@ -107,14 +107,6 @@ fn test_tree_should_match_frozen_rust_mirror() { assert_eq!(actual, expected); } -#[test] -fn frozen_openai_fixture_set_should_be_complete() { - let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/openai/responses/fixtures"); - let files = all_files(&root); - - assert_eq!(files.len(), 69); -} - #[test] fn admin_routes_should_use_only_get_post_and_static_paths() { let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/admin"); diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/golden/reasoning_replay_request.json b/backend/crates/gateway-api/tests/openai/responses/fixtures/golden/reasoning_replay_request.json deleted file mode 100644 index 7cba567eb..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/golden/reasoning_replay_request.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "type": "response.create", - "model": "gpt-5.5", - "instructions": "answer briefly", - "input": [ - { - "type": "reasoning", - "id": "rs_implicit_resume", - "status": "completed", - "summary": [ - { - "type": "summary_text", - "text": "cached reasoning" - } - ], - "encrypted_content": "enc_reasoning_replay" - }, - { - "role": "user", - "content": "continue" - } - ], - "store": false, - "stream": false, - "previous_response_id": "resp_implicit_resume_first" -} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_401.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_401.sse deleted file mode 100644 index 201c7d963..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_401.sse +++ /dev/null @@ -1,5 +0,0 @@ -event: response.output_text.delta -data: {"delta":"auth fallback hello"} - -event: response.completed -data: {"response":{"id":"resp_after_401","object":"response","status":"completed","usage":{"input_tokens":3,"output_tokens":1}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_402.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_402.sse deleted file mode 100644 index 448132d28..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_402.sse +++ /dev/null @@ -1,5 +0,0 @@ -event: response.output_text.delta -data: {"delta":"quota fallback hello"} - -event: response.completed -data: {"response":{"id":"resp_after_402","object":"response","status":"completed","usage":{"input_tokens":6,"output_tokens":2}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_403.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_403.sse deleted file mode 100644 index fa20326d9..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_403.sse +++ /dev/null @@ -1,5 +0,0 @@ -event: response.output_text.delta -data: {"delta":"banned fallback hello"} - -event: response.completed -data: {"response":{"id":"resp_after_403","object":"response","status":"completed","usage":{"input_tokens":5,"output_tokens":1}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_5xx_retry.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_5xx_retry.sse deleted file mode 100644 index 3d296dd55..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_5xx_retry.sse +++ /dev/null @@ -1,5 +0,0 @@ -event: response.output_text.delta -data: {"delta":"retry hello"} - -event: response.completed -data: {"response":{"id":"resp_after_5xx_retry","object":"response","status":"completed","usage":{"input_tokens":4,"output_tokens":2}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_cloudflare.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_cloudflare.sse deleted file mode 100644 index aaee516ce..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_cloudflare.sse +++ /dev/null @@ -1,5 +0,0 @@ -event: response.output_text.delta -data: {"delta":"cloudflare fallback hello"} - -event: response.completed -data: {"response":{"id":"resp_after_cloudflare","object":"response","status":"completed","usage":{"input_tokens":5,"output_tokens":2}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_model_unsupported.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_model_unsupported.sse deleted file mode 100644 index fe9d85435..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/after_model_unsupported.sse +++ /dev/null @@ -1,5 +0,0 @@ -event: response.output_text.delta -data: {"delta":"model fallback hello"} - -event: response.completed -data: {"response":{"id":"resp_after_model_unsupported","object":"response","status":"completed","usage":{"input_tokens":4,"output_tokens":2}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/chat_delta_completed_usage.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/chat_delta_completed_usage.sse deleted file mode 100644 index 52171b0fb..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/chat_delta_completed_usage.sse +++ /dev/null @@ -1,6 +0,0 @@ -event: response.output_text.delta -data: {"delta":"hello"} - -event: response.completed -data: {"response":{"usage":{"input_tokens":2,"output_tokens":3}}} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/chat_delta_completed_usage_with_id.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/chat_delta_completed_usage_with_id.sse deleted file mode 100644 index 18d0e7eee..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/chat_delta_completed_usage_with_id.sse +++ /dev/null @@ -1,6 +0,0 @@ -event: response.output_text.delta -data: {"delta":"hello"} - -event: response.completed -data: {"response":{"id":"resp_1","usage":{"input_tokens":2,"output_tokens":3}}} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/chat_delta_incomplete_usage.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/chat_delta_incomplete_usage.sse deleted file mode 100644 index 0d2a0a49d..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/chat_delta_incomplete_usage.sse +++ /dev/null @@ -1,5 +0,0 @@ -event: response.output_text.delta -data: {"type":"response.output_text.delta","delta":"hello"} - -event: response.incomplete -data: {"type":"response.incomplete","response":{"id":"resp_incomplete","object":"response","status":"incomplete","incomplete_details":{"reason":"max_output_tokens"},"usage":{"input_tokens":2,"output_tokens":3},"output":[{"type":"message","status":"incomplete","role":"assistant","content":[{"type":"output_text","text":"hello","annotations":[]}]}],"output_text":"hello"}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/chat_reasoning_text_completed.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/chat_reasoning_text_completed.sse deleted file mode 100644 index 3742f43b6..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/chat_reasoning_text_completed.sse +++ /dev/null @@ -1,9 +0,0 @@ -event: response.reasoning_summary_text.delta -data: {"delta":"I considered the context."} - -event: response.output_text.delta -data: {"delta":"hello"} - -event: response.completed -data: {"response":{"id":"resp_1","output_text":"hello","usage":{"input_tokens":9,"output_tokens":3}}} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_empty.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_empty.sse deleted file mode 100644 index 52fb4d71e..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_empty.sse +++ /dev/null @@ -1,3 +0,0 @@ -event: response.completed -data: {} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_empty_done_crlf.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_empty_done_crlf.sse deleted file mode 100644 index 8487a922f..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_empty_done_crlf.sse +++ /dev/null @@ -1,5 +0,0 @@ -event: response.completed -data: {} - -data: [DONE] - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_image_tool_usage.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_image_tool_usage.sse deleted file mode 100644 index 470e6765d..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_image_tool_usage.sse +++ /dev/null @@ -1,3 +0,0 @@ -event: response.completed -data: {"response":{"id":"resp_1","usage":{"input_tokens":12,"output_tokens":5,"input_tokens_details":{"cached_tokens":3}},"tool_usage":{"image_gen":{"input_tokens":31,"output_tokens":9}}}} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_image_usage.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_image_usage.sse deleted file mode 100644 index c551f8b34..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_image_usage.sse +++ /dev/null @@ -1,5 +0,0 @@ -event: response.output_text.delta -data: {"delta":"image done"} - -event: response.completed -data: {"response":{"id":"resp_image","object":"response","status":"completed","output_text":"image done","usage":{"input_tokens":7,"output_tokens":4,"input_tokens_details":{"cached_tokens":2}},"tool_usage":{"image_gen":{"input_tokens":31,"output_tokens":9}}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_order_usage.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_order_usage.sse deleted file mode 100644 index b9e8e80f0..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_order_usage.sse +++ /dev/null @@ -1,3 +0,0 @@ -event: response.completed -data: {"response":{"id":"resp_order","status":"completed","usage":{"input_tokens":1,"output_tokens":1}}} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_usage.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_usage.sse deleted file mode 100644 index 6c595fdd2..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_usage.sse +++ /dev/null @@ -1,5 +0,0 @@ -event: response.output_text.delta -data: {"delta":"usage hello"} - -event: response.completed -data: {"response":{"id":"resp_usage","object":"response","status":"completed","output_text":"usage hello","usage":{"input_tokens":7,"output_tokens":4,"input_tokens_details":{"cached_tokens":2}}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_usage_basic.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_usage_basic.sse deleted file mode 100644 index 461bec2b0..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/completed_usage_basic.sse +++ /dev/null @@ -1,3 +0,0 @@ -event: response.completed -data: {"response":{"id":"resp_1","usage":{"input_tokens":2,"output_tokens":3,"input_tokens_details":{"cached_tokens":1}}}} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/created_completed_usage.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/created_completed_usage.sse deleted file mode 100644 index b1a5dc385..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/created_completed_usage.sse +++ /dev/null @@ -1,6 +0,0 @@ -event: response.created -data: {"response":{"id":"resp_1","usage":{"input_tokens":3,"output_tokens":5,"input_tokens_details":{"cached_tokens":1}}}} - -event: response.completed -data: {"response":{"id":"resp_1","usage":{"input_tokens":3,"output_tokens":5,"input_tokens_details":{"cached_tokens":1}}}} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/data_type_text_delta.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/data_type_text_delta.sse deleted file mode 100644 index 6b0db1049..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/data_type_text_delta.sse +++ /dev/null @@ -1,2 +0,0 @@ -data: {"type":"response.output_text.delta","delta":"hello"} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/done_item_completed.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/done_item_completed.sse deleted file mode 100644 index f9bc50a61..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/done_item_completed.sse +++ /dev/null @@ -1,5 +0,0 @@ -event: response.output_item.done -data: {"item":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"from done item"}]}} - -event: response.completed -data: {"response":{"id":"resp_item","object":"response","status":"completed","output":[],"usage":{"input_tokens":2,"output_tokens":3}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/done_only.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/done_only.sse deleted file mode 100644 index bcdf0122a..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/done_only.sse +++ /dev/null @@ -1,2 +0,0 @@ -data: [DONE] - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/empty_completed.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/empty_completed.sse deleted file mode 100644 index e80392ca2..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/empty_completed.sse +++ /dev/null @@ -1,2 +0,0 @@ -event: response.completed -data: {"response":{"id":"resp_empty","object":"response","status":"completed"}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/empty_text_delta.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/empty_text_delta.sse deleted file mode 100644 index 031a2b15a..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/empty_text_delta.sse +++ /dev/null @@ -1,3 +0,0 @@ -event: response.output_text.delta -data: {"delta":""} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/failed_auth.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/failed_auth.sse deleted file mode 100644 index d2dfa0463..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/failed_auth.sse +++ /dev/null @@ -1,2 +0,0 @@ -event: response.failed -data: {"response":{"id":"resp_sse_auth_failed","object":"response","status":"failed","error":{"code":"token_invalid","message":"token revoked"}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/failed_cyber_policy.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/failed_cyber_policy.sse deleted file mode 100644 index 61fabb494..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/failed_cyber_policy.sse +++ /dev/null @@ -1,3 +0,0 @@ -event: response.failed -data: {"type":"response.failed","response":{"id":"resp_cyber_policy","object":"response","status":"failed","error":{"code":"cyber_policy","message":"This request has been flagged for possible cybersecurity risk."}}} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/failed_model_unsupported.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/failed_model_unsupported.sse deleted file mode 100644 index d2daa5daf..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/failed_model_unsupported.sse +++ /dev/null @@ -1,2 +0,0 @@ -event: response.failed -data: {"response":{"id":"resp_model_unsupported_failed","object":"response","status":"failed","error":{"code":"model_not_supported","message":"Model gpt-5.5 is not supported on this account plan"}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/failed_quota.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/failed_quota.sse deleted file mode 100644 index 0f33bf2aa..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/failed_quota.sse +++ /dev/null @@ -1,2 +0,0 @@ -event: response.failed -data: {"response":{"id":"resp_sse_quota_failed","object":"response","status":"failed","error":{"code":"quota_exceeded","message":"quota exhausted"}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/failed_server_overloaded.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/failed_server_overloaded.sse deleted file mode 100644 index 72970adb2..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/failed_server_overloaded.sse +++ /dev/null @@ -1,2 +0,0 @@ -event: response.failed -data: {"response":{"id":"resp_server_overloaded","object":"response","status":"failed","error":{"code":"server_is_overloaded","message":"upstream is temporarily overloaded"}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/function_call_arguments_delta.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/function_call_arguments_delta.sse deleted file mode 100644 index 931c97401..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/function_call_arguments_delta.sse +++ /dev/null @@ -1,3 +0,0 @@ -event: response.function_call_arguments.delta -data: {"delta":"{\"path\""} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/incomplete_text_delta.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/incomplete_text_delta.sse deleted file mode 100644 index 4d50ccbe1..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/incomplete_text_delta.sse +++ /dev/null @@ -1,2 +0,0 @@ -event: response.output_text.delta -data: {"delta":"hello"} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/live_stream_completed.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/live_stream_completed.sse deleted file mode 100644 index 6aa5e2704..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/live_stream_completed.sse +++ /dev/null @@ -1,3 +0,0 @@ -event: response.completed -data: {"response":{"id":"resp_live_stream","object":"response","status":"completed","usage":{"input_tokens":3,"output_tokens":4}}} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/live_stream_failed_auth.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/live_stream_failed_auth.sse deleted file mode 100644 index 54e188961..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/live_stream_failed_auth.sse +++ /dev/null @@ -1,2 +0,0 @@ -event: response.failed -data: {"response":{"id":"resp_live_auth_failed","object":"response","status":"failed","error":{"code":"token_invalid","message":"live token expired"}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/live_stream_failed_quota.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/live_stream_failed_quota.sse deleted file mode 100644 index 80fbd47d8..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/live_stream_failed_quota.sse +++ /dev/null @@ -1,2 +0,0 @@ -event: response.failed -data: {"response":{"id":"resp_live_quota_failed","object":"response","status":"failed","error":{"code":"quota_exceeded","message":"live quota exhausted"}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/live_stream_hello_delta.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/live_stream_hello_delta.sse deleted file mode 100644 index 1c309d99c..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/live_stream_hello_delta.sse +++ /dev/null @@ -1,3 +0,0 @@ -event: response.output_text.delta -data: {"delta":"live stream hello"} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/metadata_rate_limits_no_first_output.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/metadata_rate_limits_no_first_output.sse deleted file mode 100644 index 566ef3e54..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/metadata_rate_limits_no_first_output.sse +++ /dev/null @@ -1,9 +0,0 @@ -event: codex.rate_limits -data: {"type":"codex.rate_limits","rate_limits":{"primary":{"used_percent":42}}} - -event: response.metadata -data: {"type":"response.metadata","x-codex-turn-state":"state"} - -event: response.in_progress -data: {"type":"response.in_progress","response":{"id":"resp_1","status":"in_progress"}} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/output_then_cyber_policy.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/output_then_cyber_policy.sse deleted file mode 100644 index a36deca52..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/output_then_cyber_policy.sse +++ /dev/null @@ -1,8 +0,0 @@ -event: response.created -data: {"type":"response.created","response":{"id":"resp_http_cyber_policy","status":"in_progress"}} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","response_id":"resp_http_cyber_policy","output_index":0,"content_index":0,"delta":"partial output before HTTP SSE policy failure"} - -event: response.failed -data: {"type":"response.failed","response":{"id":"resp_http_cyber_policy","status":"failed","error":{"code":"cyber_policy","message":"This request has been flagged for possible cybersecurity risk."}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/output_then_previous_response_not_found.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/output_then_previous_response_not_found.sse deleted file mode 100644 index 8f86b0406..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/output_then_previous_response_not_found.sse +++ /dev/null @@ -1,8 +0,0 @@ -event: response.created -data: {"type":"response.created","response":{"id":"resp_http_output_then_missing","status":"in_progress"}} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","response_id":"resp_http_output_then_missing","output_index":0,"content_index":0,"delta":"visible HTTP SSE output"} - -event: response.failed -data: {"type":"response.failed","response":{"id":"resp_http_output_then_missing","status":"failed","error":{"code":"previous_response_not_found","message":"Previous response was not found"}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/partial_clean_close.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/partial_clean_close.sse deleted file mode 100644 index e67c553e3..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/partial_clean_close.sse +++ /dev/null @@ -1,6 +0,0 @@ -event: response.created -data: {"response":{"id":"resp_clean_close"}} - -event: response.output_text.delta -data: {"delta":"partial before clean close"} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/partial_logged_disconnect.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/partial_logged_disconnect.sse deleted file mode 100644 index 25712f11f..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/partial_logged_disconnect.sse +++ /dev/null @@ -1,3 +0,0 @@ -event: response.output_text.delta -data: {"delta":"partial before logged disconnect"} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/partial_transport_failure.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/partial_transport_failure.sse deleted file mode 100644 index f104bc22b..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/partial_transport_failure.sse +++ /dev/null @@ -1,3 +0,0 @@ -event: response.output_text.delta -data: {"delta":"partial before transport failure"} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/reasoning_delta.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/reasoning_delta.sse deleted file mode 100644 index 1c59bc1ed..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/reasoning_delta.sse +++ /dev/null @@ -1,3 +0,0 @@ -event: response.reasoning_summary_text.delta -data: {"delta":"thinking"} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/response_created.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/response_created.sse deleted file mode 100644 index 8df199a1c..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/response_created.sse +++ /dev/null @@ -1,3 +0,0 @@ -event: response.created -data: {"type":"response.created","response":{"id":"resp_1","status":"in_progress"}} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_401.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_401.sse deleted file mode 100644 index d56d141d7..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_401.sse +++ /dev/null @@ -1,5 +0,0 @@ -event: response.output_text.delta -data: {"delta":"auth stream fallback hello"} - -event: response.completed -data: {"response":{"id":"resp_stream_after_401","object":"response","status":"completed","usage":{"input_tokens":4,"output_tokens":2}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_402.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_402.sse deleted file mode 100644 index 3472203a3..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_402.sse +++ /dev/null @@ -1,5 +0,0 @@ -event: response.output_text.delta -data: {"delta":"quota stream fallback hello"} - -event: response.completed -data: {"response":{"id":"resp_stream_after_402","object":"response","status":"completed","usage":{"input_tokens":6,"output_tokens":3}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_403.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_403.sse deleted file mode 100644 index fbca9a1ee..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_403.sse +++ /dev/null @@ -1,5 +0,0 @@ -event: response.output_text.delta -data: {"delta":"banned stream fallback hello"} - -event: response.completed -data: {"response":{"id":"resp_stream_after_403","object":"response","status":"completed","usage":{"input_tokens":5,"output_tokens":2}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_429.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_429.sse deleted file mode 100644 index 164add60f..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_429.sse +++ /dev/null @@ -1,5 +0,0 @@ -event: response.output_text.delta -data: {"delta":"rate fallback hello"} - -event: response.completed -data: {"response":{"id":"resp_stream_after_429","object":"response","status":"completed","usage":{"input_tokens":4,"output_tokens":1}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_5xx_retry.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_5xx_retry.sse deleted file mode 100644 index 2ea4ecfbe..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_5xx_retry.sse +++ /dev/null @@ -1,5 +0,0 @@ -event: response.output_text.delta -data: {"delta":"retry stream hello"} - -event: response.completed -data: {"response":{"id":"resp_stream_after_5xx_retry","object":"response","status":"completed","usage":{"input_tokens":7,"output_tokens":2}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_cloudflare.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_cloudflare.sse deleted file mode 100644 index 632335133..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_cloudflare.sse +++ /dev/null @@ -1,5 +0,0 @@ -event: response.output_text.delta -data: {"delta":"cloudflare stream fallback hello"} - -event: response.completed -data: {"response":{"id":"resp_stream_after_cloudflare","object":"response","status":"completed","usage":{"input_tokens":6,"output_tokens":2}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_model_unsupported.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_model_unsupported.sse deleted file mode 100644 index 1b33fcf47..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_after_model_unsupported.sse +++ /dev/null @@ -1,5 +0,0 @@ -event: response.output_text.delta -data: {"delta":"model stream fallback hello"} - -event: response.completed -data: {"response":{"id":"resp_stream_after_model_unsupported","object":"response","status":"completed","usage":{"input_tokens":5,"output_tokens":2}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_usage.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_usage.sse deleted file mode 100644 index ddcbce193..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/stream_usage.sse +++ /dev/null @@ -1,5 +0,0 @@ -event: response.output_text.delta -data: {"delta":"stream hello"} - -event: response.completed -data: {"response":{"id":"resp_stream_usage","object":"response","status":"completed","usage":{"input_tokens":3,"output_tokens":5}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/structural_then_previous_response_not_found.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/structural_then_previous_response_not_found.sse deleted file mode 100644 index a6096a063..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/structural_then_previous_response_not_found.sse +++ /dev/null @@ -1,8 +0,0 @@ -event: response.created -data: {"type":"response.created","response":{"id":"resp_http_previous_missing","status":"in_progress"}} - -event: response.content_part.added -data: {"type":"response.content_part.added","part":{"type":"output_text","text":""}} - -event: response.failed -data: {"type":"response.failed","response":{"id":"resp_http_previous_missing","status":"failed","error":{"code":"previous_response_not_found","message":"Previous response was not found"}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/success.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/success.sse deleted file mode 100644 index 273f5ec4c..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/success.sse +++ /dev/null @@ -1,5 +0,0 @@ -event: response.output_text.delta -data: {"delta":"response hello"} - -event: response.completed -data: {"response":{"id":"resp_response_1","object":"response","status":"completed","usage":{"input_tokens":5,"output_tokens":2}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/text_delta_completed_usage.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/text_delta_completed_usage.sse deleted file mode 100644 index 2378392f7..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/text_delta_completed_usage.sse +++ /dev/null @@ -1,6 +0,0 @@ -event: response.output_text.delta -data: {"delta":"hello"} - -event: response.completed -data: {"response":{"id":"resp_1","object":"response","status":"completed","usage":{"input_tokens":2,"output_tokens":3}}} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/text_delta_crlf.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/text_delta_crlf.sse deleted file mode 100644 index 75682eef0..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/text_delta_crlf.sse +++ /dev/null @@ -1,3 +0,0 @@ -event: response.output_text.delta -data: {"type":"response.output_text.delta","delta":"hello"} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/text_delta_hello.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/text_delta_hello.sse deleted file mode 100644 index e24ca78a2..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/text_delta_hello.sse +++ /dev/null @@ -1,3 +0,0 @@ -event: response.output_text.delta -data: {"delta":"hello"} - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/text_deltas_completed.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/text_deltas_completed.sse deleted file mode 100644 index e5b61890c..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/text_deltas_completed.sse +++ /dev/null @@ -1,8 +0,0 @@ -event: response.output_text.delta -data: {"delta":"hello "} - -event: response.output_text.delta -data: {"delta":"from deltas"} - -event: response.completed -data: {"response":{"id":"resp_text","object":"response","status":"completed","output":[],"usage":{"input_tokens":2,"output_tokens":3}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/tuple_object.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/tuple_object.sse deleted file mode 100644 index 37b3b3a10..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/tuple_object.sse +++ /dev/null @@ -1,5 +0,0 @@ -event: response.output_text.delta -data: {"type":"response.output_text.delta","delta":"{\"point\":{\"0\":1,\"1\":2}}"} - -event: response.completed -data: {"response":{"id":"resp_tuple","object":"response","status":"completed","output_text":"{\"point\":{\"0\":0,\"1\":0}}","output":[{"type":"message","role":"assistant","content":[{"type":"output_text","text":"{\"point\":{\"0\":1,\"1\":2}}"}]}],"usage":{"input_tokens":1,"output_tokens":1}}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/sse/multiline_data_with_metadata.sse b/backend/crates/gateway-api/tests/openai/responses/fixtures/sse/multiline_data_with_metadata.sse deleted file mode 100644 index 8c464c595..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/sse/multiline_data_with_metadata.sse +++ /dev/null @@ -1,6 +0,0 @@ -event: message -id: evt_1 -data: hello -data: world -retry: 10 - diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/completed_with_reasoning_replay.json b/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/completed_with_reasoning_replay.json deleted file mode 100644 index 351c1d613..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/completed_with_reasoning_replay.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "type": "response.completed", - "response": { - "id": "resp_implicit_resume_first", - "object": "response", - "output": [ - { - "type": "reasoning", - "id": "rs_implicit_resume", - "status": "completed", - "summary": [ - { - "type": "summary_text", - "text": "cached reasoning" - } - ], - "content": [ - { - "type": "reasoning_text", - "text": "raw reasoning should not be replayed" - } - ], - "encrypted_content": "enc_reasoning_replay" - } - ], - "usage": { - "input_tokens": 8, - "output_tokens": 2, - "total_tokens": 10 - } - } -} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/first_account_limited.json b/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/first_account_limited.json deleted file mode 100644 index e88fea407..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/first_account_limited.json +++ /dev/null @@ -1 +0,0 @@ -{"error":{"message":"first account limited"}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/history_rate_limited.json b/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/history_rate_limited.json deleted file mode 100644 index 049498d77..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/history_rate_limited.json +++ /dev/null @@ -1 +0,0 @@ -{"error":{"message":"history account rate limited"}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/invalid_encrypted_content.json b/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/invalid_encrypted_content.json deleted file mode 100644 index 64d2a0a43..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/invalid_encrypted_content.json +++ /dev/null @@ -1 +0,0 @@ -{"type":"response.failed","response":{"id":"resp_invalid_encrypted_content","object":"response","status":"failed","error":{"code":"invalid_encrypted_content","message":"Invalid encrypted content in reasoning replay"}},"error":{"code":"invalid_encrypted_content","message":"Invalid encrypted content in reasoning replay"}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/previous_response_not_found.json b/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/previous_response_not_found.json deleted file mode 100644 index 584e48a4e..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/previous_response_not_found.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "error", - "error": { - "code": "previous_response_not_found", - "message": "Previous response with id resp_missing was not found" - } -} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/rate_limited.json b/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/rate_limited.json deleted file mode 100644 index 711ca5a8e..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/rate_limited.json +++ /dev/null @@ -1 +0,0 @@ -{"error":{"message":"rate limited"}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/second_account_limited.json b/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/second_account_limited.json deleted file mode 100644 index b3da3c897..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/second_account_limited.json +++ /dev/null @@ -1 +0,0 @@ -{"error":{"message":"second account limited"}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/token_revoked.json b/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/token_revoked.json deleted file mode 100644 index f0cda0b80..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/token_revoked.json +++ /dev/null @@ -1 +0,0 @@ -{"error":{"code":"token_revoked"}} diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/unanswered_function_call.json b/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/unanswered_function_call.json deleted file mode 100644 index 443a238d6..000000000 --- a/backend/crates/gateway-api/tests/openai/responses/fixtures/websocket/unanswered_function_call.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "error", - "error": { - "code": "invalid_request", - "message": "No tool output found for function call call_missing" - } -} diff --git a/backend/crates/gateway-core/src/engine/coordinator.rs b/backend/crates/gateway-core/src/engine/coordinator.rs index 59098779f..58adf0234 100644 --- a/backend/crates/gateway-core/src/engine/coordinator.rs +++ b/backend/crates/gateway-core/src/engine/coordinator.rs @@ -22,7 +22,7 @@ use crate::error::{GatewayError, GatewayErrorKind, ProviderError, ProviderErrorK use crate::event::{ GatewayEvent, ProviderEvent, ProviderResponseHeader, ProviderResponseObservation, }; -use crate::operation::{Operation, ProviderSessionState, RetrySafety}; +use crate::operation::{Operation, ProviderSessionState}; use crate::routing::RoutingPlan; use futures::future::Fuse; use futures::{FutureExt, StreamExt, pin_mut, select_biased}; @@ -214,7 +214,6 @@ struct FailureFinalization { upstream_status_code: Option, provider_error_code: Option, retry_after_ms: Option, - provider_response_id: Option, } /// API 可逐事件消费的 Core 执行会话。 @@ -477,7 +476,7 @@ where if state.provider() != provider.as_str() { return None; } - let account = current.metadata.provider_account_id()?.clone(); + let account = current.metadata.provider_account_id().clone(); let response_id = self.upstream_response_id.as_deref()?; let previous_response_id = PreviousResponseId::new(response_id.to_owned()); let upstream_response_id = PreviousResponseId::new(response_id.to_owned()); @@ -602,7 +601,6 @@ where upstream_status_code: None, provider_error_code: None, retry_after_ms: None, - provider_response_id: None, }) .await?; return Err(EngineError::EmptyRoutingPlan); @@ -749,7 +747,6 @@ where upstream_status_code: None, provider_error_code: None, retry_after_ms: None, - provider_response_id: None, }) .await?; return Err(EngineError::ProviderMetadataMismatch); @@ -757,9 +754,9 @@ where let metadata = stream.metadata().clone(); if !self.account_selection.is_diagnostic() - && metadata - .provider_account_id() - .is_none_or(|account| !candidate.account_scope().allows(account)) + && !candidate + .account_scope() + .allows(metadata.provider_account_id()) { let error = GatewayError::new( GatewayErrorKind::Internal, @@ -772,14 +769,13 @@ where upstream_status_code: None, provider_error_code: None, retry_after_ms: None, - provider_response_id: None, }) .await?; return Err(EngineError::AccountOutsideClientScope); } if pinned_account .as_ref() - .is_some_and(|required| metadata.provider_account_id() != Some(required)) + .is_some_and(|required| metadata.provider_account_id() != required) { let error = GatewayError::new( GatewayErrorKind::Internal, @@ -792,7 +788,6 @@ where upstream_status_code: None, provider_error_code: None, retry_after_ms: None, - provider_response_id: None, }) .await?; return Err(EngineError::RequiredAccountMismatch); @@ -802,9 +797,7 @@ where .as_ref() .and_then(ContinuationBinding::pinned) && self.continuation_attempt == ContinuationAttempt::Native - && !metadata - .provider_account_id() - .is_some_and(|account| pin.matches(metadata.provider(), account)) + && !pin.matches(metadata.provider(), metadata.provider_account_id()) { let error = GatewayError::new( GatewayErrorKind::Internal, @@ -817,17 +810,14 @@ where upstream_status_code: None, provider_error_code: None, retry_after_ms: None, - provider_response_id: None, }) .await?; return Err(EngineError::ContinuationPinMismatch); } - if self.account_state_owner.is_none() - && let Some(account) = metadata.provider_account_id() - { + if self.account_state_owner.is_none() { self.account_state_owner = Some(ProviderAccountStateOwner::new( metadata.provider().clone(), - account.clone(), + metadata.provider_account_id().clone(), )); } let attempt_record = AttemptRecord { @@ -835,8 +825,8 @@ where attempt_count: next_attempt, trigger, provider_kind: metadata.provider().clone(), - provider_account_id: metadata.provider_account_id().cloned(), - provider_account_ref: metadata.provider_account_id().cloned(), + provider_account_id: Some(metadata.provider_account_id().clone()), + provider_account_ref: Some(metadata.provider_account_id().clone()), upstream_model_id: metadata.upstream_model().cloned(), upstream_transport: metadata.transport().as_str().to_owned(), http_version: None, @@ -1032,8 +1022,7 @@ where && self.downstream_committed_at.is_none() && !self.delivery_pending && attempt_send_state != UpstreamSendState::Ambiguous - && (self.operation.retry_safety() == RetrySafety::Idempotent - || provider_proved_replay_safe) + && provider_proved_replay_safe && self.attempts < self.plan.max_attempts().get(); let same_account_retry = error.retries_same_account() && provider_proved_replay_safe @@ -1041,14 +1030,9 @@ where && !self.delivery_pending && attempt_send_state != UpstreamSendState::Ambiguous && self.attempts < self.plan.max_attempts().get() - && current - .metadata - .provider_account_id() - .is_some_and(|account| { - !self - .credential_recovery_attempted_accounts - .contains(account) - }); + && !self + .credential_recovery_attempted_accounts + .contains(current.metadata.provider_account_id()); let retryable = continuation_retry || same_account_retry || ordinary_retry || pre_delivery_retry; @@ -1057,17 +1041,15 @@ where // 所有权保留到下一次 attempt 成功,或最终空选路时返回客户端。 let persistence_error = self.request_persisted.then(|| error.clone()); if same_account_retry { - if let Some(account) = current.metadata.provider_account_id() { - self.credential_recovery_attempted_accounts - .insert(account.clone()); - // 只钉住紧随其后的 replay attempt;replay 再遇可重试错误时, - // ordinary/continuation 重试门不受影响,仍可换号。 - self.recovery_account = Some(account.clone()); - } - } else if !continuation_retry - && let Some(account) = current.metadata.provider_account_id() - { - self.excluded_accounts.insert(account.clone()); + let account = current.metadata.provider_account_id().clone(); + self.credential_recovery_attempted_accounts + .insert(account.clone()); + // 只钉住紧随其后的 replay attempt;replay 再遇可重试错误时, + // ordinary/continuation 重试门不受影响,仍可换号。 + self.recovery_account = Some(account); + } else if !continuation_retry { + self.excluded_accounts + .insert(current.metadata.provider_account_id().clone()); } self.last_retryable_failure_events = atomic_client_events; self.last_retryable_failure = Some(error); @@ -1082,7 +1064,7 @@ where attempt_index: current.index, trigger: current.trigger, provider_kind: current.metadata.provider().clone(), - account_id: current.metadata.provider_account_id().cloned(), + account_id: Some(current.metadata.provider_account_id().clone()), upstream_model_id: current.metadata.upstream_model().cloned(), upstream_status_code: current .response_observation @@ -1157,14 +1139,12 @@ where } ContinuationAttempt::Native | ContinuationAttempt::ReplayOwner => { self.continuation_attempt = ContinuationAttempt::ReplayAny; - if let Some(account) = current.metadata.provider_account_id() { - self.excluded_accounts.insert(account.clone()); - } + self.excluded_accounts + .insert(current.metadata.provider_account_id().clone()); } ContinuationAttempt::ReplayAny => { - if let Some(account) = current.metadata.provider_account_id() { - self.excluded_accounts.insert(account.clone()); - } + self.excluded_accounts + .insert(current.metadata.provider_account_id().clone()); } ContinuationAttempt::None => return false, } @@ -1317,9 +1297,6 @@ where upstream_status_code: error.upstream_status(), provider_error_code: error.upstream_code().map(|code| code.as_str().to_owned()), retry_after_ms: error.retry_after().map(duration_ms), - provider_response_id: error - .upstream_response_id() - .map(|value| value.as_str().to_owned()), }) .await } @@ -1373,7 +1350,6 @@ where upstream_status_code: None, provider_error_code: None, retry_after_ms: None, - provider_response_id: None, }) .await } @@ -1433,9 +1409,7 @@ where .or(observed_status_code), client_response_id: self.client_response_id.clone(), upstream_request_id, - upstream_response_id: finalization - .provider_response_id - .or_else(|| self.upstream_response_id.clone()), + upstream_response_id: self.upstream_response_id.clone(), upstream_transport, http_version, websocket_pool, diff --git a/backend/crates/gateway-core/src/engine/provider.rs b/backend/crates/gateway-core/src/engine/provider.rs index c79549b08..4825506ba 100644 --- a/backend/crates/gateway-core/src/engine/provider.rs +++ b/backend/crates/gateway-core/src/engine/provider.rs @@ -11,9 +11,7 @@ use async_trait::async_trait; use futures::Stream; use thiserror::Error; -use crate::engine::credential::{ - AccountAttemptFeedback, AccountFeedbackStats, CredentialRevision, ProviderAccountId, -}; +use crate::engine::credential::{AccountAttemptFeedback, AccountFeedbackStats, ProviderAccountId}; use crate::engine::{AttemptContext, UpstreamSendState}; use crate::error::{ IdentifierError, OpaqueUpstreamValue, ProviderError, ProviderErrorKind, validate_text, @@ -28,74 +26,6 @@ use crate::routing::{ pub type EventStream = Pin> + Send + 'static>>; -/// 匿名 credential/resource 引用。 -/// -/// 有 credential 的值必须是带算法/版本标识的不可逆伪名;无 credential 的 -/// Provider 使用 `__none__`。邮箱、API Key prefix 等身份信息不能进入该类型。 -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ResourceId(String); - -impl ResourceId { - /// 无 credential 的稳定 sentinel。 - #[must_use] - pub fn none() -> Self { - Self("__none__".to_owned()) - } - - /// 创建匿名资源 ID。 - /// - /// # Errors - /// - /// ID 为空、过长、含控制字符或使用 `__` 保留前缀时返回错误。 - pub fn anonymous(value: impl Into) -> Result { - let value = value.into(); - validate_text(&value, 128, true, None)?; - Ok(Self(value)) - } - - /// 返回匿名引用。 - #[must_use] - pub fn as_str(&self) -> &str { - &self.0 - } -} - -/// Provider 本次调用实际使用的账号或匿名资源。 -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ProviderResource { - Account { - id: ProviderAccountId, - revision: CredentialRevision, - }, - Anonymous(ResourceId), -} - -impl ProviderResource { - #[must_use] - pub const fn account_id(&self) -> Option<&ProviderAccountId> { - match self { - Self::Account { id, .. } => Some(id), - Self::Anonymous(_) => None, - } - } - - #[must_use] - pub const fn credential_revision(&self) -> Option { - match self { - Self::Account { revision, .. } => Some(*revision), - Self::Anonymous(_) => None, - } - } - - #[must_use] - pub const fn anonymous_id(&self) -> Option<&ResourceId> { - match self { - Self::Account { .. } => None, - Self::Anonymous(resource) => Some(resource), - } - } -} - /// 上游 transport 注册名称。 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct UpstreamTransport(String); @@ -124,7 +54,7 @@ impl UpstreamTransport { pub struct ProviderCallMetadata { provider: ProviderKind, upstream_model: Option, - resource: ProviderResource, + provider_account_id: ProviderAccountId, upstream_request_id: Option, transport: UpstreamTransport, } @@ -135,13 +65,13 @@ impl ProviderCallMetadata { pub const fn new( provider: ProviderKind, upstream_model: UpstreamModelId, - resource: ProviderResource, + provider_account_id: ProviderAccountId, transport: UpstreamTransport, ) -> Self { Self { provider, upstream_model: Some(upstream_model), - resource, + provider_account_id, upstream_request_id: None, transport, } @@ -151,13 +81,13 @@ impl ProviderCallMetadata { #[must_use] pub const fn for_provider_endpoint( provider: ProviderKind, - resource: ProviderResource, + provider_account_id: ProviderAccountId, transport: UpstreamTransport, ) -> Self { Self { provider, upstream_model: None, - resource, + provider_account_id, upstream_request_id: None, transport, } @@ -182,22 +112,10 @@ impl ProviderCallMetadata { self.upstream_model.as_ref() } - /// 返回匿名资源;账号调用返回 `None`。 - #[must_use] - pub const fn anonymous_resource(&self) -> Option<&ResourceId> { - self.resource.anonymous_id() - } - /// 返回 live Provider account ID。 #[must_use] - pub const fn provider_account_id(&self) -> Option<&ProviderAccountId> { - self.resource.account_id() - } - - /// 返回本次冻结的 credential revision。 - #[must_use] - pub const fn upstream_credential_revision(&self) -> Option { - self.resource.credential_revision() + pub const fn provider_account_id(&self) -> &ProviderAccountId { + &self.provider_account_id } /// 返回安全上游 request ID。 @@ -334,16 +252,14 @@ impl ProviderStream { /// 让公共 stream 边界统一回灌账号成功率与首个有效输出延迟。 #[must_use] pub fn with_account_feedback(mut self, stats: Arc) -> Self { - if let Some(account_id) = self.metadata.provider_account_id().cloned() { - self.account_feedback = Some(ProviderStreamAccountFeedback { - stats, - provider_kind: self.metadata.provider().clone(), - account_id, - started_at: None, - first_output_ms: None, - reported: false, - }); - } + self.account_feedback = Some(ProviderStreamAccountFeedback { + stats, + provider_kind: self.metadata.provider().clone(), + account_id: self.metadata.provider_account_id().clone(), + started_at: None, + first_output_ms: None, + reported: false, + }); self } diff --git a/backend/crates/gateway-core/src/error.rs b/backend/crates/gateway-core/src/error.rs index 5645cc75a..ec62b8e8b 100644 --- a/backend/crates/gateway-core/src/error.rs +++ b/backend/crates/gateway-core/src/error.rs @@ -373,7 +373,6 @@ pub struct ProviderError { struct ProviderErrorUpstreamValues { code: Option, request_id: Option, - response_id: Option, } #[derive(Clone)] @@ -424,13 +423,6 @@ impl ProviderError { self } - /// 附加 adapter 已分类为非 bearer 的上游 response ID。 - #[must_use] - pub fn with_upstream_response_id(mut self, response_id: OpaqueUpstreamValue) -> Self { - self.upstream_values_mut().response_id = Some(response_id); - self - } - fn upstream_values_mut(&mut self) -> &mut ProviderErrorUpstreamValues { self.upstream_values .get_or_insert_with(|| Box::new(ProviderErrorUpstreamValues::default())) @@ -551,14 +543,6 @@ impl ProviderError { .and_then(|values| values.request_id.as_ref()) } - /// 返回安全分类的上游 response ID。 - #[must_use] - pub fn upstream_response_id(&self) -> Option<&OpaqueUpstreamValue> { - self.upstream_values - .as_deref() - .and_then(|values| values.response_id.as_ref()) - } - /// 返回 Provider 建议的冷却时间。 #[must_use] pub const fn retry_after(&self) -> Option { @@ -666,10 +650,6 @@ impl fmt::Debug for ProviderError { "upstream_request_id", &self.upstream_request_id().map(|_| ""), ) - .field( - "upstream_response_id", - &self.upstream_response_id().map(|_| ""), - ) .field("retry_after", &self.retry_after) .field("continuation_failure", &self.continuation_failure) .field("replay_safe", &self.replay_safe) diff --git a/backend/crates/gateway-core/src/operation/mod.rs b/backend/crates/gateway-core/src/operation/mod.rs index 5655b476d..0e1f7f395 100644 --- a/backend/crates/gateway-core/src/operation/mod.rs +++ b/backend/crates/gateway-core/src/operation/mod.rs @@ -20,14 +20,8 @@ use crate::error::{OperationError, validate_text}; pub enum OperationKind { /// 文本、多模态和工具生成。 Generate, - /// 向量嵌入。 - Embed, - /// 文档重排。 - Rerank, /// 图像生成。 GenerateImage, - /// 语音生成。 - Speech, } impl OperationKind { @@ -36,23 +30,11 @@ impl OperationKind { pub const fn as_str(self) -> &'static str { match self { Self::Generate => "generate", - Self::Embed => "embed", - Self::Rerank => "rerank", Self::GenerateImage => "generate_image", - Self::Speech => "speech", } } } -/// 请求在 commit 前是否允许跨目标重放。 -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum RetrySafety { - /// 相同业务 payload 可以安全重放。 - Idempotent, - /// 默认禁止跨 Provider fallback。 - NonIdempotent, -} - /// Router 理解的稳定能力。 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[non_exhaustive] @@ -473,101 +455,6 @@ fn contains_type(value: Option<&Value>, expected: &str) -> bool { } } -/// Embedding 请求。 -#[derive(Clone, PartialEq, Eq)] -pub struct EmbedRequest { - input: Vec, - dimensions: Option, -} - -impl EmbedRequest { - /// 创建 embedding 请求。 - /// - /// # Errors - /// - /// 输入为空时返回错误。 - pub fn new(input: Vec) -> Result { - if input.is_empty() || input.iter().any(String::is_empty) { - return Err(OperationError::EmptyField { field: "input" }); - } - Ok(Self { - input, - dimensions: None, - }) - } - - /// 设置输出维度。 - #[must_use] - pub const fn with_dimensions(mut self, dimensions: u32) -> Self { - self.dimensions = Some(dimensions); - self - } - - /// 返回输入文本。 - #[must_use] - pub fn input(&self) -> &[String] { - &self.input - } -} - -impl fmt::Debug for EmbedRequest { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter - .debug_struct("EmbedRequest") - .field("input_items", &self.input.len()) - .field("dimensions", &self.dimensions) - .finish() - } -} - -/// Rerank 请求。 -#[derive(Clone, PartialEq, Eq)] -pub struct RerankRequest { - query: String, - documents: Vec, - top_n: Option, -} - -impl RerankRequest { - /// 创建 rerank 请求。 - /// - /// # Errors - /// - /// Query 或文档为空时返回错误。 - pub fn new(query: impl Into, documents: Vec) -> Result { - let query = query.into(); - if query.is_empty() { - return Err(OperationError::EmptyField { field: "query" }); - } - if documents.is_empty() || documents.iter().any(String::is_empty) { - return Err(OperationError::EmptyField { field: "documents" }); - } - Ok(Self { - query, - documents, - top_n: None, - }) - } - - /// 设置返回条数。 - #[must_use] - pub const fn with_top_n(mut self, top_n: u32) -> Self { - self.top_n = Some(top_n); - self - } -} - -impl fmt::Debug for RerankRequest { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter - .debug_struct("RerankRequest") - .field("query", &"") - .field("documents", &self.documents.len()) - .field("top_n", &self.top_n) - .finish() - } -} - /// 图像 API 的稳定端点语义。 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ImageRequestKind { @@ -614,56 +501,14 @@ impl fmt::Debug for ImageRequest { } } -/// 语音生成请求。 -#[derive(Clone, PartialEq, Eq)] -pub struct SpeechRequest { - input: String, - voice: String, -} - -impl SpeechRequest { - /// 创建语音生成请求。 - /// - /// # Errors - /// - /// 输入或 voice 为空时返回错误。 - pub fn new(input: impl Into, voice: impl Into) -> Result { - let input = input.into(); - let voice = voice.into(); - if input.is_empty() { - return Err(OperationError::EmptyField { field: "input" }); - } - if voice.is_empty() { - return Err(OperationError::EmptyField { field: "voice" }); - } - Ok(Self { input, voice }) - } -} - -impl fmt::Debug for SpeechRequest { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter - .debug_struct("SpeechRequest") - .field("input", &"") - .field("voice", &self.voice) - .finish() - } -} - /// 网关内部业务请求;不包含任何客户端 wire 或 Provider SDK 类型。 #[derive(Clone, PartialEq)] #[non_exhaustive] pub enum Operation { /// 生成。 Generate(GenerateRequest), - /// Embedding。 - Embed(EmbedRequest), - /// Rerank。 - Rerank(RerankRequest), /// 图像生成。 GenerateImage(ImageRequest), - /// 语音生成。 - Speech(SpeechRequest), } impl Operation { @@ -688,10 +533,7 @@ impl Operation { pub const fn kind(&self) -> OperationKind { match self { Self::Generate(_) => OperationKind::Generate, - Self::Embed(_) => OperationKind::Embed, - Self::Rerank(_) => OperationKind::Rerank, Self::GenerateImage(_) => OperationKind::GenerateImage, - Self::Speech(_) => OperationKind::Speech, } } @@ -700,21 +542,7 @@ impl Operation { pub fn capability_requirements(&self) -> CapabilityRequirements { match self { Self::Generate(request) => request.requirements(), - Self::Embed(_) => CapabilityRequirements::new(OperationKind::Embed), - Self::Rerank(_) => CapabilityRequirements::new(OperationKind::Rerank), Self::GenerateImage(_) => CapabilityRequirements::new(OperationKind::GenerateImage), - Self::Speech(_) => CapabilityRequirements::new(OperationKind::Speech), - } - } - - /// 返回跨目标重放安全性。 - #[must_use] - pub const fn retry_safety(&self) -> RetrySafety { - match self { - Self::Embed(_) | Self::Rerank(_) => RetrySafety::Idempotent, - Self::Generate(_) | Self::GenerateImage(_) | Self::Speech(_) => { - RetrySafety::NonIdempotent - } } } @@ -724,7 +552,6 @@ impl Operation { match self { Self::Generate(request) => request.image_generation_requested(), Self::GenerateImage(_) => true, - Self::Embed(_) | Self::Rerank(_) | Self::Speech(_) => false, } } @@ -733,7 +560,7 @@ impl Operation { pub fn provider_session_state(&self, provider: &str) -> Option<&ProviderSessionState> { match self { Self::Generate(request) => request.provider_session_state(provider), - Self::Embed(_) | Self::Rerank(_) | Self::GenerateImage(_) | Self::Speech(_) => None, + Self::GenerateImage(_) => None, } } } diff --git a/backend/crates/gateway-core/tests/engine/coordinator.rs b/backend/crates/gateway-core/tests/engine/coordinator.rs index a2169569e..52a4a44ea 100644 --- a/backend/crates/gateway-core/tests/engine/coordinator.rs +++ b/backend/crates/gateway-core/tests/engine/coordinator.rs @@ -15,12 +15,12 @@ use gateway_core::engine::continuation::{ ContinuationBinding, NativeContinuationPin, PreviousResponseId, }; use gateway_core::engine::credential::{ - AccountSelectionPolicy, CredentialRevision, ProviderAccountId, RotationStrategy, + AccountSelectionPolicy, ProviderAccountId, RotationStrategy, }; use gateway_core::engine::execution::gateway_error_from_engine; use gateway_core::engine::provider::{ Provider, ProviderCallMetadata, ProviderCatalogGeneration, ProviderModelCapabilities, - ProviderRegistry, ProviderRequest, ProviderResource, ProviderStream, UpstreamTransport, + ProviderRegistry, ProviderRequest, ProviderStream, UpstreamTransport, }; use gateway_core::engine::{ AttemptContext, AttemptCoordinator, AttemptRecord, CancellationToken, CommitRequirement, @@ -38,9 +38,7 @@ use gateway_core::event::{ ProviderResponseObservation, ProviderResponseTimings, ResponseMeta, ToolCallDelta, UpstreamHttpVersion, WebSocketPoolKind, }; -use gateway_core::operation::{ - EmbedRequest, GenerateRequest, Operation, ProtocolPayload, ProviderSessionState, RetrySafety, -}; +use gateway_core::operation::{GenerateRequest, Operation, ProtocolPayload, ProviderSessionState}; use gateway_core::policy::ClientApiKeyId; use gateway_core::routing::{ AccountRoutingSnapshot, ClientRoutingScope, ConfigRevision, FrozenAccountScope, @@ -321,10 +319,7 @@ impl Provider for ScriptedProvider { .upstream_model() .cloned() .expect("model route candidate"), - ProviderResource::Account { - id: ProviderAccountId::new(account_id).expect("account id"), - revision: CredentialRevision::new(1).expect("revision"), - }, + ProviderAccountId::new(account_id).expect("account id"), UpstreamTransport::new("http_sse").expect("transport"), ) .with_upstream_request_id(OpaqueUpstreamValue::new("upstream-request")); @@ -344,10 +339,7 @@ impl Provider for ScriptedProvider { .upstream_model() .cloned() .expect("model route candidate"), - ProviderResource::Account { - id: ProviderAccountId::new(account_id).expect("account id"), - revision: CredentialRevision::new(1).expect("revision"), - }, + ProviderAccountId::new(account_id).expect("account id"), UpstreamTransport::new("http_sse").expect("transport"), ); Ok(ProviderStream::new( @@ -367,10 +359,7 @@ impl Provider for ScriptedProvider { .upstream_model() .cloned() .expect("model route candidate"), - ProviderResource::Account { - id: ProviderAccountId::new(account_id).expect("account id"), - revision: CredentialRevision::new(1).expect("revision"), - }, + ProviderAccountId::new(account_id).expect("account id"), UpstreamTransport::new("websocket").expect("planned transport"), ); Ok(ProviderStream::new( @@ -383,15 +372,6 @@ impl Provider for ScriptedProvider { } } -fn operation(retry_safety: RetrySafety) -> Operation { - if retry_safety == RetrySafety::Idempotent { - return Operation::Embed( - EmbedRequest::new(vec!["hello".to_owned()]).expect("embedding request"), - ); - } - generate_operation() -} - fn generate_operation() -> Operation { let body = json!({ "model": "gpt-5", @@ -678,7 +658,7 @@ fn terminal_non_idempotent_failure( items: Vec>, continuation: Option, ) -> (Arc, Arc) { - let operation = operation(RetrySafety::NonIdempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, store, provider) = coordinator(vec![ Script::Stream { @@ -709,7 +689,7 @@ fn terminal_non_idempotent_failure( #[test] fn success_updates_one_model_request_and_persists_usage() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, store, _) = coordinator(vec![Script::Stream { account_id: "acct_one", @@ -775,7 +755,7 @@ fn observation_store_failures_never_block_successful_delivery() { StoreWriteFailure::Commit, StoreWriteFailure::Finalize, ] { - let operation = operation(RetrySafety::NonIdempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let store = Arc::new(FakeStore::failing(failure)); let (coordinator, _, provider) = coordinator_with_store( @@ -1006,7 +986,7 @@ fn failed_image_request_should_persist_failure() { #[test] fn response_observation_is_persisted_but_never_delivered() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let observation = ProviderResponseObservation::new( UpstreamTransport::new("http_sse").expect("actual transport"), @@ -1067,7 +1047,7 @@ fn response_observation_is_persisted_but_never_delivered() { #[test] fn mismatched_response_observation_should_not_interrupt_client_events() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let first = ProviderEvent::observation(ProviderResponseObservation::new( UpstreamTransport::new("websocket").expect("first transport"), @@ -1109,7 +1089,7 @@ fn mismatched_response_observation_should_not_interrupt_client_events() { #[test] fn response_observation_should_persist_provider_metadata_and_output_timings() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let metadata = gateway_core::event::ProviderResponseMetadata::new( serde_json::json!({"effectiveModel": "gpt-test", "firstTokenMs": 23}).to_string(), @@ -1165,7 +1145,7 @@ fn response_observation_should_persist_provider_metadata_and_output_timings() { #[test] fn empty_tool_call_delta_should_not_preempt_provider_first_token_timing() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let transport = UpstreamTransport::new("websocket").expect("actual transport"); let initial_observation = ProviderResponseObservation::new(transport.clone()); @@ -1218,7 +1198,7 @@ fn empty_tool_call_delta_should_not_preempt_provider_first_token_timing() { #[test] fn unknown_wire_event_before_response_identity_is_discarded_with_retried_attempt() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let unknown = ProtocolWireEvent::json( "openai", @@ -1231,10 +1211,10 @@ fn unknown_wire_event_before_response_identity_is_discarded_with_retried_attempt account_id: "acct_first", items: vec![ Ok(ProviderEvent::wire(unknown)), - Err(ProviderError::new( - ProviderErrorKind::Unavailable, - UpstreamSendState::Sent, - )), + Err( + ProviderError::new(ProviderErrorKind::Unavailable, UpstreamSendState::Sent) + .with_replay_safe(), + ), ], }, Script::Stream { @@ -1264,7 +1244,7 @@ fn unknown_wire_event_before_response_identity_is_discarded_with_retried_attempt #[test] fn discarded_attempt_observation_does_not_leak_into_retry_result() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let first_observation = ProviderResponseObservation::new( UpstreamTransport::new("websocket").expect("first transport"), @@ -1348,7 +1328,7 @@ fn discarded_attempt_observation_does_not_leak_into_retry_result() { #[test] fn websocket_success_keeps_client_http_status_absent() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, store, _) = coordinator(vec![Script::Stream { account_id: "acct_one", @@ -1374,7 +1354,7 @@ fn websocket_success_keeps_client_http_status_absent() { #[test] fn success_without_response_observation_persists_no_upstream_status() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, store, _) = coordinator(vec![Script::Stream { account_id: "acct_one", @@ -1731,7 +1711,7 @@ fn continuation_history_failure_without_replay_proof_is_delivered_without_hidden #[test] fn diagnostic_account_reaches_provider_and_matching_metadata_succeeds() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, _, provider) = coordinator(vec![Script::Stream { account_id: "acct_required", @@ -1758,7 +1738,7 @@ fn diagnostic_account_reaches_provider_and_matching_metadata_succeeds() { #[test] fn provider_metadata_for_another_account_fails_closed() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, store, provider) = coordinator(vec![ Script::Stream { @@ -1795,7 +1775,7 @@ fn provider_metadata_for_another_account_fails_closed() { #[test] fn required_account_disables_account_retry_after_stream_creation() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, store, provider) = coordinator(vec![ Script::Stream { @@ -1803,7 +1783,8 @@ fn required_account_disables_account_retry_after_stream_creation() { items: vec![Err(ProviderError::new( ProviderErrorKind::Unavailable, UpstreamSendState::NotSent, - ))], + ) + .with_replay_safe())], }, Script::Stream { account_id: "acct_other", @@ -1843,7 +1824,7 @@ fn required_account_disables_account_retry_after_stream_creation() { #[test] fn latest_provider_reported_cost_is_persisted_as_known_usd_total() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let events = vec![ Ok(GatewayEvent::Started(ResponseMeta::new( @@ -1886,7 +1867,7 @@ fn latest_provider_reported_cost_is_persisted_as_known_usd_total() { #[test] fn calculated_cost_is_persisted_when_provider_does_not_report_cost() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let events = vec![ Ok(GatewayEvent::Started(ResponseMeta::new( @@ -1924,7 +1905,7 @@ fn calculated_cost_is_persisted_when_provider_does_not_report_cost() { #[test] fn provider_reported_cost_should_not_be_replaced_by_calculated_cost() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let events = vec![ Ok(GatewayEvent::Started(ResponseMeta::new( @@ -1973,7 +1954,7 @@ fn provider_reported_cost_should_not_be_replaced_by_calculated_cost() { #[test] fn discarded_attempt_cost_never_leaks_into_retry_result() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, store, _) = coordinator(vec![ Script::Stream { @@ -1989,10 +1970,10 @@ fn discarded_attempt_cost_never_leaks_into_retry_result() { Ok(GatewayEvent::ProviderCost( ProviderReportedCost::from_usd_ticks(999).expect("discarded cost"), )), - Err(ProviderError::new( - ProviderErrorKind::Unavailable, - UpstreamSendState::Sent, - )), + Err( + ProviderError::new(ProviderErrorKind::Unavailable, UpstreamSendState::Sent) + .with_replay_safe(), + ), ], }, Script::Stream { @@ -2018,7 +1999,7 @@ fn discarded_attempt_cost_never_leaks_into_retry_result() { #[test] fn pre_commit_failure_excludes_account_and_retries_same_target() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, store, provider) = coordinator(vec![ Script::Stream { @@ -2028,10 +2009,10 @@ fn pre_commit_failure_excludes_account_and_retries_same_target() { "discarded-response", "gpt-5", ))), - Err(ProviderError::new( - ProviderErrorKind::Unauthorized, - UpstreamSendState::Sent, - )), + Err( + ProviderError::new(ProviderErrorKind::Unauthorized, UpstreamSendState::Sent) + .with_replay_safe(), + ), ], }, Script::Stream { @@ -2082,7 +2063,7 @@ fn pre_commit_failure_excludes_account_and_retries_same_target() { #[test] fn recovered_credential_retries_the_same_account_exactly_once() { - let operation = operation(RetrySafety::NonIdempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, store, provider) = coordinator(vec![ Script::Stream { @@ -2127,7 +2108,7 @@ fn recovered_credential_retries_the_same_account_exactly_once() { #[test] fn retryable_error_after_credential_recovery_switches_account_instead_of_terminating() { - let operation = operation(RetrySafety::NonIdempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, store, provider) = coordinator(vec![ Script::Stream { @@ -2183,7 +2164,7 @@ fn retryable_error_after_credential_recovery_switches_account_instead_of_termina #[test] fn non_idempotent_explicit_429_rejection_rotates_account_before_output() { - let operation = operation(RetrySafety::NonIdempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, store, provider) = coordinator(vec![ Script::Stream { @@ -2228,7 +2209,7 @@ fn non_idempotent_explicit_429_rejection_rotates_account_before_output() { #[test] fn rate_limited_account_exhaustion_survives_a_later_empty_selection() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, store, _) = coordinator(vec![ Script::Stream { @@ -2321,7 +2302,7 @@ fn explicit_429_with_ambiguous_send_state_is_not_retried() { #[test] fn explicit_429_after_structural_event_should_retry_before_commit() { - let operation = operation(RetrySafety::NonIdempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, store, provider) = coordinator(vec![ Script::Stream { @@ -2684,7 +2665,7 @@ fn external_continuation_explicit_429_is_not_retried() { #[test] fn ambiguous_send_state_stops_retry() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, store, provider) = coordinator(vec![ Script::Stream { @@ -2692,7 +2673,8 @@ fn ambiguous_send_state_stops_retry() { items: vec![Err(ProviderError::new( ProviderErrorKind::Transport, UpstreamSendState::Ambiguous, - ))], + ) + .with_replay_safe())], }, Script::Stream { account_id: "acct_second", @@ -2730,7 +2712,7 @@ fn ambiguous_send_state_stops_retry() { #[test] fn pre_delivery_transport_failure_rotates_account_for_non_idempotent_generation() { - let operation = operation(RetrySafety::NonIdempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, store, provider) = coordinator(vec![ Script::Stream { @@ -2773,7 +2755,7 @@ fn pre_delivery_transport_failure_rotates_account_for_non_idempotent_generation( #[test] fn final_capacity_exhaustion_returns_the_last_retryable_upstream_failure() { - let operation = operation(RetrySafety::NonIdempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let raw_body = Bytes::from_static(b"{\"error\":\"last upstream body\"}\x00"); let upstream_error = @@ -2840,7 +2822,7 @@ fn final_capacity_exhaustion_returns_the_last_retryable_upstream_failure() { #[test] fn provider_infrastructure_failure_does_not_restore_the_last_upstream_failure() { - let operation = operation(RetrySafety::NonIdempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let upstream_error = ProviderError::new(ProviderErrorKind::Transport, UpstreamSendState::Ambiguous) @@ -2895,7 +2877,7 @@ fn provider_infrastructure_failure_does_not_restore_the_last_upstream_failure() #[test] fn pre_delivery_retry_marker_is_ignored_after_downstream_commit() { - let operation = operation(RetrySafety::NonIdempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, store, provider) = coordinator(vec![ Script::Stream { @@ -2946,7 +2928,7 @@ fn pre_delivery_retry_marker_is_ignored_after_downstream_commit() { #[test] fn structural_event_before_replay_safe_failure_should_switch_account_before_commit() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, store, provider) = coordinator(vec![ Script::Stream { @@ -3005,7 +2987,7 @@ fn structural_event_before_replay_safe_failure_should_switch_account_before_comm #[test] fn cancellation_before_pending_delivery_commit_reaches_terminal_state() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, store, _) = coordinator(vec![Script::Stream { account_id: "acct_first", @@ -3039,7 +3021,7 @@ fn cancellation_before_pending_delivery_commit_reaches_terminal_state() { #[test] fn no_eligible_account_before_stream_does_not_create_request_detail() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, store, _) = coordinator(vec![Script::Error(ProviderError::new( ProviderErrorKind::NoEligibleAccount, @@ -3072,7 +3054,7 @@ fn no_eligible_account_before_stream_does_not_create_request_detail() { #[test] fn expired_deadline_finalizes_without_calling_provider() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, store, provider) = coordinator(vec![]); @@ -3098,7 +3080,7 @@ fn expired_deadline_finalizes_without_calling_provider() { #[test] fn deadline_after_commit_is_incomplete_without_provider_circuit_failure() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, store, _) = coordinator(vec![Script::HangingStream { account_id: "acct_first", @@ -3138,7 +3120,7 @@ fn deadline_after_commit_is_incomplete_without_provider_circuit_failure() { #[test] fn deadline_before_first_event_records_no_provider_circuit_failure() { - let operation = operation(RetrySafety::Idempotent); + let operation = generate_operation(); let route_plan = plan(&operation); let (coordinator, store, _) = coordinator(vec![Script::HangingStream { account_id: "acct_first", diff --git a/backend/crates/gateway-core/tests/engine/execution.rs b/backend/crates/gateway-core/tests/engine/execution.rs index 32e294455..75538cc45 100644 --- a/backend/crates/gateway-core/tests/engine/execution.rs +++ b/backend/crates/gateway-core/tests/engine/execution.rs @@ -18,7 +18,7 @@ use gateway_core::engine::continuation::{ NativeContinuationPin, NativeContinuationPort, NativeContinuationStoreError, PreviousResponseId, }; use gateway_core::engine::credential::{ - AccountSelectionPolicy, CredentialRevision, ProviderAccountId, RotationStrategy, + AccountSelectionPolicy, ProviderAccountId, RotationStrategy, }; use gateway_core::engine::execution::{ ClientApiKeyUsageSink, ClientTransport, DefaultExecutionService, ExecutionRequestMetadata, @@ -28,7 +28,7 @@ use gateway_core::engine::execution::{ use gateway_core::engine::probe::{AccountProbe, AccountProbeErrorSource, AccountProbeRequest}; use gateway_core::engine::provider::{ Provider, ProviderCallMetadata, ProviderCatalogGeneration, ProviderModelCapabilities, - ProviderRegistry, ProviderRequest, ProviderResource, ProviderStream, UpstreamTransport, + ProviderRegistry, ProviderRequest, ProviderStream, UpstreamTransport, }; use gateway_core::engine::{ AttemptContext, AttemptRecord, ExecutionStore, IntermediateFailure, ModelRequestFinalization, @@ -283,10 +283,7 @@ impl Provider for ColdFailingProvider { ) -> Result { let metadata = ProviderCallMetadata::for_provider_endpoint( request.candidate().provider().clone(), - ProviderResource::Account { - id: ProviderAccountId::new("acct_usage").expect("account"), - revision: CredentialRevision::new(1).expect("revision"), - }, + ProviderAccountId::new("acct_usage").expect("account"), UpstreamTransport::new("http_json").expect("transport"), ); Ok(ProviderStream::new( diff --git a/backend/crates/gateway-core/tests/engine/provider.rs b/backend/crates/gateway-core/tests/engine/provider.rs index 371a69aa9..299901970 100644 --- a/backend/crates/gateway-core/tests/engine/provider.rs +++ b/backend/crates/gateway-core/tests/engine/provider.rs @@ -6,16 +6,14 @@ use async_trait::async_trait; use futures::{StreamExt, stream}; use futures_timer::Delay; -use gateway_core::engine::credential::{ - AccountFeedbackStats, CredentialRevision, ProviderAccountId, -}; +use gateway_core::engine::credential::{AccountFeedbackStats, ProviderAccountId}; use gateway_core::engine::provider::{ EventStream, Provider, ProviderCallMetadata, ProviderCatalogGeneration, - ProviderModelCapabilities, ProviderRegistry, ProviderRequest, ProviderResource, ProviderStream, - RegistryError, ResourceId, UpstreamTransport, + ProviderModelCapabilities, ProviderRegistry, ProviderRequest, ProviderStream, RegistryError, + UpstreamTransport, }; use gateway_core::engine::{AttemptContext, UpstreamSendState}; -use gateway_core::error::{IdentifierError, ProviderError, ProviderErrorKind}; +use gateway_core::error::{ProviderError, ProviderErrorKind}; use gateway_core::event::{ ContentItem, ContentKind, GatewayEvent, ProtocolWireEvent, ProviderEvent, ResponseMeta, TextDelta, @@ -112,7 +110,7 @@ fn provider_stream_should_release_owned_lease_on_drop() { let metadata = ProviderCallMetadata::new( ProviderKind::new("openai").expect("valid provider"), UpstreamModelId::new("gpt-5").expect("valid model"), - ProviderResource::Anonymous(ResourceId::none()), + ProviderAccountId::new("acct_drop").expect("account"), UpstreamTransport::new("http_sse").expect("valid transport"), ); let events: EventStream = Box::pin(stream::empty()); @@ -131,10 +129,7 @@ fn provider_stream_should_report_common_account_success_and_first_output() { let metadata = ProviderCallMetadata::new( provider.clone(), UpstreamModelId::new("gpt-5").expect("valid model"), - ProviderResource::Account { - id: account.clone(), - revision: CredentialRevision::new(1).expect("revision"), - }, + account.clone(), UpstreamTransport::new("http_sse").expect("valid transport"), ); let response = ResponseMeta::new("resp_upstream", "gpt-5"); @@ -173,7 +168,7 @@ fn provider_stream_should_not_sequence_gate_canonical_observation_attached_to_wi let metadata = ProviderCallMetadata::new( ProviderKind::new("openai").expect("valid provider"), UpstreamModelId::new("gpt-5").expect("valid model"), - ProviderResource::Anonymous(ResourceId::none()), + ProviderAccountId::new("acct_wire").expect("account"), UpstreamTransport::new("http_sse").expect("valid transport"), ); let wire = ProtocolWireEvent::json( @@ -217,10 +212,7 @@ fn provider_stream_should_report_confirmed_sent_failure_but_ignore_unconfirmed_f let metadata = ProviderCallMetadata::new( provider.clone(), UpstreamModelId::new("grok-4.5").expect("valid model"), - ProviderResource::Account { - id: account, - revision: CredentialRevision::new(1).expect("revision"), - }, + account, UpstreamTransport::new("http_sse").expect("valid transport"), ); let events: EventStream = Box::pin(stream::iter([Err(ProviderError::new( @@ -252,19 +244,3 @@ fn provider_stream_should_report_confirmed_sent_failure_but_ignore_unconfirmed_f (None, None) ); } - -#[test] -fn resource_id_should_accept_versioned_hmac_pseudonym() { - let resource = ResourceId::anonymous("rr_hmac_sha256_v1:opaque-digest") - .expect("versioned pseudonym is valid"); - - assert_eq!(resource.as_str(), "rr_hmac_sha256_v1:opaque-digest"); -} - -#[test] -fn resource_id_should_reserve_none_sentinel_for_constructor() { - let error = ResourceId::anonymous("__none__") - .expect_err("sentinel must only be created by ResourceId::none"); - - assert_eq!(error, IdentifierError::ReservedPrefix); -} diff --git a/backend/crates/gateway-core/tests/operation/mod.rs b/backend/crates/gateway-core/tests/operation/mod.rs index 7d7054807..85786d9b2 100644 --- a/backend/crates/gateway-core/tests/operation/mod.rs +++ b/backend/crates/gateway-core/tests/operation/mod.rs @@ -1,9 +1,7 @@ use bytes::Bytes; -use gateway_core::error::OperationError; use gateway_core::operation::{ - EmbedRequest, Feature, GenerateRequest, ImageRequest, ImageRequestKind, Operation, - OperationKind, ProtocolPayload, ProviderSessionState, RawJsonPayload, RerankRequest, - RetrySafety, SpeechRequest, + Feature, GenerateRequest, ImageRequest, ImageRequestKind, Operation, OperationKind, + ProtocolPayload, ProviderSessionState, RawJsonPayload, }; use serde_json::{Map, Value, json}; @@ -117,43 +115,16 @@ fn provider_session_state_should_only_be_visible_to_its_provider() { } #[test] -fn non_generate_requests_should_reject_missing_required_fields() { - assert_eq!( - EmbedRequest::new(Vec::new()).expect_err("input is required"), - OperationError::EmptyField { field: "input" } - ); - assert_eq!( - RerankRequest::new("", vec!["document".to_owned()]).expect_err("query is required"), - OperationError::EmptyField { field: "query" } - ); - assert_eq!( - SpeechRequest::new("hello", "").expect_err("voice is required"), - OperationError::EmptyField { field: "voice" } - ); -} - -#[test] -fn operation_kind_and_retry_safety_should_remain_stable() { +fn operation_kind_should_remain_stable() { let generate = Operation::Generate(generate(json!({"model": "gpt-test"}))); - let embed = Operation::Embed( - EmbedRequest::new(vec!["input".to_owned()]).expect("embedding request is valid"), - ); - let rerank = Operation::Rerank( - RerankRequest::new("query", vec!["document".to_owned()]).expect("rerank request is valid"), - ); let image = Operation::GenerateImage(image( ImageRequestKind::Generation, json!({"model": "gpt-image-2", "prompt": "draw"}), )); - let speech = Operation::Speech(SpeechRequest::new("hello", "alloy").expect("speech request")); assert_eq!(generate.kind(), OperationKind::Generate); - assert_eq!(generate.retry_safety(), RetrySafety::NonIdempotent); - assert_eq!(embed.retry_safety(), RetrySafety::Idempotent); - assert_eq!(rerank.retry_safety(), RetrySafety::Idempotent); assert_eq!(image.kind(), OperationKind::GenerateImage); assert!(image.image_generation_requested()); - assert_eq!(speech.kind(), OperationKind::Speech); } #[test] diff --git a/backend/crates/gateway-core/tests/routing/mod.rs b/backend/crates/gateway-core/tests/routing/mod.rs index dc434e61a..53331b6d5 100644 --- a/backend/crates/gateway-core/tests/routing/mod.rs +++ b/backend/crates/gateway-core/tests/routing/mod.rs @@ -494,7 +494,7 @@ fn upstream_feature_validation_should_preserve_operation_and_limit_gates() { .require(Feature::JsonSchema); let oversized_output = CapabilityRequirements::new(OperationKind::Generate) .with_requested_output_tokens(Some(16_001)); - let unsupported_operation = CapabilityRequirements::new(OperationKind::Speech); + let unsupported_operation = CapabilityRequirements::new(OperationKind::GenerateImage); assert_eq!( ( diff --git a/backend/crates/gateway-host/Cargo.toml b/backend/crates/gateway-host/Cargo.toml index 967ff93b9..71a6ce7b7 100644 --- a/backend/crates/gateway-host/Cargo.toml +++ b/backend/crates/gateway-host/Cargo.toml @@ -33,7 +33,6 @@ tracing-subscriber = { version = "0.3.23", features = ["env-filter", "json"] } filetime = "0.2.27" tempfile = "3.27.0" tokio = { version = "1.52.3", default-features = false, features = ["rt-multi-thread", "test-util"] } -tower = { version = "0.5.3", features = ["util"] } wiremock = "0.6.5" [[test]] diff --git a/backend/crates/gateway-store/src/postgres/execution_buffer.rs b/backend/crates/gateway-store/src/postgres/execution_buffer.rs index c4741b924..851e13a61 100644 --- a/backend/crates/gateway-store/src/postgres/execution_buffer.rs +++ b/backend/crates/gateway-store/src/postgres/execution_buffer.rs @@ -675,7 +675,6 @@ fn provider_error_bytes(error: &ProviderError) -> usize { let mut bytes = text_bytes([ error.upstream_code().map(|value| value.as_str()), error.upstream_request_id().map(|value| value.as_str()), - error.upstream_response_id().map(|value| value.as_str()), ]); if let Some(client_error) = error.client_visible_upstream_error() { bytes = bytes.saturating_add(text_bytes([ diff --git a/backend/crates/providers/openai/Cargo.toml b/backend/crates/providers/openai/Cargo.toml index b46d94b0a..120c0ead1 100644 --- a/backend/crates/providers/openai/Cargo.toml +++ b/backend/crates/providers/openai/Cargo.toml @@ -48,7 +48,6 @@ tokio-tungstenite = { git = "https://github.com/openai-oss-forks/tokio-tungsteni tracing = "0.1.44" tungstenite = { git = "https://github.com/openai-oss-forks/tungstenite-rs", rev = "4fffad30fe373adbdcffab9545e9e9bf4f2fc19f", features = ["deflate", "proxy"] } url = "2.5.8" -urlencoding = "2.1.3" uuid = { version = "1.23.3", features = ["v4", "v7"] } # 官方 Codex app-server 对 /v1/responses 请求体默认做 zstd 压缩(level 3), # 并携带 Content-Encoding: zstd;保持同版本同级别以对齐上游指纹。 diff --git a/backend/crates/providers/openai/src/credential/mod.rs b/backend/crates/providers/openai/src/credential/mod.rs index 133a341f8..68927f863 100644 --- a/backend/crates/providers/openai/src/credential/mod.rs +++ b/backend/crates/providers/openai/src/credential/mod.rs @@ -61,5 +61,5 @@ pub(crate) use selector::{CodexCyberPolicyScope, SelectCodexProviderEndpointCred pub use types::{ CODEX_AUTHENTICATION_KIND_OAUTH, CodexAccountProfile, CodexCookie, CodexCookieCaptureOutcome, CodexCredentialData, CodexCredentialPrincipal, CodexOAuthCredentialData, CodexOAuthSecret, - RotateCodexCredential, RuntimeCodexCookie, UpsertCodexCookie, + RuntimeCodexCookie, UpsertCodexCookie, }; diff --git a/backend/crates/providers/openai/src/credential/repository.rs b/backend/crates/providers/openai/src/credential/repository.rs index 78d239b6b..e3ea1277b 100644 --- a/backend/crates/providers/openai/src/credential/repository.rs +++ b/backend/crates/providers/openai/src/credential/repository.rs @@ -3,10 +3,9 @@ use std::sync::Arc; use std::time::SystemTime; -use chrono::{DateTime, Utc}; use gateway_core::engine::credential::{ AccountErrorReason, AccountStateChange, CredentialCasOutcome, CredentialCasUpdate, - CredentialRevision, CredentialState, ProviderAccount, ProviderAccountId, ProviderAccountStore, + CredentialRevision, CredentialState, ProviderAccount, ProviderAccountStore, ProviderAccountUpdate, }; use gateway_core::routing::ProviderKind; @@ -14,7 +13,7 @@ use secrecy::ExposeSecret; use thiserror::Error; use super::security::{CodexCredentialCodec, CodexCredentialDataError, CodexRuntimeCredential}; -use super::types::{CodexCredentialData, CodexOAuthSecret, RotateCodexCredential}; +use super::types::{CodexCredentialData, CodexOAuthSecret}; const PROVIDER_NAME: &str = "openai"; @@ -34,51 +33,6 @@ impl CodexCredentialRepository { &self.store } - pub async fn rotate_oauth_secret( - &self, - input: RotateCodexCredential, - ) -> Result { - let account_id = ProviderAccountId::new(input.account_id) - .map_err(|_| CredentialRepositoryError::InvalidInput("account_id"))?; - let expected = CredentialRevision::new(input.expected_credential_revision) - .map_err(|_| CredentialRepositoryError::InvalidInput("credential_revision"))?; - let current = self.store.load_credential(&account_id, expected).await?; - let mut data = CodexCredentialCodec::decode_complete(¤t.credential)?; - let oauth = data - .oauth_mut() - .ok_or(CredentialRepositoryError::InvalidCredentialData)?; - oauth.access_token = input.secret.access_token.expose_secret().to_owned(); - oauth.refresh_token = input - .secret - .refresh_token - .as_ref() - .map(|value| value.expose_secret().to_owned()); - oauth.id_token = input - .secret - .id_token - .as_ref() - .map(|value| value.expose_secret().to_owned()); - let credential = CodexCredentialCodec::encode_complete(data)?; - let update = CredentialCasUpdate::new( - account_id.clone(), - expected, - ProviderAccountUpdate { - account_id, - name: current.account.name().to_owned(), - email: input.verified_account.email.clone(), - plan_type: input.verified_account.plan_type.clone(), - }, - credential, - input.secret.refresh_token.is_some(), - Some(required_time( - input.verified_account.access_token_expires_at, - )?), - optional_time(input.next_refresh_at), - ) - .map_err(|_| CredentialRepositoryError::InvalidCredentialData)?; - cas_revision(self.store.compare_and_swap_credential(update).await?) - } - /// 持久化成功 RT exchange 的 token,同时保留既有账号身份投影。 /// /// Refresh endpoint 已经是这次 AT/RT 轮换的授权边界;这里仅以 revision @@ -294,20 +248,8 @@ fn cas_revision( } } -fn required_time(value: Option>) -> Result { - value - .map(SystemTime::from) - .ok_or(CredentialRepositoryError::InvalidCredentialData) -} - -fn optional_time(value: Option>) -> Option { - value.map(SystemTime::from) -} - #[derive(Debug, Error)] pub enum CredentialRepositoryError { - #[error("invalid Codex credential input: {0}")] - InvalidInput(&'static str), #[error("Codex credential data is invalid")] InvalidCredentialData, #[error("Codex credential revision conflict")] diff --git a/backend/crates/providers/openai/src/credential/selector.rs b/backend/crates/providers/openai/src/credential/selector.rs index 1599b0a16..6ffea2308 100644 --- a/backend/crates/providers/openai/src/credential/selector.rs +++ b/backend/crates/providers/openai/src/credential/selector.rs @@ -1320,8 +1320,7 @@ pub enum CredentialSelectionError { impl From for CredentialSelectionError { fn from(error: CredentialRepositoryError) -> Self { match error { - CredentialRepositoryError::InvalidInput(_) - | CredentialRepositoryError::InvalidCredentialData => Self::InvalidCredential, + CredentialRepositoryError::InvalidCredentialData => Self::InvalidCredential, CredentialRepositoryError::RevisionConflict | CredentialRepositoryError::Store => { Self::Store } diff --git a/backend/crates/providers/openai/src/credential/token_client.rs b/backend/crates/providers/openai/src/credential/token_client.rs index 6d58bedf6..d6c0408bf 100644 --- a/backend/crates/providers/openai/src/credential/token_client.rs +++ b/backend/crates/providers/openai/src/credential/token_client.rs @@ -274,14 +274,6 @@ pub fn openai_token_client( Ok(OpenAiTokenClient::new(client, config)) } -/// 构建固定官方 endpoint 的 Codex token client。 -pub fn official_openai_token_client() -> Result { - openai_token_client(TokenClientConfig { - client_id: OFFICIAL_CODEX_OAUTH_CLIENT_ID.to_owned(), - token_endpoint: OFFICIAL_CODEX_TOKEN_ENDPOINT.to_owned(), - }) -} - #[derive(Deserialize)] struct RefreshTokenResponse { access_token: Option, diff --git a/backend/crates/providers/openai/src/credential/types.rs b/backend/crates/providers/openai/src/credential/types.rs index 828953643..0e002c293 100644 --- a/backend/crates/providers/openai/src/credential/types.rs +++ b/backend/crates/providers/openai/src/credential/types.rs @@ -344,12 +344,6 @@ impl CodexCredentialData { } } - pub fn set_installation_id(&mut self, installation_id: String) { - match self { - Self::OAuth(data) => data.installation_id = installation_id, - } - } - #[must_use] pub fn cookies(&self) -> &[CodexCookie] { match self { @@ -391,31 +385,6 @@ impl fmt::Debug for CodexCredentialData { } } -/// 刷新成功后的 CAS 输入。 -pub struct RotateCodexCredential { - pub account_id: String, - pub expected_credential_revision: u64, - pub secret: CodexOAuthSecret, - pub verified_account: CodexAccountProfile, - pub next_refresh_at: Option>, -} - -impl fmt::Debug for RotateCodexCredential { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter - .debug_struct("RotateCodexCredential") - .field("account_id", &self.account_id) - .field( - "expected_credential_revision", - &self.expected_credential_revision, - ) - .field("secret", &self.secret) - .field("verified_account", &self.verified_account) - .field("next_refresh_at", &self.next_refresh_at) - .finish() - } -} - /// 运行时使用的 Cookie;值受 `secrecy` 保护。 pub struct RuntimeCodexCookie { pub name: String, diff --git a/backend/crates/providers/openai/src/provider.rs b/backend/crates/providers/openai/src/provider.rs index 604a57d87..f48633102 100644 --- a/backend/crates/providers/openai/src/provider.rs +++ b/backend/crates/providers/openai/src/provider.rs @@ -12,8 +12,8 @@ use gateway_core::engine::continuation::{ContinuationBinding, NativeContinuation use gateway_core::engine::credential::{AccountFeedbackStats, ProviderAccount}; use gateway_core::engine::provider::{ EventStream, Provider, ProviderCallMetadata, ProviderCatalogGeneration, - ProviderModelCapabilities, ProviderRequest, ProviderRequestObservation, ProviderResource, - ProviderStream, UpstreamTransport, + ProviderModelCapabilities, ProviderRequest, ProviderRequestObservation, ProviderStream, + UpstreamTransport, }; use gateway_core::engine::{ AttemptContext, CancellationToken, ContinuationAttempt, UpstreamSendState, @@ -386,10 +386,7 @@ impl Provider for CodexProvider { let metadata = ProviderCallMetadata::new( provider_kind, upstream_model.clone(), - ProviderResource::Account { - id: lease.account_id().clone(), - revision: lease.account().revision(), - }, + lease.account_id().clone(), UpstreamTransport::new(transport_name(transport)).map_err(|_| { provider_error(ProviderErrorKind::Protocol, UpstreamSendState::NotSent) })?, @@ -464,10 +461,7 @@ impl CodexProvider { .map_err(|_| provider_error(ProviderErrorKind::Protocol, UpstreamSendState::NotSent))?; let metadata = ProviderCallMetadata::for_provider_endpoint( provider_kind, - ProviderResource::Account { - id: lease.account_id().clone(), - revision: lease.account().revision(), - }, + lease.account_id().clone(), UpstreamTransport::new(HTTP_JSON_TRANSPORT).map_err(|_| { provider_error(ProviderErrorKind::Protocol, UpstreamSendState::NotSent) })?, diff --git a/backend/crates/providers/openai/tests/transport/canonical.rs b/backend/crates/providers/openai/tests/transport/canonical.rs index 6f66ebb15..7500fbc89 100644 --- a/backend/crates/providers/openai/tests/transport/canonical.rs +++ b/backend/crates/providers/openai/tests/transport/canonical.rs @@ -6,9 +6,7 @@ use provider_openai::transport::canonical::{ use provider_openai::transport::protocol::websocket::websocket_event_to_sse_frame; use serde_json::json; -const METADATA_PREFIX_FIXTURE: &str = include_str!( - "../../../../gateway-api/tests/openai/responses/fixtures/http_sse/metadata_only_prefix.sse" -); +const METADATA_PREFIX_FIXTURE: &str = include_str!("fixtures/metadata_only_prefix.sse"); #[test] fn decoder_should_not_forward_codex_rate_limit_metadata_fixture_as_openai_wire() { diff --git a/backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/metadata_only_prefix.sse b/backend/crates/providers/openai/tests/transport/fixtures/metadata_only_prefix.sse similarity index 100% rename from backend/crates/gateway-api/tests/openai/responses/fixtures/http_sse/metadata_only_prefix.sse rename to backend/crates/providers/openai/tests/transport/fixtures/metadata_only_prefix.sse diff --git a/backend/crates/providers/xai/src/provider.rs b/backend/crates/providers/xai/src/provider.rs index a186caeca..0387cafd0 100644 --- a/backend/crates/providers/xai/src/provider.rs +++ b/backend/crates/providers/xai/src/provider.rs @@ -455,7 +455,7 @@ fn provider_call_metadata( Ok(ProviderCallMetadata::new( ProviderKind::new(XAI_PROVIDER_NAME).map_err(|_| protocol_not_sent())?, candidate_upstream_model(candidate)?.clone(), - selected.resource(), + selected.account_id().clone(), UpstreamTransport::new(HTTP_SSE_TRANSPORT).map_err(|_| protocol_not_sent())?, )) } diff --git a/backend/crates/providers/xai/src/transport/session.rs b/backend/crates/providers/xai/src/transport/session.rs index 87231bb4b..bd19805d8 100644 --- a/backend/crates/providers/xai/src/transport/session.rs +++ b/backend/crates/providers/xai/src/transport/session.rs @@ -8,7 +8,6 @@ use std::time::{Duration, SystemTime}; use gateway_core::engine::credential::{ AccountEligibilityPolicy, AccountSelectionPolicy, CredentialRevision, ProviderAccountId, }; -use gateway_core::engine::provider::ProviderResource; use gateway_core::policy::ClientApiKeyId; use gateway_core::routing::{FrozenAccountScope, UpstreamModelId}; use sha2::{Digest as _, Sha256}; @@ -147,15 +146,6 @@ impl SelectedGrokSession { self.credential_revision } - /// 返回 Core 为本次上游调用记录的元数据。 - #[must_use] - pub fn resource(&self) -> ProviderResource { - ProviderResource::Account { - id: self.account_id.clone(), - revision: self.credential_revision, - } - } - /// 返回用于显式构造 header 的 OAuth access token。 #[must_use] pub const fn access_token(&self) -> &SecretValue { diff --git a/backend/crates/providers/xai/tests/provider/contract.rs b/backend/crates/providers/xai/tests/provider/contract.rs index 9136e446a..009504d19 100644 --- a/backend/crates/providers/xai/tests/provider/contract.rs +++ b/backend/crates/providers/xai/tests/provider/contract.rs @@ -1125,7 +1125,7 @@ async fn execute_forwards_required_account_to_selector() { .await .expect("required account stream"); - assert_eq!(stream.metadata().provider_account_id(), Some(&required)); + assert_eq!(stream.metadata().provider_account_id(), &required); assert_eq!( selector .required_accounts @@ -1151,7 +1151,7 @@ async fn execute_returns_cold_stream_and_records_selected_account() { assert_eq!(transport.calls.load(Ordering::SeqCst), 0); assert_eq!( stream.metadata().provider_account_id(), - Some(&account_id("provider")) + &account_id("provider") ); let events = stream.by_ref().collect::>().await; assert_eq!(transport.calls.load(Ordering::SeqCst), 1); diff --git a/frontend/src/api/modules/accounts.ts b/frontend/src/api/modules/accounts.ts index 852bc53ba..3132484ed 100644 --- a/frontend/src/api/modules/accounts.ts +++ b/frontend/src/api/modules/accounts.ts @@ -360,14 +360,6 @@ export function recoverAccount(data: AccountIdParam) { }) } -export function getAccountQuota(data: AccountIdParam) { - return request({ - url: '/api/admin/accounts/quota', - method: 'GET', - params: data, - }) -} - export function getAccountProfileStatistics(data: AccountIdParam) { return request({ url: '/api/admin/accounts/profile-statistics', diff --git a/frontend/src/components/AccountGroupMultiSelect.vue b/frontend/src/components/AccountGroupMultiSelect.vue deleted file mode 100644 index bcf53f0c7..000000000 --- a/frontend/src/components/AccountGroupMultiSelect.vue +++ /dev/null @@ -1,164 +0,0 @@ - - - diff --git a/frontend/src/views/settings/composables/useBackupRecords.ts b/frontend/src/views/settings/composables/useBackupRecords.ts index 4be8a790b..5caf77d0b 100644 --- a/frontend/src/views/settings/composables/useBackupRecords.ts +++ b/frontend/src/views/settings/composables/useBackupRecords.ts @@ -203,5 +203,3 @@ export function useBackupRecords() { stopPolling, } } - -export type BackupRecordsStore = ReturnType diff --git a/frontend/src/views/settings/composables/useBackupSettings.ts b/frontend/src/views/settings/composables/useBackupSettings.ts index cc2fc244c..2bf36a7d1 100644 --- a/frontend/src/views/settings/composables/useBackupSettings.ts +++ b/frontend/src/views/settings/composables/useBackupSettings.ts @@ -168,5 +168,3 @@ export function useBackupSettings() { saveSchedule, } } - -export type BackupSettingsStore = ReturnType diff --git a/frontend/src/views/usage/utils/format.ts b/frontend/src/views/usage/utils/format.ts index 6586f18c4..c827727c0 100644 --- a/frontend/src/views/usage/utils/format.ts +++ b/frontend/src/views/usage/utils/format.ts @@ -55,12 +55,6 @@ export function formatUsd(value?: string | number | null, precise = false) { }).format(safeValue) } -export function formatEstimatedAmount(currency: string, value?: string | null) { - if (value == null) - return '—' - return currency.toUpperCase() === 'USD' ? formatUsd(value, true) : `${currency} ${value}` -} - export function formatUsdAxis(value: number) { const safeValue = finiteOrZero(value) if (Math.abs(safeValue) >= 1_000) diff --git a/frontend/src/views/usage/utils/records.ts b/frontend/src/views/usage/utils/records.ts index 9713282dd..5b6cb4137 100644 --- a/frontend/src/views/usage/utils/records.ts +++ b/frontend/src/views/usage/utils/records.ts @@ -1,6 +1,6 @@ // Usage 记录的单一规范化 view model 消费层。 // -// 组件只消费 `UsageDisplayRecord`(即 `UsageViewModel`);兼容性回退只在本文件发生。 +// 组件只消费 `UsageDisplayRecord`(即 `UsageViewModel`)。 import type { UsageAttempt, @@ -16,7 +16,7 @@ import type { import { isRecord } from '@/utils/object' import { formatDuration } from './format' -// Usage 记录的规范化 view model:组件只消费这个形状,兼容性回退只发生在本文件。 +// Usage 记录的规范化 view model:组件只消费这个形状。 export interface UsageViewModel { id: string requestId: string @@ -34,8 +34,6 @@ export interface UsageViewModel { serviceTier: string | null statusCode: number | null transport: string | null - stream: boolean | null - apiKind: string | null attemptIndex: number | null attemptCount: number responseId: string | null @@ -81,47 +79,9 @@ export interface UsageViewModel { attemptsComplete?: boolean } -const CORE_METADATA_KEYS = new Set([ - 'protocol', - 'logicalOutcome', - 'attemptCount', - 'requestedModel', - 'upstreamModel', - 'clientIp', - 'userAgent', - 'reasoningEffort', - 'reasoningPreset', - 'compact', - 'requestKind', - 'subagentKind', - 'transport', - 'httpVersion', - 'clientStatusCode', - 'upstreamStatusCode', - 'responseId', - 'upstreamRequestId', - 'websocketPool', - 'imageGenerationRequested', - 'imageGenerationSucceeded', - 'latencyDetails', -]) - -function metadataRecord(value: unknown): Record { - return isRecord(value) ? value : {} -} - -/** 一次性兼容 normalize:顶层 Core 字段优先,旧响应中的 metadata 回退只发生在这里。 */ +/** 将现行 Usage API 记录收口为组件消费的单一形状。 */ export function normalizeUsageRecord(record: UsageRecord | UsageRecordDetail): UsageViewModel { - const metadata = metadataRecord(record.metadata) - const legacy = (top: T | null | undefined, key: string): T | null | undefined => - top ?? (metadata[key] as T | undefined) - - const latencyDetails = legacy(record.latencyDetails, 'latencyDetails') ?? null - const providerMetadata: Record = {} - for (const [key, value] of Object.entries(metadata)) { - if (!CORE_METADATA_KEYS.has(key)) - providerMetadata[key] = value - } + const metadata = record.metadata return { id: record.id, @@ -135,29 +95,25 @@ export function normalizeUsageRecord(record: UsageRecord | UsageRecordDetail): U accountName: record.accountName, route: record.route, model: record.model, - requestedModel: legacy(record.requestedModel, 'requestedModel') ?? null, - upstreamModel: legacy(record.upstreamModel, 'upstreamModel') ?? null, + requestedModel: record.requestedModel, + upstreamModel: record.upstreamModel, serviceTier: record.serviceTier, statusCode: record.statusCode, - transport: legacy(record.transport, 'transport') ?? null, - stream: typeof metadata.stream === 'boolean' ? metadata.stream : null, - apiKind: typeof metadata.apiKind === 'string' ? metadata.apiKind : null, + transport: record.transport, attemptIndex: record.attemptIndex, - attemptCount: legacy(record.attemptCount, 'attemptCount') ?? 0, - responseId: legacy(record.responseId, 'responseId') ?? null, - upstreamRequestId: legacy(record.upstreamRequestId, 'upstreamRequestId') ?? null, - protocol: legacy(record.protocol, 'protocol') ?? '', - httpVersion: legacy(record.httpVersion, 'httpVersion') ?? null, - clientStatusCode: legacy(record.clientStatusCode, 'clientStatusCode') ?? null, - upstreamStatusCode: legacy(record.upstreamStatusCode, 'upstreamStatusCode') ?? null, - websocketPool: legacy(record.websocketPool, 'websocketPool') ?? null, - imageGenerationRequested: - legacy(record.imageGenerationRequested, 'imageGenerationRequested') ?? false, - imageGenerationSucceeded: - legacy(record.imageGenerationSucceeded, 'imageGenerationSucceeded') ?? null, + attemptCount: record.attemptCount, + responseId: record.responseId, + upstreamRequestId: record.upstreamRequestId, + protocol: record.protocol, + httpVersion: record.httpVersion, + clientStatusCode: record.clientStatusCode, + upstreamStatusCode: record.upstreamStatusCode, + websocketPool: record.websocketPool, + imageGenerationRequested: record.imageGenerationRequested, + imageGenerationSucceeded: record.imageGenerationSucceeded, latencyMs: record.latencyMs, firstTokenLatencyMs: record.firstTokenLatencyMs, - latencyDetails, + latencyDetails: record.latencyDetails, inputTokens: record.inputTokens, outputTokens: record.outputTokens, cachedTokens: record.cachedTokens, @@ -168,21 +124,21 @@ export function normalizeUsageRecord(record: UsageRecord | UsageRecordDetail): U message: record.message, createdAt: record.createdAt, createdAtDisplay: record.createdAtDisplay, - clientIp: legacy(record.clientIp, 'clientIp') ?? null, - userAgent: legacy(record.userAgent, 'userAgent') ?? null, - reasoningEffort: legacy(record.reasoningEffort, 'reasoningEffort') ?? null, - reasoningPreset: legacy(record.reasoningPreset, 'reasoningPreset') ?? null, - compact: legacy(record.compact, 'compact') ?? false, - requestKind: legacy(record.requestKind, 'requestKind') ?? null, - subagentKind: legacy(record.subagentKind, 'subagentKind') ?? null, + clientIp: record.clientIp, + userAgent: record.userAgent, + reasoningEffort: record.reasoningEffort, + reasoningPreset: record.reasoningPreset, + compact: record.compact ?? false, + requestKind: record.requestKind, + subagentKind: record.subagentKind, tokenDetails: record.tokenDetails, billing: record.billing, costs: record.costs, costCoverage: record.costCoverage, firstTokenLatencyMsDisplay: record.firstTokenLatencyMsDisplay, latencyMsDisplay: record.latencyMsDisplay, - logicalOutcome: legacy(record.logicalOutcome, 'logicalOutcome') ?? '', - providerMetadata, + logicalOutcome: record.logicalOutcome, + providerMetadata: metadata, requestBody: metadata.requestBody, responseBody: metadata.responseBody, attempts: 'attempts' in record ? record.attempts : undefined, @@ -196,13 +152,9 @@ export function usageRecordType(record: UsageDisplayRecord) { if (record.transport === 'websocket') return 'WS' - if (record.stream === true || record.transport === 'http_sse') + if (record.transport === 'http_sse') return 'SSE' - - if (record.stream === false) - return 'HTTP' - - return record.apiKind === 'chat' ? 'Chat' : 'HTTP' + return 'HTTP' } export function usageRecordTypeClass(record: UsageDisplayRecord) { @@ -211,8 +163,6 @@ export function usageRecordTypeClass(record: UsageDisplayRecord) { return 'bg-cp-info-bg text-cp-info-text' if (type === 'SSE') return 'bg-cp-success-bg text-cp-success-text' - if (type === 'Chat') - return 'bg-cp-warning-bg text-cp-warning-text' return 'bg-cp-fill-tertiary text-cp-info-text' }