diff --git a/backend/crates/gateway-admin/src/model/observability.rs b/backend/crates/gateway-admin/src/model/observability.rs index 5ec07c46c..719f0dc77 100644 --- a/backend/crates/gateway-admin/src/model/observability.rs +++ b/backend/crates/gateway-admin/src/model/observability.rs @@ -740,7 +740,6 @@ pub struct UsageListRecord { pub provider_account_authentication_kind: Option, pub upstream_model_id: Option, pub upstream_transport: Option, - pub upstream_response_model: Option, pub service_tier: Option, pub input_tokens: Option, pub output_tokens: Option, @@ -802,7 +801,6 @@ pub struct UsageRecord { pub upstream_transport: Option, pub http_version: Option, pub websocket_pool: Option, - pub upstream_response_model: Option, pub service_tier: Option, /// Provider 已筛选的专有观测 JSON;管理领域保持不透明 pub provider_metadata_json: Option, diff --git a/backend/crates/gateway-admin/tests/use_case/observability/mod.rs b/backend/crates/gateway-admin/tests/use_case/observability/mod.rs index f55ad8dc9..4c4b02a46 100644 --- a/backend/crates/gateway-admin/tests/use_case/observability/mod.rs +++ b/backend/crates/gateway-admin/tests/use_case/observability/mod.rs @@ -1361,7 +1361,6 @@ fn total_record( provider_account_authentication_kind: None, upstream_model_id: Some("gpt-5.5".to_owned()), upstream_transport: None, - upstream_response_model: None, service_tier: None, input_tokens: Some(800), output_tokens: Some(200), diff --git a/backend/crates/gateway-api/src/admin/observability/export/archive.rs b/backend/crates/gateway-api/src/admin/observability/export/archive.rs index 20ce224a9..26b2582e0 100644 --- a/backend/crates/gateway-api/src/admin/observability/export/archive.rs +++ b/backend/crates/gateway-api/src/admin/observability/export/archive.rs @@ -28,13 +28,12 @@ pub(super) fn build(document: &Value) -> io::Result> { let detail = &evidence["detail"]["request"]; writeln!( archive, - "\n结果:{};状态码:{};尝试数:{}\n模型:请求={},发送={},上游报告={}\n传输:{} → {}\n时延:{}", + "\n结果:{};状态码:{};尝试数:{}\n模型:请求={},发送={}\n传输:{} → {}\n时延:{}", detail["logicalOutcome"], detail["statusCode"], detail["attemptCount"], detail["requestedModel"], detail["upstreamModel"], - detail["upstreamResponseModel"], detail["clientTransport"], detail["upstreamTransport"], detail["latencyDetails"] diff --git a/backend/crates/gateway-api/src/admin/observability/presenter.rs b/backend/crates/gateway-api/src/admin/observability/presenter.rs index fcb949803..e5ddedcd2 100644 --- a/backend/crates/gateway-api/src/admin/observability/presenter.rs +++ b/backend/crates/gateway-api/src/admin/observability/presenter.rs @@ -279,7 +279,6 @@ pub(crate) fn usage_list_record_view( model, requested_model: record.requested_model_id, upstream_model: record.upstream_model_id, - upstream_response_model: record.upstream_response_model, service_tier: record.service_tier, client_transport: record.client_transport, upstream_transport: record.upstream_transport, @@ -377,7 +376,6 @@ pub(crate) fn usage_record_view( model, requested_model: record.requested_model_id, upstream_model: record.upstream_model_id, - upstream_response_model: record.upstream_response_model, service_tier: record.service_tier, status_code, client_transport: record.client_transport, diff --git a/backend/crates/gateway-api/src/admin/observability/wire.rs b/backend/crates/gateway-api/src/admin/observability/wire.rs index 4e4579374..8cabcc265 100644 --- a/backend/crates/gateway-api/src/admin/observability/wire.rs +++ b/backend/crates/gateway-api/src/admin/observability/wire.rs @@ -103,7 +103,6 @@ pub struct UsageListRecordView { pub model: Option, pub requested_model: Option, pub upstream_model: Option, - pub upstream_response_model: Option, pub service_tier: Option, pub client_transport: String, pub upstream_transport: Option, @@ -142,7 +141,6 @@ pub struct UsageRecordView { pub model: Option, pub requested_model: Option, pub upstream_model: Option, - pub upstream_response_model: Option, pub service_tier: Option, pub status_code: Option, pub client_transport: String, diff --git a/backend/crates/gateway-api/tests/admin/observability/response.rs b/backend/crates/gateway-api/tests/admin/observability/response.rs index 64a8a6d6f..738326453 100644 --- a/backend/crates/gateway-api/tests/admin/observability/response.rs +++ b/backend/crates/gateway-api/tests/admin/observability/response.rs @@ -764,7 +764,6 @@ pub(super) fn usage_record_with_account( upstream_transport: Some("http_sse".to_owned()), http_version: Some("h2".to_owned()), websocket_pool: None, - upstream_response_model: None, service_tier: None, provider_metadata_json: None, attempt_count: 1, @@ -909,7 +908,6 @@ async fn usage_route_should_expose_table_facts_without_detail_payload() { provider_account_authentication_kind: Some("oauth".to_owned()), upstream_model_id: Some("grok-4.5".to_owned()), upstream_transport: Some("http_sse".to_owned()), - upstream_response_model: Some("grok-4.6-build".to_owned()), service_tier: Some("default".to_owned()), input_tokens: Some(1), output_tokens: Some(1), @@ -1059,7 +1057,6 @@ async fn usage_route_should_expose_table_facts_without_detail_payload() { "imageOutputTokens": value["data"]["items"][0]["tokenDetails"]["imageOutputTokens"], "requestedModel": value["data"]["items"][0]["requestedModel"], "upstreamModel": value["data"]["items"][0]["upstreamModel"], - "upstreamResponseModel": value["data"]["items"][0]["upstreamResponseModel"], "reasoningEffort": value["data"]["items"][0]["reasoningEffort"], "reasoningPreset": value["data"]["items"][0]["reasoningPreset"], "subagentKind": value["data"]["items"][0]["subagentKind"], @@ -1083,7 +1080,6 @@ async fn usage_route_should_expose_table_facts_without_detail_payload() { "imageOutputTokens": 9, "requestedModel": "grok-4.5", "upstreamModel": "grok-4.5", - "upstreamResponseModel": "grok-4.6-build", "reasoningEffort": "max", "reasoningPreset": "ultra", "subagentKind": "worker", diff --git a/backend/crates/gateway-api/tests/key_usage/fixtures.rs b/backend/crates/gateway-api/tests/key_usage/fixtures.rs index 674bb6259..318c66f99 100644 --- a/backend/crates/gateway-api/tests/key_usage/fixtures.rs +++ b/backend/crates/gateway-api/tests/key_usage/fixtures.rs @@ -110,7 +110,6 @@ fn usage_record() -> UsageListRecord { provider_account_authentication_kind: Some("private-sentinel".to_owned()), upstream_model_id: Some("private-sentinel".to_owned()), upstream_transport: Some("websocket".to_owned()), - upstream_response_model: Some("private-sentinel".to_owned()), service_tier: Some("private-sentinel".to_owned()), input_tokens: Some(1000), output_tokens: Some(100), diff --git a/backend/crates/gateway-core/src/engine/coordinator.rs b/backend/crates/gateway-core/src/engine/coordinator.rs index 554f6edff..7ea88cfd1 100644 --- a/backend/crates/gateway-core/src/engine/coordinator.rs +++ b/backend/crates/gateway-core/src/engine/coordinator.rs @@ -2302,12 +2302,6 @@ where http_version, websocket_pool, service_tier, - upstream_response_model: self - .current - .as_ref() - .and_then(|current| current.response_observation.as_ref()) - .and_then(ProviderResponseObservation::upstream_response_model) - .map(str::to_owned), provider_metadata_json, diagnostic_trace_json: self.trace.snapshot().map(|value| value.to_string()), error: None, @@ -2490,12 +2484,6 @@ where http_version, websocket_pool, service_tier, - upstream_response_model: self - .current - .as_ref() - .and_then(|current| current.response_observation.as_ref()) - .and_then(ProviderResponseObservation::upstream_response_model) - .map(str::to_owned), provider_metadata_json, diagnostic_trace_json: self.trace.snapshot().map(|value| value.to_string()), error: Some(finalization.error), diff --git a/backend/crates/gateway-core/src/engine/mod.rs b/backend/crates/gateway-core/src/engine/mod.rs index cabee6dc1..fc3386e36 100644 --- a/backend/crates/gateway-core/src/engine/mod.rs +++ b/backend/crates/gateway-core/src/engine/mod.rs @@ -1070,7 +1070,6 @@ pub struct ModelRequestFinalization { pub http_version: Option, pub websocket_pool: Option, pub service_tier: Option, - pub upstream_response_model: Option, /// Provider 已筛选的专有观测 JSON;Core 不解释字段 pub provider_metadata_json: Option, /// 请求全程的有界诊断快照,跨 Provider 与重试保留 diff --git a/backend/crates/gateway-core/src/engine/observation.rs b/backend/crates/gateway-core/src/engine/observation.rs index d1a1d3b81..91c4c35f8 100644 --- a/backend/crates/gateway-core/src/engine/observation.rs +++ b/backend/crates/gateway-core/src/engine/observation.rs @@ -249,7 +249,6 @@ pub struct RequestObservation { provider: Option, account_id: Option, upstream_model: Option, - response_model: Option, service_tier: Option, outcome: RequestObservationOutcome, send_state: UpstreamSendState, @@ -287,7 +286,6 @@ impl RequestObservation { provider: None, account_id: None, upstream_model: None, - response_model: None, service_tier: None, outcome, send_state, @@ -341,11 +339,6 @@ impl RequestObservation { self.upstream_model.as_ref() } - #[must_use] - pub fn response_model(&self) -> Option<&str> { - self.response_model.as_deref() - } - #[must_use] pub fn service_tier(&self) -> Option<&str> { self.service_tier.as_deref() @@ -626,7 +619,6 @@ impl RequestObservationDispatch { outcome: RequestObservationOutcome::Rejected, account_id: None, upstream_model: None, - response_model: None, service_tier: None, send_state: UpstreamSendState::NotSent, attempt_count: 0, @@ -699,7 +691,6 @@ impl RequestObservationDispatch { provider, account_id: attempt.map(|attempt| attempt.provider_account_id().clone()), upstream_model: attempt.and_then(|attempt| attempt.upstream_model().cloned()), - response_model: finalization.upstream_response_model.clone(), service_tier: finalization.service_tier.clone(), outcome, send_state: finalization.send_state, diff --git a/backend/crates/gateway-core/src/event.rs b/backend/crates/gateway-core/src/event.rs index fcf0fa920..d1b1ee268 100644 --- a/backend/crates/gateway-core/src/event.rs +++ b/backend/crates/gateway-core/src/event.rs @@ -624,7 +624,6 @@ pub struct ProviderResponseObservation { status_code: Option, request_id: Option, service_tier: Option, - upstream_response_model: Option, timings: ProviderResponseTimings, // 流式事件分别持有快照,共享不可变头集合,避免逐事件复制头名和集合 client_headers: Arc<[ProviderResponseHeader]>, @@ -641,7 +640,6 @@ impl ProviderResponseObservation { status_code: None, request_id: None, service_tier: None, - upstream_response_model: None, timings: ProviderResponseTimings::default(), client_headers: Arc::default(), provider_metadata: None, @@ -697,21 +695,6 @@ impl ProviderResponseObservation { self } - /// 记录 Provider 从上游响应明确取得的模型,不改写发送模型或计费合同 - #[must_use] - pub fn with_upstream_response_model_if_valid(mut self, model: &str) -> Self { - let model = model.trim(); - if validate_text(model, 256, false, None).is_ok() { - self.upstream_response_model = Some(model.to_owned()); - } - self - } - - #[must_use] - pub fn upstream_response_model(&self) -> Option<&str> { - self.upstream_response_model.as_deref() - } - #[must_use] pub const fn with_timings(mut self, timings: ProviderResponseTimings) -> Self { self.timings = timings; diff --git a/backend/crates/gateway-core/tests/engine/coordinator.rs b/backend/crates/gateway-core/tests/engine/coordinator.rs index c6ae3a7cb..a547d6ab4 100644 --- a/backend/crates/gateway-core/tests/engine/coordinator.rs +++ b/backend/crates/gateway-core/tests/engine/coordinator.rs @@ -491,7 +491,6 @@ struct FinalState { http_version: Option, websocket_pool: Option, service_tier: Option, - upstream_response_model: Option, upstream_request_id: Option, upstream_status_code: Option, transport_decision_wait_ms: Option, @@ -682,7 +681,6 @@ impl ExecutionStore for FakeStore { http_version: finalization.http_version, websocket_pool: finalization.websocket_pool, service_tier: finalization.service_tier, - upstream_response_model: finalization.upstream_response_model, upstream_request_id: finalization.upstream_request_id, upstream_status_code: finalization.upstream_status_code, transport_decision_wait_ms: finalization.timings.transport_decision_wait_ms, @@ -1764,7 +1762,6 @@ fn response_observation_is_persisted_but_never_delivered() { .with_websocket_pool(WebSocketPoolKind::New) .with_status_code(200) .with_request_id(OpaqueUpstreamValue::new("upstream-observed")) - .with_upstream_response_model_if_valid("gpt-returned") .try_with_service_tier("priority") .expect("service tier") .with_timings(ProviderResponseTimings { @@ -1804,10 +1801,6 @@ fn response_observation_is_persisted_but_never_delivered() { assert_eq!(finalization.http_version.as_deref(), Some("HTTP/2")); assert_eq!(finalization.websocket_pool.as_deref(), Some("new")); assert_eq!(finalization.service_tier.as_deref(), Some("priority")); - assert_eq!( - finalization.upstream_response_model.as_deref(), - Some("gpt-returned") - ); assert_eq!( finalization.upstream_request_id.as_deref(), Some("upstream-observed") @@ -2083,7 +2076,6 @@ fn discarded_attempt_observation_does_not_leak_into_retry_result() { .with_http_version(UpstreamHttpVersion::Http11) .with_status_code(503) .with_request_id(OpaqueUpstreamValue::new("discarded-request")) - .with_upstream_response_model_if_valid("discarded-model") .with_timings(ProviderResponseTimings { transport_decision_wait_ms: Some(7), connect_ms: Some(11), @@ -2146,7 +2138,6 @@ fn discarded_attempt_observation_does_not_leak_into_retry_result() { let state = store.state.lock().expect("store lock"); let finalization = &state.finalizations[0]; - assert_eq!(finalization.upstream_response_model, None); let trace: Value = serde_json::from_str(finalization.diagnostic_trace_json.as_deref().unwrap()).unwrap(); let events = trace["events"].as_array().unwrap(); diff --git a/backend/crates/gateway-core/tests/engine/execution/mod.rs b/backend/crates/gateway-core/tests/engine/execution/mod.rs index e860704cf..3e57bbe8c 100644 --- a/backend/crates/gateway-core/tests/engine/execution/mod.rs +++ b/backend/crates/gateway-core/tests/engine/execution/mod.rs @@ -2282,7 +2282,6 @@ impl Provider for ObservationProvider { gateway_core::event::ProviderResponseObservation::new( UpstreamTransport::new("http_sse").unwrap(), ) - .with_upstream_response_model_if_valid("gpt-reported") .with_service_tier_if_valid("priority"), ); Ok(ProviderStream::new( @@ -2615,7 +2614,6 @@ fn final_observation_is_emitted_once_for_success_rejection_and_detached_cancella .map(UpstreamModelId::as_str), Some("gpt-start") ); - assert_eq!(observations[0].response_model(), Some("gpt-reported")); assert_eq!(observations[0].service_tier(), Some("priority")); assert_eq!( observations[0].outcome(), diff --git a/backend/crates/gateway-plugin/runtime/src/adapter/observer.rs b/backend/crates/gateway-plugin/runtime/src/adapter/observer.rs index 6aa31818f..8de3437fa 100644 --- a/backend/crates/gateway-plugin/runtime/src/adapter/observer.rs +++ b/backend/crates/gateway-plugin/runtime/src/adapter/observer.rs @@ -455,7 +455,6 @@ fn wire_observation(observation: &RequestObservation) -> RequestCompleted { upstream_model: observation .upstream_model() .map(|model| model.as_str().to_owned()), - response_model: observation.response_model().map(str::to_owned), service_tier: observation.service_tier().map(str::to_owned), requested_model: observation .requested_model() diff --git a/backend/crates/gateway-plugin/runtime/src/adapter/upstream_adapter/observation.rs b/backend/crates/gateway-plugin/runtime/src/adapter/upstream_adapter/observation.rs index 9549a1096..1676b1859 100644 --- a/backend/crates/gateway-plugin/runtime/src/adapter/upstream_adapter/observation.rs +++ b/backend/crates/gateway-plugin/runtime/src/adapter/upstream_adapter/observation.rs @@ -47,12 +47,6 @@ pub(super) fn merge( .try_with_service_tier(value) .map_err(|source| invalid(sent).with_source(source))?; } - if let Some(value) = update.upstream_response_model { - current = current.with_upstream_response_model_if_valid(&value); - if current.upstream_response_model() != Some(value.trim()) { - return Err(invalid(sent)); - } - } if !update.client_headers.is_empty() { current = current.with_client_headers( update diff --git a/backend/crates/gateway-plugin/runtime/src/callback/facts.rs b/backend/crates/gateway-plugin/runtime/src/callback/facts.rs index d10929a9a..856a88b64 100644 --- a/backend/crates/gateway-plugin/runtime/src/callback/facts.rs +++ b/backend/crates/gateway-plugin/runtime/src/callback/facts.rs @@ -119,7 +119,6 @@ fn observation(value: &ProviderResponseObservation) -> facts::ResponseObservatio status_code: value.status_code(), request_id: value.request_id().map(|value| value.as_str().to_owned()), service_tier: value.service_tier().map(str::to_owned), - upstream_response_model: value.upstream_response_model().map(str::to_owned), timings: facts::ResponseTimings { transport_decision_wait_ms: timings.transport_decision_wait_ms, connect_ms: timings.connect_ms, diff --git a/backend/crates/gateway-plugin/runtime/tests/generation/policy/facts/mod.rs b/backend/crates/gateway-plugin/runtime/tests/generation/policy/facts/mod.rs index 5c9366196..e09db0542 100644 --- a/backend/crates/gateway-plugin/runtime/tests/generation/policy/facts/mod.rs +++ b/backend/crates/gateway-plugin/runtime/tests/generation/policy/facts/mod.rs @@ -189,7 +189,6 @@ async fn facts_are_readable_without_replacing_or_duplicating_the_original_execut .with_status_code(200) .with_request_id(OpaqueUpstreamValue::new("response-request-id")) .with_service_tier_if_valid("priority") - .with_upstream_response_model_if_valid("returned-model") .with_client_headers(vec![ProviderResponseHeader::new( "set-cookie", Bytes::from_static(b"fixture=secret"), diff --git a/backend/crates/gateway-plugin/runtime/tests/generation/upstream_adapter.rs b/backend/crates/gateway-plugin/runtime/tests/generation/upstream_adapter.rs index e4ac2e4b5..36553cbb7 100644 --- a/backend/crates/gateway-plugin/runtime/tests/generation/upstream_adapter.rs +++ b/backend/crates/gateway-plugin/runtime/tests/generation/upstream_adapter.rs @@ -667,7 +667,7 @@ async fn adapter_preserves_observation_updates_and_measures_only_real_output() { {"event":{"facts":[{"type":"started","id":"response-one","model":"native-model"}, {"type":"content_added","index":0,"kind":"reasoning"}]}, "observation":{"transport":"http_sse","http_version":"HTTP/2","status_code":200, - "request_id":"upstream-494","upstream_response_model":"real-model", + "request_id":"upstream-494", "client_headers":[{"name":"x-bytes","value":[255]},{"name":"x-bytes","value":[254]}], "provider_metadata":"{ \"future\": 1e3 }","timings":{"connect_ms":5,"headers_ms":8}}}, {"event":{"facts":[{"type":"reasoning_delta","index":0,"text":""}]}}, @@ -711,7 +711,6 @@ async fn adapter_preserves_observation_updates_and_measures_only_real_output() { assert_eq!(last.status_code(), Some(200)); assert_eq!(last.http_version().unwrap().as_str(), "HTTP/2"); assert_eq!(last.request_id().unwrap().as_str(), "upstream-494"); - assert_eq!(last.upstream_response_model(), Some("real-model")); assert_eq!(last.service_tier(), Some("priority")); assert_eq!( last.provider_metadata().unwrap().as_json(), @@ -1359,7 +1358,7 @@ async fn cancelling_adapter_body_closes_pending_websocket_and_releases_selected_ } #[tokio::test] -async fn usage_without_response_model_still_uses_native_account_pricing() { +async fn upstream_adapter_usage_uses_native_account_pricing() { let server = MockServer::start().await; Mock::given(method("POST")) .respond_with(ResponseTemplate::new(200).set_body_string("{}")) diff --git a/backend/crates/gateway-plugin/sdk/src/call/model/facts.rs b/backend/crates/gateway-plugin/sdk/src/call/model/facts.rs index 63e327672..8d238775d 100644 --- a/backend/crates/gateway-plugin/sdk/src/call/model/facts.rs +++ b/backend/crates/gateway-plugin/sdk/src/call/model/facts.rs @@ -72,7 +72,6 @@ pub struct ResponseObservation { pub status_code: Option, pub request_id: Option, pub service_tier: Option, - pub upstream_response_model: Option, #[serde(default)] pub timings: ResponseTimings, #[serde(default)] @@ -119,7 +118,6 @@ impl ResponseObservation { status_code: None, request_id: None, service_tier: None, - upstream_response_model: None, timings: ResponseTimings::default(), client_headers: Vec::new(), provider_metadata: None, diff --git a/backend/crates/gateway-plugin/sdk/src/call/observation.rs b/backend/crates/gateway-plugin/sdk/src/call/observation.rs index 658da10de..194d43b9a 100644 --- a/backend/crates/gateway-plugin/sdk/src/call/observation.rs +++ b/backend/crates/gateway-plugin/sdk/src/call/observation.rs @@ -187,9 +187,6 @@ pub struct RequestCompleted { /// 本次实际选中账号使用的上游模型,未进入上游时可能不存在 #[serde(default, skip_serializing_if = "Option::is_none")] pub upstream_model: Option, - /// 上游响应报告的模型,与请求中的模型名称分开保存 - #[serde(default, skip_serializing_if = "Option::is_none")] - pub response_model: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub service_tier: Option, #[serde(default, skip_serializing_if = "Option::is_none")] diff --git a/backend/crates/gateway-plugin/sdk/tests/call/observation.rs b/backend/crates/gateway-plugin/sdk/tests/call/observation.rs index ce16cdfd4..0e4fdc430 100644 --- a/backend/crates/gateway-plugin/sdk/tests/call/observation.rs +++ b/backend/crates/gateway-plugin/sdk/tests/call/observation.rs @@ -64,7 +64,6 @@ fn observation() -> RequestCompleted { client_key_id: Some("key_fixture".into()), account_id: Some("acct_fixture".into()), upstream_model: Some("upstream-model".into()), - response_model: Some("reported-model".into()), service_tier: Some("default".into()), provider: None, completed_at_ms: 1234, @@ -114,19 +113,12 @@ fn observation_exposes_selected_and_reported_facts_without_inventing_values() { assert_eq!(wire["client_key_id"], "key_fixture"); assert_eq!(wire["account_id"], "acct_fixture"); assert_eq!(wire["upstream_model"], "upstream-model"); - assert_eq!(wire["response_model"], "reported-model"); assert_eq!(wire["service_tier"], "default"); request.account_id = None; request.upstream_model = None; - request.response_model = None; request.service_tier = None; let wire = serde_json::to_value(request).unwrap(); - for field in [ - "account_id", - "upstream_model", - "response_model", - "service_tier", - ] { + for field in ["account_id", "upstream_model", "service_tier"] { assert!(wire.get(field).is_none(), "unknown {field} must be omitted"); } } diff --git a/backend/crates/gateway-protocol/src/openai/events.rs b/backend/crates/gateway-protocol/src/openai/events.rs index 18057f62d..845dc3363 100644 --- a/backend/crates/gateway-protocol/src/openai/events.rs +++ b/backend/crates/gateway-protocol/src/openai/events.rs @@ -1,4 +1,4 @@ -//! 从 OpenAI 事件与响应头提取模型、用量、额度和重试等待事实 +//! 从 OpenAI 事件与响应头提取用量、额度和重试等待事实 use std::{collections::BTreeMap, time::Duration}; @@ -7,56 +7,6 @@ use serde_json::Value; use super::sse::{SseError, parse_sse_events}; -/// 单次 Responses 流明确声明的模型;终态优先,缺失时不使用请求模型补齐 -#[derive(Debug, Default)] -pub struct ResponseModelObservation { - model: Option, - terminal: bool, -} - -impl ResponseModelObservation { - /// 从原始事件读取响应模型,避免工具转换或 canonical 兜底改写观测事实 - pub fn observe(&mut self, event_type: Option<&str>, value: &Value) { - let terminal = matches!( - event_type, - Some( - "response.completed" - | "response.done" - | "response.incomplete" - | "response.failed" - | "response.cancelled" - | "response.canceled" - ) - ); - if self.terminal || (self.model.is_some() && !terminal) { - return; - } - let Some(model) = value - .pointer("/response/model") - .and_then(Value::as_str) - .and_then(observed_model_name) - else { - return; - }; - self.model = Some(model.to_owned()); - self.terminal = terminal; - } - - /// 返回原始响应声明,未声明时保持未知 - #[must_use] - pub fn model(&self) -> Option<&str> { - self.model.as_deref() - } -} - -/// 规范化可展示的上游模型名,非法值不影响响应交付 -#[must_use] -pub fn observed_model_name(value: &str) -> Option<&str> { - let value = value.trim(); - (!value.is_empty() && value.len() <= 256 && !value.chars().any(char::is_control)) - .then_some(value) -} - /// 从 Codex/OpenAI usage 结构中提取出的标准化 token 用量 #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] diff --git a/backend/crates/gateway-protocol/tests/openai/events.rs b/backend/crates/gateway-protocol/tests/openai/events.rs index 31b94a422..ec40e6493 100644 --- a/backend/crates/gateway-protocol/tests/openai/events.rs +++ b/backend/crates/gateway-protocol/tests/openai/events.rs @@ -7,50 +7,6 @@ use gateway_protocol::openai::events::{ }; use serde_json::json; -#[test] -fn response_model_observation_keeps_first_declaration_until_terminal() { - use gateway_protocol::openai::events::ResponseModelObservation; - - let mut observed = ResponseModelObservation::default(); - observed.observe( - Some("response.created"), - &json!({"response": {"model": " first "}}), - ); - observed.observe( - Some("response.in_progress"), - &json!({"response": {"model": "intermediate"}}), - ); - assert_eq!(observed.model(), Some("first")); - observed.observe( - Some("response.completed"), - &json!({"response": {"model": "final"}}), - ); - observed.observe( - Some("response.in_progress"), - &json!({"response": {"model": "late"}}), - ); - assert_eq!(observed.model(), Some("final")); - assert_eq!(ResponseModelObservation::default().model(), None); -} - -#[test] -fn response_model_observation_ignores_missing_and_invalid_values() { - use gateway_protocol::openai::events::ResponseModelObservation; - - for response in [ - json!({}), - json!({"model": null}), - json!({"model": 42}), - json!({"model": " "}), - json!({"model": "a\nb"}), - json!({"model": "a".repeat(257)}), - ] { - let mut observed = ResponseModelObservation::default(); - observed.observe(Some("response.completed"), &json!({"response": response})); - assert_eq!(observed.model(), None); - } -} - #[test] fn billable_usage_should_validate_cache_writes_totals_and_overflow() { for (raw, expected) in [ diff --git a/backend/crates/gateway-store/src/postgres/execution/mod.rs b/backend/crates/gateway-store/src/postgres/execution/mod.rs index 4f13734ad..e44b4f7e0 100644 --- a/backend/crates/gateway-store/src/postgres/execution/mod.rs +++ b/backend/crates/gateway-store/src/postgres/execution/mod.rs @@ -248,7 +248,6 @@ pub struct ModelRequestFinalization { pub http_version: Option, pub websocket_pool: Option, pub service_tier: Option, - pub upstream_response_model: Option, pub provider_metadata_json: Option, pub diagnostic_trace_json: Option, pub error_kind: Option, @@ -314,12 +313,6 @@ impl ModelRequestFinalization { return Err(invalid("service tier is invalid")); } } - if let Some(model) = self.upstream_response_model.as_deref() { - require_nonempty(ENTITY, "upstream_response_model", model)?; - if model.len() > 256 || model.chars().any(char::is_control) { - return Err(invalid("upstream response model is invalid")); - } - } if self .provider_metadata_json .as_ref() @@ -1180,7 +1173,6 @@ impl ExecutionStore for PgExecutionStore { http_version: finalization.http_version, websocket_pool: finalization.websocket_pool, service_tier: finalization.service_tier, - upstream_response_model: finalization.upstream_response_model, provider_metadata_json, diagnostic_trace_json: finalization .diagnostic_trace_json diff --git a/backend/crates/gateway-store/src/postgres/execution/request_observation.rs b/backend/crates/gateway-store/src/postgres/execution/request_observation.rs index 676d42d7d..2eaa6ac16 100644 --- a/backend/crates/gateway-store/src/postgres/execution/request_observation.rs +++ b/backend/crates/gateway-store/src/postgres/execution/request_observation.rs @@ -21,8 +21,6 @@ pub(super) struct RequestObservation { #[serde(skip_serializing_if = "Option::is_none")] transport: Option, #[serde(skip_serializing_if = "Option::is_none")] - response: Option, - #[serde(skip_serializing_if = "Option::is_none")] continuation: Option, } @@ -146,13 +144,6 @@ struct ConnectionObservation { idle_ms: Option, } -#[derive(Default, Serialize)] -#[serde(rename_all = "camelCase")] -struct ResponseObservation { - #[serde(skip_serializing_if = "Option::is_none")] - response_model: Option, -} - #[derive(Default, Serialize)] #[serde(rename_all = "camelCase")] struct ContinuationObservation { @@ -263,9 +254,6 @@ impl RequestObservation { idle_ms: value.upstream_connection_idle_ms, }), }), - response: Some(ResponseObservation { - response_model: value.upstream_response_model.clone(), - }), error: Some(ErrorObservation { provider_error_code: value.provider_error_code.clone(), message: value.error_message.clone(), diff --git a/backend/crates/gateway-store/src/postgres/execution_buffer.rs b/backend/crates/gateway-store/src/postgres/execution_buffer.rs index 3d2ff596e..0242c4713 100644 --- a/backend/crates/gateway-store/src/postgres/execution_buffer.rs +++ b/backend/crates/gateway-store/src/postgres/execution_buffer.rs @@ -849,7 +849,6 @@ impl ExecutionObservationWrite { finalization.http_version.as_deref(), finalization.websocket_pool.as_deref(), finalization.service_tier.as_deref(), - finalization.upstream_response_model.as_deref(), finalization.provider_metadata_json.as_deref(), finalization.diagnostic_trace_json.as_deref(), finalization.provider_error_code.as_deref(), diff --git a/backend/crates/gateway-store/src/postgres/observability/mapping.rs b/backend/crates/gateway-store/src/postgres/observability/mapping.rs index adc96d1f2..8a4c3492e 100644 --- a/backend/crates/gateway-store/src/postgres/observability/mapping.rs +++ b/backend/crates/gateway-store/src/postgres/observability/mapping.rs @@ -329,7 +329,6 @@ pub(crate) fn usage_list_record_from_row( provider_account_authentication_kind: get(row, "provider_account_authentication_kind")?, upstream_model_id: get(row, "upstream_model_id")?, upstream_transport: get(row, "upstream_transport")?, - upstream_response_model: get(row, "upstream_response_model")?, service_tier: get(row, "service_tier")?, input_tokens: optional_unsigned(row, "input_tokens")?, output_tokens: optional_unsigned(row, "output_tokens")?, @@ -404,7 +403,6 @@ pub(crate) fn usage_record_from_row(row: &sqlx::postgres::PgRow) -> StoreResult< upstream_transport: get(row, "upstream_transport")?, http_version: get(row, "http_version")?, websocket_pool: get(row, "websocket_pool")?, - upstream_response_model: get(row, "upstream_response_model")?, service_tier: get(row, "service_tier")?, provider_metadata_json: get::>(row, "provider_observation_json")? .map(|value| serde_json::to_string(&value)) diff --git a/backend/crates/gateway-store/src/postgres/observability/queries/usage.rs b/backend/crates/gateway-store/src/postgres/observability/queries/usage.rs index 4ef32e36e..47c96ea5c 100644 --- a/backend/crates/gateway-store/src/postgres/observability/queries/usage.rs +++ b/backend/crates/gateway-store/src/postgres/observability/queries/usage.rs @@ -146,7 +146,7 @@ pub(crate) const USAGE_LIST_RECORD_SELECT: &str = mr.provider_account_plan_type_snapshot as provider_account_plan_type, mr.provider_account_authentication_kind_snapshot as provider_account_authentication_kind, - mr.upstream_model_id, mr.upstream_transport, mr.upstream_response_model, mr.service_tier, + mr.upstream_model_id, mr.upstream_transport, mr.service_tier, mr.input_tokens, mr.output_tokens, mr.cached_input_tokens, mr.cache_write_input_tokens, mr.reasoning_output_tokens, mr.image_input_tokens, mr.image_output_tokens, mr.total_tokens, mr.billing_snapshot_json, mr.cost_source, mr.cost_amount::text, mr.cost_currency, @@ -171,7 +171,7 @@ pub(crate) const USAGE_RECORD_DETAIL_SELECT: &str = mr.provider_account_authentication_kind_snapshot as provider_account_authentication_kind, mr.upstream_model_id, mr.upstream_transport, mr.http_version, mr.websocket_pool, - mr.service_tier, mr.upstream_response_model, mr.provider_observation_json, mr.diagnostic_trace_json, + mr.service_tier, mr.provider_observation_json, mr.diagnostic_trace_json, mr.attempt_count, mr.upstream_send_state, mr.downstream_committed_at, mr.outcome, mr.client_status_code, mr.upstream_status_code, mr.client_response_id, mr.upstream_request_id, mr.upstream_response_id, diff --git a/backend/crates/gateway-store/tests/postgres/execution/mod.rs b/backend/crates/gateway-store/tests/postgres/execution/mod.rs index 2f67c66a2..548ed311e 100644 --- a/backend/crates/gateway-store/tests/postgres/execution/mod.rs +++ b/backend/crates/gateway-store/tests/postgres/execution/mod.rs @@ -407,7 +407,6 @@ async fn core_adapter_should_persist_calculated_cost_exactly() { finalization.downstream_committed_at = Some(std::time::SystemTime::now()); finalization.websocket_pool = Some("reuse".to_owned()); finalization.service_tier = Some("priority".to_owned()); - finalization.upstream_response_model = Some("grok-4.6-build".to_owned()); finalization.cost = CalculatedCost::from_usd_ticks(12_345) .expect("calculated cost") .into_estimate(); @@ -452,10 +451,6 @@ async fn core_adapter_should_persist_calculated_cost_exactly() { detail.request.upstream_model_id.as_deref(), Some("grok-4.5") ); - assert_eq!( - detail.request.upstream_response_model.as_deref(), - Some("grok-4.6-build") - ); let page = observation .list_usage_records(UsageRecordQuery { status: Default::default(), @@ -471,10 +466,6 @@ async fn core_adapter_should_persist_calculated_cost_exactly() { .await .expect("request list"); assert_eq!(page.items.len(), 1); - assert_eq!( - page.items[0].upstream_response_model.as_deref(), - Some("grok-4.6-build") - ); database.close().await; } @@ -668,7 +659,6 @@ fn successful_core_finalization(id: &str) -> CoreModelRequestFinalization { upstream_transport: Some("websocket".to_owned()), http_version: Some("HTTP/2".to_owned()), websocket_pool: None, - upstream_response_model: None, service_tier: None, provider_metadata_json: None, error: None, @@ -1589,7 +1579,6 @@ pub(super) fn early_failure(request: &CoreNewModelRequest) -> CoreModelRequestFi upstream_transport: None, http_version: None, websocket_pool: None, - upstream_response_model: None, service_tier: None, provider_metadata_json: None, diagnostic_trace_json: trace.snapshot().map(|value| value.to_string()), diff --git a/backend/crates/providers/openai/src/provider/execution.rs b/backend/crates/providers/openai/src/provider/execution.rs index 07e21dc3b..055374672 100644 --- a/backend/crates/providers/openai/src/provider/execution.rs +++ b/backend/crates/providers/openai/src/provider/execution.rs @@ -966,7 +966,6 @@ pub(super) fn cold_response_stream(response: ColdResponse) -> EventStream { let mut decoder = CodexCanonicalDecoder::new(upstream_model.as_str()) .with_trace(trace.clone()) .with_pricing(context.pricing().get("openai").and_then(|models| models.get(upstream_model.as_str())).cloned()) - .with_reported_model(response.response_metadata.effective_model.as_deref()) .with_requested_service_tier(request.service_tier()) .with_request_tool_pricing(upstream_model.as_str(), request.tools()) .with_raw_sse_passthrough(); @@ -1025,7 +1024,6 @@ pub(super) fn cold_response_stream(response: ColdResponse) -> EventStream { response_metadata_updates.as_ref(), &mut session_capture, &mut observation_state, - &mut decoder, ) .await; let observation_event = if rate_limits_changed || metadata_merge.is_some() @@ -1103,12 +1101,9 @@ pub(super) fn cold_response_stream(response: ColdResponse) -> EventStream { response_metadata_updates.as_ref(), &mut session_capture, &mut observation_state, - &mut decoder, ) .await; let metadata_changed = metadata_merge.unwrap_or(false); - let response_model_changed = observation_state - .observe_upstream_response_model(decoder.response_model()); let service_tier_changed = observation_state .observe_upstream_service_tier(decoder.response_service_tier()); let terminal_failure = canonical_failure.map(|(error, semantic_output_seen)| { @@ -1196,7 +1191,6 @@ pub(super) fn cold_response_stream(response: ColdResponse) -> EventStream { })); } if (rate_limits_changed - || response_model_changed || service_tier_changed || timing_changed || metadata_changed @@ -1266,8 +1260,6 @@ pub(super) fn cold_response_stream(response: ColdResponse) -> EventStream { ) }); let timing_signals = decoder.take_timing_signals(); - let response_model_changed = observation_state - .observe_upstream_response_model(decoder.response_model()); let service_tier_changed = observation_state .observe_upstream_service_tier(decoder.response_service_tier()); let output_timing_changed = observation_state @@ -1302,7 +1294,6 @@ pub(super) fn cold_response_stream(response: ColdResponse) -> EventStream { response_metadata_updates.as_ref(), &mut session_capture, &mut observation_state, - &mut decoder, ) .await .unwrap_or(false); @@ -1328,8 +1319,7 @@ pub(super) fn cold_response_stream(response: ColdResponse) -> EventStream { .observe_cyber_policy_success(cyber_policy_scope.as_ref()) .await; } - if (response_model_changed - || service_tier_changed + if (service_tier_changed || timing_changed || rate_limits_changed || metadata_changed @@ -1371,26 +1361,10 @@ async fn merge_response_metadata_updates( updates: Option<&CodexResponseMetadataUpdates>, session_capture: &mut Option, observation_state: &mut OpenAiResponseObservationState, - decoder: &mut CodexCanonicalDecoder, ) -> Option { - let updates = updates?; - let mut pending = updates.lock().await; - let turn_state = pending.turn_state.take(); - let reported_model = pending.reported_model.clone(); - drop(pending); - if turn_state.is_none() && reported_model.is_none() { - return None; + let turn_state = updates?.lock().await.turn_state.take()?; + if let Some(capture) = session_capture.as_mut() { + capture.turn_state = Some(turn_state.clone()); } - let mut changed = false; - if let Some(turn_state) = turn_state { - if let Some(capture) = session_capture.as_mut() { - capture.turn_state = Some(turn_state.clone()); - } - changed |= observation_state.merge_client_header("x-codex-turn-state", &turn_state); - } - if let Some(model) = reported_model { - decoder.observe_reported_model(&model); - changed |= observation_state.observe_upstream_response_model(decoder.response_model()); - } - Some(changed) + Some(observation_state.merge_client_header("x-codex-turn-state", &turn_state)) } diff --git a/backend/crates/providers/openai/src/provider/observation.rs b/backend/crates/providers/openai/src/provider/observation.rs index b00e785d9..8b2aeee79 100644 --- a/backend/crates/providers/openai/src/provider/observation.rs +++ b/backend/crates/providers/openai/src/provider/observation.rs @@ -33,7 +33,6 @@ pub(super) struct OpenAiResponseObservationState { stream: bool, requested_service_tier: Option, upstream_service_tier: Option, - upstream_response_model: Option, rate_limit_headers: Vec<(String, String)>, timings: ProviderResponseTimings, terminal: Option, @@ -80,7 +79,6 @@ impl OpenAiResponseObservationState { stream: request.stream(), requested_service_tier: normalize_service_tier(request.service_tier()), upstream_service_tier: None, - upstream_response_model: response.response_metadata.effective_model.clone(), rate_limit_headers: selected_observation_headers(&response.rate_limit_headers), timings: openai_response_timings( &response.transport_metrics, @@ -123,23 +121,9 @@ impl OpenAiResponseObservationState { if let Some(service_tier) = &self.requested_service_tier { observation = observation.with_service_tier_if_valid(service_tier.clone()); } - if let Some(model) = &self.upstream_response_model { - observation = observation.with_upstream_response_model_if_valid(model); - } Some(observation) } - pub(super) fn observe_upstream_response_model(&mut self, model: Option<&str>) -> bool { - let Some(model) = model else { - return false; - }; - if self.upstream_response_model.as_deref() == Some(model) { - return false; - } - self.upstream_response_model = Some(model.to_owned()); - true - } - pub(super) fn observe_stream_chunk(&mut self, chunk: &[u8], started_at: Instant) -> bool { if chunk.is_empty() { return false; @@ -263,17 +247,6 @@ impl OpenAiResponseObservationState { { metadata.insert("websocketOpeningRequestId".to_owned(), json!(request_id)); } - if let Some(model) = self - .response_metadata - .effective_model - .as_deref() - .filter(|model| !model.is_empty()) - { - metadata.insert( - "upstreamReportedModel".to_owned(), - Value::String(model.to_owned()), - ); - } metadata.insert( "modelsEtag".to_owned(), self.response_metadata diff --git a/backend/crates/providers/openai/src/transport/canonical.rs b/backend/crates/providers/openai/src/transport/canonical.rs index 533ba58ce..f1b209a42 100644 --- a/backend/crates/providers/openai/src/transport/canonical.rs +++ b/backend/crates/providers/openai/src/transport/canonical.rs @@ -14,10 +14,7 @@ use gateway_core::event::{ }; use gateway_core::metering::Usage; use gateway_core::upstream::UpstreamSendState; -use gateway_protocol::openai::events::{ - ResponseModelObservation, TokenUsage, billable_usage_is_complete, extract_usage, - observed_model_name, -}; +use gateway_protocol::openai::events::{TokenUsage, billable_usage_is_complete, extract_usage}; use gateway_protocol::openai::sse::{SseEvent, SseEventDecoder, SseFrame, sse_frame_is_done}; use serde_json::Value; use thiserror::Error; @@ -59,8 +56,6 @@ pub struct CodexCanonicalDecoder { response_service_tier: Option, upstream_response_ms: Option, upstream_timing_metrics: ResponseTimingMetrics, - response_model: ResponseModelObservation, - reported_model: Option, web_search_pricing: Option, timing_signals: ResponseEventSignals, raw_sse_passthrough: bool, @@ -171,8 +166,6 @@ impl CodexCanonicalDecoder { response_service_tier: None, upstream_response_ms: None, upstream_timing_metrics: ResponseTimingMetrics::default(), - response_model: ResponseModelObservation::default(), - reported_model: None, web_search_pricing: None, pricing: None, timing_signals: ResponseEventSignals::default(), @@ -288,28 +281,6 @@ impl CodexCanonicalDecoder { self.upstream_timing_metrics } - /// 真实 HTTP 响应头提供初始报告;流内请求级报告可覆盖它 - #[must_use] - pub fn with_reported_model(mut self, model: Option<&str>) -> Self { - self.reported_model = model.and_then(observed_model_name).map(str::to_owned); - self - } - - /// 接收 transport 已解析的内部 metadata 报告,内部帧无需交付客户端 - pub(crate) fn observe_reported_model(&mut self, model: &str) { - if let Some(model) = observed_model_name(model) { - self.reported_model = Some(model.to_owned()); - } - } - - /// 官方服务端报告优先;缺少报告时仅使用正文明确声明,不使用请求兜底值 - #[must_use] - pub fn response_model(&self) -> Option<&str> { - self.reported_model - .as_deref() - .or_else(|| self.response_model.model()) - } - fn decode(&mut self, events: Vec) -> CodexCanonicalOutcome { let mut output = Vec::new(); for event in events { @@ -419,10 +390,6 @@ impl CodexCanonicalDecoder { } } self.observe_response_service_tier(&value); - self.response_model.observe(event_type, &value); - if let Some(model) = super::response_meta::reported_model_from_event(&value) { - self.reported_model = Some(model.to_owned()); - } let signals = response_event_signals(event_type, &value); self.merge_timing_signals(signals); if matches!(event_type, Some("response.failed" | "error")) { diff --git a/backend/crates/providers/openai/src/transport/response_meta.rs b/backend/crates/providers/openai/src/transport/response_meta.rs index 4f64a01da..b260deb45 100644 --- a/backend/crates/providers/openai/src/transport/response_meta.rs +++ b/backend/crates/providers/openai/src/transport/response_meta.rs @@ -11,8 +11,6 @@ use super::diagnostics::CodexUpstreamDiagnostics; /// Codex Responses 上游响应元数据 #[derive(Clone, Default, PartialEq, Eq)] pub struct CodexResponseMetadata { - /// 上游实际选用的模型 - pub effective_model: Option, /// 模型目录版本 pub models_etag: Option, /// 上游是否声明响应包含 reasoning @@ -25,7 +23,6 @@ impl fmt::Debug for CodexResponseMetadata { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter .debug_struct("CodexResponseMetadata") - .field("effective_model", &self.effective_model) .field("models_etag", &self.models_etag) .field("reasoning_included", &self.reasoning_included) .field("client_header_count", &self.client_headers.len()) @@ -119,37 +116,12 @@ fn observe_typed_response_header(metadata: &mut CodexResponseMetadata, name: &st return; } match name.trim().to_ascii_lowercase().as_str() { - "openai-model" | "x-openai-model" => { - metadata.effective_model = - gateway_protocol::openai::events::observed_model_name(value).map(str::to_owned) - } "x-models-etag" => metadata.models_etag = Some(value.to_string()), "x-reasoning-included" => metadata.reasoning_included = true, _ => {} } } -/// 与官方 Codex 一致,优先读取 response.headers,再读取 WS metadata 的顶层 headers -pub(super) fn reported_model_from_event(value: &serde_json::Value) -> Option<&str> { - [value.pointer("/response/headers"), value.get("headers")] - .into_iter() - .flatten() - .filter_map(serde_json::Value::as_object) - .find_map(|headers| { - headers.iter().find_map(|(name, value)| { - if !name.eq_ignore_ascii_case("openai-model") - && !name.eq_ignore_ascii_case("x-openai-model") - { - return None; - } - value - .as_str() - .or_else(|| value.as_array()?.first()?.as_str()) - .and_then(gateway_protocol::openai::events::observed_model_name) - }) - }) -} - fn filter_client_headers( headers: impl IntoIterator, ) -> Vec<(String, Bytes)> { diff --git a/backend/crates/providers/openai/src/transport/websocket/exchange/mod.rs b/backend/crates/providers/openai/src/transport/websocket/exchange/mod.rs index 278fd1f01..50ce7b8b7 100644 --- a/backend/crates/providers/openai/src/transport/websocket/exchange/mod.rs +++ b/backend/crates/providers/openai/src/transport/websocket/exchange/mod.rs @@ -59,8 +59,6 @@ pub type CodexWebSocketRateLimitUpdates = Arc>>; pub struct CodexWebSocketResponseMetadataUpdate { /// 当前响应首次声明的会话续接状态 pub turn_state: Option, - /// 当前响应最新的服务端模型报告 - pub reported_model: Option, } pub type CodexWebSocketResponseMetadataUpdates = Arc>; @@ -72,8 +70,7 @@ pub(super) fn reusable_websocket_metadata( metadata.set_cookie_headers.clear(); metadata.rate_limit_headers.clear(); metadata.turn_state = None; - // 模型报告属于上一轮请求,池中连接不能把它带到下一轮缺失报告的响应 - metadata.response_metadata.effective_model = None; + // 请求级响应头不能随池中连接带到下一轮响应 metadata .response_metadata .client_headers diff --git a/backend/crates/providers/openai/src/transport/websocket/exchange/reducer.rs b/backend/crates/providers/openai/src/transport/websocket/exchange/reducer.rs index 7735185d0..fca8d3b3f 100644 --- a/backend/crates/providers/openai/src/transport/websocket/exchange/reducer.rs +++ b/backend/crates/providers/openai/src/transport/websocket/exchange/reducer.rs @@ -62,9 +62,6 @@ pub(super) fn reduce_websocket_event( &mut metadata.response_metadata, websocket_metadata_headers(&value), ); - if let Some(model) = response_meta::reported_model_from_event(&value) { - metadata.response_metadata.effective_model = Some(model.to_owned()); - } let turn_state_update = websocket_metadata_turn_state(&value).and_then(|turn_state| { if metadata.turn_state.is_some() { return None; diff --git a/backend/crates/providers/openai/src/transport/websocket/exchange/stream.rs b/backend/crates/providers/openai/src/transport/websocket/exchange/stream.rs index 6da84fd67..6cf896ac9 100644 --- a/backend/crates/providers/openai/src/transport/websocket/exchange/stream.rs +++ b/backend/crates/providers/openai/src/transport/websocket/exchange/stream.rs @@ -75,7 +75,6 @@ pub(in crate::transport::websocket) fn stream_websocket_response( let rate_limit_updates_for_task = Arc::clone(&rate_limit_updates); let response_metadata_updates = Arc::new(Mutex::new(CodexWebSocketResponseMetadataUpdate { turn_state: metadata.turn_state.clone(), - reported_model: None, })); let response_metadata_updates_for_task = Arc::clone(&response_metadata_updates); let (tx, rx) = mpsc::channel(WEBSOCKET_STREAM_BUFFER); @@ -306,9 +305,6 @@ async fn forward_websocket_response_stream(state: WebSocketStreamForwardState) { pending.turn_state = Some(turn_state); } } - if let Some(model) = metadata.response_metadata.effective_model.as_ref() { - response_metadata_updates.lock().await.reported_model = Some(model.clone()); - } let terminal = match reduced.action { ExchangeAction::RateLimits(rate_limits) => { rate_limit_updates.lock().await.push(rate_limits); diff --git a/backend/crates/providers/openai/tests/provider/contract/mod.rs b/backend/crates/providers/openai/tests/provider/contract/mod.rs index 9882433fb..69c174fb7 100644 --- a/backend/crates/providers/openai/tests/provider/contract/mod.rs +++ b/backend/crates/providers/openai/tests/provider/contract/mod.rs @@ -618,7 +618,7 @@ async fn attempt_middleware_can_change_reasoning_before_native_encoding() { } #[tokio::test] -async fn responses_bill_sent_model_and_observe_unpriced_response_without_rewriting_it() { +async fn responses_bill_sent_model_and_preserve_returned_model() { let store = Arc::new(MemoryAccountStore::default()); create_account(&store, "acct_provider_contract").await; let server = MockServer::start().await; @@ -655,7 +655,6 @@ async fn responses_bill_sent_model_and_observe_unpriced_response_without_rewriti ) .await .unwrap(); - let mut observed = None; let mut returned = None; let mut costs = Vec::new(); while let Some(event) = stream.next().await { @@ -665,9 +664,6 @@ async fn responses_bill_sent_model_and_observe_unpriced_response_without_rewriti costs.push(cost.total().amount().scaled()); } } - if let Some(observation) = event.response_observation() { - observed = observation.upstream_response_model().map(str::to_owned); - } if let Some(wire) = event.wire_event() && wire.data().get("type").and_then(Value::as_str) == Some("response.completed") { @@ -681,12 +677,11 @@ async fn responses_bill_sent_model_and_observe_unpriced_response_without_rewriti let requests = server.received_requests().await.expect("upstream requests"); assert_eq!(captured_request_body(&requests[0])["model"], "gpt-5.4"); assert_eq!(costs, vec![3_437_500]); - assert_eq!(observed.as_deref(), Some("gpt-6-sol")); - assert_eq!(returned, observed); + assert_eq!(returned.as_deref(), Some("gpt-6-sol")); } #[tokio::test] -async fn websocket_bills_sent_model_and_observes_internal_model_report() { +async fn websocket_bills_sent_model_and_preserves_response_metadata() { for metadata_type in ["response.metadata", "codex.response.metadata"] { let store = Arc::new(MemoryAccountStore::default()); create_account(&store, "acct_provider_contract").await; @@ -729,7 +724,6 @@ async fn websocket_bills_sent_model_and_observes_internal_model_report() { ) .await .expect("provider stream"); - let mut observed = None; let mut returned = None; let mut costs = Vec::new(); while let Some(event) = stream.next().await { @@ -739,9 +733,6 @@ async fn websocket_bills_sent_model_and_observes_internal_model_report() { costs.push(cost.total().amount().scaled()); } } - if let Some(observation) = event.response_observation() { - observed = observation.upstream_response_model().map(str::to_owned); - } if let Some(wire) = event.wire_event() && wire.data().get("type").and_then(Value::as_str) == Some("response.completed") { @@ -754,11 +745,6 @@ async fn websocket_bills_sent_model_and_observes_internal_model_report() { } server.await.expect("server task"); assert_eq!(costs, vec![3_437_500]); - assert_eq!( - observed.as_deref(), - Some("gpt-server-report"), - "{metadata_type}" - ); assert_eq!(returned.as_deref(), Some("gpt-6-astra")); } } @@ -4116,10 +4102,6 @@ async fn websocket_fast_path_miss_uses_http_and_keeps_background_preconnect() { ); } assert_eq!(metadata["upstreamStatus"], 200); - assert_eq!( - metadata["upstreamReportedModel"], - "gpt-reported-by-upstream" - ); assert!(metadata["requestSummary"].is_object()); assert!(metadata["upstreamTraceHeaders"].is_array()); first_decision = metadata diff --git a/backend/crates/providers/openai/tests/transport/canonical.rs b/backend/crates/providers/openai/tests/transport/canonical.rs index 169faf726..3df42dbbc 100644 --- a/backend/crates/providers/openai/tests/transport/canonical.rs +++ b/backend/crates/providers/openai/tests/transport/canonical.rs @@ -83,56 +83,6 @@ fn diagnostic_parse_failure_keeps_raw_passthrough_without_persisting_plaintext() assert!(!snapshot.to_string().contains("private-invalid-json")); } -#[test] -fn response_model_observation_prefers_reported_headers_without_changing_wire() { - for websocket in [false, true] { - let mut decoder = CodexCanonicalDecoder::new("requested") - .with_reported_model(Some("opening-model")) - .with_raw_sse_passthrough(); - for (value, expected) in [ - ( - json!({"type":"response.created","response":{"id":"resp_model","model":"body-model"}}), - "opening-model", - ), - ( - json!({"type":"codex.response.metadata","headers":{"X-OpenAI-Model":["metadata-model"]}}), - "metadata-model", - ), - ( - json!({"type":"response.completed","headers":{"openai-model":"top-level"},"response":{"id":"resp_model","model":"final-body-model","headers":{"OpenAI-Model":"terminal-model"}}}), - "terminal-model", - ), - ] { - let raw = value.to_string(); - let frame = if websocket { - websocket_event_to_sse_frame(&raw).expect("WS event") - } else { - format!("data: {raw}\n\n") - }; - let events = decoder - .push(frame.as_bytes()) - .expect("decode model observation"); - assert_eq!(decoder.response_model(), Some(expected)); - assert!( - events - .iter() - .any(|event| event.wire_event().is_some_and(|wire| wire.data() == &value)) - ); - } - } -} - -#[test] -fn response_model_observation_uses_explicit_body_and_never_request_fallback() { - let mut decoder = CodexCanonicalDecoder::new("requested"); - decoder - .push(b"data: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_model\"}}\n\n") - .expect("response model event"); - assert_eq!(decoder.response_model(), None); - decoder.push(b"data: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_model\",\"model\":\"returned\"}}\n\n").expect("response model event"); - assert_eq!(decoder.response_model(), Some("returned")); -} - const METADATA_PREFIX_FIXTURE: &str = include_str!("fixtures/metadata_only_prefix.sse"); #[test] @@ -475,7 +425,6 @@ fn decoder_should_bill_the_sent_model_independently_of_the_response_model() { _ => None, }); assert_eq!(cost, expected_cost, "{sent} -> {returned:?}"); - assert_eq!(decoder.response_model(), returned); assert!(canonical_facts(&events).into_iter().any(|event| matches!( event, GatewayEvent::Completed(meta) if meta.model() == Some(returned.unwrap_or(sent)) diff --git a/backend/crates/providers/openai/tests/transport/http_client.rs b/backend/crates/providers/openai/tests/transport/http_client.rs index f1b64917d..fa8cc4f0b 100644 --- a/backend/crates/providers/openai/tests/transport/http_client.rs +++ b/backend/crates/providers/openai/tests/transport/http_client.rs @@ -431,10 +431,6 @@ async fn codex_backend_client_should_capture_forwardable_response_metadata() { .await .expect("response should succeed"); - assert_eq!( - response.response_metadata.effective_model.as_deref(), - Some("gpt-5.5-2026-07-01") - ); assert_eq!( response.response_metadata.models_etag.as_deref(), Some("models-v2") diff --git a/backend/crates/providers/openai/tests/transport/mod.rs b/backend/crates/providers/openai/tests/transport/mod.rs index 4f7209b1b..5594fe1c9 100644 --- a/backend/crates/providers/openai/tests/transport/mod.rs +++ b/backend/crates/providers/openai/tests/transport/mod.rs @@ -180,7 +180,6 @@ struct CollectedBackendResponse { rate_limit_headers: Vec<(String, String)>, websocket_pool_decision: Option, response_metadata: CodexResponseMetadata, - reported_model: Option, transport_metrics: CodexTransportMetrics, connection_local_continuation: bool, } @@ -286,11 +285,9 @@ async fn collect_backend_response( rate_limit_headers.extend(rate_limits_to_header_pairs(update)); } } - let mut reported_model = response_metadata.effective_model.clone(); if let Some(update) = response_metadata_updates { let update = update.lock().await; turn_state = update.turn_state.clone().or(turn_state); - reported_model = update.reported_model.clone().or(reported_model); } let body = String::from_utf8_lossy(&body_bytes).into_owned(); let usage = extract_sse_usage(&body).map_err(CodexClientError::InvalidSse)?; @@ -303,7 +300,6 @@ async fn collect_backend_response( rate_limit_headers, websocket_pool_decision, response_metadata, - reported_model, transport_metrics, connection_local_continuation, }) diff --git a/backend/crates/providers/openai/tests/transport/websocket/metadata.rs b/backend/crates/providers/openai/tests/transport/websocket/metadata.rs index 0e0b5341c..320887d05 100644 --- a/backend/crates/providers/openai/tests/transport/websocket/metadata.rs +++ b/backend/crates/providers/openai/tests/transport/websocket/metadata.rs @@ -80,7 +80,6 @@ async fn reused_websocket_should_keep_response_metadata_scoped_to_each_exchange( .expect("response within timeout") .expect("response should complete on the same socket"); assert_eq!(response.response_metadata, expected, "exchange {round}"); - assert_eq!(response.reported_model, Some(format!("model-{round}"))); assert!(response.body.contains(&format!("etag-{round}"))); assert_eq!(response.turn_state, Some(format!("turn-{round}"))); if round > 0 { diff --git a/backend/crates/providers/openai/tests/transport/websocket/mod.rs b/backend/crates/providers/openai/tests/transport/websocket/mod.rs index c77af8cb8..ae5b89fcb 100644 --- a/backend/crates/providers/openai/tests/transport/websocket/mod.rs +++ b/backend/crates/providers/openai/tests/transport/websocket/mod.rs @@ -993,10 +993,6 @@ async fn websocket_execute_response_create_request_should_capture_internal_metad server.await.unwrap(); assert_eq!(response.turn_state.as_deref(), Some("turn-from-metadata")); - assert_eq!( - response.reported_model.as_deref(), - Some("gpt-internal-report") - ); assert!(!response.body.contains("codex.rate_limits")); assert!(response.body.contains("response.metadata")); assert!(response.body.contains("event: response.completed")); @@ -1132,11 +1128,6 @@ async fn reused_websocket_should_not_leak_turn_state_into_the_next_response() { .websocket_pool_decision .is_some_and(WebSocketPoolDecision::is_reuse) ); - assert_eq!( - first.reported_model.as_deref(), - Some("first-response-model") - ); - assert_eq!(second.reported_model, None); assert_eq!(second.turn_state, None); assert!( second diff --git a/backend/crates/providers/xai/src/provider/stream.rs b/backend/crates/providers/xai/src/provider/stream.rs index 53664d298..5073ad7e3 100644 --- a/backend/crates/providers/xai/src/provider/stream.rs +++ b/backend/crates/providers/xai/src/provider/stream.rs @@ -322,10 +322,6 @@ pub(super) fn cold_compaction_http_sse_stream( } } - if let Some(model) = canonical.response_model() { - observation = observation.with_upstream_response_model_if_valid(model); - yield ProviderEvent::observation(observation.clone()); - } if let Some(tier) = canonical.response_service_tier() { observation = observation.with_service_tier_if_valid(tier.to_owned()); yield ProviderEvent::observation(observation); @@ -606,12 +602,6 @@ pub(super) fn cold_http_sse_stream( if observe_output_timings(&mut observation, &events, context.timing_started_at()) { yield ProviderEvent::observation(observation.clone()); } - if let Some(model) = decoder.response_model() - && observation.upstream_response_model() != Some(model) - { - observation = observation.with_upstream_response_model_if_valid(model); - yield ProviderEvent::observation(observation.clone()); - } if let Some(tier) = decoder.response_service_tier() && observation.service_tier() != Some(tier) { @@ -667,12 +657,6 @@ pub(super) fn cold_http_sse_stream( .iter() .flat_map(ProviderEvent::canonical_facts) .any(|event| matches!(event, GatewayEvent::Completed(_))); - if let Some(model) = decoder.response_model() - && observation.upstream_response_model() != Some(model) - { - observation = observation.with_upstream_response_model_if_valid(model); - yield ProviderEvent::observation(observation.clone()); - } if let Some(tier) = decoder.response_service_tier() && observation.service_tier() != Some(tier) { diff --git a/backend/crates/providers/xai/src/transport/canonical.rs b/backend/crates/providers/xai/src/transport/canonical.rs index 9891e5ff0..9b9cfdd82 100644 --- a/backend/crates/providers/xai/src/transport/canonical.rs +++ b/backend/crates/providers/xai/src/transport/canonical.rs @@ -15,9 +15,7 @@ use gateway_core::metering::{ CurrencyCode, Decimal, Money, ProviderReportedCost, Usage, }; use gateway_core::upstream::UpstreamSendState; -use gateway_protocol::openai::events::{ - ResponseModelObservation, TokenUsage, billable_usage_is_complete, extract_usage, -}; +use gateway_protocol::openai::events::{TokenUsage, billable_usage_is_complete, extract_usage}; use gateway_protocol::openai::sse::{SseEvent, SseEventDecoder}; use serde_json::Value; @@ -295,7 +293,6 @@ pub struct GrokCanonicalDecoder { tool_arguments_seen: BTreeSet, usage_emitted: bool, response_service_tier: Option, - response_model: ResponseModelObservation, requires_provider_cost: bool, } @@ -372,7 +369,6 @@ impl GrokCanonicalDecoder { tool_arguments_seen: BTreeSet::new(), usage_emitted: false, response_service_tier: None, - response_model: ResponseModelObservation::default(), requires_provider_cost: false, } } @@ -416,12 +412,6 @@ impl GrokCanonicalDecoder { self.response_service_tier.as_deref() } - /// 返回原始上游响应声明的模型,缺失时不使用请求模型补齐 - #[must_use] - pub fn response_model(&self) -> Option<&str> { - self.response_model.model() - } - pub fn push(&mut self, chunk: &[u8]) -> Result, ProviderError> { let events = self.decoder.push(chunk).map_err(protocol_error)?; self.decode(events).map(|batch| batch.projected_events) @@ -522,9 +512,6 @@ impl GrokCanonicalDecoder { event.retry, ) .map_err(|_| protocol_error_marker())?; - if !event_type.is_empty() { - self.response_model.observe(Some(&event_type), &value); - } // 工具转换可能隐藏注入的调用,计费事实必须从转换前的上游事件读取 if let Some(response) = value.get("response") { if let Some(tier) = response.get("service_tier").and_then(Value::as_str) { diff --git a/backend/crates/providers/xai/tests/transport/canonical.rs b/backend/crates/providers/xai/tests/transport/canonical.rs index d710d20f8..bc966c5fd 100644 --- a/backend/crates/providers/xai/tests/transport/canonical.rs +++ b/backend/crates/providers/xai/tests/transport/canonical.rs @@ -50,7 +50,6 @@ fn decoder_should_bill_the_sent_model_independently_of_the_response_model() { provider_cost_ticks(&facts), reported_cost.filter(|ticks| *ticks > 0) ); - assert_eq!(decoder.response_model(), returned); assert!(facts.iter().any(|event| matches!( event, GatewayEvent::Completed(meta) if meta.model() == Some(returned.unwrap_or(sent)) @@ -58,18 +57,6 @@ fn decoder_should_bill_the_sent_model_independently_of_the_response_model() { } } -#[test] -fn response_model_observation_preserves_raw_model_and_missing_values() { - for model in [None, Some("grok-4.6-build")] { - let mut decoder = GrokCanonicalDecoder::new("grok-4.6"); - let body = serde_json::json!({"type":"response.created","response":{"id":"resp_model","model":model}}); - decoder - .push(format!("data: {body}\n\n").as_bytes()) - .expect("decode created"); - assert_eq!(decoder.response_model(), model); - } -} - fn terminal_cost_events( model: &str, input_tokens: u64, diff --git a/docs/api.md b/docs/api.md index 624b892b2..a25c80408 100644 --- a/docs/api.md +++ b/docs/api.md @@ -2099,12 +2099,7 @@ Dashboard 返回原始 `asOf` 及 `asOfDisplay`,作为当前数据的查询锚 详情不出现在 Key 用量接口和普通错误信封中;[诊断导出](#诊断与恢复关联)在服务端重新脱敏后包含已存详情。 记录仍受异步观测写入与保留周期约束 -管理端请求列表与详情分别保留 `requestedModel`(客户端请求)、`upstreamModel`(网关发送)与 -`upstreamResponseModel`(上游返回)。返回模型缺失时为 `null`,不使用请求或映射模型补齐。 -OpenAI 优先采用服务端 `openai-model` / `x-openai-model` 报告(流内报告可覆盖初始响应头), -没有报告时采用正文明确声明的 `response.model`;xAI 采用原始正文声明。正文模型以终态优先, -缺少终态声明时保留首次声明。这些值仅表示上游报告,不作为模型真实性证明,也不参与路由、 -聚合或本地计价模型选择。没有保存上游模型报告的记录保持未知 +管理端请求列表与详情保留 `requestedModel`(客户端请求)与 `upstreamModel`(网关发送) 请求明细的 `clientApiKeyName`、`accountName` 按字面量包含匹配请求保存的历史名称快照,区分大小写, 不解析为 ID,也不依赖当前配置是否存在。`model` 按字面量包含匹配请求模型或上游模型,`%`、`_` 和 `\` 按普通字符处理 diff --git a/frontend/src/api/modules/usage.ts b/frontend/src/api/modules/usage.ts index 7bd585d81..a48b00b23 100644 --- a/frontend/src/api/modules/usage.ts +++ b/frontend/src/api/modules/usage.ts @@ -98,7 +98,6 @@ export interface UsageListRecord { model: string | null requestedModel: string | null upstreamModel: string | null - upstreamResponseModel: string | null serviceTier: string | null clientTransport: string upstreamTransport: string | null @@ -131,7 +130,6 @@ export interface UsageRecord { model: string | null requestedModel: string | null upstreamModel: string | null - upstreamResponseModel: string | null serviceTier: string | null statusCode: number | null clientTransport: string diff --git a/frontend/src/components/AppLanguageMenu.vue b/frontend/src/components/AppLanguageMenu.vue index 1b1b1e30e..c4d667757 100644 --- a/frontend/src/components/AppLanguageMenu.vue +++ b/frontend/src/components/AppLanguageMenu.vue @@ -2,7 +2,7 @@ import type { IconButtonProps } from '@codex-proxy/ui' import type { Locale } from '@/i18n/locale' import { ZIconButton, ZMenuItem, ZPopover } from '@codex-proxy/ui' -import { Check, Languages } from '@lucide/vue' +import { Check, Languages, LoaderCircle } from '@lucide/vue' import chineseFlag from 'circle-flags/flags/cn.svg?url&no-inline' import englishFlag from 'circle-flags/flags/us.svg?url&no-inline' import { computed, shallowRef, useTemplateRef } from 'vue' @@ -61,7 +61,8 @@ async function selectLocale(value: Locale, close: () => void) { v-for="option in localeOptions" :key="option.value" :aria-pressed="option.value === locale" - :loading="pendingLocale === option.value" + :disabled="pendingLocale === option.value" + :aria-busy="pendingLocale === option.value || undefined" class="aria-pressed:bg-cp-control-item-bg-active aria-pressed:text-cp-primary-text aria-pressed:hover:bg-cp-control-item-bg-active" @click="selectLocale(option.value, close)" > @@ -70,7 +71,10 @@ async function selectLocale(value: Locale, close: () => void) { {{ option.label }} - diff --git a/frontend/src/components/usage/UsageModelCell.vue b/frontend/src/components/usage/UsageModelCell.vue index b79e14405..ae1bdfd72 100644 --- a/frontend/src/components/usage/UsageModelCell.vue +++ b/frontend/src/components/usage/UsageModelCell.vue @@ -24,19 +24,17 @@ const { t } = useI18n({ useScope: 'global' }) {{ modelDisplay.primary }}
diff --git a/frontend/src/components/usage/shared/columns.ts b/frontend/src/components/usage/shared/columns.ts index 88d5609ba..85e1a64c4 100644 --- a/frontend/src/components/usage/shared/columns.ts +++ b/frontend/src/components/usage/shared/columns.ts @@ -6,7 +6,7 @@ import { t } from '@/i18n' import { formatProviderLabel } from '@/utils/providers' export const usageDataColumns = computed(() => ([ - { key: 'model', label: t('usage.fields.model'), kind: 'custom', size: 'xl' }, + { key: 'model', label: t('usage.fields.model'), kind: 'custom', size: 'lg' }, { key: 'reasoningEffort', label: t('usage.fields.reasoningEffort'), kind: 'status', size: 'md', align: 'left' }, { key: 'route', label: t('usage.fields.endpoint'), kind: 'mono' }, { key: 'upstreamTransport', label: t('usage.fields.upstream'), kind: 'status', size: 'md' }, diff --git a/frontend/src/components/usage/shared/presenter.ts b/frontend/src/components/usage/shared/presenter.ts index c928dea7c..fdfb2b852 100644 --- a/frontend/src/components/usage/shared/presenter.ts +++ b/frontend/src/components/usage/shared/presenter.ts @@ -10,7 +10,7 @@ export type UsagePerformanceRecord = Pick | null } -export type UsageModelRecord = Pick & Partial> +export type UsageModelRecord = Pick & Partial> export type UsageReasoningRecord = Pick & Partial> interface UsageTimingItem { @@ -93,27 +93,7 @@ export function usageModelDisplay(record: UsageModelRecord) { ? storedModel : '' - const responseModel = record.upstreamResponseModel || '' - const returned = responseModel && responseModel !== primary ? responseModel : '' - const routes = [] - if (secondary && secondary !== returned) { - routes.push({ - model: secondary, - kind: 'mapped' as const, - description: t('usage.status.mappedModel', { secondary }), - }) - } - if (returned) { - routes.push({ - model: returned, - kind: 'returned' as const, - description: returned === secondary - ? t('usage.status.returnedModel', { returned }) - : t('usage.status.returnedModelHelp', { returned }), - }) - } - - return { primary, secondary, routes } + return { primary, secondary } } export function usagePerformanceDetails(record: UsagePerformanceRecord) { diff --git a/frontend/src/i18n/locales/en/usage.json b/frontend/src/i18n/locales/en/usage.json index 9c7df9a0a..f0d4a3b89 100644 --- a/frontend/src/i18n/locales/en/usage.json +++ b/frontend/src/i18n/locales/en/usage.json @@ -82,6 +82,7 @@ "reviewSubagentRequest": "Review subagent request" }, "filters": { + "modelPlaceholder": "Filter by model", "error": "Error", "requestDetailType": "Request detail type", "label": "Usage record filters and actions", @@ -127,8 +128,6 @@ "future": "Future", "status": { "mappedModel": "Model sent upstream after gateway mapping: {secondary}", - "returnedModel": "Upstream response model: {returned} (matches the mapped request model)", - "returnedModelHelp": "Model returned by upstream: {returned}", "firstOutputWaiting": "First output", "reasoningToText": "Reasoning → Text", "firstOneIsOutputToText": "First output → Text", @@ -168,6 +167,7 @@ "requestsPerMinute": "RPM" }, "header": { + "title": "Analytics", "currentKey": "Current key", "keyPrefix": "Key prefix", "keyConfiguration": "Key configuration", @@ -201,8 +201,8 @@ "clickRefreshAtTopToTryAgain": ", please click refresh at the top to try again", "description": "View usage, performance trends, and request errors", "dateRangeLabel": "Statistics date range, limited to the last 31 days", - "requestDetails": "Request details", - "recordsDescription": "Successful and failed request details", + "requestDetails": "Request log", + "recordsDescription": "Successful and failed request records", "failedToLoad": "Failed to load: {error}", "noRequestsMatchTheseFilters": "No requests match these filters", "viewErrorDetails": "View error details", @@ -310,7 +310,6 @@ "message": "Message", "upstreamTimeConsumption": "Upstream time consumption", "storageModel": "Storage model", - "upstreamReturnModel": "Upstream return model", "clientIp": "Client IP", "eventType": "Event type", "trySerialNumber": "Try serial number", diff --git a/frontend/src/i18n/locales/zh-Hans/usage.json b/frontend/src/i18n/locales/zh-Hans/usage.json index d0a626c28..e8327a7bf 100644 --- a/frontend/src/i18n/locales/zh-Hans/usage.json +++ b/frontend/src/i18n/locales/zh-Hans/usage.json @@ -82,6 +82,7 @@ "reviewSubagentRequest": "Review 子代理请求" }, "filters": { + "modelPlaceholder": "按模型筛选", "error": "错误", "requestDetailType": "请求明细类型", "label": "使用记录筛选与操作", @@ -127,8 +128,6 @@ "future": "未来", "status": { "mappedModel": "网关映射后发送给上游的模型:{secondary}", - "returnedModel": "上游返回模型:{returned}(与网关映射后发送的模型一致)", - "returnedModelHelp": "上游返回模型:{returned}", "firstOutputWaiting": "首个输出等待", "reasoningToText": "推理到正文", "firstOneIsOutputToText": "首个输出到正文", @@ -168,6 +167,7 @@ "requestsPerMinute": "RPM" }, "header": { + "title": "使用统计", "currentKey": "当前密钥", "keyPrefix": "密钥前缀", "keyConfiguration": "密钥配置", @@ -201,8 +201,8 @@ "clickRefreshAtTopToTryAgain": ",请点击顶部刷新重试", "description": "查看请求用量、性能趋势与调用错误记录", "dateRangeLabel": "统计日期范围,仅可选择最近 31 天", - "requestDetails": "请求明细", - "recordsDescription": "成功请求与失败请求明细", + "requestDetails": "请求记录", + "recordsDescription": "成功请求与失败请求记录", "failedToLoad": "加载失败:{error}", "noRequestsMatchTheseFilters": "所选条件下暂无请求记录", "viewErrorDetails": "查看错误详情", @@ -310,7 +310,6 @@ "message": "消息", "upstreamTimeConsumption": "上游耗时", "storageModel": "存储模型", - "upstreamReturnModel": "上游返回模型", "clientIp": "客户端 IP", "eventType": "事件类型", "trySerialNumber": "尝试序号", diff --git a/frontend/src/i18n/locales/zh-Hant/usage.json b/frontend/src/i18n/locales/zh-Hant/usage.json index 663072b89..30b9d4217 100644 --- a/frontend/src/i18n/locales/zh-Hant/usage.json +++ b/frontend/src/i18n/locales/zh-Hant/usage.json @@ -82,6 +82,7 @@ "reviewSubagentRequest": "Review 子代理請求" }, "filters": { + "modelPlaceholder": "按模型篩選", "error": "錯誤", "requestDetailType": "請求明細類型", "label": "使用記錄篩選與操作", @@ -127,8 +128,6 @@ "future": "未來", "status": { "mappedModel": "網關映射後發送給上游的模型:{secondary}", - "returnedModel": "上游返回模型:{returned}(與網關映射後發送的模型一致)", - "returnedModelHelp": "上游返回模型:{returned}", "firstOutputWaiting": "首個輸出等待", "reasoningToText": "推理到正文", "firstOneIsOutputToText": "首個輸出到正文", @@ -168,6 +167,7 @@ "requestsPerMinute": "RPM" }, "header": { + "title": "使用統計", "currentKey": "當前密鑰", "keyPrefix": "密鑰前綴", "keyConfiguration": "密鑰配置", @@ -201,8 +201,8 @@ "clickRefreshAtTopToTryAgain": ",請點擊頂部刷新重試", "description": "查看請求用量、性能趨勢與調用錯誤記錄", "dateRangeLabel": "統計日期範圍,僅可選擇最近 31 天", - "requestDetails": "請求明細", - "recordsDescription": "成功請求與失敗請求明細", + "requestDetails": "請求記錄", + "recordsDescription": "成功請求與失敗請求記錄", "failedToLoad": "加載失敗:{error}", "noRequestsMatchTheseFilters": "所選條件下暫無請求記錄", "viewErrorDetails": "查看錯誤詳情", @@ -310,7 +310,6 @@ "message": "消息", "upstreamTimeConsumption": "上游耗時", "storageModel": "存儲模型", - "upstreamReturnModel": "上游返回模型", "clientIp": "客戶端 IP", "eventType": "事件類型", "trySerialNumber": "嘗試序號", diff --git a/frontend/src/layout/components/AppSidebar.vue b/frontend/src/layout/components/AppSidebar.vue index a9bb59143..72e4e17f6 100644 --- a/frontend/src/layout/components/AppSidebar.vue +++ b/frontend/src/layout/components/AppSidebar.vue @@ -440,7 +440,7 @@ onBeforeUnmount(() => {