fix: restore security hardening compatibility and validation

Restore authorized rule reveal, explicit full HTTP capture and retention, video task business fields, and valid payment URLs. Add opt-in credential preservation for trusted recovery, fix frontend type contracts and async races, and eliminate PostgreSQL test fixture resource leaks. Document audit coverage and successful fmt and CI-scoped Clippy checks.
This commit is contained in:
elky
2026-09-07 21:14:27 +08:00
parent a5c3699ae9
commit a90d564931
191 changed files with 6785 additions and 1643 deletions
+17 -21
View File
@@ -1,5 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { AxiosAdapter, AxiosInstance, InternalAxiosRequestConfig } from 'axios'
import type { AxiosAdapter, InternalAxiosRequestConfig } from 'axios'
import apiClient, {
AUTH_SESSION_SIGNAL_KEY,
@@ -8,10 +8,6 @@ import apiClient, {
} from '@/api/client'
import { cache, cachedRequest } from '@/utils/cache'
type TestableApiClient = typeof apiClient & {
client: AxiosInstance
}
describe('apiClient auth state change event', () => {
beforeEach(() => {
localStorage.clear()
@@ -55,10 +51,10 @@ describe('apiClient auth state change event', () => {
})
it('restores a session through the refresh cookie and stores the result in memory only', async () => {
const rawClient = apiClient as TestableApiClient
const previousAdapter = rawClient.client.defaults.adapter
const rawClient = apiClient['client']
const previousAdapter = rawClient.defaults.adapter
rawClient.client.defaults.adapter = (async (config: InternalAxiosRequestConfig) => ({
rawClient.defaults.adapter = (async (config: InternalAxiosRequestConfig) => ({
data: { access_token: 'restored-access-token' },
status: 200,
statusText: 'OK',
@@ -72,16 +68,16 @@ describe('apiClient auth state change event', () => {
expect(localStorage.getItem('access_token')).toBeNull()
expect(sessionStorage.getItem('access_token')).toBeNull()
} finally {
rawClient.client.defaults.adapter = previousAdapter
rawClient.defaults.adapter = previousAdapter
}
})
it('does not resurrect a session when logout wins an in-flight restore', async () => {
const rawClient = apiClient as TestableApiClient
const previousAdapter = rawClient.client.defaults.adapter
const rawClient = apiClient['client']
const previousAdapter = rawClient.defaults.adapter
let resolveRefresh!: (response: Awaited<ReturnType<AxiosAdapter>>) => void
rawClient.client.defaults.adapter = (() => new Promise((resolve) => {
rawClient.defaults.adapter = (() => new Promise((resolve) => {
resolveRefresh = resolve
})) as AxiosAdapter
@@ -100,7 +96,7 @@ describe('apiClient auth state change event', () => {
await expect(restore).rejects.toThrow('Auth state changed')
expect(apiClient.getToken()).toBeNull()
} finally {
rawClient.client.defaults.adapter = previousAdapter
rawClient.defaults.adapter = previousAdapter
}
})
@@ -141,11 +137,11 @@ describe('apiClient auth state change event', () => {
})
it('sends auth refresh without a request body', async () => {
const rawClient = apiClient as TestableApiClient
const previousAdapter = rawClient.client.defaults.adapter
const rawClient = apiClient['client']
const previousAdapter = rawClient.defaults.adapter
const requests: InternalAxiosRequestConfig[] = []
rawClient.client.defaults.adapter = (async (config: InternalAxiosRequestConfig) => {
rawClient.defaults.adapter = (async (config: InternalAxiosRequestConfig) => {
requests.push(config)
return {
data: { access_token: 'new-access-token' },
@@ -165,16 +161,16 @@ describe('apiClient auth state change event', () => {
expect(requests[0].method).toBe('post')
expect(requests[0].data).toBeUndefined()
} finally {
rawClient.client.defaults.adapter = previousAdapter
rawClient.defaults.adapter = previousAdapter
}
})
it('authenticates protected gateway operational requests', async () => {
const rawClient = apiClient as TestableApiClient
const previousAdapter = rawClient.client.defaults.adapter
const rawClient = apiClient['client']
const previousAdapter = rawClient.defaults.adapter
const requests: InternalAxiosRequestConfig[] = []
rawClient.client.defaults.adapter = (async (config: InternalAxiosRequestConfig) => {
rawClient.defaults.adapter = (async (config: InternalAxiosRequestConfig) => {
requests.push(config)
return {
data: '',
@@ -193,7 +189,7 @@ describe('apiClient auth state change event', () => {
expect(requests[0].headers.Authorization).toBe('Bearer operational-access-token')
expect(requests[0].headers['X-Client-Device-Id']).toBeTruthy()
} finally {
rawClient.client.defaults.adapter = previousAdapter
rawClient.defaults.adapter = previousAdapter
}
})
})
@@ -0,0 +1,26 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { revealEndpointRules } from '../endpoints/endpoints'
const client = vi.hoisted(() => ({ get: vi.fn() }))
vi.mock('../client', () => ({ default: client }))
beforeEach(() => {
client.get.mockReset().mockResolvedValue({ data: { header_rules: [], body_rules: [], response_header_rules: [] } })
})
describe('endpoint rule reveal API', () => {
it('uses the scoped route and forwards cancellation', async () => {
const controller = new AbortController()
await revealEndpointRules('endpoint/with?reserved', controller.signal)
expect(client.get).toHaveBeenCalledWith(
'/api/admin/endpoints/endpoint%2Fwith%3Freserved/rules/reveal',
{ signal: controller.signal },
)
})
it('fetches each reveal without retaining a cached response', async () => {
await revealEndpointRules('endpoint-1')
await revealEndpointRules('endpoint-1')
expect(client.get).toHaveBeenCalledTimes(2)
})
})
+27 -5
View File
@@ -215,7 +215,18 @@ export const adminWalletApi = {
credited_at: string | null
}
}> {
const response = await apiClient.post(`/api/admin/wallets/${walletId}/recharge`, payload)
const response = await apiClient.post<{
wallet: AdminWallet
payment_order: {
id: string
order_no: string
amount_usd: number
payment_method: string
status: string
created_at: string
credited_at: string | null
}
}>(`/api/admin/wallets/${walletId}/recharge`, payload)
return response.data
},
@@ -223,7 +234,10 @@ export const adminWalletApi = {
wallet: AdminWallet
transaction: WalletTransaction
}> {
const response = await apiClient.post(`/api/admin/wallets/${walletId}/adjust`, payload)
const response = await apiClient.post<{
wallet: AdminWallet
transaction: WalletTransaction
}>(`/api/admin/wallets/${walletId}/adjust`, payload)
return response.data
},
@@ -232,7 +246,11 @@ export const adminWalletApi = {
refund: RefundRequest
transaction: WalletTransaction
}> {
const response = await apiClient.post(
const response = await apiClient.post<{
wallet: AdminWallet
refund: RefundRequest
transaction: WalletTransaction
}>(
`/api/admin/wallets/${walletId}/refunds/${refundId}/process`,
{}
)
@@ -244,7 +262,11 @@ export const adminWalletApi = {
refund: RefundRequest
transaction: WalletTransaction | null
}> {
const response = await apiClient.post(
const response = await apiClient.post<{
wallet: AdminWallet
refund: RefundRequest
transaction: WalletTransaction | null
}>(
`/api/admin/wallets/${walletId}/refunds/${refundId}/fail`,
payload
)
@@ -256,7 +278,7 @@ export const adminWalletApi = {
refundId: string,
payload: RefundCompleteRequest
): Promise<{ refund: RefundRequest }> {
const response = await apiClient.post(
const response = await apiClient.post<{ refund: RefundRequest }>(
`/api/admin/wallets/${walletId}/refunds/${refundId}/complete`,
payload
)
+7 -2
View File
@@ -8,6 +8,11 @@ import type { ApiKeyInstallSession, InstallSessionTargetSystem, InstallTargetCli
const SYSTEM_DATA_IMPORT_TIMEOUT_MS = 10 * 60 * 1000
const ALL_SYSTEM_CONFIGS_CACHE_KEY = 'admin:system:configs'
export interface AdminTimeSeriesPoint extends Record<string, unknown> {
date: string
total_cost: number
}
export interface AdminSystemConfigItem {
key: string
value: unknown
@@ -1594,12 +1599,12 @@ export const adminApi = {
provider_name?: string
},
options?: AdminAnalyticsRequestOptions
): Promise<Array<Record<string, unknown>>> {
): Promise<AdminTimeSeriesPoint[]> {
const cacheKey = buildCacheKey('admin:stats:time-series', params)
return cachedRequest(
cacheKey,
async () => {
const response = await apiClient.get<Array<Record<string, unknown>>>('/api/admin/stats/time-series', { params })
const response = await apiClient.get<AdminTimeSeriesPoint[]>('/api/admin/stats/time-series', { params })
return response.data
},
options?.skipCache ? 0 : 20 * 1000
+10 -10
View File
@@ -57,61 +57,61 @@ export const announcementApi = {
limit?: number
offset?: number
}): Promise<AnnouncementListResponse> {
const response = await apiClient.get('/api/announcements', { params })
const response = await apiClient.get<AnnouncementListResponse>('/api/announcements', { params })
return response.data
},
// 获取当前有效的公告
async getActiveAnnouncements(): Promise<AnnouncementListResponse> {
const response = await apiClient.get('/api/announcements/active')
const response = await apiClient.get<AnnouncementListResponse>('/api/announcements/active')
return response.data
},
// 获取单个公告
async getAnnouncement(id: string): Promise<Announcement> {
const response = await apiClient.get(`/api/announcements/${id}`)
const response = await apiClient.get<Announcement>(`/api/announcements/${id}`)
return response.data
},
// 标记公告为已读
async markAsRead(id: string): Promise<{ message: string }> {
const response = await apiClient.patch(`/api/announcements/${id}/read-status`)
const response = await apiClient.patch<{ message: string }>(`/api/announcements/${id}/read-status`)
return response.data
},
// 标记所有公告为已读
async markAllAsRead(): Promise<{ message: string }> {
const response = await apiClient.post('/api/announcements/read-all')
const response = await apiClient.post<{ message: string }>('/api/announcements/read-all')
return response.data
},
// 获取未读公告数量
async getUnreadCount(): Promise<{ unread_count: number }> {
const response = await apiClient.get('/api/announcements/users/me/unread-count')
const response = await apiClient.get<{ unread_count: number }>('/api/announcements/users/me/unread-count')
return response.data
},
async getRequiredUnreadAnnouncements(): Promise<AnnouncementListResponse> {
const response = await apiClient.get('/api/announcements/users/me/required-unread')
const response = await apiClient.get<AnnouncementListResponse>('/api/announcements/users/me/required-unread')
return response.data
},
// 管理员方法
// 创建公告
async createAnnouncement(data: CreateAnnouncementRequest): Promise<{ id: string; title: string; message: string }> {
const response = await apiClient.post('/api/announcements', data)
const response = await apiClient.post<{ id: string; title: string; message: string }>('/api/announcements', data)
return response.data
},
// 更新公告
async updateAnnouncement(id: string, data: UpdateAnnouncementRequest): Promise<{ message: string }> {
const response = await apiClient.put(`/api/announcements/${id}`, data)
const response = await apiClient.put<{ message: string }>(`/api/announcements/${id}`, data)
return response.data
},
// 删除公告
async deleteAnnouncement(id: string): Promise<{ message: string }> {
const response = await apiClient.delete(`/api/announcements/${id}`)
const response = await apiClient.delete<{ message: string }>(`/api/announcements/${id}`)
return response.data
}
}
+6 -6
View File
@@ -180,27 +180,27 @@ export const asyncTasksApi = {
const query = searchParams.toString()
const url = query ? `/api/admin/tasks?${query}` : '/api/admin/tasks'
const response = await apiClient.get(url)
const response = await apiClient.get<AsyncTaskListResponse>(url)
return response.data
},
async getStats(): Promise<AsyncTaskStatsResponse> {
const response = await apiClient.get('/api/admin/tasks/stats')
const response = await apiClient.get<AsyncTaskStatsResponse>('/api/admin/tasks/stats')
return response.data
},
async getDetail(taskId: string): Promise<AsyncTaskDetail> {
const response = await apiClient.get(`/api/admin/tasks/${taskId}`)
const response = await apiClient.get<AsyncTaskDetail>(`/api/admin/tasks/${taskId}`)
return response.data
},
async getEvents(taskId: string): Promise<{ items: AsyncTaskEvent[] }> {
const response = await apiClient.get(`/api/admin/tasks/${taskId}/events`)
const response = await apiClient.get<{ items: AsyncTaskEvent[] }>(`/api/admin/tasks/${taskId}/events`)
return response.data
},
async cancel(taskId: string): Promise<{ id: string; status: string; message: string }> {
const response = await apiClient.post(`/api/admin/tasks/${taskId}/cancel`)
const response = await apiClient.post<{ id: string; status: string; message: string }>(`/api/admin/tasks/${taskId}/cancel`)
return response.data
},
@@ -213,7 +213,7 @@ export const asyncTasksApi = {
},
async trigger(taskKey: string, payload: Record<string, unknown> = {}): Promise<{ run_id: string; status: string }> {
const response = await apiClient.post(`/api/admin/tasks/${taskKey}/trigger`, payload)
const response = await apiClient.post<{ run_id: string; status: string }>(`/api/admin/tasks/${taskKey}/trigger`, payload)
return response.data
},
}
+10 -4
View File
@@ -57,13 +57,13 @@ export const auditApi = {
limit?: number
offset?: number
}): Promise<AuditLogsResponse> {
const response = await apiClient.get('/api/monitoring/my-audit-logs', { params: filters })
const response = await apiClient.get<Record<string, unknown>>('/api/monitoring/my-audit-logs', { params: filters })
return normalizeAuditResponse(response.data)
},
// 获取所有审计日志 (管理员)
async getAuditLogs(filters?: AuditFilters): Promise<AuditLogsResponse> {
const response = await apiClient.get('/api/admin/monitoring/audit-logs', { params: filters })
const response = await apiClient.get<Record<string, unknown>>('/api/admin/monitoring/audit-logs', { params: filters })
return normalizeAuditResponse(response.data)
},
@@ -72,7 +72,10 @@ export const auditApi = {
activities: AuditLog[]
count: number
}> {
const response = await apiClient.get('/api/admin/monitoring/suspicious-activities', {
const response = await apiClient.get<{
activities: AuditLog[]
count: number
}>('/api/admin/monitoring/suspicious-activities', {
params: { hours, limit }
})
return response.data
@@ -83,7 +86,10 @@ export const auditApi = {
analysis: Record<string, unknown>
recommendations: string[]
}> {
const response = await apiClient.get(`/api/admin/monitoring/user-behavior/${userId}`, {
const response = await apiClient.get<{
analysis: Record<string, unknown>
recommendations: string[]
}>(`/api/admin/monitoring/user-behavior/${userId}`, {
params: { days }
})
return response.data
+21 -15
View File
@@ -89,7 +89,7 @@ export const cacheApi = {
* 获取缓存统计信息
*/
async getStats(): Promise<CacheStats> {
const response = await api.get('/api/admin/monitoring/cache/stats')
const response = await api.get<{ data: CacheStats }>('/api/admin/monitoring/cache/stats')
return response.data.data
},
@@ -97,7 +97,7 @@ export const cacheApi = {
* 获取缓存配置
*/
async getConfig(): Promise<CacheConfig> {
const response = await api.get('/api/admin/monitoring/cache/config')
const response = await api.get<{ data: CacheConfig }>('/api/admin/monitoring/cache/config')
return response.data.data
},
@@ -107,7 +107,7 @@ export const cacheApi = {
* @param userIdentifier 用户标识符,支持:用户名、邮箱、User UUID、API Key ID
*/
async getUserAffinity(userIdentifier: string): Promise<UserAffinity[] | null> {
const response = await api.get(`/api/admin/monitoring/cache/affinity/${userIdentifier}`)
const response = await api.get<{ status: string; affinities: UserAffinity[] }>(`/api/admin/monitoring/cache/affinity/${userIdentifier}`)
if (response.data.status === 'not_found') {
return null
}
@@ -151,7 +151,7 @@ export const cacheApi = {
* 清除所有缓存
*/
async clearAllCache(): Promise<{ count: number }> {
const response = await api.delete('/api/admin/monitoring/cache')
const response = await api.delete<{ count: number }>('/api/admin/monitoring/cache')
return response.data
},
@@ -159,7 +159,7 @@ export const cacheApi = {
* 清除指定Provider的所有缓存
*/
async clearProviderCache(providerId: string): Promise<{ count: number; provider_id: string }> {
const response = await api.delete(`/api/admin/monitoring/cache/providers/${providerId}`)
const response = await api.delete<{ count: number; provider_id: string }>(`/api/admin/monitoring/cache/providers/${providerId}`)
return response.data
},
@@ -167,7 +167,13 @@ export const cacheApi = {
* 获取缓存亲和性列表
*/
async listAffinities(keyword?: string): Promise<AffinityListResponse> {
const response = await api.get('/api/admin/monitoring/cache/affinities', {
const response = await api.get<{
data?: {
items?: UserAffinity[]
meta?: { total?: number }
matched_user_id?: string | null
}
}>('/api/admin/monitoring/cache/affinities', {
params: keyword ? { keyword } : undefined
})
const data = response.data.data ?? {}
@@ -212,7 +218,7 @@ export const redisCacheApi = {
* 获取 Redis 缓存分类概览
*/
async getCategories(): Promise<RedisCacheCategoriesResponse> {
const response = await api.get('/api/admin/monitoring/cache/redis-keys')
const response = await api.get<{ data: RedisCacheCategoriesResponse }>('/api/admin/monitoring/cache/redis-keys')
return response.data.data
},
@@ -220,7 +226,7 @@ export const redisCacheApi = {
* 清除指定分类的 Redis 缓存
*/
async clearCategory(category: string): Promise<{ status: string; message: string; category: string; deleted_count: number }> {
const response = await api.delete(`/api/admin/monitoring/cache/redis-keys/${category}`)
const response = await api.delete<{ status: string; message: string; category: string; deleted_count: number }>(`/api/admin/monitoring/cache/redis-keys/${category}`)
return response.data
}
}
@@ -308,7 +314,7 @@ export const cacheAnalysisApi = {
api_key_id?: string
hours?: number
}): Promise<TTLAnalysisResponse> {
const response = await api.get('/api/admin/usage/cache-affinity/ttl-analysis', { params })
const response = await api.get<TTLAnalysisResponse>('/api/admin/usage/cache-affinity/ttl-analysis', { params })
return response.data
},
@@ -320,7 +326,7 @@ export const cacheAnalysisApi = {
api_key_id?: string
hours?: number
}): Promise<CacheHitAnalysisResponse> {
const response = await api.get('/api/admin/usage/cache-affinity/hit-analysis', { params })
const response = await api.get<CacheHitAnalysisResponse>('/api/admin/usage/cache-affinity/hit-analysis', { params })
return response.data
},
@@ -339,7 +345,7 @@ export const cacheAnalysisApi = {
return cachedRequest(
cacheKey,
async () => {
const response = await api.get('/api/admin/usage/cache-affinity/interval-timeline', { params })
const response = await api.get<IntervalTimelineResponse>('/api/admin/usage/cache-affinity/interval-timeline', { params })
return response.data
},
30000
@@ -408,7 +414,7 @@ export const modelMappingCacheApi = {
* 获取模型映射缓存统计
*/
async getStats(): Promise<ModelMappingCacheStats> {
const response = await api.get('/api/admin/monitoring/cache/model-mapping/stats')
const response = await api.get<{ data: ModelMappingCacheStats }>('/api/admin/monitoring/cache/model-mapping/stats')
return response.data.data
},
@@ -416,7 +422,7 @@ export const modelMappingCacheApi = {
* 清除所有模型映射缓存
*/
async clearAll(): Promise<ClearModelMappingCacheResponse> {
const response = await api.delete('/api/admin/monitoring/cache/model-mapping')
const response = await api.delete<ClearModelMappingCacheResponse>('/api/admin/monitoring/cache/model-mapping')
return response.data
},
@@ -424,7 +430,7 @@ export const modelMappingCacheApi = {
* 清除指定模型名称的映射缓存
*/
async clearByName(modelName: string): Promise<ClearModelMappingCacheResponse> {
const response = await api.delete(`/api/admin/monitoring/cache/model-mapping/${encodeURIComponent(modelName)}`)
const response = await api.delete<ClearModelMappingCacheResponse>(`/api/admin/monitoring/cache/model-mapping/${encodeURIComponent(modelName)}`)
return response.data
},
@@ -432,7 +438,7 @@ export const modelMappingCacheApi = {
* 清除指定 Provider 和 GlobalModel 的映射缓存
*/
async clearProviderModel(providerId: string, globalModelId: string): Promise<ClearModelMappingCacheResponse> {
const response = await api.delete(`/api/admin/monitoring/cache/model-mapping/provider/${providerId}/${globalModelId}`)
const response = await api.delete<ClearModelMappingCacheResponse>(`/api/admin/monitoring/cache/model-mapping/provider/${providerId}/${globalModelId}`)
return response.data
}
}
+1 -1
View File
@@ -224,7 +224,7 @@ class ApiClient {
/**
* 处理响应错误
*/
private async handleResponseError(error: unknown): Promise<never> {
private async handleResponseError(error: unknown): Promise<AxiosResponse> {
// 请求被取消
if (axios.isCancel(error)) {
return Promise.reject(error)
+8 -8
View File
@@ -278,14 +278,14 @@ export interface RequestDetail {
end_to_end_first_byte_time_ms?: number | null
created_at: string
updated_at?: string | null
request_headers?: Record<string, unknown>
request_body?: Record<string, unknown>
provider_request_headers?: Record<string, unknown>
provider_request_body?: Record<string, unknown>
response_headers?: Record<string, unknown>
client_response_headers?: Record<string, unknown>
response_body?: Record<string, unknown>
client_response_body?: Record<string, unknown>
request_headers?: Record<string, unknown> | null
request_body?: Record<string, unknown> | null
provider_request_headers?: Record<string, unknown> | null
provider_request_body?: Record<string, unknown> | null
response_headers?: Record<string, unknown> | null
client_response_headers?: Record<string, unknown> | null
response_body?: Record<string, unknown> | null
client_response_body?: Record<string, unknown> | null
has_request_body?: boolean
has_provider_request_body?: boolean
has_response_body?: boolean
+16 -4
View File
@@ -17,7 +17,13 @@ export async function toggleAdaptiveMode(
rpm_limit: number | null
effective_limit: number | null
}> {
const response = await client.patch(`/api/admin/adaptive/keys/${keyId}/mode`, data)
const response = await client.patch<{
message: string
key_id: string
is_adaptive: boolean
rpm_limit: number | null
effective_limit: number | null
}>(`/api/admin/adaptive/keys/${keyId}/mode`, data)
return response.data
}
@@ -34,7 +40,13 @@ export async function setRpmLimit(
rpm_limit: number
previous_mode: string
}> {
const response = await client.patch(`/api/admin/adaptive/keys/${keyId}/limit`, null, {
const response = await client.patch<{
message: string
key_id: string
is_adaptive: boolean
rpm_limit: number
previous_mode: string
}>(`/api/admin/adaptive/keys/${keyId}/limit`, null, {
params: { limit }
})
return response.data
@@ -44,7 +56,7 @@ export async function setRpmLimit(
* 获取 Key 的自适应统计
*/
export async function getAdaptiveStats(keyId: string): Promise<AdaptiveStatsResponse> {
const response = await client.get(`/api/admin/adaptive/keys/${keyId}/stats`)
const response = await client.get<AdaptiveStatsResponse>(`/api/admin/adaptive/keys/${keyId}/stats`)
return response.data
}
@@ -52,6 +64,6 @@ export async function getAdaptiveStats(keyId: string): Promise<AdaptiveStatsResp
* 重置 Key 的学习状态
*/
export async function resetAdaptiveLearning(keyId: string): Promise<{ message: string; key_id: string }> {
const response = await client.delete(`/api/admin/adaptive/keys/${keyId}/learning`)
const response = await client.delete<{ message: string; key_id: string }>(`/api/admin/adaptive/keys/${keyId}/learning`)
return response.data
}
+20 -6
View File
@@ -1,11 +1,25 @@
import client from '../client'
import type { ProviderEndpoint, ProxyConfig, HeaderRule, BodyRule, FormatAcceptanceConfig } from './types'
export interface ProviderEndpointRules {
header_rules: HeaderRule[]
body_rules: BodyRule[]
response_header_rules: HeaderRule[]
}
export async function revealEndpointRules(endpointId: string, signal?: AbortSignal): Promise<ProviderEndpointRules> {
const response = await client.get<ProviderEndpointRules>(
`/api/admin/endpoints/${encodeURIComponent(endpointId)}/rules/reveal`,
{ signal },
)
return response.data
}
/**
* 获取指定 Provider 的所有 Endpoints
*/
export async function getProviderEndpoints(providerId: string): Promise<ProviderEndpoint[]> {
const response = await client.get(`/api/admin/endpoints/providers/${providerId}/endpoints`)
const response = await client.get<ProviderEndpoint[]>(`/api/admin/endpoints/providers/${providerId}/endpoints`)
return response.data
}
@@ -13,7 +27,7 @@ export async function getProviderEndpoints(providerId: string): Promise<Provider
* 获取 Endpoint 详情
*/
export async function getEndpoint(endpointId: string): Promise<ProviderEndpoint> {
const response = await client.get(`/api/admin/endpoints/${endpointId}`)
const response = await client.get<ProviderEndpoint>(`/api/admin/endpoints/${endpointId}`)
return response.data
}
@@ -36,7 +50,7 @@ export async function createEndpoint(
format_acceptance_config?: FormatAcceptanceConfig | null
}
): Promise<ProviderEndpoint> {
const response = await client.post(`/api/admin/endpoints/providers/${providerId}/endpoints`, data)
const response = await client.post<ProviderEndpoint>(`/api/admin/endpoints/providers/${providerId}/endpoints`, data)
return response.data
}
@@ -57,7 +71,7 @@ export async function updateEndpoint(
format_acceptance_config: FormatAcceptanceConfig | null
}>
): Promise<ProviderEndpoint> {
const response = await client.put(`/api/admin/endpoints/${endpointId}`, data)
const response = await client.put<ProviderEndpoint>(`/api/admin/endpoints/${endpointId}`, data)
return response.data
}
@@ -65,7 +79,7 @@ export async function updateEndpoint(
* 删除 Endpoint
*/
export async function deleteEndpoint(endpointId: string): Promise<{ message: string; affected_keys_count: number }> {
const response = await client.delete(`/api/admin/endpoints/${endpointId}`)
const response = await client.delete<{ message: string; affected_keys_count: number }>(`/api/admin/endpoints/${endpointId}`)
return response.data
}
@@ -75,6 +89,6 @@ export async function deleteEndpoint(endpointId: string): Promise<{ message: str
export async function getDefaultBodyRules(apiFormat: string, providerType?: string): Promise<{ api_format: string; body_rules: BodyRule[] }> {
const params: Record<string, string> = {}
if (providerType) params.provider_type = providerType
const response = await client.get(`/api/admin/endpoints/defaults/${apiFormat}/body-rules`, { params })
const response = await client.get<{ api_format: string; body_rules: BodyRule[] }>(`/api/admin/endpoints/defaults/${apiFormat}/body-rules`, { params })
return response.data
}
+19 -8
View File
@@ -12,6 +12,7 @@ import type {
// 重新导出路由相关类型供外部使用
export type {
GlobalModelResponse,
RoutingKeyInfo,
RoutingEndpointInfo,
RoutingModelMapping,
@@ -37,7 +38,7 @@ export async function getGlobalModels(params?: {
return cachedRequest(
key,
async () => {
const response = await client.get('/api/admin/models/global', { params })
const response = await client.get<GlobalModelListResponse>('/api/admin/models/global', { params })
return response.data
},
cacheTtlMs,
@@ -49,7 +50,7 @@ export async function getGlobalModels(params?: {
*/
export async function getGlobalModel(id: string): Promise<GlobalModelWithStats> {
return dedupedRequest(`global-models:detail:${id}`, async () => {
const response = await client.get(`/api/admin/models/global/${id}`)
const response = await client.get<GlobalModelWithStats>(`/api/admin/models/global/${id}`)
return response.data
})
}
@@ -58,7 +59,7 @@ export async function getGlobalModel(id: string): Promise<GlobalModelWithStats>
* 创建 GlobalModel
*/
export async function createGlobalModel(data: GlobalModelCreate): Promise<GlobalModelResponse> {
const response = await client.post('/api/admin/models/global', data)
const response = await client.post<GlobalModelResponse>('/api/admin/models/global', data)
return response.data
}
@@ -69,7 +70,7 @@ export async function updateGlobalModel(
id: string,
data: GlobalModelUpdate
): Promise<GlobalModelResponse> {
const response = await client.patch(`/api/admin/models/global/${id}`, data)
const response = await client.patch<GlobalModelResponse>(`/api/admin/models/global/${id}`, data)
return response.data
}
@@ -89,7 +90,7 @@ export async function deleteGlobalModel(
export async function batchDeleteGlobalModels(
ids: string[]
): Promise<{ success_count: number; failed: Array<{ id: string; error: string }> }> {
const response = await client.post('/api/admin/models/global/batch-delete', { ids })
const response = await client.post<{ success_count: number; failed: Array<{ id: string; error: string }> }>('/api/admin/models/global/batch-delete', { ids })
return response.data
}
@@ -113,7 +114,17 @@ export async function batchAssignToProviders(
error: string
}>
}> {
const response = await client.post(
const response = await client.post<{
success: Array<{
provider_id: string
provider_name: string
model_id?: string
}>
errors: Array<{
provider_id: string
error: string
}>
}>(
`/api/admin/models/global/${globalModelId}/assign-to-providers`,
data
)
@@ -128,7 +139,7 @@ export async function getGlobalModelProviders(globalModelId: string): Promise<{
total: number
}> {
return dedupedRequest(`global-models:providers:${globalModelId}`, async () => {
const response = await client.get(
const response = await client.get<{ providers: ModelCatalogProviderDetail[]; total: number }>(
`/api/admin/models/global/${globalModelId}/providers`
)
return response.data
@@ -141,7 +152,7 @@ export async function getGlobalModelProviders(globalModelId: string): Promise<{
export async function getGlobalModelRoutingPreview(
globalModelId: string
): Promise<ModelRoutingPreviewResponse> {
const response = await client.get(
const response = await client.get<ModelRoutingPreviewResponse>(
`/api/admin/models/global/${globalModelId}/routing`
)
return response.data
+28 -12
View File
@@ -14,7 +14,7 @@ import type {
* 获取健康状态摘要
*/
export async function getHealthSummary(): Promise<HealthSummary> {
const response = await client.get('/api/admin/endpoints/health/summary')
const response = await client.get<HealthSummary>('/api/admin/endpoints/health/summary')
return response.data
}
@@ -22,7 +22,7 @@ export async function getHealthSummary(): Promise<HealthSummary> {
* 获取 Endpoint 健康状态
*/
export async function getEndpointHealth(endpointId: string): Promise<HealthStatus> {
const response = await client.get(`/api/admin/endpoints/health/endpoint/${endpointId}`)
const response = await client.get<HealthStatus>(`/api/admin/endpoints/health/endpoint/${endpointId}`)
return response.data
}
@@ -30,7 +30,7 @@ export async function getEndpointHealth(endpointId: string): Promise<HealthStatu
* 获取 Key 健康状态
*/
export async function getKeyHealth(keyId: string): Promise<HealthStatus> {
const response = await client.get(`/api/admin/endpoints/health/key/${keyId}`)
const response = await client.get<HealthStatus>(`/api/admin/endpoints/health/key/${keyId}`)
return response.data
}
@@ -48,7 +48,15 @@ export async function recoverKeyHealth(keyId: string, apiFormat?: string): Promi
is_active: boolean
}
}> {
const response = await client.patch(`/api/admin/endpoints/health/keys/${keyId}`, null, {
const response = await client.patch<{
message: string
details: {
api_format?: string
health_score: number
circuit_breaker_open: boolean
is_active: boolean
}
}>(`/api/admin/endpoints/health/keys/${keyId}`, null, {
params: apiFormat ? { api_format: apiFormat } : undefined
})
return response.data
@@ -66,7 +74,15 @@ export async function recoverAllKeysHealth(): Promise<{
endpoint_id: string
}>
}> {
const response = await client.patch('/api/admin/endpoints/health/keys')
const response = await client.patch<{
message: string
recovered_count: number
recovered_keys: Array<{
key_id: string
key_name: string
endpoint_id: string
}>
}>('/api/admin/endpoints/health/keys')
return response.data
}
@@ -77,7 +93,7 @@ export async function getEndpointStatusMonitor(params?: {
lookback_hours?: number
per_format_limit?: number
}): Promise<EndpointStatusMonitorResponse> {
const response = await client.get('/api/admin/endpoints/health/api-formats', {
const response = await client.get<EndpointStatusMonitorResponse>('/api/admin/endpoints/health/api-formats', {
params
})
return response.data
@@ -90,7 +106,7 @@ export async function getPublicEndpointStatusMonitor(params?: {
lookback_hours?: number
per_format_limit?: number
}): Promise<PublicEndpointStatusMonitorResponse> {
const response = await client.get('/api/public/health/api-formats', {
const response = await client.get<PublicEndpointStatusMonitorResponse>('/api/public/health/api-formats', {
params
})
return response.data
@@ -104,7 +120,7 @@ export async function getModelStatusMonitor(params?: {
model_limit?: number
per_model_limit?: number
}): Promise<ModelStatusMonitorResponse> {
const response = await client.get('/api/admin/endpoints/health/models', {
const response = await client.get<ModelStatusMonitorResponse>('/api/admin/endpoints/health/models', {
params
})
return response.data
@@ -118,7 +134,7 @@ export async function getPublicModelStatusMonitor(params?: {
model_limit?: number
per_model_limit?: number
}): Promise<ModelStatusMonitorResponse> {
const response = await client.get('/api/public/health/models', {
const response = await client.get<ModelStatusMonitorResponse>('/api/public/health/models', {
params
})
return response.data
@@ -133,7 +149,7 @@ export async function getProviderStatusMonitor(params?: {
per_provider_model_limit?: number
per_model_limit?: number
}): Promise<ProviderStatusMonitorResponse> {
const response = await client.get('/api/admin/endpoints/health/providers', {
const response = await client.get<ProviderStatusMonitorResponse>('/api/admin/endpoints/health/providers', {
params
})
return response.data
@@ -146,7 +162,7 @@ export async function getHealthRelatedMonitor(params: {
related_limit?: number
per_item_limit?: number
}): Promise<HealthRelatedMonitorResponse> {
const response = await client.get('/api/admin/endpoints/health/related', {
const response = await client.get<HealthRelatedMonitorResponse>('/api/admin/endpoints/health/related', {
params
})
return response.data
@@ -159,7 +175,7 @@ export async function getPublicHealthRelatedMonitor(params: {
related_limit?: number
per_item_limit?: number
}): Promise<HealthRelatedMonitorResponse> {
const response = await client.get('/api/public/health/related', {
const response = await client.get<HealthRelatedMonitorResponse>('/api/public/health/related', {
params
})
return response.data
+18 -14
View File
@@ -37,7 +37,7 @@ export interface ModelCapabilitiesResponse {
* 获取所有能力定义
*/
export async function getAllCapabilities(): Promise<CapabilityDefinition[]> {
const response = await client.get('/api/capabilities')
const response = await client.get<{ capabilities: CapabilityDefinition[] }>('/api/capabilities')
return response.data.capabilities
}
@@ -45,7 +45,7 @@ export async function getAllCapabilities(): Promise<CapabilityDefinition[]> {
* 获取用户可配置的能力列表
*/
export async function getUserConfigurableCapabilities(): Promise<CapabilityDefinition[]> {
const response = await client.get('/api/capabilities/user-configurable')
const response = await client.get<{ capabilities: CapabilityDefinition[] }>('/api/capabilities/user-configurable')
return response.data.capabilities
}
@@ -53,7 +53,7 @@ export async function getUserConfigurableCapabilities(): Promise<CapabilityDefin
* 获取指定模型支持的能力列表
*/
export async function getModelCapabilities(modelName: string): Promise<ModelCapabilitiesResponse> {
const response = await client.get(`/api/capabilities/model/${encodeURIComponent(modelName)}`)
const response = await client.get<ModelCapabilitiesResponse>(`/api/capabilities/model/${encodeURIComponent(modelName)}`)
return response.data
}
@@ -68,7 +68,7 @@ export interface RevealKeyResult {
}
export async function revealEndpointKey(keyId: string): Promise<RevealKeyResult> {
const response = await client.get(`/api/admin/endpoints/keys/${keyId}/reveal`)
const response = await client.get<RevealKeyResult>(`/api/admin/endpoints/keys/${keyId}/reveal`)
return response.data
}
@@ -76,7 +76,7 @@ export async function revealEndpointKey(keyId: string): Promise<RevealKeyResult>
* 导出 OAuth Key 凭据(扁平 JSON,用于跨实例迁移)
*/
export async function exportKey(keyId: string): Promise<Record<string, unknown>> {
const response = await client.get(`/api/admin/endpoints/keys/${keyId}/export`)
const response = await client.get<Record<string, unknown>>(`/api/admin/endpoints/keys/${keyId}/export`)
return response.data
}
@@ -84,7 +84,7 @@ export async function exportKey(keyId: string): Promise<Record<string, unknown>>
* 删除 Key
*/
export async function deleteEndpointKey(keyId: string): Promise<{ message: string }> {
const response = await client.delete(`/api/admin/endpoints/keys/${keyId}`)
const response = await client.delete<{ message: string }>(`/api/admin/endpoints/keys/${keyId}`)
return response.data
}
@@ -98,7 +98,7 @@ export interface BatchDeleteKeysResult {
}
export async function batchDeleteEndpointKeys(ids: string[]): Promise<BatchDeleteKeysResult> {
const response = await client.post('/api/admin/endpoints/keys/batch-delete', { ids })
const response = await client.post<BatchDeleteKeysResult>('/api/admin/endpoints/keys/batch-delete', { ids })
return response.data
}
@@ -215,7 +215,7 @@ export async function addProviderKey(
model_exclude_patterns?: string[] // 模型排除规则
}
): Promise<EndpointAPIKey> {
const response = await client.post(`/api/admin/endpoints/providers/${providerId}/keys`, data)
const response = await client.post<EndpointAPIKey>(`/api/admin/endpoints/providers/${providerId}/keys`, data)
return response.data
}
@@ -251,7 +251,7 @@ export async function updateProviderKey(
}>,
requestOptions?: KeyRequestOptions,
): Promise<EndpointAPIKey> {
const response = await client.put(
const response = await client.put<EndpointAPIKey>(
`/api/admin/endpoints/keys/${keyId}`,
data,
requestOptions,
@@ -263,7 +263,7 @@ export async function updateProviderKey(
* 清除 Key 的 OAuth 失效标记
*/
export async function clearOAuthInvalid(keyId: string): Promise<{ message: string }> {
const response = await client.post(`/api/admin/endpoints/keys/${keyId}/clear-oauth-invalid`)
const response = await client.post<{ message: string }>(`/api/admin/endpoints/keys/${keyId}/clear-oauth-invalid`)
return response.data
}
@@ -275,7 +275,11 @@ export async function resetProviderKeyCycleStats(keyId: string): Promise<{
reset_at: number
windows: number
}> {
const response = await client.post(`/api/admin/endpoints/keys/${keyId}/reset-cycle-stats`)
const response = await client.post<{
message: string
reset_at: number
windows: number
}>(`/api/admin/endpoints/keys/${keyId}/reset-cycle-stats`)
return response.data
}
@@ -311,7 +315,7 @@ export async function refreshProviderQuota(
keyIds?: string[],
): Promise<RefreshQuotaResult> {
const body = keyIds && keyIds.length > 0 ? { key_ids: keyIds } : undefined
const response = await client.post(
const response = await client.post<RefreshQuotaResult>(
`/api/admin/endpoints/providers/${providerId}/refresh-quota`,
body,
{ timeout: 5 * 60 * 1000 },
@@ -350,7 +354,7 @@ export async function consumeCodexResetCredit(
keyId: string,
payload: ConsumeCodexResetCreditPayload,
): Promise<ConsumeCodexResetCreditResult> {
const response = await client.post(
const response = await client.post<ConsumeCodexResetCreditResult>(
`/api/admin/endpoints/keys/${keyId}/codex-reset-credit/consume`,
payload,
{ timeout: 5 * 60 * 1000 },
@@ -383,7 +387,7 @@ export async function batchImportOAuth(
credentials: string,
proxyNodeId?: string
): Promise<BatchImportResult> {
const response = await client.post(`/api/admin/provider-oauth/providers/${providerId}/batch-import`, {
const response = await client.post<BatchImportResult>(`/api/admin/provider-oauth/providers/${providerId}/batch-import`, {
credentials,
proxy_node_id: proxyNodeId || undefined,
})
+20 -10
View File
@@ -19,7 +19,7 @@ export async function getProviderModels(
limit?: number
}
): Promise<Model[]> {
const response = await client.get(`/api/admin/providers/${providerId}/models`, { params })
const response = await client.get<Model[]>(`/api/admin/providers/${providerId}/models`, { params })
return response.data
}
@@ -30,7 +30,7 @@ export async function createModel(
providerId: string,
data: ModelCreate
): Promise<Model> {
const response = await client.post(`/api/admin/providers/${providerId}/models`, data)
const response = await client.post<Model>(`/api/admin/providers/${providerId}/models`, data)
return response.data
}
@@ -41,7 +41,7 @@ export async function getModel(
providerId: string,
modelId: string
): Promise<Model> {
const response = await client.get(`/api/admin/providers/${providerId}/models/${modelId}`)
const response = await client.get<Model>(`/api/admin/providers/${providerId}/models/${modelId}`)
return response.data
}
@@ -53,7 +53,7 @@ export async function updateModel(
modelId: string,
data: ModelUpdate
): Promise<Model> {
const response = await client.patch(`/api/admin/providers/${providerId}/models/${modelId}`, data)
const response = await client.patch<Model>(`/api/admin/providers/${providerId}/models/${modelId}`, data)
return response.data
}
@@ -64,7 +64,7 @@ export async function deleteModel(
providerId: string,
modelId: string
): Promise<{ message: string }> {
const response = await client.delete(`/api/admin/providers/${providerId}/models/${modelId}`)
const response = await client.delete<{ message: string }>(`/api/admin/providers/${providerId}/models/${modelId}`)
return response.data
}
@@ -75,7 +75,7 @@ export async function batchCreateModels(
providerId: string,
modelsData: ModelCreate[]
): Promise<Model[]> {
const response = await client.post(`/api/admin/providers/${providerId}/models/batch`, modelsData)
const response = await client.post<Model[]>(`/api/admin/providers/${providerId}/models/batch`, modelsData)
return response.data
}
@@ -83,7 +83,7 @@ export async function batchCreateModels(
* 获取统一模型目录
*/
export async function getModelCatalog(): Promise<ModelCatalogResponse> {
const response = await client.get('/api/admin/models/catalog')
const response = await client.get<ModelCatalogResponse>('/api/admin/models/catalog')
return response.data
}
@@ -93,7 +93,7 @@ export async function getModelCatalog(): Promise<ModelCatalogResponse> {
export async function getProviderAvailableSourceModels(
providerId: string
): Promise<ProviderAvailableSourceModelsResponse> {
const response = await client.get(`/api/admin/providers/${providerId}/available-source-models`)
const response = await client.get<ProviderAvailableSourceModelsResponse>(`/api/admin/providers/${providerId}/available-source-models`)
return response.data
}
@@ -114,7 +114,17 @@ export async function batchAssignModelsToProvider(
error: string
}>
}> {
const response = await client.post(
const response = await client.post<{
success: Array<{
global_model_id: string
global_model_name: string
model_id: string
}>
errors: Array<{
global_model_id: string
error: string
}>
}>(
`/api/admin/providers/${providerId}/assign-global-models`,
{ global_model_ids: globalModelIds }
)
@@ -137,7 +147,7 @@ export async function importModelsFromUpstream(
price_per_request?: number
}
): Promise<ImportFromUpstreamResponse> {
const response = await client.post(
const response = await client.post<ImportFromUpstreamResponse>(
`/api/admin/providers/${providerId}/import-from-upstream`,
{
model_ids: modelIds,
+4 -3
View File
@@ -2,6 +2,7 @@ import client from '../client'
import { buildCacheKey, cachedRequest } from '@/utils/cache'
import type {
AllowedModels,
ProviderType,
OAuthOrganizationInfo,
ProxyConfig,
UpstreamMetadata,
@@ -77,7 +78,7 @@ export async function resetPoolCost(
export interface PoolOverviewItem {
provider_id: string
provider_name: string
provider_type: string
provider_type: ProviderType
total_keys: number
active_keys: number
cooldown_count: number
@@ -508,7 +509,7 @@ export async function batchActionPoolKeys(
providerId: string,
body: PoolBatchAction,
): Promise<{ affected: number; message: string; task_id?: string }> {
const response = await client.post(
const response = await client.post<{ affected: number; message: string; task_id?: string }>(
`/api/admin/pool/${providerId}/keys/batch-action`,
body,
{ timeout: POOL_BATCH_ACTION_TIMEOUT_MS },
@@ -561,7 +562,7 @@ export async function getPoolBatchDeleteTask(
export async function cleanupBannedPoolKeys(
providerId: string,
): Promise<{ affected: number; message: string }> {
const response = await client.post(
const response = await client.post<{ affected: number; message: string }>(
`/api/admin/pool/${providerId}/keys/cleanup-banned`,
undefined,
{ timeout: POOL_BATCH_ACTION_TIMEOUT_MS },
+11 -11
View File
@@ -230,14 +230,14 @@ export function normalizeBatchImportCredentials(text: string): BatchImportCreden
}
export async function refreshProviderOAuth(keyId: string): Promise<ProviderOAuthCompleteResponse> {
const resp = await client.post(`/api/admin/provider-oauth/keys/${keyId}/refresh`)
const resp = await client.post<ProviderOAuthCompleteResponse>(`/api/admin/provider-oauth/keys/${keyId}/refresh`)
return resp.data
}
// Provider-level OAuth (不需要预先创建 key)
export async function startProviderLevelOAuth(providerId: string): Promise<ProviderOAuthStartResponse> {
const resp = await client.post(`/api/admin/provider-oauth/providers/${providerId}/start`)
const resp = await client.post<ProviderOAuthStartResponse>(`/api/admin/provider-oauth/providers/${providerId}/start`)
return resp.data
}
@@ -245,7 +245,7 @@ export async function completeProviderLevelOAuth(
providerId: string,
data: ProviderOAuthCompleteRequest
): Promise<ProviderOAuthCompleteResponseWithKey> {
const resp = await client.post(`/api/admin/provider-oauth/providers/${providerId}/complete`, data)
const resp = await client.post<ProviderOAuthCompleteResponseWithKey>(`/api/admin/provider-oauth/providers/${providerId}/complete`, data)
return resp.data
}
@@ -253,7 +253,7 @@ export async function authorizeProviderWithCookie(
providerId: string,
data: ProviderCookieAuthorizeRequest
): Promise<ProviderOAuthCompleteResponseWithKey> {
const resp = await client.post(
const resp = await client.post<ProviderOAuthCompleteResponseWithKey>(
`/api/admin/provider-oauth/providers/${providerId}/cookie-authorize`,
data,
{ timeout: CLAUDE_COOKIE_AUTHORIZE_TIMEOUT_MS },
@@ -265,7 +265,7 @@ export async function startProviderCookieAuthorizeTask(
providerId: string,
data: ProviderCookieAuthorizeBatchTaskRequest,
): Promise<OAuthBatchImportTaskStartResponse> {
const resp = await client.post(
const resp = await client.post<OAuthBatchImportTaskStartResponse>(
`/api/admin/provider-oauth/providers/${providerId}/cookie-authorize/tasks`,
data,
)
@@ -276,7 +276,7 @@ export async function getProviderCookieAuthorizeTaskStatus(
providerId: string,
taskId: string,
): Promise<OAuthBatchImportTaskStatusResponse> {
const resp = await client.get(
const resp = await client.get<OAuthBatchImportTaskStatusResponse>(
`/api/admin/provider-oauth/providers/${providerId}/cookie-authorize/tasks/${taskId}`,
)
return resp.data
@@ -313,7 +313,7 @@ export async function importProviderRefreshToken(
headers?: Record<string, string>
}
): Promise<ProviderOAuthCompleteResponseWithKey> {
const resp = await client.post(`/api/admin/provider-oauth/providers/${providerId}/import-refresh-token`, data)
const resp = await client.post<ProviderOAuthCompleteResponseWithKey>(`/api/admin/provider-oauth/providers/${providerId}/import-refresh-token`, data)
return resp.data
}
@@ -325,7 +325,7 @@ export async function startBatchImportOAuthTask(
const route = containsAgentIdentityImport(credentials)
? 'agent-identity-import/tasks'
: 'batch-import/tasks'
const resp = await client.post(`/api/admin/provider-oauth/providers/${providerId}/${route}`, {
const resp = await client.post<OAuthBatchImportTaskStartResponse>(`/api/admin/provider-oauth/providers/${providerId}/${route}`, {
credentials,
proxy_node_id: proxyNodeId || undefined,
})
@@ -339,7 +339,7 @@ export async function getBatchImportOAuthTaskStatus(
const route = taskId.startsWith('agent-identity-')
? 'agent-identity-import/tasks'
: 'batch-import/tasks'
const resp = await client.get(`/api/admin/provider-oauth/providers/${providerId}/${route}/${taskId}`)
const resp = await client.get<OAuthBatchImportTaskStatusResponse>(`/api/admin/provider-oauth/providers/${providerId}/${route}/${taskId}`)
return resp.data
}
@@ -412,7 +412,7 @@ export async function startDeviceAuthorize(
providerId: string,
data: DeviceAuthorizeRequest
): Promise<DeviceAuthorizeResponse> {
const resp = await client.post(`/api/admin/provider-oauth/providers/${providerId}/device-authorize`, data)
const resp = await client.post<DeviceAuthorizeResponse>(`/api/admin/provider-oauth/providers/${providerId}/device-authorize`, data)
return resp.data
}
@@ -420,6 +420,6 @@ export async function pollDeviceAuthorize(
providerId: string,
data: DevicePollRequest
): Promise<DevicePollResponse> {
const resp = await client.post(`/api/admin/provider-oauth/providers/${providerId}/device-poll`, data)
const resp = await client.post<DevicePollResponse>(`/api/admin/provider-oauth/providers/${providerId}/device-poll`, data)
return resp.data
}
+5 -4
View File
@@ -14,6 +14,7 @@ import {
normalizePoolAdvancedConfig as normalizePoolAdvanced,
} from './types'
export type { ProviderWithEndpointsSummary } from './types'
interface ProviderRequestOptions {
timeout?: number
}
@@ -140,7 +141,7 @@ export async function updateProvider(
}>,
requestOptions?: ProviderRequestOptions,
): Promise<ProviderWithEndpointsSummary> {
const response = await client.patch(`/api/admin/providers/${providerId}`, data, requestOptions)
const response = await client.patch<ProviderWithEndpointsSummary>(`/api/admin/providers/${providerId}`, data, requestOptions)
return normalizeProviderSummary(response.data)
}
@@ -175,7 +176,7 @@ export async function createProvider(
config?: ProviderConfig | null
}
): Promise<{ id: string; name: string; message?: string }> {
const response = await client.post('/api/admin/providers/', data)
const response = await client.post<{ id: string; name: string; message?: string }>('/api/admin/providers/', data)
return response.data
}
@@ -261,7 +262,7 @@ export async function testModel(
data: TestModelRequest,
options: { signal?: AbortSignal } = {},
): Promise<TestModelResponse> {
const response = await client.post('/api/admin/provider-query/test-model', data, {
const response = await client.post<TestModelResponse>('/api/admin/provider-query/test-model', data, {
timeout: 10 * 60 * 1000,
signal: options.signal,
})
@@ -350,7 +351,7 @@ export async function testModelFailover(
const failoverModels = Array.isArray(data.failover_models) && data.failover_models.length > 0
? data.failover_models
: (normalizedModelName ? [normalizedModelName] : undefined)
const response = await client.post('/api/admin/provider-query/test-model-failover', {
const response = await client.post<TestModelFailoverResponse>('/api/admin/provider-query/test-model-failover', {
...data,
...(failoverModels ? { failover_models: failoverModels } : {}),
}, {
+27 -7
View File
@@ -1,5 +1,12 @@
import type { ProviderModelMapping } from './provider'
export interface ModelProviderReference {
id: string
model_id?: string | null
name: string
is_active: boolean
}
// ========== 阶梯计费类型 ==========
/** 缓存时长定价配置 */
@@ -67,13 +74,26 @@ export interface ProviderTieredPricingConfig {
[key: string]: unknown
}
export interface ModelConfig extends Record<string, unknown> {
description?: string
model_mappings?: string[]
api_formats?: string[]
billing?: {
video?: {
price_per_second_by_resolution?: Record<string, number>
[key: string]: unknown
}
[key: string]: unknown
}
}
export interface Model {
id: string
provider_id: string
global_model_id: string // 关联的 GlobalModel ID
provider_model_name: string // Provider 侧的主模型名称
provider_model_mappings?: ProviderModelMapping[] | null // 模型名称映射列表(带优先级)
config?: Record<string, unknown> | null // 额外配置(如 billing/video 等)
config?: ModelConfig | null // 额外配置(如 billing/video 等)
// 原始配置值(可能为空,为空时使用 GlobalModel 默认值)
price_per_request?: number | null // 按次计费价格
tiered_pricing?: ProviderTieredPricingConfig | null // Provider 原始覆盖,可仅包含 processing_tiers
@@ -102,7 +122,7 @@ export interface Model {
global_model_name?: string
global_model_display_name?: string
// 有效配置(合并 Model 和 GlobalModel 的 config)
effective_config?: Record<string, unknown> | null
effective_config?: ModelConfig | null
model_test_capabilities?: ModelTestCapabilities | null
}
@@ -120,7 +140,7 @@ export interface ModelCreate {
supports_extended_thinking?: boolean
supports_image_generation?: boolean
is_active?: boolean
config?: Record<string, unknown>
config?: ModelConfig
}
export interface ModelUpdate {
@@ -136,7 +156,7 @@ export interface ModelUpdate {
supports_image_generation?: boolean
is_active?: boolean
is_available?: boolean
config?: Record<string, unknown> | null
config?: ModelConfig | null
}
export interface ModelCapabilities {
@@ -243,7 +263,7 @@ export interface GlobalModelCreate {
// Key 能力配置 - 模型支持的能力列表
supported_capabilities?: string[]
// 模型配置(JSON格式)- 包含能力、规格、元信息等
config?: Record<string, unknown>
config?: ModelConfig
is_active?: boolean
}
@@ -257,7 +277,7 @@ export interface GlobalModelUpdate {
// Key 能力配置 - 模型支持的能力列表
supported_capabilities?: string[] | null
// 模型配置(JSON格式)- 包含能力、规格、元信息等
config?: Record<string, unknown> | null
config?: ModelConfig | null
}
export interface GlobalModelResponse {
@@ -273,7 +293,7 @@ export interface GlobalModelResponse {
supported_capabilities?: string[] | null
supports_embedding?: boolean | null
// 模型配置(JSON格式)
config?: Record<string, unknown> | null
config?: ModelConfig | null
// 统计数据
provider_count?: number
active_provider_count?: number
+6 -6
View File
@@ -68,7 +68,7 @@ export const geminiFilesApi = {
* 获取文件映射统计
*/
async getStats(): Promise<FileMappingStatsResponse> {
const response = await apiClient.get('/api/admin/gemini-files/stats')
const response = await apiClient.get<FileMappingStatsResponse>('/api/admin/gemini-files/stats')
return response.data
},
@@ -76,7 +76,7 @@ export const geminiFilesApi = {
* 列出文件映射
*/
async listMappings(params?: ListMappingsParams): Promise<FileMappingListResponse> {
const response = await apiClient.get('/api/admin/gemini-files/mappings', { params })
const response = await apiClient.get<FileMappingListResponse>('/api/admin/gemini-files/mappings', { params })
return response.data
},
@@ -84,7 +84,7 @@ export const geminiFilesApi = {
* 删除指定映射
*/
async deleteMapping(mappingId: string): Promise<{ message: string; file_name: string }> {
const response = await apiClient.delete(`/api/admin/gemini-files/mappings/${mappingId}`)
const response = await apiClient.delete<{ message: string; file_name: string }>(`/api/admin/gemini-files/mappings/${mappingId}`)
return response.data
},
@@ -92,7 +92,7 @@ export const geminiFilesApi = {
* 清理过期映射
*/
async cleanupExpired(): Promise<{ message: string; deleted_count: number }> {
const response = await apiClient.delete('/api/admin/gemini-files/mappings')
const response = await apiClient.delete<{ message: string; deleted_count: number }>('/api/admin/gemini-files/mappings')
return response.data
},
@@ -100,7 +100,7 @@ export const geminiFilesApi = {
* 获取可用的 Key 列表
*/
async getCapableKeys(): Promise<CapableKeyResponse[]> {
const response = await apiClient.get('/api/admin/gemini-files/capable-keys')
const response = await apiClient.get<CapableKeyResponse[]>('/api/admin/gemini-files/capable-keys')
return response.data
},
@@ -110,7 +110,7 @@ export const geminiFilesApi = {
async uploadFile(file: File, keyIds: string[]): Promise<UploadResponse> {
const formData = new FormData()
formData.append('file', file)
const response = await apiClient.post(
const response = await apiClient.post<UploadResponse>(
`/api/admin/gemini-files/upload?key_ids=${keyIds.join(',')}`,
formData,
{
+86 -16
View File
@@ -1,4 +1,5 @@
import apiClient from './client'
import type { ImageProgress } from './requestTrace'
import type { ActivityHeatmap } from '@/types/activity'
import type { TieredPricingConfig } from './endpoints/types'
import { cachedRequest, buildCacheKey } from '@/utils/cache'
@@ -238,13 +239,13 @@ export const meApi = {
username?: string
feature_settings?: FeatureSettingsMap | null
}): Promise<{ message: string }> {
const response = await apiClient.put('/api/users/me', data)
const response = await apiClient.put<{ message: string }>('/api/users/me', data)
return response.data
},
// 修改密码
async changePassword(data: ChangePasswordRequest): Promise<{ message: string }> {
const response = await apiClient.patch('/api/users/me/password', data)
const response = await apiClient.patch<{ message: string }>('/api/users/me/password', data)
return response.data
},
@@ -261,12 +262,12 @@ export const meApi = {
},
async revokeSession(sessionId: string): Promise<{ message: string }> {
const response = await apiClient.delete(`/api/users/me/sessions/${sessionId}`)
const response = await apiClient.delete<{ message: string }>(`/api/users/me/sessions/${sessionId}`)
return response.data
},
async revokeOtherSessions(): Promise<{ message: string; revoked_count: number }> {
const response = await apiClient.delete('/api/users/me/sessions/others')
const response = await apiClient.delete<{ message: string; revoked_count: number }>('/api/users/me/sessions/others')
return response.data
},
@@ -303,7 +304,7 @@ export const meApi = {
},
async deleteApiKey(keyId: string): Promise<{ message: string }> {
const response = await apiClient.delete(`/api/users/me/api-keys/${keyId}`)
const response = await apiClient.delete<{ message: string }>(`/api/users/me/api-keys/${keyId}`)
return response.data
},
@@ -396,16 +397,61 @@ export const meApi = {
reasoning_effort?: string | null
service_tier?: string | null
actual_service_tier?: string | null
image_progress?: ImageProgress | null
}>
}> {
const params = ids ? { ids } : {}
const response = await apiClient.get('/api/users/me/usage/active', { params })
const response = await apiClient.get<{
requests: Array<{
id: string
status: 'pending' | 'streaming' | 'completed' | 'failed' | 'cancelled'
input_tokens: number
effective_input_tokens?: number | null
output_tokens: number
cache_creation_input_tokens?: number | null
cache_creation_ephemeral_5m_input_tokens?: number | null
cache_creation_ephemeral_1h_input_tokens?: number | null
cache_read_input_tokens?: number | null
cost: number
actual_cost?: number | null
rate_multiplier?: number | null
response_time_ms: number | null
first_byte_time_ms: number | null
end_to_end_time_ms?: number | null
end_to_end_first_byte_time_ms?: number | null
updated_at?: string | null
response_time_updated_at?: string | null
status_code?: number | null
error_message?: string | null
api_format?: string | null
endpoint_api_format?: string | null
is_stream?: boolean | null
is_websocket?: boolean | null
websocket_transport?: string | null
usage_available?: boolean | null
usage_pricing_available?: boolean | null
input_audio_tokens?: number | null
output_audio_tokens?: number | null
upstream_is_stream?: boolean | null
client_requested_stream?: boolean | null
client_is_stream?: boolean | null
has_format_conversion?: boolean | null
has_fallback?: boolean | null
target_model?: string | null
request_type?: string | null
requested_reasoning_effort?: string | null
reasoning_effort?: string | null
service_tier?: string | null
actual_service_tier?: string | null
image_progress?: ImageProgress | null
}>
}>('/api/users/me/usage/active', { params })
return response.data
},
// 获取可用的提供商
async getAvailableProviders(): Promise<Array<Record<string, unknown>>> {
const response = await apiClient.get('/api/users/me/providers')
const response = await apiClient.get<Array<Record<string, unknown>>>('/api/users/me/providers')
return response.data
},
@@ -429,24 +475,38 @@ export const meApi = {
}>
total: number
}> {
const response = await apiClient.get('/api/users/me/available-models', { params })
const response = await apiClient.get<{
models: Array<{
id: string
name: string
display_name: string | null
is_active: boolean
default_price_per_request: number | null
default_tiered_pricing: TieredPricingConfig | null
supported_capabilities: string[] | null
supports_embedding?: boolean | null
config: Record<string, unknown> | null
usage_count: number
}>
total: number
}>('/api/users/me/available-models', { params })
return response.data
},
// 获取端点状态(不包含敏感信息)
async getEndpointStatus(): Promise<Array<Record<string, unknown>>> {
const response = await apiClient.get('/api/users/me/endpoint-status')
const response = await apiClient.get<Array<Record<string, unknown>>>('/api/users/me/endpoint-status')
return response.data
},
// 偏好设置
async getPreferences(): Promise<UserPreferences> {
const response = await apiClient.get('/api/users/me/preferences')
const response = await apiClient.get<UserPreferences>('/api/users/me/preferences')
return response.data
},
async updatePreferences(data: Partial<UserPreferences>): Promise<{ message: string }> {
const response = await apiClient.put('/api/users/me/preferences', data)
const response = await apiClient.put<{ message: string }>('/api/users/me/preferences', data)
return response.data
},
@@ -456,7 +516,7 @@ export const meApi = {
async updateApiKeyProviders(keyId: string, data: {
allowed_providers?: ProviderConfig[]
}): Promise<{ message: string }> {
const response = await apiClient.put(`/api/users/me/api-keys/${keyId}/providers`, data)
const response = await apiClient.put<{ message: string }>(`/api/users/me/api-keys/${keyId}/providers`, data)
return response.data
},
@@ -464,7 +524,7 @@ export const meApi = {
async updateApiKeyCapabilities(keyId: string, data: {
force_capabilities?: Record<string, boolean> | null
}): Promise<{ message: string; force_capabilities?: Record<string, boolean> | null }> {
const response = await apiClient.put(`/api/users/me/api-keys/${keyId}/capabilities`, data)
const response = await apiClient.put<{ message: string; force_capabilities?: Record<string, boolean> | null }>(`/api/users/me/api-keys/${keyId}/capabilities`, data)
return response.data
},
@@ -472,7 +532,9 @@ export const meApi = {
async getModelCapabilitySettings(): Promise<{
model_capability_settings: Record<string, Record<string, boolean>>
}> {
const response = await apiClient.get('/api/users/me/model-capabilities')
const response = await apiClient.get<{
model_capability_settings: Record<string, Record<string, boolean>>
}>('/api/users/me/model-capabilities')
return response.data
},
@@ -482,7 +544,10 @@ export const meApi = {
message: string
model_capability_settings: Record<string, Record<string, boolean>> | null
}> {
const response = await apiClient.put('/api/users/me/model-capabilities', data)
const response = await apiClient.put<{
message: string
model_capability_settings: Record<string, Record<string, boolean>> | null
}>('/api/users/me/model-capabilities', data)
return response.data
},
@@ -500,7 +565,12 @@ export const meApi = {
return cachedRequest(
cacheKey,
async () => {
const response = await apiClient.get('/api/users/me/usage/interval-timeline', { params })
const response = await apiClient.get<{
analysis_period_hours: number
total_points: number
points: Array<{ x: string; y: number; model?: string }>
models?: string[]
}>('/api/users/me/usage/interval-timeline', { params })
return response.data
},
30000
+64
View File
@@ -0,0 +1,64 @@
export interface CredentialsSchema {
type: 'object'
properties: Record<string, SchemaProperty>
required?: string[]
'x-field-groups'?: SchemaFieldGroup[]
'x-auth-type'?: string
'x-auth-method'?: string
'x-validation'?: SchemaValidation[]
'x-quota-divisor'?: number | null
'x-currency'?: string
'x-default-base-url'?: string
'x-balance-extra-format'?: BalanceExtraFormat[]
'x-field-hooks'?: Record<string, { action: string; target: string }>
}
export interface SchemaProperty {
type: string
title?: string
description?: string
'x-sensitive'?: boolean
'x-input-type'?: string
'x-default-value'?: string
'x-help'?: string
}
export interface SchemaFieldGroup {
fields: string[]
layout?: 'inline' | 'vertical'
'x-flex'?: Record<string, number>
'x-help'?: string
}
interface SchemaValidation {
type: 'required' | 'any_required' | 'conditional_required'
fields?: string[]
message: string
/** conditional_required: 当此字段有值时 */
if?: string
/** conditional_required: 除非此字段有值 */
unless?: string
/** conditional_required: 则这些字段必填 */
then?: string[]
}
export interface BalanceExtraFormat {
label: string
type: 'window_limit' | 'daily_quota' | 'weekly_spent' | 'monthly_expiry'
/** window_limit: extra 中的字段名 */
source?: string
/** window_limit: 单位除数 */
unit_divisor?: number
/** daily_quota / weekly_spent: limit 字段名 */
source_limit?: string
/** daily_quota: remaining 字段名 */
source_remaining?: string
/** daily_quota: 每日重置基准时间字段名(计算下次重置时间) */
source_start_date?: string
/** weekly_spent: spent 字段名 */
source_spent?: string
/** weekly_spent: resets_at 字段名 */
source_resets_at?: string
/** monthly_expiry: 到期日期字段名 */
source_end_date?: string
}
+3 -2
View File
@@ -8,6 +8,7 @@
*/
import client from './client'
import type { CredentialsSchema } from './providerCredentials'
// ==================== Types ====================
@@ -46,11 +47,11 @@ export interface ArchitectureInfo {
architecture_id: string
display_name: string
description: string
credentials_schema: Record<string, unknown>
credentials_schema: CredentialsSchema
supported_auth_types: Array<{
type: string
display_name: string
credentials_schema?: Record<string, unknown>
credentials_schema?: CredentialsSchema
}>
supported_actions: Array<{
type: string
+1 -1
View File
@@ -36,6 +36,6 @@ export async function getPublicGlobalModels(params?: {
is_active?: boolean
search?: string
}): Promise<PublicGlobalModelListResponse> {
const response = await client.get('/api/public/global-models', { params })
const response = await client.get<PublicGlobalModelListResponse>('/api/public/global-models', { params })
return response.data
}
+4 -4
View File
@@ -87,7 +87,7 @@ export const referralApi = {
async getAdminReferrals(
params: ReferralRelationshipQuery = {}
): Promise<ReferralListResponse<ReferralRelationshipRecord>> {
const response = await apiClient.get('/api/admin/referrals', {
const response = await apiClient.get<ReferralListResponse<ReferralRelationshipRecord>>('/api/admin/referrals', {
params: cleanParams(params as Record<string, unknown>)
})
return response.data
@@ -96,19 +96,19 @@ export const referralApi = {
async getAdminReferralRewards(
params: ReferralRewardQuery = {}
): Promise<ReferralListResponse<ReferralRewardRecord>> {
const response = await apiClient.get('/api/admin/referral-rewards', {
const response = await apiClient.get<ReferralListResponse<ReferralRewardRecord>>('/api/admin/referral-rewards', {
params: cleanParams(params as Record<string, unknown>)
})
return response.data
},
async retryReferralReward(id: string, note?: string): Promise<{ reward: ReferralRewardRecord }> {
const response = await apiClient.post(`/api/admin/referral-rewards/${id}/retry`, { note })
const response = await apiClient.post<{ reward: ReferralRewardRecord }>(`/api/admin/referral-rewards/${id}/retry`, { note })
return response.data
},
async voidReferralReward(id: string, note?: string): Promise<{ reward: ReferralRewardRecord }> {
const response = await apiClient.post(`/api/admin/referral-rewards/${id}/void`, { note })
const response = await apiClient.post<{ reward: ReferralRewardRecord }>(`/api/admin/referral-rewards/${id}/void`, { note })
return response.data
}
}
+42
View File
@@ -31,6 +31,32 @@ export interface CandidateResponseBoundary {
body_state?: string | null
}
export interface CandidateProxyTiming {
body_read_ms?: number | null
decompress_ms?: number | null
wire_size?: number | null
body_size?: number | null
ttfb_ms?: number | null
upstream_ms?: number | null
response_wait_ms?: number | null
connection_acquire_ms?: number | null
upstream_processing_ms?: number | null
connect_ms?: number | null
tls_ms?: number | null
dns_ms?: number | null
total_ms?: number | null
connection_reused?: boolean | null
}
export interface CandidateProxy {
node_name?: string | null
node_id?: string | null
url?: string | null
source?: string | null
ttfb_ms?: number | null
timing?: CandidateProxyTiming | null
}
export interface CandidateRecord {
id: string
request_id: string
@@ -70,6 +96,22 @@ export interface CandidateRecord {
ranking?: CandidateRankingMetadata | null
image_progress?: ImageProgress | null
extra_data?: Record<string, unknown> & {
first_byte_time_ms?: number | null
needs_conversion?: boolean
provider_api_format?: string | null
proxy?: CandidateProxy | null
pool_selection?: {
reason: string
cost_soft_threshold?: number | boolean | null
cost_window_usage?: number | null
cost_limit?: number | null
} | null
pool_skip?: {
type: string
cooldown_reason?: string | null
cooldown_ttl?: number | null
cost_window_usage?: number | null
} | null
upstream_response?: CandidateResponseBoundary
image_progress?: ImageProgress | null
}
+3 -3
View File
@@ -59,7 +59,7 @@ export const blacklistApi = {
* 获取黑名单统计
*/
async getStats(): Promise<BlacklistStats> {
const response = await apiClient.get('/api/admin/security/ip/blacklist/stats')
const response = await apiClient.get<BlacklistStats>('/api/admin/security/ip/blacklist/stats')
return response.data
},
@@ -67,7 +67,7 @@ export const blacklistApi = {
* 获取黑名单列表
*/
async getList(): Promise<BlacklistResponse> {
const response = await apiClient.get('/api/admin/security/ip/blacklist')
const response = await apiClient.get<BlacklistResponse>('/api/admin/security/ip/blacklist')
return response.data
}
}
@@ -96,7 +96,7 @@ export const whitelistApi = {
* 获取白名单列表
*/
async getList(): Promise<WhitelistResponse> {
const response = await apiClient.get('/api/admin/security/ip/whitelist')
const response = await apiClient.get<WhitelistResponse>('/api/admin/security/ip/whitelist')
return response.data
}
}
+64 -3
View File
@@ -2,6 +2,7 @@ import apiClient from './client'
import { cachedRequest, dedupedRequest, buildCacheKey } from '@/utils/cache'
import type { ActivityHeatmap } from '@/types/activity'
import type { ImageProgress } from './requestTrace'
import type { UsageRecord as UsageListRecord } from './usageRecords'
const ACTIVITY_HEATMAP_CACHE_TTL_MS = 30 * 60 * 1000
const USAGE_ANALYTICS_CACHE_TTL_MS = 30 * 1000
@@ -58,6 +59,12 @@ export interface UsageStats {
avg_response_time: number
error_count?: number
error_rate?: number
cache_stats?: {
cache_creation_tokens: number
cache_read_tokens: number
cache_creation_cost: number
cache_read_cost: number
}
today?: {
requests: number
tokens: number
@@ -67,6 +74,7 @@ export interface UsageStats {
}
export interface UsageByModel {
actual_cost?: number
model: string
request_count: number
total_tokens: number
@@ -511,7 +519,7 @@ export const usageApi = {
limit?: number
offset?: number
}): Promise<{
records: Array<Record<string, unknown>>
records: UsageListRecord[]
total: number
limit: number
offset: number
@@ -519,7 +527,13 @@ export const usageApi = {
}> {
const key = buildCacheKey('usage:records', params as Record<string, unknown> | undefined)
return dedupedRequest(key, async () => {
const response = await apiClient.get('/api/admin/usage/records', { params })
const response = await apiClient.get<{
records: UsageListRecord[]
total: number
limit: number
offset: number
total_is_estimated?: boolean
}>('/api/admin/usage/records', { params })
return response.data
})
},
@@ -630,7 +644,54 @@ export const usageApi = {
if (typeof timeRange?.tz_offset_minutes === 'number') {
params.tz_offset_minutes = timeRange.tz_offset_minutes
}
const response = await apiClient.get('/api/admin/usage/active', { params })
const response = await apiClient.get<{
requests: Array<{
id: string
status: 'pending' | 'streaming' | 'completed' | 'failed' | 'cancelled'
input_tokens: number
effective_input_tokens?: number | null
output_tokens: number
cache_creation_input_tokens?: number | null
cache_creation_ephemeral_5m_input_tokens?: number | null
cache_creation_ephemeral_1h_input_tokens?: number | null
cache_read_input_tokens?: number | null
cost: number
actual_cost?: number | null
rate_multiplier?: number | null
response_time_ms: number | null
first_byte_time_ms: number | null
end_to_end_time_ms?: number | null
end_to_end_first_byte_time_ms?: number | null
updated_at?: string | null
response_time_updated_at?: string | null
status_code?: number | null
error_message?: string | null
provider?: string | null
api_key_name?: string | null
provider_key_name?: string | null
api_format?: string | null
endpoint_api_format?: string | null
is_stream?: boolean | null
is_websocket?: boolean | null
websocket_transport?: string | null
usage_available?: boolean | null
usage_pricing_available?: boolean | null
input_audio_tokens?: number | null
output_audio_tokens?: number | null
upstream_is_stream?: boolean | null
client_requested_stream?: boolean | null
client_is_stream?: boolean | null
has_format_conversion?: boolean | null
has_fallback?: boolean | null
target_model?: string | null
request_type?: string | null
requested_reasoning_effort?: string | null
reasoning_effort?: string | null
service_tier?: string | null
actual_service_tier?: string | null
image_progress?: ImageProgress | null
}>
}>('/api/admin/usage/active', { params })
return response.data
},
+69
View File
@@ -0,0 +1,69 @@
import type { ImageProgress } from './requestTrace'
export type RequestStatus = 'pending' | 'streaming' | 'completed' | 'failed' | 'cancelled'
export interface UsageRecord {
id: string
user_id?: string
username?: string
user_email?: string
api_key?: {
id: string | null
name: string | null
display: string | null
} | null
provider?: string // 仅管理员可见
api_key_name?: string
provider_key_name?: string | null
rate_multiplier?: number
model: string
target_model?: string | null // 映射后的目标模型名(若无映射则为空)
model_version?: string | null // Provider 返回的实际模型版本(列表轻量字段)
request_type?: string | null // 由请求语义识别出的操作类型
requested_reasoning_effort?: string | null // 用户请求侧 reasoning 级别,用于展示转换关系
reasoning_effort?: string | null // 从发送给 Provider 的请求体提取的 reasoning 级别
service_tier?: string | null // 从发送给 Provider 的请求体提取的服务层级
actual_service_tier?: string | null // 响应侧审计事实,不用于 Fast 展示或计费
api_format?: string
endpoint_api_format?: string // 端点原生格式
has_format_conversion?: boolean // 是否发生了格式转换
input_tokens: number
effective_input_tokens?: number
output_tokens: number
reasoning_tokens?: number
cache_creation_input_tokens?: number
cache_creation_ephemeral_5m_input_tokens?: number
cache_creation_ephemeral_1h_input_tokens?: number
cache_read_input_tokens?: number
total_tokens: number
cost: number
actual_cost?: number
response_time_ms?: number | null
first_byte_time_ms?: number | null // 首字时间 (TTFB)
end_to_end_time_ms?: number | null // 客户端从请求进入网关到完成的总耗时
end_to_end_first_byte_time_ms?: number | null // 客户端从请求进入网关到首字节的耗时
is_stream: boolean
is_websocket?: boolean
websocket_transport?: string | null
usage_available?: boolean
usage_pricing_available?: boolean
input_audio_tokens?: number | null
output_audio_tokens?: number | null
upstream_is_stream?: boolean
client_requested_stream?: boolean
client_is_stream?: boolean
client_family?: string | null
client_ip?: string | null
user_agent?: string | null
request_path?: string | null
request_path_and_query?: string | null
status_code?: number
error_message?: string
status?: RequestStatus // 请求状态: pending, streaming, completed, failed
created_at: string
updated_at?: string | null
response_time_updated_at?: string | null
has_fallback?: boolean
has_retry?: boolean
image_progress?: ImageProgress | null
}
+1 -1
View File
@@ -60,7 +60,7 @@ export interface User {
export interface CreateUserRequest {
username: string
password: string
email: string
email?: string
role?: UserRole
initial_gift_usd?: number | null
unlimited?: boolean
+17 -4
View File
@@ -223,7 +223,10 @@ export const walletApi = {
order: PaymentOrder
payment_instructions: Record<string, unknown>
}> {
const response = await apiClient.post('/api/wallet/recharge', payload)
const response = await apiClient.post<{
order: PaymentOrder
payment_instructions: Record<string, unknown>
}>('/api/wallet/recharge', payload)
return response.data
},
@@ -238,12 +241,17 @@ export const walletApi = {
limit: number
offset: number
}> {
const response = await apiClient.get('/api/wallet/recharge', { params })
const response = await apiClient.get<{
items: PaymentOrder[]
total: number
limit: number
offset: number
}>('/api/wallet/recharge', { params })
return response.data
},
async getRechargeOrder(orderId: string): Promise<{ order: PaymentOrder }> {
const response = await apiClient.get(`/api/wallet/recharge/${orderId}`)
const response = await apiClient.get<{ order: PaymentOrder }>(`/api/wallet/recharge/${orderId}`)
return response.data
},
@@ -253,7 +261,12 @@ export const walletApi = {
limit: number
offset: number
}> {
const response = await apiClient.get('/api/wallet/refunds', { params })
const response = await apiClient.get<{
items: RefundRequest[]
total: number
limit: number
offset: number
}>('/api/wallet/refunds', { params })
return response.data
},