Skip to content

Commit 9f8cffe

Browse files
QTomclaude
andcommitted
feat(openai): 新增"手动输入 Mobile RT"入口,使用 SoraClientID 刷新
在 OpenAI 平台添加独立的"手动输入 Mobile RT"选项,使用 client_id=app_LlGpXReQgckcGGUo2JrYvtJK 刷新 token,与现有 "手动输入 RT"(Codex CLI client_id)互不影响。 共享同一 UI 和批量创建逻辑,通过 clientId 参数区分。 同时修复空名称触发 ent NotEmpty() 校验导致 500 的问题。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 995bee1 commit 9f8cffe

5 files changed

Lines changed: 56 additions & 15 deletions

File tree

frontend/src/api/admin/accounts.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -550,14 +550,18 @@ export async function getAntigravityDefaultModelMapping(): Promise<Record<string
550550
export async function refreshOpenAIToken(
551551
refreshToken: string,
552552
proxyId?: number | null,
553-
endpoint: string = '/admin/openai/refresh-token'
553+
endpoint: string = '/admin/openai/refresh-token',
554+
clientId?: string
554555
): Promise<Record<string, unknown>> {
555-
const payload: { refresh_token: string; proxy_id?: number } = {
556+
const payload: { refresh_token: string; proxy_id?: number; client_id?: string } = {
556557
refresh_token: refreshToken
557558
}
558559
if (proxyId) {
559560
payload.proxy_id = proxyId
560561
}
562+
if (clientId) {
563+
payload.client_id = clientId
564+
}
561565
const { data } = await apiClient.post<Record<string, unknown>>(endpoint, payload)
562566
return data
563567
}

frontend/src/components/account/CreateAccountModal.vue

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2504,13 +2504,15 @@
25042504
:allow-multiple="form.platform === 'anthropic'"
25052505
:show-cookie-option="form.platform === 'anthropic'"
25062506
:show-refresh-token-option="form.platform === 'openai' || form.platform === 'sora' || form.platform === 'antigravity'"
2507+
:show-mobile-refresh-token-option="form.platform === 'openai'"
25072508
:show-session-token-option="form.platform === 'sora'"
25082509
:show-access-token-option="form.platform === 'sora'"
25092510
:platform="form.platform"
25102511
:show-project-id="geminiOAuthType === 'code_assist'"
25112512
@generate-url="handleGenerateUrl"
25122513
@cookie-auth="handleCookieAuth"
25132514
@validate-refresh-token="handleValidateRefreshToken"
2515+
@validate-mobile-refresh-token="handleOpenAIValidateMobileRT"
25142516
@validate-session-token="handleValidateSessionToken"
25152517
@import-access-token="handleImportAccessToken"
25162518
/>
@@ -4360,11 +4362,14 @@ const handleOpenAIExchange = async (authCode: string) => {
43604362
}
43614363
43624364
// OpenAI 手动 RT 批量验证和创建
4363-
const handleOpenAIValidateRT = async (refreshTokenInput: string) => {
4365+
// OpenAI Mobile RT 使用的 client_id(与后端 openai.SoraClientID 一致)
4366+
const OPENAI_MOBILE_RT_CLIENT_ID = 'app_LlGpXReQgckcGGUo2JrYvtJK'
4367+
4368+
// OpenAI/Sora RT 批量验证和创建(共享逻辑)
4369+
const handleOpenAIBatchRT = async (refreshTokenInput: string, clientId?: string) => {
43644370
const oauthClient = activeOpenAIOAuth.value
43654371
if (!refreshTokenInput.trim()) return
43664372
4367-
// Parse multiple refresh tokens (one per line)
43684373
const refreshTokens = refreshTokenInput
43694374
.split('\n')
43704375
.map((rt) => rt.trim())
@@ -4389,7 +4394,8 @@ const handleOpenAIValidateRT = async (refreshTokenInput: string) => {
43894394
try {
43904395
const tokenInfo = await oauthClient.validateRefreshToken(
43914396
refreshTokens[i],
4392-
form.proxy_id
4397+
form.proxy_id,
4398+
clientId
43934399
)
43944400
if (!tokenInfo) {
43954401
failedCount++
@@ -4399,6 +4405,9 @@ const handleOpenAIValidateRT = async (refreshTokenInput: string) => {
43994405
}
44004406
44014407
const credentials = oauthClient.buildCredentials(tokenInfo)
4408+
if (clientId) {
4409+
credentials.client_id = clientId
4410+
}
44024411
const oauthExtra = oauthClient.buildExtraInfo(tokenInfo) as Record<string, unknown> | undefined
44034412
const extra = buildOpenAIExtra(oauthExtra)
44044413
@@ -4410,8 +4419,9 @@ const handleOpenAIValidateRT = async (refreshTokenInput: string) => {
44104419
}
44114420
}
44124421
4413-
// Generate account name with index for batch
4414-
const accountName = refreshTokens.length > 1 ? `${form.name} #${i + 1}` : form.name
4422+
// Generate account name; fallback to email if name is empty (ent schema requires NotEmpty)
4423+
const baseName = form.name || tokenInfo.email || 'OpenAI OAuth Account'
4424+
const accountName = refreshTokens.length > 1 ? `${baseName} #${i + 1}` : baseName
44154425
44164426
let openaiAccountId: string | number | undefined
44174427
@@ -4494,6 +4504,12 @@ const handleOpenAIValidateRT = async (refreshTokenInput: string) => {
44944504
}
44954505
}
44964506
4507+
// 手动输入 RT(Codex CLI client_id,默认)
4508+
const handleOpenAIValidateRT = (rt: string) => handleOpenAIBatchRT(rt)
4509+
4510+
// 手动输入 Mobile RT(SoraClientID)
4511+
const handleOpenAIValidateMobileRT = (rt: string) => handleOpenAIBatchRT(rt, OPENAI_MOBILE_RT_CLIENT_ID)
4512+
44974513
// Sora 手动 ST 批量验证和创建
44984514
const handleSoraValidateST = async (sessionTokenInput: string) => {
44994515
const oauthClient = activeOpenAIOAuth.value

frontend/src/components/account/OAuthAuthorizationFlow.vue

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@
4848
t(getOAuthKey('refreshTokenAuth'))
4949
}}</span>
5050
</label>
51+
<label v-if="showMobileRefreshTokenOption" class="flex cursor-pointer items-center gap-2">
52+
<input
53+
v-model="inputMethod"
54+
type="radio"
55+
value="mobile_refresh_token"
56+
class="text-blue-600 focus:ring-blue-500"
57+
/>
58+
<span class="text-sm text-blue-900 dark:text-blue-200">{{
59+
t('admin.accounts.oauth.openai.mobileRefreshTokenAuth', '手动输入 Mobile RT')
60+
}}</span>
61+
</label>
5162
<label v-if="showSessionTokenOption" class="flex cursor-pointer items-center gap-2">
5263
<input
5364
v-model="inputMethod"
@@ -73,8 +84,8 @@
7384
</div>
7485
</div>
7586

76-
<!-- Refresh Token Input (OpenAI / Antigravity) -->
77-
<div v-if="inputMethod === 'refresh_token'" class="space-y-4">
87+
<!-- Refresh Token Input (OpenAI / Antigravity / Mobile RT) -->
88+
<div v-if="inputMethod === 'refresh_token' || inputMethod === 'mobile_refresh_token'" class="space-y-4">
7889
<div
7990
class="rounded-lg border border-blue-300 bg-white/80 p-4 dark:border-blue-600 dark:bg-gray-800/80"
8091
>
@@ -759,6 +770,7 @@ interface Props {
759770
methodLabel?: string
760771
showCookieOption?: boolean // Whether to show cookie auto-auth option
761772
showRefreshTokenOption?: boolean // Whether to show refresh token input option (OpenAI only)
773+
showMobileRefreshTokenOption?: boolean // Whether to show mobile refresh token option (OpenAI only)
762774
showSessionTokenOption?: boolean // Whether to show session token input option (Sora only)
763775
showAccessTokenOption?: boolean // Whether to show access token input option (Sora only)
764776
platform?: AccountPlatform // Platform type for different UI/text
@@ -776,6 +788,7 @@ const props = withDefaults(defineProps<Props>(), {
776788
methodLabel: 'Authorization Method',
777789
showCookieOption: true,
778790
showRefreshTokenOption: false,
791+
showMobileRefreshTokenOption: false,
779792
showSessionTokenOption: false,
780793
showAccessTokenOption: false,
781794
platform: 'anthropic',
@@ -787,6 +800,7 @@ const emit = defineEmits<{
787800
'exchange-code': [code: string]
788801
'cookie-auth': [sessionKey: string]
789802
'validate-refresh-token': [refreshToken: string]
803+
'validate-mobile-refresh-token': [refreshToken: string]
790804
'validate-session-token': [sessionToken: string]
791805
'import-access-token': [accessToken: string]
792806
'update:inputMethod': [method: AuthInputMethod]
@@ -834,7 +848,7 @@ const oauthState = ref('')
834848
const projectId = ref('')
835849
836850
// Computed: show method selection when either cookie or refresh token option is enabled
837-
const showMethodSelection = computed(() => props.showCookieOption || props.showRefreshTokenOption || props.showSessionTokenOption || props.showAccessTokenOption)
851+
const showMethodSelection = computed(() => props.showCookieOption || props.showRefreshTokenOption || props.showMobileRefreshTokenOption || props.showSessionTokenOption || props.showAccessTokenOption)
838852
839853
// Clipboard
840854
const { copied, copyToClipboard } = useClipboard()
@@ -945,7 +959,11 @@ const handleCookieAuth = () => {
945959
946960
const handleValidateRefreshToken = () => {
947961
if (refreshTokenInput.value.trim()) {
948-
emit('validate-refresh-token', refreshTokenInput.value.trim())
962+
if (inputMethod.value === 'mobile_refresh_token') {
963+
emit('validate-mobile-refresh-token', refreshTokenInput.value.trim())
964+
} else {
965+
emit('validate-refresh-token', refreshTokenInput.value.trim())
966+
}
949967
}
950968
}
951969

frontend/src/composables/useAccountOAuth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useAppStore } from '@/stores/app'
33
import { adminAPI } from '@/api/admin'
44

55
export type AddMethod = 'oauth' | 'setup-token'
6-
export type AuthInputMethod = 'manual' | 'cookie' | 'refresh_token' | 'session_token' | 'access_token'
6+
export type AuthInputMethod = 'manual' | 'cookie' | 'refresh_token' | 'mobile_refresh_token' | 'session_token' | 'access_token'
77

88
export interface OAuthState {
99
authUrl: string

frontend/src/composables/useOpenAIOAuth.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,11 @@ export function useOpenAIOAuth(options?: UseOpenAIOAuthOptions) {
126126
}
127127

128128
// Validate refresh token and get full token info
129+
// clientId: 指定 OAuth client_id(用于第三方渠道获取的 RT,如 app_LlGpXReQgckcGGUo2JrYvtJK)
129130
const validateRefreshToken = async (
130131
refreshToken: string,
131-
proxyId?: number | null
132+
proxyId?: number | null,
133+
clientId?: string
132134
): Promise<OpenAITokenInfo | null> => {
133135
if (!refreshToken.trim()) {
134136
error.value = 'Missing refresh token'
@@ -143,11 +145,12 @@ export function useOpenAIOAuth(options?: UseOpenAIOAuthOptions) {
143145
const tokenInfo = await adminAPI.accounts.refreshOpenAIToken(
144146
refreshToken.trim(),
145147
proxyId,
146-
`${endpointPrefix}/refresh-token`
148+
`${endpointPrefix}/refresh-token`,
149+
clientId
147150
)
148151
return tokenInfo as OpenAITokenInfo
149152
} catch (err: any) {
150-
error.value = err.response?.data?.detail || 'Failed to validate refresh token'
153+
error.value = err.response?.data?.detail || err.message || 'Failed to validate refresh token'
151154
appStore.showError(error.value)
152155
return null
153156
} finally {

0 commit comments

Comments
 (0)