diff --git a/backend/crates/gateway-admin/src/model/observability.rs b/backend/crates/gateway-admin/src/model/observability.rs index 74125a7e9..e4923ce20 100644 --- a/backend/crates/gateway-admin/src/model/observability.rs +++ b/backend/crates/gateway-admin/src/model/observability.rs @@ -135,6 +135,7 @@ pub struct OpsErrorFilter { pub provider_kind: Option, pub provider_account_ref: Option, pub operation: Option, + pub endpoint: Option, pub model: Option, pub transport: Option, pub attempt_index: Option, @@ -762,6 +763,10 @@ pub struct OpsError { pub client_api_key_ref: Option, pub component: String, pub operation: String, + pub protocol: Option, + pub client_transport: Option, + pub requested_model_id: Option, + pub service_tier: Option, pub endpoint: Option, pub provider_kind: Option, pub provider_account_ref: Option, @@ -779,6 +784,13 @@ pub struct OpsError { pub latency_ms: Option, pub message: String, pub occurrence_count: u32, + pub client_ip: Option, + pub user_agent: Option, + pub reasoning_effort: Option, + pub reasoning_preset: Option, + pub request_kind: Option, + pub subagent_kind: Option, + pub compact: Option, pub occurred_at: DateTime, pub stable_sort_id: String, } diff --git a/backend/crates/gateway-api/src/admin/observability/presenter.rs b/backend/crates/gateway-api/src/admin/observability/presenter.rs index f3c012e18..2c99805fc 100644 --- a/backend/crates/gateway-api/src/admin/observability/presenter.rs +++ b/backend/crates/gateway-api/src/admin/observability/presenter.rs @@ -531,24 +531,45 @@ pub(crate) fn ops_error_view(error: domain::OpsError) -> OpsErrorView { .or(error.provider_account_name.as_ref()) .or(error.provider_account_ref.as_ref()) .cloned(); + let model = error + .upstream_model_id + .clone() + .or_else(|| error.requested_model_id.clone()); OpsErrorView { id: error.event_id, request_id: error.request_id, client_api_key_id: error.client_api_key_ref, kind: error.component.clone(), + operation: error.operation, + protocol: error.protocol, + client_transport: error.client_transport, provider: error.provider_kind, authentication_kind: error.provider_account_authentication_kind, account_id: error.provider_account_ref, + account_name: error.provider_account_name, + account_email: error.provider_account_email, route: error.endpoint.unwrap_or_else(|| "—".to_owned()), - model: error.upstream_model_id, + model, + requested_model: error.requested_model_id, + upstream_model: error.upstream_model_id, + service_tier: error.service_tier, client_status_code: error.client_status_code.map(i64::from), upstream_status_code: error.upstream_status_code.map(i64::from), transport: error.upstream_transport, attempt_index: error.attempt_index, failure_class: error.failure_kind, + provider_error_code: error.provider_error_code, + occurrence_count: error.occurrence_count, response_id: error.client_response_id, upstream_request_id: error.upstream_request_id, latency_ms: error.latency_ms, + client_ip: error.client_ip, + user_agent: error.user_agent, + reasoning_effort: error.reasoning_effort, + reasoning_preset: error.reasoning_preset, + request_kind: error.request_kind, + subagent_kind: error.subagent_kind, + compact: error.compact, message: error.message, metadata: OpsErrorMetadataView { source: error.source, diff --git a/backend/crates/gateway-api/src/admin/observability/query.rs b/backend/crates/gateway-api/src/admin/observability/query.rs index 07bb18aaa..40fa1abba 100644 --- a/backend/crates/gateway-api/src/admin/observability/query.rs +++ b/backend/crates/gateway-api/src/admin/observability/query.rs @@ -351,7 +351,8 @@ pub(crate) fn ops_command(query: &OpsQuery) -> Result, pub client_api_key_id: Option, pub kind: String, + pub operation: String, + pub protocol: Option, + pub client_transport: Option, pub provider: Option, pub authentication_kind: Option, pub account_id: Option, + pub account_name: Option, + pub account_email: Option, pub route: String, pub model: Option, + pub requested_model: Option, + pub upstream_model: Option, + pub service_tier: Option, pub client_status_code: Option, pub upstream_status_code: Option, pub transport: Option, pub attempt_index: Option, pub failure_class: String, + pub provider_error_code: Option, + pub occurrence_count: u32, pub response_id: Option, pub upstream_request_id: Option, pub latency_ms: Option, + pub client_ip: Option, + pub user_agent: Option, + pub reasoning_effort: Option, + pub reasoning_preset: Option, + pub request_kind: Option, + pub subagent_kind: Option, + pub compact: Option, pub message: String, pub metadata: OpsErrorMetadataView, pub created_at: DateTime, diff --git a/backend/crates/gateway-api/tests/admin/observability/response.rs b/backend/crates/gateway-api/tests/admin/observability/response.rs index a3e020fd7..fc4fd84d7 100644 --- a/backend/crates/gateway-api/tests/admin/observability/response.rs +++ b/backend/crates/gateway-api/tests/admin/observability/response.rs @@ -539,6 +539,10 @@ async fn ops_errors_should_keep_account_label_and_authentication_contract() { client_api_key_ref: Some("key_err".to_owned()), component: "model_request".to_owned(), operation: "responses".to_owned(), + protocol: Some("openai".to_owned()), + client_transport: Some("http_sse".to_owned()), + requested_model_id: Some("gpt-5.4".to_owned()), + service_tier: Some("priority".to_owned()), endpoint: Some("/v1/responses".to_owned()), provider_kind: Some("openai".to_owned()), provider_account_ref: Some("acct_err".to_owned()), @@ -556,6 +560,13 @@ async fn ops_errors_should_keep_account_label_and_authentication_contract() { latency_ms: Some(90), message: "snapshot error".to_owned(), occurrence_count: 1, + client_ip: Some("203.0.113.8".to_owned()), + user_agent: Some("codex-cli/0.144.0".to_owned()), + reasoning_effort: Some("medium".to_owned()), + reasoning_preset: None, + request_kind: Some("root".to_owned()), + subagent_kind: None, + compact: Some(false), occurred_at: Utc::now(), stable_sort_id: "model_request:req_err".to_owned(), }); @@ -585,6 +596,11 @@ async fn ops_errors_should_keep_account_label_and_authentication_contract() { "accountLabel": value["data"]["items"][0]["metadata"]["accountLabel"], "clientStatusCode": value["data"]["items"][0]["clientStatusCode"], "route": value["data"]["items"][0]["route"], + "requestedModel": value["data"]["items"][0]["requestedModel"], + "clientTransport": value["data"]["items"][0]["clientTransport"], + "clientIp": value["data"]["items"][0]["clientIp"], + "userAgent": value["data"]["items"][0]["userAgent"], + "providerErrorCode": value["data"]["items"][0]["providerErrorCode"], }), serde_json::json!({ "provider": "openai", @@ -594,6 +610,11 @@ async fn ops_errors_should_keep_account_label_and_authentication_contract() { "accountLabel": "err@example.invalid", "clientStatusCode": 502, "route": "/v1/responses", + "requestedModel": "gpt-5.4", + "clientTransport": "http_sse", + "clientIp": "203.0.113.8", + "userAgent": "codex-cli/0.144.0", + "providerErrorCode": "upstream", }) ); } diff --git a/backend/crates/gateway-store/src/postgres/observability/mapping.rs b/backend/crates/gateway-store/src/postgres/observability/mapping.rs index eb1010274..4945a87d9 100644 --- a/backend/crates/gateway-store/src/postgres/observability/mapping.rs +++ b/backend/crates/gateway-store/src/postgres/observability/mapping.rs @@ -51,6 +51,7 @@ pub(crate) fn store_ops_error_filter( provider_account_ref: filter.provider_account_ref, provider_kind: filter.provider_kind, operation: filter.operation, + endpoint: filter.endpoint, model: filter.model, transport: filter.transport, attempt_index: filter.attempt_index, @@ -700,6 +701,10 @@ pub(crate) fn admin_ops_error(error: OpsErrorRecord) -> admin_observability::Ops client_api_key_ref: error.client_api_key_ref, component: error.component, operation: error.operation, + protocol: error.protocol, + client_transport: error.client_transport, + requested_model_id: error.requested_model_id, + service_tier: error.service_tier, endpoint: error.endpoint, provider_kind: error.provider_kind, provider_account_ref: error.provider_account_ref, @@ -717,6 +722,13 @@ pub(crate) fn admin_ops_error(error: OpsErrorRecord) -> admin_observability::Ops latency_ms: error.latency_ms, message: error.message, occurrence_count: error.occurrence_count, + client_ip: error.client_ip, + user_agent: error.user_agent, + reasoning_effort: error.reasoning_effort, + reasoning_preset: error.reasoning_preset, + request_kind: error.request_kind, + subagent_kind: error.subagent_kind, + compact: error.compact, occurred_at: error.occurred_at, stable_sort_id: error.stable_sort_id, } @@ -866,6 +878,10 @@ pub(crate) fn ops_error_from_row(row: &sqlx::postgres::PgRow) -> StoreResult StoreResult, pub provider_kind: Option, pub operation: Option, + pub endpoint: Option, pub model: Option, pub transport: Option, pub attempt_index: Option, @@ -147,6 +148,7 @@ impl OpsErrorFilter { ), (self.provider_kind.as_deref(), "provider filter"), (self.operation.as_deref(), "operation filter"), + (self.endpoint.as_deref(), "endpoint filter"), (self.model.as_deref(), "model filter"), (self.transport.as_deref(), "transport filter"), ( @@ -694,6 +696,10 @@ pub struct OpsErrorRecord { pub client_api_key_ref: Option, pub component: String, pub operation: String, + pub protocol: Option, + pub client_transport: Option, + pub requested_model_id: Option, + pub service_tier: Option, pub endpoint: Option, pub provider_kind: Option, pub provider_account_ref: Option, @@ -711,6 +717,13 @@ pub struct OpsErrorRecord { pub latency_ms: Option, pub message: String, pub occurrence_count: u32, + pub client_ip: Option, + pub user_agent: Option, + pub reasoning_effort: Option, + pub reasoning_preset: Option, + pub request_kind: Option, + pub subagent_kind: Option, + pub compact: Option, pub occurred_at: DateTime, pub stable_sort_id: String, } diff --git a/backend/crates/gateway-store/src/postgres/observability/queries/ops.rs b/backend/crates/gateway-store/src/postgres/observability/queries/ops.rs index b23be3531..99a8e006b 100644 --- a/backend/crates/gateway-store/src/postgres/observability/queries/ops.rs +++ b/backend/crates/gateway-store/src/postgres/observability/queries/ops.rs @@ -6,6 +6,7 @@ const REQUEST_ERROR_SELECT: &str = "select 'model_request'::text as source, mr.id as event_id, mr.id as request_id, nullif(mr.attempt_count, 0) as attempt_index, mr.client_api_key_ref, 'model_request'::text as component, mr.operation, + mr.protocol, mr.client_transport, mr.requested_model_id, mr.service_tier, mr.endpoint, mr.provider_kind, mr.provider_account_ref, mr.provider_account_name_snapshot as provider_account_name, mr.provider_account_email_snapshot as provider_account_email, @@ -17,7 +18,11 @@ const REQUEST_ERROR_SELECT: &str = "select 'model_request'::text as source, mr.provider_error_code, mr.client_response_id, mr.upstream_request_id, mr.latency_ms, coalesce(mr.error_message, mr.error_kind, 'request failed') as message, - 1::integer as occurrence_count, mr.completed_at as occurred_at, + 1::integer as occurrence_count, + mr.client_ip::text as client_ip, mr.user_agent, + mr.reasoning_effort, mr.reasoning_preset, mr.request_kind, + mr.subagent_kind, mr.compact, + mr.completed_at as occurred_at, 'model_request:' || mr.id as stable_sort_id from model_requests mr where mr.outcome = 'failed'"; @@ -25,7 +30,8 @@ where mr.outcome = 'failed'"; const OPS_EVENT_SELECT: &str = "select 'ops_event'::text as source, oe.id as event_id, oe.model_request_id as request_id, oe.attempt_index, mr.client_api_key_ref, oe.component, oe.operation, - null::text as endpoint, oe.provider_kind, oe.provider_account_ref, + mr.protocol, mr.client_transport, mr.requested_model_id, mr.service_tier, + mr.endpoint, oe.provider_kind, oe.provider_account_ref, oe.provider_account_name_snapshot as provider_account_name, oe.provider_account_email_snapshot as provider_account_email, oe.provider_account_authentication_kind_snapshot @@ -33,7 +39,11 @@ const OPS_EVENT_SELECT: &str = "select 'ops_event'::text as source, oe.upstream_model_id, null::text as upstream_transport, oe.failure_kind, null::integer as client_status_code, oe.status_code as upstream_status_code, oe.provider_error_code, mr.client_response_id, oe.upstream_request_id, - oe.latency_ms, oe.message, oe.occurrence_count, oe.created_at as occurred_at, + oe.latency_ms, oe.message, oe.occurrence_count, + mr.client_ip::text as client_ip, mr.user_agent, + mr.reasoning_effort, mr.reasoning_preset, mr.request_kind, + mr.subagent_kind, mr.compact, + oe.created_at as occurred_at, 'ops_event:' || oe.id as stable_sort_id from ops_events oe left join model_requests mr on mr.id = oe.model_request_id @@ -107,6 +117,7 @@ fn push_request_error_predicates( ("mr.provider_account_ref", &filter.provider_account_ref), ("mr.provider_kind", &filter.provider_kind), ("mr.operation", &filter.operation), + ("mr.endpoint", &filter.endpoint), ("mr.upstream_transport", &filter.transport), ("mr.upstream_request_id", &filter.upstream_request_id), ] { @@ -153,6 +164,7 @@ fn push_ops_event_predicates( ("oe.provider_account_ref", &filter.provider_account_ref), ("oe.provider_kind", &filter.provider_kind), ("oe.operation", &filter.operation), + ("mr.endpoint", &filter.endpoint), ("oe.upstream_request_id", &filter.upstream_request_id), ("oe.failure_kind", &filter.failure_kind), ] { diff --git a/backend/crates/gateway-store/tests/postgres/observability.rs b/backend/crates/gateway-store/tests/postgres/observability.rs index b5c0d4735..51f630116 100644 --- a/backend/crates/gateway-store/tests/postgres/observability.rs +++ b/backend/crates/gateway-store/tests/postgres/observability.rs @@ -761,6 +761,8 @@ async fn admin_observability_adapter_preserves_utc_queries_metrics_costs_and_det request_id: Some("req_observe_failed".to_owned()), provider_kind: Some("openai".to_owned()), provider_account_ref: Some("acct_observe".to_owned()), + operation: Some("responses".to_owned()), + endpoint: Some("/v1/responses".to_owned()), model: Some("upstream-model".to_owned()), failure_kind: Some("rate_limited".to_owned()), status_code: Some(429), @@ -1191,6 +1193,15 @@ async fn observability_queries_preserve_request_account_cost_and_diagnostic_fact .expect("request error"); assert_eq!(request_error.client_status_code, Some(502)); assert_eq!(request_error.upstream_status_code, Some(429)); + assert_eq!(request_error.client_ip.as_deref(), Some("203.0.113.9")); + assert_eq!( + request_error.user_agent.as_deref(), + Some("codex-cli/0.144.0") + ); + assert_eq!( + request_error.requested_model_id.as_deref(), + Some("public-model") + ); let attempt_error = errors .items .iter() @@ -1198,6 +1209,12 @@ async fn observability_queries_preserve_request_account_cost_and_diagnostic_fact .expect("attempt error"); assert_eq!(attempt_error.client_status_code, None); assert_eq!(attempt_error.upstream_status_code, Some(429)); + assert_eq!(attempt_error.endpoint.as_deref(), Some("/v1/responses")); + assert_eq!(attempt_error.client_ip.as_deref(), Some("203.0.113.9")); + assert_eq!( + attempt_error.user_agent.as_deref(), + Some("codex-cli/0.144.0") + ); database.close().await; } @@ -1286,7 +1303,7 @@ async fn seed_observability_facts( sqlx::query( "insert into model_requests ( id, client_api_key_ref, config_revision, protocol, operation, endpoint, - client_transport, requested_model_id, + client_transport, requested_model_id, service_tier, provider_kind, provider_account_id, provider_account_ref, upstream_model_id, upstream_transport, attempt_count, provider_account_name_snapshot, provider_account_email_snapshot, @@ -1295,16 +1312,20 @@ async fn seed_observability_facts( error_kind, provider_error_code, error_message, retry_after_ms, input_tokens, cached_tokens, image_generation_requested, image_generation_succeeded, cost_source, latency_ms, + client_ip, user_agent, reasoning_effort, reasoning_preset, + request_kind, subagent_kind, compact, started_at, deadline_at, completed_at, routing_scope, routing_group_refs, routing_group_names_snapshot ) values ( 'req_observe_failed', 'key_observe', 1, 'openai', 'responses', '/v1/responses', - 'http_sse', 'public-model', 'openai', 'acct_observe', + 'http_sse', 'public-model', 'priority', 'openai', 'acct_observe', 'acct_observe', 'upstream-model', 'http_sse', 2, 'primary', 'account@example.invalid', 'oauth', 'sent', 'failed', 502, 429, 'rate_limited', 'rate_limit', 'upstream limited', 1000, 0, 0, true, false, 'unavailable', 700, + '203.0.113.9', 'codex-cli/0.144.0', 'medium', null, + 'root', null, false, $1 - interval '10 minutes', $1 + interval '20 minutes', $1 - interval '9 minutes', 'all', '{}'::text[], '[]'::jsonb )", diff --git a/frontend/src/api/modules/usage.ts b/frontend/src/api/modules/usage.ts index 84bc2766b..469ed22e1 100644 --- a/frontend/src/api/modules/usage.ts +++ b/frontend/src/api/modules/usage.ts @@ -211,19 +211,36 @@ export interface OpsError { requestId: string | null clientApiKeyId: string | null kind: string + operation: string + protocol: string | null + clientTransport: string | null provider: string | null authenticationKind: string | null accountId: string | null + accountName: string | null + accountEmail: string | null route: string model: string | null + requestedModel: string | null + upstreamModel: string | null + serviceTier: string | null clientStatusCode: number | null upstreamStatusCode: number | null transport: string | null attemptIndex: number | null failureClass: string + providerErrorCode: string | null + occurrenceCount: number responseId: string | null upstreamRequestId: string | null latencyMs: number | null + clientIp: string | null + userAgent: string | null + reasoningEffort: string | null + reasoningPreset: string | null + requestKind: string | null + subagentKind: string | null + compact: boolean | null message: string metadata: OpsErrorMetadata createdAt: string diff --git a/frontend/src/views/usage/components/OpsErrorDetailModal.vue b/frontend/src/views/usage/components/OpsErrorDetailModal.vue index 3dab19be7..9e1b3ea9b 100644 --- a/frontend/src/views/usage/components/OpsErrorDetailModal.vue +++ b/frontend/src/views/usage/components/OpsErrorDetailModal.vue @@ -6,6 +6,7 @@ import BaseModal from '@/components/base/BaseModal/index.vue' import { displayValue } from '../utils/detail' import { presentOpsError } from '../utils/opsErrorPresentation' import UsageDetailCodePanel from './UsageDetailCodePanel.vue' +import UsageDetailFieldGrid from './UsageDetailFieldGrid.vue' import UsageStatusCodeBadge from './UsageStatusCodeBadge.vue' const props = defineProps<{ @@ -16,23 +17,52 @@ const open = defineModel({ default: false }) const presentation = computed(() => props.record ? presentOpsError(props.record) : null) -const fields = computed(() => [ - { label: '账号', value: props.record?.metadata.accountLabel }, - { label: '时间', value: props.record?.createdAtDisplay }, +const panelClass = 'min-w-0 rounded-cp-card bg-cp-fill-quaternary px-4 py-3.5' +const panelTitleClass = 'm-0 text-cp-sm leading-none font-heavy text-cp-text-secondary' + +const accountLabel = computed(() => props.record?.accountEmail + || props.record?.accountName + || props.record?.metadata.accountLabel + || props.record?.accountId) + +const requestFields = computed(() => [ + { label: '客户端 IP', value: props.record?.clientIp, mono: true }, + { label: 'User-Agent', value: props.record?.userAgent, mono: true, wrap: true }, + { label: '协议', value: props.record?.protocol, mono: true }, + { label: '客户端传输', value: props.record?.clientTransport, mono: true }, + { label: '客户端 Key ID', value: props.record?.clientApiKeyId, mono: true }, + { label: '请求 ID', value: props.record?.requestId, mono: true }, + { label: '响应 ID', value: props.record?.responseId, mono: true }, + { label: '上游请求 ID', value: props.record?.upstreamRequestId, mono: true }, +]) + +const routeFields = computed(() => [ + { label: '端点', value: props.record?.route, mono: true }, + { label: '请求模型', value: props.record?.requestedModel, mono: true }, + { label: '上游模型', value: props.record?.upstreamModel, mono: true }, + { label: '记录模型', value: props.record?.model, mono: true }, + { label: '推理强度', value: props.record?.reasoningEffort, mono: true }, + { label: '推理预设', value: props.record?.reasoningPreset, mono: true }, + { label: '请求类型', value: props.record?.requestKind, mono: true }, + { label: '子代理类型', value: props.record?.subagentKind, mono: true }, + { label: '压缩请求', value: booleanDisplay(props.record?.compact) }, + { label: '服务档位', value: props.record?.serviceTier, mono: true }, + { label: '上游传输', value: props.record?.transport, mono: true }, + { label: '尝试序号', value: props.record?.attemptIndex }, +]) + +const diagnosticFields = computed(() => [ + { label: '账号', value: accountLabel.value, mono: true }, + { label: '时间', value: props.record?.createdAtDisplay, mono: true }, { label: '记录来源', value: presentation.value?.sourceLabel }, { label: '组件', value: presentation.value?.componentLabel }, + { label: '操作/触发', value: props.record?.operation, mono: true }, { label: '失败分类', value: presentation.value?.failureClassLabel }, { label: '平台/类型', value: providerKindLabel(props.record) }, - { label: '端点', value: props.record?.route }, - { label: '模型', value: props.record?.model }, - { label: '账号 ID', value: props.record?.accountId }, - { label: '客户端 Key ID', value: props.record?.clientApiKeyId }, - { label: '传输方式', value: props.record?.transport }, - { label: '尝试序号', value: props.record?.attemptIndex }, - { label: '耗时', value: latencyDisplay(props.record?.latencyMs) }, - { label: '请求 ID', value: props.record?.requestId, wide: true }, - { label: '响应 ID', value: props.record?.responseId, wide: true }, - { label: '上游请求 ID', value: props.record?.upstreamRequestId, wide: true }, + { label: '账号 ID', value: props.record?.accountId, mono: true }, + { label: 'Provider 错误码', value: props.record?.providerErrorCode, mono: true }, + { label: '聚合次数', value: props.record?.occurrenceCount }, + { label: '耗时', value: latencyDisplay(props.record?.latencyMs), mono: true }, ]) const metadataText = computed(() => { @@ -46,6 +76,14 @@ function latencyDisplay(value: unknown) { return typeof value === 'number' ? `${value} ms` : '—' } +function booleanDisplay(value: boolean | null | undefined) { + if (value === true) + return '是' + if (value === false) + return '否' + return '—' +} + function providerKindLabel(record: OpsError | null) { if (!record?.provider) return '—' @@ -59,7 +97,7 @@ function providerKindLabel(record: OpsError | null) { @@ -79,34 +117,37 @@ function providerKindLabel(record: OpsError | null) {

-
-
-
- {{ field.label }} -
-
- {{ displayValue(field.value) }} -
-
-
+
+
+

+ 请求追踪 +

+ +
-
+
+

+ 模型与链路 +

+ +
+
+ +
+

+ 错误事件 +

+ +
+ +
diff --git a/frontend/src/views/usage/components/OpsErrorPanel.vue b/frontend/src/views/usage/components/OpsErrorPanel.vue index c3b294b67..2d4f53e71 100644 --- a/frontend/src/views/usage/components/OpsErrorPanel.vue +++ b/frontend/src/views/usage/components/OpsErrorPanel.vue @@ -8,11 +8,13 @@ import BaseIconButton from '@/components/base/BaseIconButton.vue' import BaseInput from '@/components/base/BaseInput.vue' import BaseTablePagination from '@/components/base/BaseTable/BaseTablePagination.vue' import BaseTable from '@/components/base/BaseTable/index.vue' +import ProviderIconGroup from '@/components/ProviderIconGroup.vue' import { useOpsErrorsTable } from '../composables/useOpsErrorsTable' import { opsErrorColumns } from '../constants' import { presentOpsError } from '../utils/opsErrorPresentation' import OpsErrorDetailModal from './OpsErrorDetailModal.vue' import UsageStatusCodeBadge from './UsageStatusCodeBadge.vue' +import UsageTransportBadge from './UsageTransportBadge.vue' const props = defineProps<{ timeRangeParams: UsageTimeRangeParams @@ -46,6 +48,30 @@ function failureClassText(record: OpsError) { function errorSummary(record: OpsError) { return presentOpsError(record).summary } + +function accountText(record: OpsError) { + return record.accountEmail + || record.accountName + || record.metadata.accountLabel + || record.accountId + || '—' +} + +function modelText(record: OpsError) { + return record.requestedModel || record.model || record.upstreamModel || '—' +} + +function modelTitle(record: OpsError) { + const requested = record.requestedModel + const upstream = record.upstreamModel + return requested && upstream && requested !== upstream + ? `${requested} → ${upstream}` + : modelText(record) +} + +function reasoningText(record: OpsError) { + return record.reasoningPreset || record.reasoningEffort || '—' +} +