Files
codex-proxy-rs/frontend/src/api/modules/accounts.ts
T
b4aea0ea09 fix(accounts): add missing model catalog fetch timestamps and capability hints (#560)
* fix(admin): clarify upstream model catalog evidence

* test(admin): cover model catalog timestamps over HTTP

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

* docs(api): define model catalog observation semantics

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

* fix(accounts): refine model catalog help and transport labels

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

---------

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Zyy <zyycn@users.noreply.github.com>
2026-10-30 23:15:05 +08:00

806 lines
20 KiB
TypeScript

import type { RequestOptions } from '../request'
import type { RelativeTimeView } from '../time'
import type { AccountGroupRef } from './groups'
import request from '../request'
export type AccountStatus
= 'normal' | 'quota_exhausted' | 'rate_limited' | 'disabled' | 'error'
export type AccountErrorReason
= 'account_unverified'
| 'access_token_expired'
| 'credential_expired'
| 'credential_invalid'
| 'account_banned'
export interface AccountQuotaWindow {
key: string
group: string
limitId: string | null
limitName: string | null
role: 'primary' | 'secondary' | 'monthly' | null
windowSeconds: number | null
labelDisplay: string
windowLabelDisplay: string
usedPercent: number | null
usedPercentDisplay: string
limitReached: boolean
localUsage?: unknown
resetAtDisplay: string
}
export interface AccountQuotaCredits {
hasCredits: boolean
unlimited: boolean
balance: string | null
}
export interface AccountProtectionRule {
id: string
name: string
}
export interface AccountQuota {
refreshedAt: string | null
refreshedAtRelative: RelativeTimeView | null
refreshedAtDisplay: string
limitReached: boolean
// 429 临时限流(Redis 冷却)到期时间;非限流中为 null。
rateLimitedUntil: string | null
rateLimitRecoveryDisplay: string | null
rateLimitReason: 'upstream_rate_limit' | 'error_protection' | null
protectionRules: AccountProtectionRule[]
recoveryProbeRequired: boolean
windows: AccountQuotaWindow[]
credits: AccountQuotaCredits | null
}
export interface AccountCurrencyCost {
currency: string
estimatedAmount: string
estimatedAmountDisplay: string
}
export interface AccountModelUsage {
model: string
requestCount: number
requestCountDisplay: string
successRate: number | null
successRateDisplay: string
inputTokens: number | null
inputTokensDisplay: string
outputTokens: number | null
outputTokensDisplay: string
cachedInputTokens: number | null
cachedInputTokensDisplay: string
imageInputTokens: number | null
imageInputTokensDisplay: string
imageOutputTokens: number | null
imageOutputTokensDisplay: string
imageRequestCount: number
imageRequestCountDisplay: string
imageRequestFailedCount: number
imageRequestFailedCountDisplay: string
totalTokens: number | null
totalTokensDisplay: string
billingAmountUsd: string | null
billingAmountUsdDisplay: string
costEstimateStatus: string
knownCostCount: number
partialCostCount: number
unknownCostCount: number
costs: AccountCurrencyCost[]
lastUsedAt: string
lastUsedAtDisplay: string
lastUsedAtFullDisplay: string | null
}
export interface AccountUsage {
windowKind: 'general' | 'weekly' | 'monthly' | 'quota'
requestCount: number | null
requestCountDisplay: string
inputTokens: number | null
inputTokensDisplay: string
outputTokens: number | null
outputTokensDisplay: string
cachedInputTokens: number | null
cachedInputTokensDisplay: string
reasoningOutputTokens: number | null
reasoningOutputTokensDisplay: string
imageInputTokens: number | null
imageInputTokensDisplay: string
imageOutputTokens: number | null
imageOutputTokensDisplay: string
imageRequestCount: number | null
imageRequestCountDisplay: string
imageRequestFailedCount: number | null
imageRequestFailedCountDisplay: string
totalTokens: number | null
totalTokensDisplay: string
cacheWriteInputTokens: number | null
cacheWriteInputTokensDisplay: string
lastUsedAt: string | null
lastUsedAtDisplay: string
lastUsedAtFullDisplay: string | null
costEstimateStatus: string
knownCostCount: number | null
partialCostCount: number | null
unknownCostCount: number | null
costs: AccountCurrencyCost[]
models: AccountModelUsage[]
}
export interface AccountModelAccess {
mode: 'all' | 'allowlist' | 'denylist'
models: string[]
}
export interface Account {
capabilities: AccountCapabilities
outboundProxyId: string | null
outboundProxyEndpoint: string | null
id: string
name: string
notes: string | null
provider: string
resourceRef: string
email: string | null
accountId: string | null
userId: string | null
label: string | null
planType: string | null
planTypeDisplay: string
authenticationKind: string
hasRefreshToken: boolean
status: AccountStatus
errorReason: AccountErrorReason | null
errorMessage: string | null
enabled: boolean
concurrencyLimit: number | null
capacity: {
usedSlots: number | null
totalSlots: number | null
}
weight: number
modelAccess: AccountModelAccess
accessTokenExpiresAt: string | null
accessTokenExpiresAtDisplay: string | null
refreshTokenExpiresAt: string | null
nextRefreshAt: string | null
nextRefreshAtDisplay: string | null
addedAt: string
addedAtDisplay: string
updatedAt: string
updatedAtDisplay: string
quota: AccountQuota
usage: AccountUsage
groups: AccountGroupRef[]
}
export interface AccountCapabilities {
quota: boolean
quotaRefresh: boolean
profile: boolean
subscription: boolean
avatar: boolean
resetCredits: boolean
consumeResetCredit: boolean
}
export interface AccountQuotaForecast {
period: 'weekly' | 'monthly'
targetDays: number
extrapolated: boolean
source: {
label: string
usedPercent: number | null
usedPercentDisplay: string
observedAt: string | null
observedAtDisplay: string
resetAt: string
tokensDisplay: string
usdDisplay: string
} | null
unavailableReason: string | null
lowSample: boolean
incompleteCost: boolean
incompleteTokens: boolean
estimatedTokens: number | null
estimatedTokensDisplay: string
estimatedUsd: number | null
estimatedUsdDisplay: string
}
export interface AccountQuotaForecastResponse {
accountId: string
generatedAt: string
forecasts: AccountQuotaForecast[]
}
export interface AccountPageMeta {
page: number
pageSize: number
total: number
totalPages: number
}
export interface AccountSummary {
total: number
normal: number
quotaExhausted: number
rateLimited: number
disabled: number
error: number
}
export interface AccountListResponse {
items: Account[]
page: AccountPageMeta
summary: AccountSummary
}
export interface AccountRefreshResponse {
account: Account
result?: string
error?: string
}
export interface AccountQuotaResponse {
account: Account
}
export interface AccountProfileStatisticsSummary {
totalTextTokens: number | null
peakTokens: number | null
longestTaskDurationMs: number | null
currentStreakDays: number | null
longestStreakDays: number | null
}
export interface AccountProfileDailyUsage {
date: string
tokens: number
}
export interface AccountProfileInvocation {
type: string
pluginId: string | null
pluginName: string | null
skillId: string | null
skillName: string | null
usageCount: number | null
}
export interface AccountProfileActivityInsights {
fastModePercent: number | null
reasoningEffort: string | null
reasoningEffortPercent: number | null
skillsExplored: number | null
totalSkillsUsed: number | null
totalThreads: number | null
invocations: AccountProfileInvocation[] | null
}
export interface AccountSubscription {
startsAt: string | null
startsAtDisplay: string | null
expiresAt: string
expiresAtDisplay: string
willRenew: boolean | null
billingPeriod: string | null
billingCurrency: string | null
observedAt: string
observedAtDisplay: string
}
export interface ProfileActivityCalendar {
rangeLabel: string
weeks: Array<{ key: string, monthLabel: string | null, cells: Array<{ date: string, dateDisplay: string, tokens: number, isFuture: boolean }> }>
}
export interface AccountProfileStatisticsResponse {
activityCalendar: ProfileActivityCalendar | null
displayName: string | null
username: string | null
imageUrl: string | null
hasStatsError: boolean
summary: AccountProfileStatisticsSummary
dailyUsage: AccountProfileDailyUsage[] | null
activityInsights: AccountProfileActivityInsights
}
export interface AccountPersonalInfoResponse {
profile: AccountProfileStatisticsResponse | null
profileError: string | null
subscription: AccountSubscription | null
}
export interface AccountResetCredit {
id: string
status: string | null
title: string | null
expiresAt: string | null
expiresAtDisplay: string | null
resetType: string | null
}
export interface AccountResetCreditsResponse {
availableCount: number
credits: AccountResetCredit[]
}
export interface AccountResetCreditResultResponse {
code: string
credit: AccountResetCredit | null
}
export interface AccountModelsResponse {
models: Array<{ id: string, label: string }>
observedAt?: string | null
observedAtDisplay?: string
}
export interface AccountModelCatalogResponse {
modelCount: number
observedAt: string
/** Codex `model_catalog_json` 的文件正文,原样落盘即可被客户端加载。 */
catalog: unknown
}
export interface AccountImportResponse {
importedCount: number
accountIds: string[]
}
export type ImportItemStatus = 'pending' | 'running' | 'succeeded' | 'failed' | 'unknown' | 'skipped'
export interface AccountImportTask {
taskId: string
createdAt: string
createdAtDisplay: string
finishedAt: string | null
finishedAtDisplay: string | null
stopRequested: boolean
total: number
counts: Record<ImportItemStatus, number> & { importedAccounts: number }
}
export interface AccountImportTaskItem {
index: number
provider: string
status: ImportItemStatus
accountIds: string[]
message: string | null
}
export interface AccountImportTaskDetail extends AccountImportTask {
items: AccountImportTaskItem[]
}
export interface AccountOAuthCompleteResponse {
accountId: string
}
export interface AccountUpdateResponse {
accountId: string
configRevision: number
}
export interface AccountBatchUpdateResponse {
accountIds: string[]
configRevision: number
}
export interface AccountDeletionResponse {
deletedCount: number
accountIds: string[]
}
export interface AccountOAuthStartResponse {
flowId: string
authorizationUrl: string
expiresAt: string
}
// 请求参数类型:仅定义 API 边界的形状,调用方不依赖显式声明。
interface AccountListParams {
page: number
pageSize: number
search?: string
provider?: string
status?: string
groupId?: string
sortBy?: string
sortDirection?: string
}
interface AccountIdParam {
accountId: string
}
interface AccountResetCreditConsumeParam extends AccountIdParam {
creditId?: string
redeemRequestId: string
}
interface AccountUpdateParam {
connection?: { baseUrl?: string, transport: ApiKeyConfiguration['transport'], apiKey?: string }
outboundProxyUrl?: string
outboundProxyId?: string
accountId: string
notes?: string
enabled: boolean
concurrencyLimit: number | null
weight: number
modelAccess?: AccountModelAccess
groupIds: string[]
}
interface AccountBatchUpdateParam {
outboundProxyUrl?: string
outboundProxyId?: string
accountIds: string[]
enabled?: boolean
concurrencyLimit?: number | null
weight?: number
modelAccess?: AccountModelAccess
groupIds?: string[]
}
interface AccountDeleteParams {
provider: string
accountIds: string[]
}
export interface AccountImportSettings {
notes?: string
enabled: boolean
concurrencyLimit: number | null
weight: number
modelAccess?: AccountModelAccess
groupIds: string[]
}
export interface AccountImportParam {
outboundProxyId?: string
settings?: AccountImportSettings
provider: string
data: unknown
}
interface AccountImportTaskIdParam {
taskId: string
}
interface CreateAccountImportTaskParam {
submissionId: string
items: AccountImportParam[]
}
interface AccountOAuthStartParam {
outboundProxyUrl?: string
outboundProxyId?: string
provider: string
name: string
accountId?: string
}
interface AccountOAuthCompleteParam {
settings?: AccountImportSettings
provider: string
flowId: string
callbackUrl: string
}
interface AccountExportParam {
accountIds: string
confirm: string
}
interface AccountExportResponse {
exportedAt: string
fileName: string
documents: unknown[]
}
interface AccountImportTaskListResponse {
items: AccountImportTask[]
}
interface AccountDetailResponse {
account: Account
credentialConfiguration?: Record<string, unknown>
}
interface AccountResetCreditAutomationParam extends AccountIdParam {
enabled: boolean
}
interface AccountConcurrencyParams extends AccountIdParam {
page: number
pageSize: number
}
export function getAccounts(data: AccountListParams, options: RequestOptions = {}) {
return request<AccountListResponse>({
url: '/api/admin/accounts',
method: 'GET',
params: data,
...options,
})
}
export function exportAccounts(data: AccountExportParam) {
return request<AccountExportResponse>({
url: '/api/admin/accounts/export',
method: 'GET',
params: data,
})
}
export function refreshAccount(data: AccountIdParam) {
return request<AccountRefreshResponse>({
url: '/api/admin/accounts/refresh',
method: 'POST',
data,
})
}
export function recoverAccount(data: AccountIdParam) {
return request<AccountRefreshResponse>({
url: '/api/admin/accounts/recover',
method: 'POST',
data,
})
}
export function getAccountPersonalInfo(data: AccountIdParam, options: RequestOptions = {}) {
return request<AccountPersonalInfoResponse>({
url: '/api/admin/accounts/personal-info',
method: 'GET',
params: data,
...options,
})
}
export function getAccountQuotaForecast(data: AccountIdParam, options: RequestOptions = {}) {
return request<AccountQuotaForecastResponse>({
url: '/api/admin/accounts/quota-forecast',
method: 'GET',
params: data,
...options,
})
}
export function accountProfileAvatarUrl(accountId: string, sourceUrl: string) {
const params = new URLSearchParams({
accountId,
version: stableAvatarVersion(sourceUrl),
})
return `/api/admin/accounts/profile-avatar?${params.toString()}`
}
function stableAvatarVersion(value: string) {
let hash = 0
for (const character of value)
hash = (hash * 33 + (character.codePointAt(0) ?? 0)) % 2_147_483_647
return hash.toString(36)
}
export function refreshAccountQuota(data: AccountIdParam, options: RequestOptions = {}) {
return request<AccountQuotaResponse>({
url: '/api/admin/accounts/quota/refresh',
method: 'POST',
data,
...options,
})
}
export function getAccountResetCredits(data: AccountIdParam, options: RequestOptions = {}) {
return request<AccountResetCreditsResponse>({
url: '/api/admin/accounts/reset-credits',
method: 'GET',
params: data,
...options,
})
}
export function consumeAccountResetCredit(data: AccountResetCreditConsumeParam, options: RequestOptions = {}) {
return request<AccountResetCreditResultResponse>({
url: '/api/admin/accounts/reset-credits',
method: 'POST',
data,
...options,
})
}
export function getAccountModels(data: AccountIdParam, options: RequestOptions = {}) {
return request<AccountModelsResponse>({
url: '/api/admin/accounts/models',
method: 'GET',
params: data,
...options,
})
}
export function getAccountModelCatalog(data: AccountIdParam, options: RequestOptions = {}) {
return request<AccountModelCatalogResponse>({
url: '/api/admin/accounts/models/catalog',
method: 'GET',
params: data,
...options,
})
}
export function refreshAccountModels(data: AccountIdParam, options: RequestOptions = {}) {
return request<AccountModelsResponse>({
url: '/api/admin/accounts/models/refresh',
method: 'POST',
data,
...options,
})
}
export function importAccounts(data: AccountImportParam, options: RequestOptions = {}) {
return request<AccountImportResponse>({
url: '/api/admin/accounts/import',
method: 'POST',
...providerInputBody(data),
...options,
})
}
export function createAccountImportTask(data: CreateAccountImportTaskParam) {
return request<AccountImportTask>({
url: '/api/admin/accounts/import-tasks',
method: 'POST',
...providerInputBody(data),
})
}
export function getAccountImportTasks(options: RequestOptions = {}) {
return request<AccountImportTaskListResponse>({
url: '/api/admin/accounts/import-tasks',
method: 'GET',
...options,
})
}
export function getAccountImportTask(data: AccountImportTaskIdParam, options: RequestOptions = {}) {
return request<AccountImportTaskDetail>({
url: '/api/admin/accounts/import-tasks/detail',
method: 'GET',
params: data,
...options,
})
}
export function stopAccountImportTask(data: AccountImportTaskIdParam) {
return request<AccountImportTaskDetail>({
url: '/api/admin/accounts/import-tasks/stop',
method: 'POST',
data,
})
}
export function updateAccount(data: AccountUpdateParam) {
return request<AccountUpdateResponse>({
url: '/api/admin/accounts/update',
method: 'POST',
data,
})
}
export function batchUpdateAccounts(data: AccountBatchUpdateParam) {
return request<AccountBatchUpdateResponse>({
url: '/api/admin/accounts/batch-update',
method: 'POST',
data,
})
}
export function deleteAccounts(data: AccountDeleteParams, options: RequestOptions = {}) {
return request<AccountDeletionResponse>({
url: '/api/admin/accounts/delete',
method: 'POST',
data,
...options,
})
}
export function startAccountOAuth(data: AccountOAuthStartParam, options: RequestOptions = {}) {
return request<AccountOAuthStartResponse>({
url: '/api/admin/accounts/oauth/start',
method: 'POST',
...providerInputBody(data),
...options,
})
}
function providerInputBody(data: object) {
// Provider 文档是不透明 JSON,先编码,避免 HTTP 客户端合并配置时过滤特殊字段名。
return {
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify(data),
}
}
export function completeAccountOAuth(data: AccountOAuthCompleteParam, options: RequestOptions = {}) {
return request<AccountOAuthCompleteResponse>({
url: '/api/admin/accounts/oauth/complete',
method: 'POST',
data,
...options,
})
}
export interface ApiKeyConfiguration {
base_url: string
transport: 'http' | 'prefer_websocket'
}
export function getAccountDetail(data: AccountIdParam, options: RequestOptions = {}) {
return request<AccountDetailResponse>({
url: '/api/admin/accounts/detail',
method: 'GET',
params: data,
...options,
})
}
export interface AccountResetCreditAutomation {
enabled: boolean
leadMinutes: number
nextDiscoveryAtDisplay: string | null
nextConsumeAtDisplay: string | null
lastResult: string | null
}
export function getAccountResetCreditAutomation(data: AccountIdParam, options: RequestOptions = {}) {
return request<AccountResetCreditAutomation>({
url: '/api/admin/accounts/reset-credits/automation',
method: 'GET',
params: data,
...options,
})
}
export function setAccountResetCreditAutomation(data: AccountResetCreditAutomationParam, options: RequestOptions = {}) {
return request<AccountResetCreditAutomation>({
url: '/api/admin/accounts/reset-credits/automation',
method: 'POST',
data,
...options,
})
}
export interface AccountConcurrencyEntry {
leaseId: string
requestId: string | null
clientKeyId: string | null
clientKeyName: string | null
model: string | null
clientIp: string | null
startedAt: string | null
startedAtDisplay: string | null
occupiedMs: number | null
}
export interface AccountConcurrencyPage {
accountId: string
entries: AccountConcurrencyEntry[]
total: number
page: number
pageSize: number
observedAt: string
}
export function getAccountConcurrency(data: AccountConcurrencyParams, options: RequestOptions = {}) {
return request<AccountConcurrencyPage>({
url: '/api/admin/accounts/concurrency',
method: 'GET',
params: data,
...options,
})
}