Files
sub2api/frontend/src/api/admin/accounts.ts
T
IanShaw027 7c62382d04 feat(grok): 吸收调度阈值、配额解析、批量用量与 CLI 身份
从 personal-dev 取优并接线到 feat/grok-complete-integration:

- 调度阈值:grok_sched_* 写入、ApplyAccountSchedulingThreshold、
  account_scheduling_thresholds 设置与 Settings UI、网关过滤
- 配额头:x-rate-limit 别名、相对秒 reset、tier/entitlement 扩展
- 批量 usage:GetUsageBatch + POST /usage/batch + AccountsView 合并加载
- CLI 身份:xai.cli_identity 统一 pin/UA;service 层 applyGrokCLIHeaders
  与 transport 最终改写对齐;media eligibility 模块落地
- UsageCell:月度 cents→USD 与 30d 进度条展示

保留既有 failure taxonomy / sticky / free recovery 等 HEAD cools。
2026-08-07 14:53:18 +08:00

1050 lines
29 KiB
TypeScript

/**
* Admin Accounts API endpoints
* Handles AI platform account management for administrators
*/
import { apiClient } from '../client'
import type {
Account,
CreateAccountRequest,
UpdateAccountRequest,
PaginatedResponse,
AccountUsageInfo,
WindowStats,
ClaudeModel,
AccountUsageStatsResponse,
TempUnschedulableStatus,
AdminDataPayload,
AdminDataImportResult,
CodexSessionImportRequest,
CodexSessionImportResult,
OpenAICodexPATCreateRequest,
CheckMixedChannelRequest,
CheckMixedChannelResponse,
UpstreamBillingProbeResult,
UpstreamBillingProbeSettings,
OllamaCloudUsageSettings,
OllamaCloudUsageState
} from '@/types'
/**
* List all accounts with pagination
* @param page - Page number (default: 1)
* @param pageSize - Items per page (default: 20)
* @param filters - Optional filters
* @returns Paginated list of accounts
*/
export async function list(
page: number = 1,
pageSize: number = 20,
filters?: {
platform?: string
type?: string
status?: string
group?: string
search?: string
privacy_mode?: string
lite?: string
include_scheduler_score?: string
sort_by?: string
sort_order?: 'asc' | 'desc'
},
options?: {
signal?: AbortSignal
}
): Promise<PaginatedResponse<Account>> {
const { data } = await apiClient.get<PaginatedResponse<Account>>('/admin/accounts', {
params: {
page,
page_size: pageSize,
...filters
},
signal: options?.signal
})
return data
}
export interface AccountListWithEtagResult {
notModified: boolean
etag: string | null
data: PaginatedResponse<Account> | null
}
export async function listWithEtag(
page: number = 1,
pageSize: number = 20,
filters?: {
platform?: string
type?: string
status?: string
group?: string
search?: string
privacy_mode?: string
lite?: string
include_scheduler_score?: string
sort_by?: string
sort_order?: 'asc' | 'desc'
},
options?: {
signal?: AbortSignal
etag?: string | null
}
): Promise<AccountListWithEtagResult> {
const headers: Record<string, string> = {}
if (options?.etag) {
headers['If-None-Match'] = options.etag
}
const response = await apiClient.get<PaginatedResponse<Account>>('/admin/accounts', {
params: {
page,
page_size: pageSize,
...filters
},
headers,
signal: options?.signal,
validateStatus: (status) => (status >= 200 && status < 300) || status === 304
})
const etagHeader = typeof response.headers?.etag === 'string' ? response.headers.etag : null
if (response.status === 304) {
return {
notModified: true,
etag: etagHeader,
data: null
}
}
return {
notModified: false,
etag: etagHeader,
data: response.data
}
}
/**
* Get account by ID
* @param id - Account ID
* @returns Account details
*/
export async function getById(id: number): Promise<Account> {
const { data } = await apiClient.get<Account>(`/admin/accounts/${id}`)
return data
}
/**
* Create new account
* @param accountData - Account data
* @returns Created account
*/
export async function create(accountData: CreateAccountRequest): Promise<Account> {
const { data } = await apiClient.post<Account>('/admin/accounts', accountData)
return data
}
/**
* Duplicate an account while keeping credentials on the server.
* @param id - Source account ID
* @returns Newly created account
*/
const duplicateOperationKeys = new Map<number, string>()
function duplicateOperationStorageKey(id: number): string {
return `sub2api:admin:account-duplicate:${id}`
}
function getStoredDuplicateOperationKey(id: number): string | null {
try {
return globalThis.sessionStorage?.getItem(duplicateOperationStorageKey(id)) ?? null
} catch {
return null
}
}
function storeDuplicateOperationKey(id: number, key: string | null): void {
try {
if (key) globalThis.sessionStorage?.setItem(duplicateOperationStorageKey(id), key)
else globalThis.sessionStorage?.removeItem(duplicateOperationStorageKey(id))
} catch {
// In-memory retry protection still works when browser storage is unavailable.
}
}
export async function duplicate(id: number): Promise<Account> {
let idempotencyKey = duplicateOperationKeys.get(id) ?? getStoredDuplicateOperationKey(id)
if (!idempotencyKey) {
const requestID = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`
idempotencyKey = `account-duplicate-${id}-${requestID}`
}
duplicateOperationKeys.set(id, idempotencyKey)
storeDuplicateOperationKey(id, idempotencyKey)
const { data } = await apiClient.post<Account>(`/admin/accounts/${id}/duplicate`, undefined, {
headers: { 'Idempotency-Key': idempotencyKey }
})
duplicateOperationKeys.delete(id)
storeDuplicateOperationKey(id, null)
return data
}
/**
* Update account
* @param id - Account ID
* @param updates - Fields to update
* @returns Updated account
*/
export async function update(id: number, updates: UpdateAccountRequest): Promise<Account> {
const { data } = await apiClient.put<Account>(`/admin/accounts/${id}`, updates)
return data
}
/**
* Check mixed-channel risk for account-group binding.
*/
export async function checkMixedChannelRisk(
payload: CheckMixedChannelRequest
): Promise<CheckMixedChannelResponse> {
const { data } = await apiClient.post<CheckMixedChannelResponse>('/admin/accounts/check-mixed-channel', payload)
return data
}
/**
* Delete account
* @param id - Account ID
* @returns Success confirmation
*/
export async function deleteAccount(id: number): Promise<{ message: string }> {
const { data } = await apiClient.delete<{ message: string }>(`/admin/accounts/${id}`)
return data
}
/**
* Toggle account status
* @param id - Account ID
* @param status - New status
* @returns Updated account
*/
export async function toggleStatus(id: number, status: 'active' | 'inactive'): Promise<Account> {
return update(id, { status })
}
/**
* Test account connectivity
* @param id - Account ID
* @returns Test result
*/
export async function testAccount(id: number): Promise<{
success: boolean
message: string
latency_ms?: number
}> {
const { data } = await apiClient.post<{
success: boolean
message: string
latency_ms?: number
}>(`/admin/accounts/${id}/test`)
return data
}
/**
* Refresh account credentials
* @param id - Account ID
* @returns Updated account
*/
export async function refreshCredentials(id: number): Promise<Account> {
const { data } = await apiClient.post<Account>(`/admin/accounts/${id}/refresh`)
return data
}
/**
* Apply OAuth credentials after re-authorization.
*
* Unlike `update()`, this endpoint:
* - never overwrites the whole `extra` JSONB (merges incrementally instead),
* so persistent settings like `base_rpm`, `window_cost_limit`, `max_sessions`,
* `quota_*` and `privacy_mode` are preserved
* - clears the account error and invalidates the token cache server-side
*/
export async function applyOAuthCredentials(
id: number,
payload: {
type: 'oauth' | 'setup-token'
credentials: Record<string, unknown>
extra?: Record<string, unknown>
}
): Promise<Account> {
const { data } = await apiClient.post<Account>(
`/admin/accounts/${id}/apply-oauth-credentials`,
payload
)
return data
}
/**
* Get account usage statistics
* @param id - Account ID
* @param days - Number of days (default: 30)
* @returns Account usage statistics with history, summary, and models
*/
export async function getStats(id: number, days: number = 30): Promise<AccountUsageStatsResponse> {
const { data } = await apiClient.get<AccountUsageStatsResponse>(`/admin/accounts/${id}/stats`, {
params: { days }
})
return data
}
/**
* Clear account error
* @param id - Account ID
* @returns Updated account
*/
export async function clearError(id: number): Promise<Account> {
const { data } = await apiClient.post<Account>(`/admin/accounts/${id}/clear-error`)
return data
}
/**
* Get account usage information (5h/7d window)
* @param id - Account ID
* @returns Account usage info
*/
export async function getUsage(id: number, source?: 'passive' | 'active', force?: boolean): Promise<AccountUsageInfo> {
const params: Record<string, string> = {}
if (source) params.source = source
if (force) params.force = 'true'
const { data } = await apiClient.get<AccountUsageInfo>(`/admin/accounts/${id}/usage`, {
params: Object.keys(params).length > 0 ? params : undefined
})
return data
}
export interface BatchAccountUsageResponse {
usage: Record<string, AccountUsageInfo>
errors: Record<string, string>
}
export async function getBatchUsage(accountIds: number[], force?: boolean): Promise<BatchAccountUsageResponse> {
const { data } = await apiClient.post<BatchAccountUsageResponse>('/admin/accounts/usage/batch', {
account_ids: accountIds,
force: force === true
})
return data
}
/**
* Clear account rate limit status
* @param id - Account ID
* @returns Updated account
*/
export async function clearRateLimit(id: number): Promise<Account> {
const { data } = await apiClient.post<Account>(
`/admin/accounts/${id}/clear-rate-limit`
)
return data
}
/**
* Recover account runtime state in one call
* @param id - Account ID
* @returns Updated account
*/
export async function recoverState(id: number): Promise<Account> {
const { data } = await apiClient.post<Account>(`/admin/accounts/${id}/recover-state`)
return data
}
/**
* Reset account quota usage
* @param id - Account ID
* @returns Updated account
*/
export async function resetAccountQuota(id: number): Promise<Account> {
const { data } = await apiClient.post<Account>(
`/admin/accounts/${id}/reset-quota`
)
return data
}
/**
* Get temporary unschedulable status
* @param id - Account ID
* @returns Status with detail state if active
*/
export async function getTempUnschedulableStatus(id: number): Promise<TempUnschedulableStatus> {
const { data } = await apiClient.get<TempUnschedulableStatus>(
`/admin/accounts/${id}/temp-unschedulable`
)
return data
}
/**
* Reset temporary unschedulable status
* @param id - Account ID
* @returns Success confirmation
*/
export async function resetTempUnschedulable(id: number): Promise<{ message: string }> {
const { data } = await apiClient.delete<{ message: string }>(
`/admin/accounts/${id}/temp-unschedulable`
)
return data
}
/**
* Generate OAuth authorization URL
* @param endpoint - API endpoint path
* @param config - Proxy configuration
* @returns Auth URL and session ID
*/
export async function generateAuthUrl(
endpoint: string,
config: { proxy_id?: number }
): Promise<{ auth_url: string; session_id: string }> {
const { data } = await apiClient.post<{ auth_url: string; session_id: string }>(endpoint, config)
return data
}
/**
* Exchange authorization code for tokens
* @param endpoint - API endpoint path
* @param exchangeData - Session ID, code, and optional proxy config
* @returns Token information
*/
export async function exchangeCode(
endpoint: string,
exchangeData: { session_id: string; code: string; state?: string; proxy_id?: number }
): Promise<Record<string, unknown>> {
const { data } = await apiClient.post<Record<string, unknown>>(endpoint, exchangeData)
return data
}
/**
* Batch create accounts
* @param accounts - Array of account data
* @returns Results of batch creation
*/
export async function batchCreate(accounts: CreateAccountRequest[]): Promise<{
success: number
failed: number
results: Array<{ success: boolean; account?: Account; error?: string }>
}> {
const { data } = await apiClient.post<{
success: number
failed: number
results: Array<{ success: boolean; account?: Account; error?: string }>
}>('/admin/accounts/batch', { accounts })
return data
}
/**
* Batch update credentials fields for multiple accounts
* @param request - Batch update request containing account IDs, field name, and value
* @returns Results of batch update
*/
export async function batchUpdateCredentials(request: {
account_ids: number[]
field: string
value: any
}): Promise<{
success: number
failed: number
results: Array<{ account_id: number; success: boolean; error?: string }>
}> {
const { data } = await apiClient.post<{
success: number
failed: number
results: Array<{ account_id: number; success: boolean; error?: string }>
}>('/admin/accounts/batch-update-credentials', request)
return data
}
/**
* Bulk update multiple accounts
* @param accountIds - Array of account IDs
* @param updates - Fields to update
* @returns Success confirmation
*/
export async function bulkUpdate(
accountIdsOrPayload: number[] | Record<string, unknown>,
updates?: Record<string, unknown>
): Promise<{
success: number
failed: number
success_ids?: number[]
failed_ids?: number[]
results: Array<{ account_id: number; success: boolean; error?: string }>
}> {
const payload = Array.isArray(accountIdsOrPayload)
? {
account_ids: accountIdsOrPayload,
...(updates ?? {})
}
: accountIdsOrPayload
const { data } = await apiClient.post<{
success: number
failed: number
success_ids?: number[]
failed_ids?: number[]
results: Array<{ account_id: number; success: boolean; error?: string }>
}>('/admin/accounts/bulk-update', payload)
return data
}
/**
* Get account today statistics
* @param id - Account ID
* @returns Today's stats (requests, tokens, cost)
*/
export async function getTodayStats(id: number): Promise<WindowStats> {
const { data } = await apiClient.get<WindowStats>(`/admin/accounts/${id}/today-stats`)
return data
}
export interface BatchTodayStatsResponse {
stats: Record<string, WindowStats>
}
/**
* 批量获取多个账号的今日统计
* @param accountIds - 账号 ID 列表
* @returns 以账号 ID(字符串)为键的统计映射
*/
export async function getBatchTodayStats(accountIds: number[]): Promise<BatchTodayStatsResponse> {
const { data } = await apiClient.post<BatchTodayStatsResponse>('/admin/accounts/today-stats/batch', {
account_ids: accountIds
})
return data
}
/**
* Set account schedulable status
* @param id - Account ID
* @param schedulable - Whether the account should participate in scheduling
* @returns Updated account
*/
export async function setSchedulable(id: number, schedulable: boolean): Promise<Account> {
const { data } = await apiClient.post<Account>(`/admin/accounts/${id}/schedulable`, {
schedulable
})
return data
}
/**
* Get available models for an account
* @param id - Account ID
* @returns List of available models for this account
*/
export async function getAvailableModels(id: number): Promise<ClaudeModel[]> {
const { data } = await apiClient.get<ClaudeModel[]>(`/admin/accounts/${id}/models`)
return data
}
export interface SyncUpstreamModelsResult {
models: string[]
}
/**
* Sync live supported models from the account's upstream model-list endpoint
* @param id - Account ID
* @returns List of model IDs returned by the upstream
*/
export async function syncUpstreamModels(id: number): Promise<SyncUpstreamModelsResult> {
const { data } = await apiClient.post<SyncUpstreamModelsResult>(`/admin/accounts/${id}/models/sync-upstream`)
return data
}
export interface SyncUpstreamPreviewParams {
platform: string
type: string
base_url?: string
api_key: string
}
/**
* Preview upstream models without a saved account (create-flow)
* @param params - Connection credentials
* @returns List of model IDs returned by the upstream
*/
export async function syncUpstreamModelsPreview(params: SyncUpstreamPreviewParams): Promise<SyncUpstreamModelsResult> {
const { data } = await apiClient.post<SyncUpstreamModelsResult>('/admin/accounts/models/sync-upstream-preview', params)
return data
}
export interface CRSPreviewAccount {
crs_account_id: string
kind: string
name: string
platform: string
type: string
}
export interface PreviewFromCRSResult {
new_accounts: CRSPreviewAccount[]
existing_accounts: CRSPreviewAccount[]
}
export async function previewFromCrs(params: {
base_url: string
username: string
password: string
}): Promise<PreviewFromCRSResult> {
const { data } = await apiClient.post<PreviewFromCRSResult>('/admin/accounts/sync/crs/preview', params)
return data
}
export async function syncFromCrs(params: {
base_url: string
username: string
password: string
sync_proxies?: boolean
selected_account_ids?: string[]
}): Promise<{
created: number
updated: number
skipped: number
failed: number
items: Array<{
crs_account_id: string
kind: string
name: string
action: string
error?: string
}>
}> {
const { data } = await apiClient.post<{
created: number
updated: number
skipped: number
failed: number
items: Array<{
crs_account_id: string
kind: string
name: string
action: string
error?: string
}>
}>('/admin/accounts/sync/crs', params, {
timeout: 180000 // 180s timeout: sync refreshes each existing account's OAuth token serially
})
return data
}
export async function exportData(options?: {
ids?: number[]
filters?: {
platform?: string
type?: string
status?: string
group?: string
privacy_mode?: string
search?: string
sort_by?: string
sort_order?: 'asc' | 'desc'
}
includeProxies?: boolean
}): Promise<AdminDataPayload> {
const params: Record<string, string> = {}
if (options?.ids && options.ids.length > 0) {
params.ids = options.ids.join(',')
} else if (options?.filters) {
const { platform, type, status, group, privacy_mode, search, sort_by, sort_order } = options.filters
if (platform) params.platform = platform
if (type) params.type = type
if (status) params.status = status
if (group) params.group = group
if (privacy_mode) params.privacy_mode = privacy_mode
if (search) params.search = search
if (sort_by) params.sort_by = sort_by
if (sort_order) params.sort_order = sort_order
}
if (options?.includeProxies === false) {
params.include_proxies = 'false'
}
const { data } = await apiClient.get<AdminDataPayload>('/admin/accounts/data', { params })
return data
}
export async function importData(payload: {
data: AdminDataPayload
skip_default_group_bind?: boolean
}): Promise<AdminDataImportResult> {
const { data } = await apiClient.post<AdminDataImportResult>('/admin/accounts/data', {
data: payload.data,
skip_default_group_bind: payload.skip_default_group_bind
})
return data
}
export async function importCodexSession(payload: CodexSessionImportRequest): Promise<CodexSessionImportResult> {
const { data } = await apiClient.post<CodexSessionImportResult>('/admin/accounts/import/codex-session', payload, {
timeout: 120000 // 120s timeout for large session imports
})
return data
}
export async function createOpenAICodexPAT(payload: OpenAICodexPATCreateRequest): Promise<Account> {
const { data } = await apiClient.post<Account>('/admin/openai/create-from-codex-pat', payload)
return data
}
/**
* Get Antigravity default model mapping from backend
* @returns Default model mapping (from -> to)
*/
export async function getAntigravityDefaultModelMapping(): Promise<Record<string, string>> {
const { data } = await apiClient.get<Record<string, string>>(
'/admin/accounts/antigravity/default-model-mapping'
)
return data
}
/**
* Refresh OpenAI token using refresh token
* @param refreshToken - The refresh token
* @param proxyId - Optional proxy ID
* @returns Token information including access_token, email, etc.
*/
export async function refreshOpenAIToken(
refreshToken: string,
proxyId?: number | null,
endpoint: string = '/admin/openai/refresh-token',
clientId?: string
): Promise<Record<string, unknown>> {
const payload: { refresh_token: string; proxy_id?: number; client_id?: string } = {
refresh_token: refreshToken
}
if (proxyId) {
payload.proxy_id = proxyId
}
if (clientId) {
payload.client_id = clientId
}
const { data } = await apiClient.post<Record<string, unknown>>(endpoint, payload)
return data
}
/**
* Batch operation result type
*/
export interface BatchOperationResult {
total: number
success: number
failed: number
success_ids?: number[]
failed_ids?: number[]
errors?: Array<{ account_id: number; error: string }>
warnings?: Array<{ account_id: number; warning: string }>
}
/**
* Revert account proxy to original before fallback
* @param id - Account ID
* @returns Success confirmation
*/
export async function revertProxyFallback(id: number): Promise<{ message: string }> {
const { data } = await apiClient.post<{ message: string }>(`/admin/accounts/${id}/revert-proxy-fallback`)
return data
}
/**
* Delete multiple accounts with bounded server-side concurrency.
*/
export async function batchDelete(accountIds: number[]): Promise<BatchOperationResult> {
const { data } = await apiClient.post<BatchOperationResult>('/admin/accounts/batch-delete', {
account_ids: accountIds
})
return data
}
/**
* Batch clear account errors
* @param accountIds - Array of account IDs
* @returns Batch operation result
*/
export async function batchClearError(accountIds: number[]): Promise<BatchOperationResult> {
const { data } = await apiClient.post<BatchOperationResult>('/admin/accounts/batch-clear-error', {
account_ids: accountIds
})
return data
}
/**
* Batch refresh account credentials
* @param accountIds - Array of account IDs
* @returns Batch operation result
*/
export async function batchRefresh(accountIds: number[]): Promise<BatchOperationResult> {
const { data } = await apiClient.post<BatchOperationResult>('/admin/accounts/batch-refresh', {
account_ids: accountIds,
}, {
timeout: 120000 // 120s timeout for large batch refreshes
})
return data
}
/**
* Set privacy for an Antigravity OAuth account
* @param id - Account ID
* @returns Updated account
*/
export async function setPrivacy(id: number): Promise<Account> {
const { data } = await apiClient.post<Account>(`/admin/accounts/${id}/set-privacy`)
return data
}
/**
* OpenAI / Codex rate-limit reset feature: query and reset upstream usage.
*/
export interface OpenAIRateLimitWindow {
used_percent: number
limit_window_seconds: number
reset_after_seconds: number
reset_at: number
}
export interface OpenAIRateLimit {
allowed: boolean
limit_reached: boolean
primary_window?: OpenAIRateLimitWindow | null
secondary_window?: OpenAIRateLimitWindow | null
}
export interface OpenAIAdditionalRateLimit {
limit_name: string
metered_feature: string
rate_limit?: OpenAIRateLimit | null
}
export interface OpenAIRateLimitResetCreditDetail {
expires_at?: string
}
export interface OpenAIRateLimitResetCredits {
available_count: number
credits?: OpenAIRateLimitResetCreditDetail[]
}
export interface OpenAIQuotaUsage {
user_id?: string
account_id?: string
email?: string
plan_type?: string
rate_limit?: OpenAIRateLimit | null
additional_rate_limits?: OpenAIAdditionalRateLimit[]
rate_limit_reset_credits?: OpenAIRateLimitResetCredits | null
fetched_at: number
}
export interface OpenAIQuotaResetCredit {
id?: string
reset_type?: string
status?: string
granted_at?: string
expires_at?: string
redeem_started_at?: string
redeemed_at?: string
}
export interface OpenAIQuotaResetResult {
code: string
credit?: OpenAIQuotaResetCredit | null
windows_reset: number
quota?: OpenAIQuotaUsage | null
account?: Account | null
cache_refreshed: boolean
account_state_recovered: boolean
warning_code?:
| 'reset_credit_cache_refresh_failed'
| 'account_state_recovery_failed'
| 'account_state_refresh_failed'
}
/** Usage payload plus whether the reset-credit snapshot was persisted. */
export interface OpenAIQuotaRefreshResult extends OpenAIQuotaUsage {
cache_persisted: boolean
}
/**
* Query the upstream quota AND persist the reset-credit snapshot on the account
* so the card can be rehydrated without an upstream round-trip. It is a POST
* because it writes account state (and must therefore be audited).
*
* The read-only `GET /admin/openai/accounts/:id/quota` endpoint still exists for
* API consumers; the panel always wants the snapshot persisted, so it has no
* client binding here.
*/
export async function refreshOpenAIQuota(id: number): Promise<OpenAIQuotaRefreshResult> {
const { data } = await apiClient.post<OpenAIQuotaRefreshResult>(
`/admin/openai/accounts/${id}/quota/refresh`
)
return data
}
/**
* Consume one rate-limit-reset credit for an OpenAI/Codex OAuth account.
*
* The credit is non-refundable and the endpoint chains an upstream reset with an
* upstream re-query, so it needs a larger budget than the default client
* timeout: aborting locally would report a successful consumption as a failure
* and invite a retry that spends a second credit.
*/
export async function resetOpenAIQuota(id: number): Promise<OpenAIQuotaResetResult> {
const { data } = await apiClient.post<OpenAIQuotaResetResult>(
`/admin/openai/accounts/${id}/reset-quota`,
undefined,
{ timeout: 90_000 }
)
return data
}
export interface SparkShadowCreatePayload {
name?: string
priority?: number
concurrency?: number
group_ids?: number[]
}
export async function createSparkShadow(parentId: number, payload: SparkShadowCreatePayload): Promise<Account> {
const { data } = await apiClient.post<Account>(`/admin/accounts/${parentId}/shadow`, payload)
return data
}
export async function getUpstreamBillingProbeSettings(): Promise<UpstreamBillingProbeSettings> {
const { data } = await apiClient.get<UpstreamBillingProbeSettings>('/admin/accounts/upstream-billing-probe/settings')
return data
}
export async function updateUpstreamBillingProbeSettings(
settings: UpstreamBillingProbeSettings
): Promise<UpstreamBillingProbeSettings> {
const { data } = await apiClient.put<UpstreamBillingProbeSettings>(
'/admin/accounts/upstream-billing-probe/settings',
settings
)
return data
}
export async function setUpstreamBillingProbeEnabled(id: number, enabled: boolean): Promise<void> {
await apiClient.put(`/admin/accounts/${id}/upstream-billing-probe`, { enabled })
}
export async function probeUpstreamBilling(id: number): Promise<UpstreamBillingProbeResult> {
const { data } = await apiClient.post<UpstreamBillingProbeResult>(`/admin/accounts/${id}/upstream-billing-probe`)
return data
}
export async function probeUpstreamBillingBatch(accountIds: number[]): Promise<UpstreamBillingProbeResult[]> {
const { data } = await apiClient.post<{ results: UpstreamBillingProbeResult[] }>(
'/admin/accounts/upstream-billing-probe/batch',
{ account_ids: accountIds }
)
return data.results
}
export async function getOllamaCloudUsageSettings(): Promise<OllamaCloudUsageSettings> {
const { data } = await apiClient.get<OllamaCloudUsageSettings>('/admin/accounts/ollama-cloud-usage/settings')
return data
}
export async function updateOllamaCloudUsageSettings(
settings: OllamaCloudUsageSettings
): Promise<OllamaCloudUsageSettings> {
const { data } = await apiClient.put<OllamaCloudUsageSettings>(
'/admin/accounts/ollama-cloud-usage/settings',
settings
)
return data
}
export async function getOllamaCloudUsage(id: number): Promise<OllamaCloudUsageState> {
const { data } = await apiClient.get<OllamaCloudUsageState>(`/admin/accounts/${id}/ollama-cloud-usage`)
return data
}
export async function saveOllamaCloudUsageSession(id: number, session: string): Promise<OllamaCloudUsageState> {
const { data } = await apiClient.put<OllamaCloudUsageState>(`/admin/accounts/${id}/ollama-cloud-usage/session`, {
session
})
return data
}
export async function deleteOllamaCloudUsageSession(id: number): Promise<OllamaCloudUsageState> {
const { data } = await apiClient.delete<OllamaCloudUsageState>(`/admin/accounts/${id}/ollama-cloud-usage/session`)
return data
}
export async function setOllamaCloudUsageAutoRefresh(id: number, enabled: boolean): Promise<OllamaCloudUsageState> {
const { data } = await apiClient.put<OllamaCloudUsageState>(`/admin/accounts/${id}/ollama-cloud-usage/auto-refresh`, {
enabled
})
return data
}
export async function refreshOllamaCloudUsage(id: number): Promise<OllamaCloudUsageState> {
const { data } = await apiClient.post<OllamaCloudUsageState>(`/admin/accounts/${id}/ollama-cloud-usage/refresh`)
return data
}
export const accountsAPI = {
list,
listWithEtag,
getById,
create,
duplicate,
update,
checkMixedChannelRisk,
delete: deleteAccount,
toggleStatus,
testAccount,
refreshCredentials,
applyOAuthCredentials,
getStats,
clearError,
getUsage,
getBatchUsage,
getTodayStats,
getBatchTodayStats,
clearRateLimit,
recoverState,
resetAccountQuota,
getTempUnschedulableStatus,
resetTempUnschedulable,
setSchedulable,
getAvailableModels,
syncUpstreamModels,
syncUpstreamModelsPreview,
generateAuthUrl,
exchangeCode,
refreshOpenAIToken,
batchCreate,
batchUpdateCredentials,
bulkUpdate,
previewFromCrs,
syncFromCrs,
exportData,
importData,
importCodexSession,
createOpenAICodexPAT,
getAntigravityDefaultModelMapping,
batchDelete,
batchClearError,
batchRefresh,
setPrivacy,
revertProxyFallback,
refreshOpenAIQuota,
resetOpenAIQuota,
createSparkShadow,
getUpstreamBillingProbeSettings,
updateUpstreamBillingProbeSettings,
setUpstreamBillingProbeEnabled,
probeUpstreamBilling,
probeUpstreamBillingBatch,
getOllamaCloudUsageSettings,
updateOllamaCloudUsageSettings,
getOllamaCloudUsage,
saveOllamaCloudUsageSession,
deleteOllamaCloudUsageSession,
setOllamaCloudUsageAutoRefresh,
refreshOllamaCloudUsage
}
export default accountsAPI