Files
codex-proxy-rs/frontend/src/api/modules/usage.ts
T
7cd17aff42 feat(usage): add request concurrency trends (#537)
* feat(usage): add request concurrency trends

* perf(usage)!: unify load insights and optimize concurrency

Aggregate concurrency on a single event timeline and reuse lifecycle
indexes instead of joining every request against every time bucket.

Align load summaries and trend points with the four chart metrics,
combine concurrency and scheduling in one view, and right-align the
statistics card selectors. Update the API contract and regression tests.

BREAKING CHANGE: Usage insights concurrency and scheduling P95 fields
move under performance.load and performance.points[].load. Unused
scheduling P50, capacity, and performance coverage fields are removed.

Co-authored-by: Codex <noreply@openai.com>

---------

Co-authored-by: Zyy <zyycn@users.noreply.github.com>
Co-authored-by: Codex <noreply@openai.com>
2026-10-28 17:22:28 +08:00

433 lines
11 KiB
TypeScript

import type { RequestOptions } from '../request'
import type { AnalyticsMetadata } from './analytics'
import { API_BASE_URL } from '../constants'
import request from '../request'
export interface UsageTokenDetails {
inputTokens: number | null
outputTokens: number | null
cachedInputTokens: number | null
cacheWriteInputTokens: number | null
reasoningOutputTokens: number | null
imageInputTokens: number | null
imageOutputTokens: number | null
totalTokens: number | null
inputTokensDisplay: string
outputTokensDisplay: string
cachedInputTokensDisplay: string
cacheWriteInputTokensDisplay: string
reasoningOutputTokensDisplay: string
imageInputTokensDisplay: string
imageOutputTokensDisplay: string
totalTokensDisplay: string
}
export interface UsageCost {
currency: string
estimatedAmount: string
}
export interface UsageCostCoverage {
known: number
partial: number
unknown: number
notBillable: number
}
export interface UsageBilling {
longContextBillingApplied: boolean
image?: {
inputAmountDisplay: string
cacheReadAmountDisplay: string
inputPriceDisplay: string
cacheReadPriceDisplay: string
}
inputAmountDisplay: string
outputAmountDisplay: string
cacheReadAmountDisplay: string
cacheWriteAmountDisplay: string
standardAmountDisplay: string
totalAmountDisplay: string
inputPriceDisplay: string
outputPriceDisplay: string
cacheReadPriceDisplay: string
cacheWritePriceDisplay: string
serviceTierDisplay: string
multiplierDisplay: string
}
export interface UsageLatencyDetails {
upstreamResponseMs?: number
upstreamApiOverheadMs?: number
upstreamEngineMs?: number
admissionDecisionMs?: number
accountSelectionWaitMs?: number
capacityUsedSlots?: number
capacityTotalSlots?: number
transportDecisionWaitMs?: number
wsConnectMs?: number
upstreamHeadersMs?: number
firstEventMs?: number
firstReasoningMs?: number
firstTextMs?: number
openaiProcessingMs?: number
}
// Provider 私有观测字段;Core 字段由顶层 UsageRecord 提供,不再复制进 metadata。
export interface UsageRecordMetadata {
[key: string]: unknown
}
export type UsageRecordStatus = 'success' | 'error' | 'all'
export interface UsageListRecord {
logicalOutcome: string
errorCode: string | null
errorMessage: string | null
clientApiKeyName: string | null
id: string
provider: string | null
authenticationKind: string | null
accountId: string | null
accountEmail: string | null
accountName: string | null
accountNotes: string | null
accountPlanType: string | null
accountPlanTypeDisplay: string | null
route: string
model: string | null
requestedModel: string | null
upstreamModel: string | null
serviceTier: string | null
clientTransport: string
upstreamTransport: string | null
reasoningEffort: string | null
reasoningPreset: string | null
subagentKind: string | null
compact: boolean
tokenDetails: UsageTokenDetails
billing: UsageBilling | null
latencyDetails: UsageLatencyDetails
firstTokenLatencyMs: number | null
latencyMs: number | null
createdAt: string
createdAtDisplay: string
clientIp: string | null
userAgent: string | null
}
export interface UsageRecord {
id: string
requestId: string
clientApiKeyId: string | null
kind: string
provider: string | null
authenticationKind: string | null
accountId: string | null
accountEmail: string | null
accountName: string | null
route: string
model: string | null
requestedModel: string | null
upstreamModel: string | null
serviceTier: string | null
statusCode: number | null
clientTransport: string
upstreamTransport: string | null
protocol: string
httpVersion: string | null
clientStatusCode: number | null
upstreamStatusCode: number | null
websocketPool: { kind: string } | null
imageGenerationRequested: boolean
imageGenerationSucceeded: boolean | null
latencyDetails: UsageLatencyDetails | null
attemptIndex: number | null
attemptCount: number
responseId: string | null
upstreamRequestId: string | null
latencyMs: number | null
inputTokens: number | null
outputTokens: number | null
cachedInputTokens: number | null
cacheWriteInputTokens: number | null
reasoningOutputTokens: number | null
imageInputTokens: number | null
imageOutputTokens: number | null
message: string
metadata: UsageRecordMetadata
createdAt: string
createdAtDisplay: string
clientIp: string | null
userAgent: string | null
reasoningEffort: string | null
reasoningPreset: string | null
compact: boolean | null
requestKind: string | null
subagentKind: string | null
tokenDetails: UsageTokenDetails
billing: UsageBilling | null
costs: UsageCost[]
costCoverage: UsageCostCoverage
firstTokenLatencyMs: number | null
latencyMsDisplay: string
logicalOutcome: string
}
export interface UsageAttempt {
id: string
attemptIndex: number
trigger: string
provider: string
model: string | null
transport: string
sendState: string
outcome: string
downstreamCommitted: boolean
statusCode: number | null
providerErrorCode: string | null
failureClass: string | null
costEstimateStatus: string
estimatedCostAmount: string | null
estimatedCostCurrency: string | null
inputTokens: number | null
outputTokens: number | null
cachedInputTokens: number | null
totalTokens: number | null
firstTokenMs: number | null
latencyMs: number | null
credentialName: string | null
accountId: string | null
accountName: string | null
accountEmail: string | null
authenticationKind: string | null
startedAt: string
completedAt: string | null
}
export type UsageRecordDetail = UsageRecord & {
attempts: UsageAttempt[]
/** 尝试列表是否完整;best-effort 下恒为 false。 */
attemptsComplete: boolean
}
export interface UsageRecordsResponse {
items: UsageListRecord[]
currentPage: number
pageSize: number
total: number
}
export interface UsageSummaryResponse {
analytics: AnalyticsMetadata
totalRequests: string
inputTokens: string
outputTokens: string
cachedInputTokens: string
cacheWriteInputTokens: string
totalTokens: string
averageLatencyMs: string
averageLatencyMsValue: number | null
}
export interface UsageOverviewHealthPoint {
bucket: string
label: string
totalRequests: number
successRequests: number
failedRequests: number
cancelledRequests: number
incompleteRequests: number
callerErrorRequests: number
errorRate: number
}
export interface UsageOverviewHealth {
totalRequests: number
successRequests: number
failedRequests: number
cancelledRequests: number
incompleteRequests: number
callerErrorRequests: number
successRate: number
completionRate: number
requestChangeRate: number | null
successRateChange: number | null
points: UsageOverviewHealthPoint[]
}
export interface UsageOverviewLoad {
averageConcurrency: number
peakConcurrency: number
admissionDecisionP95Ms: number | null
accountSelectionWaitP95Ms: number | null
}
export interface UsageOverviewPerformanceMetrics {
latencyP50Ms: number | null
latencyP95Ms: number | null
latencyP99Ms: number | null
firstTokenP50Ms: number | null
firstTokenP95Ms: number | null
firstTokenP99Ms: number | null
outputThroughputP10: number | null
outputThroughputP50: number | null
outputThroughputP90: number | null
load: UsageOverviewLoad
}
export interface UsageOverviewPerformancePoint extends UsageOverviewPerformanceMetrics {
bucket: string
label: string
}
export interface UsageOverviewPerformance extends UsageOverviewPerformanceMetrics {
points: UsageOverviewPerformancePoint[]
}
export interface UsageOverviewCostPoint {
bucket: string
label: string
inputTokens: number
outputTokens: number
cachedInputTokens: number
totalTokens: number
estimatedCost: string | null
standardCost: string | null
noCacheCost: string | null
cacheSavings: string | null
cachedTokenRate: number
cacheHitRequestRate: number | null
}
export interface UsageOverviewCost {
estimatedCost: string | null
standardCost: string | null
noCacheCost: string | null
cacheSavings: string | null
tierPremium: string | null
costPerRequest: string | null
costPerSuccessfulRequest: string | null
tokensPerRequest: number
cachedTokenRate: number
cacheHitRequestRate: number | null
inputTokens: number
outputTokens: number
cachedInputTokens: number
totalTokens: number
points: UsageOverviewCostPoint[]
coverage: UsageCostCoverage
}
export interface UsageInsightsOverviewResponse {
analytics: AnalyticsMetadata
granularity: string
health: UsageOverviewHealth
performance: UsageOverviewPerformance
cost: UsageOverviewCost
}
export interface UsageDiagnosticItem {
key: string
name: string
accountPlanType: string | null
accountPlanTypeDisplay: string | null
requestCount: number
successCount: number
errorCount: number
errorRate: number
requestShare: number
averageLatencyMs: number | null
latencyP95Ms: number | null
firstTokenP95Ms: number | null
nonCompletionCount: number
nonCompletionRate: number
retryCount: number
retryRate: number
estimatedCost: string | null
attemptCount: number
totalTokens: number
}
export interface UsageDiagnosticsResponse {
analytics: AnalyticsMetadata
dimension: string
items: UsageDiagnosticItem[]
}
// 请求参数类型:仅定义 API 边界的形状,调用方不依赖显式声明。
interface UsageRangeQuery {
startTime: string
endTime: string
provider?: string
}
interface PageQuery {
currentPage: number
pageSize: number
}
type UsagePageQuery = UsageRangeQuery & PageQuery & {
clientApiKeyName?: string
accountName?: string
model?: string
status?: UsageRecordStatus
}
interface UsageDetailQuery {
id: string
}
type UsageDiagnosticsQuery = UsageRangeQuery & { dimension: string, model?: string, statusCode?: number }
type DiagnosticsDownloadParams = { requestId: string } | { errorId: string }
export function getUsageRecords(data: UsagePageQuery, options: RequestOptions = {}) {
return request<UsageRecordsResponse>({
url: '/api/admin/usage/records',
method: 'GET',
params: data,
...options,
})
}
export function getUsageRecordDetail(data: UsageDetailQuery, options: RequestOptions = {}) {
return request<UsageRecordDetail>({
url: '/api/admin/usage/records/detail',
method: 'GET',
params: data,
...options,
})
}
export function getUsageRecordSummary(data: UsageRangeQuery, options: RequestOptions = {}) {
return request<UsageSummaryResponse>({
url: '/api/admin/usage/records/summary',
method: 'GET',
params: data,
...options,
})
}
export function getUsageRecordInsightsOverview(data: UsageRangeQuery, options: RequestOptions = {}) {
return request<UsageInsightsOverviewResponse>({
url: '/api/admin/usage/insights/overview',
method: 'GET',
params: data,
...options,
})
}
export function getUsageRecordInsightsDiagnostics(data: UsageDiagnosticsQuery, options: RequestOptions = {}) {
return request<UsageDiagnosticsResponse>({
url: '/api/admin/usage/insights/diagnostics',
method: 'GET',
params: data,
...options,
})
}
export function getDiagnosticsDownloadUrl(data: DiagnosticsDownloadParams) {
return `${API_BASE_URL}/api/admin/diagnostics/export?${new URLSearchParams(data)}`
}