Merge pull request #3310 from heathermhuang/codex/grok-subscription-support

feat: add grok subscription support
This commit is contained in:
Wesley Liddick
2026-06-26 15:41:52 +08:00
committed by GitHub
109 changed files with 5594 additions and 224 deletions
@@ -9,12 +9,13 @@ import {
type DefaultPlatformQuotasMap,
} from "@/api/admin/settings";
/** 全 null 的 4 平台 map,用于断言归一化默认值 */
/** 全 null 的 5 平台 map,用于断言归一化默认值 */
const allNullQuotas: DefaultPlatformQuotasMap = {
anthropic: { daily: null, weekly: null, monthly: null },
openai: { daily: null, weekly: null, monthly: null },
gemini: { daily: null, weekly: null, monthly: null },
antigravity: { daily: null, weekly: null, monthly: null },
grok: { daily: null, weekly: null, monthly: null },
}
describe("admin settings auth source defaults helpers", () => {
@@ -236,11 +237,12 @@ describe("normalizePlatformQuotasMap", () => {
expect(result.openai).toEqual({ daily: null, weekly: null, monthly: null });
expect(result.gemini).toEqual({ daily: null, weekly: null, monthly: null });
expect(result.antigravity).toEqual({ daily: null, weekly: null, monthly: null });
expect(result.grok).toEqual({ daily: null, weekly: null, monthly: null });
});
it("无参数时返回全 4 平台全 null", () => {
it("无参数时返回全 5 平台全 null", () => {
const result = normalizePlatformQuotasMap();
expect(Object.keys(result)).toHaveLength(4);
expect(Object.keys(result)).toHaveLength(5);
for (const v of Object.values(result)) {
expect(v).toEqual({ daily: null, weekly: null, monthly: null });
}
@@ -288,7 +290,7 @@ describe("sanitizePlatformQuotasMap", () => {
it("缺失平台填充为全 null", () => {
const result = sanitizePlatformQuotasMap({});
expect(Object.keys(result)).toHaveLength(4);
expect(Object.keys(result)).toHaveLength(5);
for (const v of Object.values(result)) {
expect(v).toEqual({ daily: null, weekly: null, monthly: null });
}
+121
View File
@@ -0,0 +1,121 @@
/**
* Admin Grok/xAI API endpoints
* Handles xAI OAuth flows for administrators.
*/
import { apiClient } from '../client'
export interface GrokAuthUrlResponse {
auth_url: string
session_id: string
state: string
}
export interface GrokAuthUrlRequest {
proxy_id?: number
redirect_uri?: string
}
export interface GrokExchangeCodeRequest {
session_id: string
state: string
code: string
proxy_id?: number
redirect_uri?: string
}
export interface GrokTokenInfo {
access_token?: string
refresh_token?: string
token_type?: string
id_token?: string
expires_at?: number | string
expires_in?: number
scope?: string
client_id?: string
email?: string
subscription_tier?: string
entitlement_status?: string
[key: string]: unknown
}
export interface GrokQuotaWindow {
limit?: number | null
remaining?: number | null
reset_unix?: number | null
reset_at?: string | null
}
export interface GrokQuotaSnapshot {
requests?: GrokQuotaWindow | null
tokens?: GrokQuotaWindow | null
retry_after_seconds?: number | null
subscription_tier?: string
entitlement_status?: string
status_code?: number
headers?: Record<string, string>
headers_observed: boolean
observation_source?: string
last_probe_at?: string
last_headers_seen_at?: string
updated_at: string
}
export interface GrokQuotaProbeResult {
source: 'active_probe'
snapshot?: GrokQuotaSnapshot | null
status_code?: number
headers_observed: boolean
reset_supported: boolean
fetched_at: number
}
export interface GrokQuotaResetResult {
supported: boolean
code: string
message: string
}
export async function generateAuthUrl(
payload: GrokAuthUrlRequest
): Promise<GrokAuthUrlResponse> {
const { data } = await apiClient.post<GrokAuthUrlResponse>(
'/admin/grok/oauth/auth-url',
payload
)
return data
}
export async function exchangeCode(payload: GrokExchangeCodeRequest): Promise<GrokTokenInfo> {
const { data } = await apiClient.post<GrokTokenInfo>(
'/admin/grok/oauth/exchange-code',
payload
)
return data
}
export async function refreshGrokToken(
refreshToken: string,
proxyId?: number | null
): Promise<GrokTokenInfo> {
const payload: Record<string, unknown> = { refresh_token: refreshToken }
if (proxyId) payload.proxy_id = proxyId
const { data } = await apiClient.post<GrokTokenInfo>(
'/admin/grok/oauth/refresh-token',
payload
)
return data
}
export async function queryQuota(id: number): Promise<GrokQuotaProbeResult> {
const { data } = await apiClient.get<GrokQuotaProbeResult>(`/admin/grok/accounts/${id}/quota`)
return data
}
export async function resetQuota(id: number): Promise<GrokQuotaResetResult> {
const { data } = await apiClient.post<GrokQuotaResetResult>(`/admin/grok/accounts/${id}/reset-quota`)
return data
}
export default { generateAuthUrl, exchangeCode, refreshGrokToken, queryQuota, resetQuota }
+3
View File
@@ -17,6 +17,7 @@ import subscriptionsAPI from './subscriptions'
import usageAPI from './usage'
import geminiAPI from './gemini'
import antigravityAPI from './antigravity'
import grokAPI from './grok'
import userAttributesAPI from './userAttributes'
import opsAPI from './ops'
import errorPassthroughAPI from './errorPassthrough'
@@ -51,6 +52,7 @@ export const adminAPI = {
usage: usageAPI,
gemini: geminiAPI,
antigravity: antigravityAPI,
grok: grokAPI,
userAttributes: userAttributesAPI,
ops: opsAPI,
errorPassthrough: errorPassthroughAPI,
@@ -83,6 +85,7 @@ export {
usageAPI,
geminiAPI,
antigravityAPI,
grokAPI,
userAttributesAPI,
opsAPI,
errorPassthroughAPI,
+2 -2
View File
@@ -17,7 +17,7 @@ export interface DefaultSubscriptionSetting {
}
// ── 平台限额类型 ──────────────────────────────────────────────────
export type PlatformType = "anthropic" | "openai" | "gemini" | "antigravity"
export type PlatformType = "anthropic" | "openai" | "gemini" | "antigravity" | "grok"
export type QuotaWindowType = "daily" | "weekly" | "monthly"
/** 单平台三档限额;null = 不限制,undefined = 未填(等价 null) */
@@ -30,7 +30,7 @@ export interface PlatformQuotaLimits {
/** 全平台默认限额 map(key = PlatformType) */
export type DefaultPlatformQuotasMap = Partial<Record<PlatformType, PlatformQuotaLimits>>
const PLATFORMS: PlatformType[] = ["anthropic", "openai", "gemini", "antigravity"]
const PLATFORMS: PlatformType[] = ["anthropic", "openai", "gemini", "antigravity", "grok"]
/** 归一化为全 4 平台 × 3 窗口(缺失填 null),供模板非空绑定 */
export function normalizePlatformQuotasMap(input?: DefaultPlatformQuotasMap | null): DefaultPlatformQuotasMap {
+1 -1
View File
@@ -307,7 +307,7 @@ export async function bindUserAuthIdentity(
/**
* Platform quota types
*/
export type PlatformQuotaPlatform = 'anthropic' | 'openai' | 'gemini' | 'antigravity'
export type PlatformQuotaPlatform = 'anthropic' | 'openai' | 'gemini' | 'antigravity' | 'grok'
export type PlatformQuotaWindow = 'daily' | 'weekly' | 'monthly'
export interface PlatformQuotaItem {