mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-24 16:05:44 +08:00
fix(openai): 调整 Agent Identity 授权入口层级
This commit is contained in:
@@ -296,7 +296,7 @@
|
||||
<!-- Account Type Selection (OpenAI) -->
|
||||
<div v-if="form.platform === 'openai'">
|
||||
<label class="input-label">{{ t('admin.accounts.accountType') }}</label>
|
||||
<div class="mt-2 grid grid-cols-1 gap-3 sm:grid-cols-2" data-tour="account-form-type">
|
||||
<div class="mt-2 grid grid-cols-2 gap-3" data-tour="account-form-type">
|
||||
<button
|
||||
type="button"
|
||||
@click="accountCategory = 'oauth-based'"
|
||||
@@ -323,60 +323,6 @@
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-testid="openai-account-type-agent-identity"
|
||||
@click="accountCategory = 'agent-identity'"
|
||||
:class="[
|
||||
'flex items-center gap-3 rounded-lg border-2 p-3 text-left transition-all',
|
||||
accountCategory === 'agent-identity'
|
||||
? 'border-emerald-500 bg-emerald-50 dark:bg-emerald-900/20'
|
||||
: 'border-gray-200 hover:border-emerald-300 dark:border-dark-600 dark:hover:border-emerald-700'
|
||||
]"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'flex h-8 w-8 shrink-0 items-center justify-center rounded-lg',
|
||||
accountCategory === 'agent-identity'
|
||||
? 'bg-emerald-500 text-white'
|
||||
: 'bg-gray-100 text-gray-500 dark:bg-dark-600 dark:text-gray-400'
|
||||
]"
|
||||
>
|
||||
<Icon name="shield" size="sm" />
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-sm font-medium text-gray-900 dark:text-white">Agent Identity</span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">{{ t('admin.accounts.types.agentIdentity') }}</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-testid="openai-account-type-codex-pat"
|
||||
@click="accountCategory = 'codex-pat'"
|
||||
:class="[
|
||||
'flex items-center gap-3 rounded-lg border-2 p-3 text-left transition-all',
|
||||
accountCategory === 'codex-pat'
|
||||
? 'border-cyan-500 bg-cyan-50 dark:bg-cyan-900/20'
|
||||
: 'border-gray-200 hover:border-cyan-300 dark:border-dark-600 dark:hover:border-cyan-700'
|
||||
]"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'flex h-8 w-8 shrink-0 items-center justify-center rounded-lg',
|
||||
accountCategory === 'codex-pat'
|
||||
? 'bg-cyan-500 text-white'
|
||||
: 'bg-gray-100 text-gray-500 dark:bg-dark-600 dark:text-gray-400'
|
||||
]"
|
||||
>
|
||||
<Icon name="key" size="sm" />
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-sm font-medium text-gray-900 dark:text-white">Codex PAT</span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">{{ t('admin.accounts.types.codexPat') }}</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click="accountCategory = 'apikey'"
|
||||
@@ -3166,17 +3112,16 @@
|
||||
:show-proxy-warning="form.platform !== 'openai' && form.platform !== 'grok' && !!form.proxy_id"
|
||||
:allow-multiple="form.platform === 'anthropic'"
|
||||
:show-cookie-option="form.platform === 'anthropic'"
|
||||
:show-refresh-token-option="(form.platform === 'openai' && accountCategory === 'oauth-based') || form.platform === 'antigravity' || form.platform === 'grok'"
|
||||
:show-mobile-refresh-token-option="form.platform === 'openai' && accountCategory === 'oauth-based'"
|
||||
:show-refresh-token-option="form.platform === 'openai' || form.platform === 'antigravity' || form.platform === 'grok'"
|
||||
:show-mobile-refresh-token-option="form.platform === 'openai'"
|
||||
:show-session-token-option="false"
|
||||
:show-access-token-option="false"
|
||||
:show-codex-session-import-option="form.platform === 'openai' && (accountCategory === 'oauth-based' || accountCategory === 'agent-identity')"
|
||||
:show-codex-pat-option="form.platform === 'openai' && accountCategory === 'codex-pat'"
|
||||
:show-codex-session-import-option="form.platform === 'openai'"
|
||||
:show-agent-identity-option="form.platform === 'openai'"
|
||||
:show-codex-pat-option="form.platform === 'openai'"
|
||||
:show-sso-option="form.platform === 'grok'"
|
||||
:show-manual-option="form.platform !== 'openai' || accountCategory === 'oauth-based'"
|
||||
:initial-input-method="openAIInitialInputMethod"
|
||||
:title-override="oauthStepTitle"
|
||||
:agent-identity-only="form.platform === 'openai' && accountCategory === 'agent-identity'"
|
||||
:show-manual-option="true"
|
||||
:initial-input-method="'manual'"
|
||||
:platform="form.platform"
|
||||
:show-project-id="geminiOAuthType === 'code_assist'"
|
||||
@generate-url="handleGenerateUrl"
|
||||
@@ -3593,8 +3538,6 @@ const { t } = useI18n()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const oauthStepTitle = computed(() => {
|
||||
if (form.platform === 'openai' && accountCategory.value === 'agent-identity') return 'Agent Identity'
|
||||
if (form.platform === 'openai' && accountCategory.value === 'codex-pat') return 'Codex PAT'
|
||||
if (form.platform === 'openai') return t('admin.accounts.oauth.openai.title')
|
||||
if (form.platform === 'gemini') return t('admin.accounts.oauth.gemini.title')
|
||||
if (form.platform === 'antigravity') return t('admin.accounts.oauth.antigravity.title')
|
||||
@@ -3690,7 +3633,7 @@ interface TempUnschedRuleForm {
|
||||
// State
|
||||
const step = ref(1)
|
||||
const submitting = ref(false)
|
||||
const accountCategory = ref<'oauth-based' | 'agent-identity' | 'codex-pat' | 'apikey' | 'bedrock' | 'service_account'>('oauth-based') // UI selection for account category
|
||||
const accountCategory = ref<'oauth-based' | 'apikey' | 'bedrock' | 'service_account'>('oauth-based') // UI selection for account category
|
||||
const addMethod = ref<AddMethod>('oauth') // For oauth-based: 'oauth' or 'setup-token'
|
||||
const apiKeyBaseUrl = ref('https://api.anthropic.com')
|
||||
const apiKeyValue = ref('')
|
||||
@@ -4075,14 +4018,7 @@ const isOAuthFlow = computed(() => {
|
||||
if (form.platform === 'anthropic' && accountCategory.value === 'bedrock') {
|
||||
return false
|
||||
}
|
||||
return accountCategory.value === 'oauth-based' || accountCategory.value === 'agent-identity' || accountCategory.value === 'codex-pat'
|
||||
})
|
||||
|
||||
const openAIInitialInputMethod = computed<AuthInputMethod>(() => {
|
||||
if (form.platform !== 'openai') return 'manual'
|
||||
if (accountCategory.value === 'agent-identity') return 'codex_session'
|
||||
if (accountCategory.value === 'codex-pat') return 'codex_pat'
|
||||
return 'manual'
|
||||
return accountCategory.value === 'oauth-based'
|
||||
})
|
||||
|
||||
const isGrokSSOInputMethod = computed(() => form.platform === 'grok' && oauthFlowRef.value?.inputMethod === 'sso_cookie')
|
||||
@@ -4160,7 +4096,7 @@ watch(
|
||||
}
|
||||
if ((form.platform === 'gemini' || form.platform === 'anthropic') && category === 'service_account') {
|
||||
form.type = 'service_account' as AccountType
|
||||
} else if (category === 'oauth-based' || category === 'agent-identity' || category === 'codex-pat') {
|
||||
} else if (category === 'oauth-based') {
|
||||
form.type = form.platform === 'anthropic' ? method as AccountType : 'oauth'
|
||||
} else {
|
||||
form.type = 'apikey'
|
||||
@@ -4261,7 +4197,7 @@ watch(
|
||||
watch(
|
||||
[accountCategory, () => form.platform],
|
||||
([category, platform]) => {
|
||||
if (platform === 'openai' && category !== 'oauth-based' && category !== 'agent-identity' && category !== 'codex-pat') {
|
||||
if (platform === 'openai' && category !== 'oauth-based') {
|
||||
codexCLIOnlyEnabled.value = false
|
||||
codexCLIOnlyAppServerEnabled.value = false
|
||||
}
|
||||
@@ -5541,7 +5477,7 @@ const handleOpenAIImportCodexSession = async (content: string) => {
|
||||
oauthClient.error.value = t('admin.accounts.oauth.openai.codexSessionEmpty')
|
||||
return
|
||||
}
|
||||
if (accountCategory.value === 'agent-identity' && !isAgentIdentityImportContent(trimmed)) {
|
||||
if (oauthFlowRef.value?.inputMethod === 'agent_identity' && !isAgentIdentityImportContent(trimmed)) {
|
||||
oauthClient.error.value = t('admin.accounts.oauth.openai.agentIdentityInvalid')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -103,6 +103,17 @@
|
||||
t('admin.accounts.oauth.openai.codexSessionAuth')
|
||||
}}</span>
|
||||
</label>
|
||||
<label v-if="showAgentIdentityOption" class="flex cursor-pointer items-center gap-2">
|
||||
<input
|
||||
v-model="inputMethod"
|
||||
type="radio"
|
||||
value="agent_identity"
|
||||
class="text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span class="text-sm text-blue-900 dark:text-blue-200">{{
|
||||
t('admin.accounts.oauth.openai.agentIdentityAuth')
|
||||
}}</span>
|
||||
</label>
|
||||
<label v-if="showCodexPatOption" class="flex cursor-pointer items-center gap-2">
|
||||
<input
|
||||
v-model="inputMethod"
|
||||
@@ -277,12 +288,12 @@
|
||||
</div>
|
||||
|
||||
<!-- Codex auth.json / session credential batch import -->
|
||||
<div v-if="inputMethod === 'codex_session'" class="space-y-4">
|
||||
<div v-if="inputMethod === 'codex_session' || inputMethod === 'agent_identity'" class="space-y-4">
|
||||
<div
|
||||
class="rounded-lg border border-blue-300 bg-white/80 p-4 dark:border-blue-600 dark:bg-gray-800/80"
|
||||
>
|
||||
<p class="mb-3 text-sm text-blue-700 dark:text-blue-300">
|
||||
{{ t(agentIdentityOnly ? 'admin.accounts.oauth.openai.agentIdentityDesc' : 'admin.accounts.oauth.openai.codexSessionDesc') }}
|
||||
{{ t(isAgentIdentityInput ? 'admin.accounts.oauth.openai.agentIdentityDesc' : 'admin.accounts.oauth.openai.codexSessionDesc') }}
|
||||
</p>
|
||||
|
||||
<div class="mb-4">
|
||||
@@ -290,7 +301,7 @@
|
||||
class="mb-2 flex items-center gap-2 text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
<Icon name="key" size="sm" class="text-blue-500" />
|
||||
{{ t(agentIdentityOnly ? 'admin.accounts.oauth.openai.agentIdentityInputLabel' : 'admin.accounts.oauth.openai.codexSessionInputLabel') }}
|
||||
{{ t(isAgentIdentityInput ? 'admin.accounts.oauth.openai.agentIdentityInputLabel' : 'admin.accounts.oauth.openai.codexSessionInputLabel') }}
|
||||
<span
|
||||
v-if="parsedCodexSessionCount > 1"
|
||||
class="rounded-full bg-blue-500 px-2 py-0.5 text-xs text-white"
|
||||
@@ -302,11 +313,11 @@
|
||||
v-model="codexSessionInput"
|
||||
rows="8"
|
||||
class="input w-full resize-y font-mono text-sm"
|
||||
:placeholder="t(agentIdentityOnly ? 'admin.accounts.oauth.openai.agentIdentityPlaceholder' : 'admin.accounts.oauth.openai.codexSessionPlaceholder')"
|
||||
:placeholder="t(isAgentIdentityInput ? 'admin.accounts.oauth.openai.agentIdentityPlaceholder' : 'admin.accounts.oauth.openai.codexSessionPlaceholder')"
|
||||
spellcheck="false"
|
||||
></textarea>
|
||||
<p class="mt-1 text-xs text-blue-600 dark:text-blue-400">
|
||||
{{ t(agentIdentityOnly ? 'admin.accounts.oauth.openai.agentIdentityHint' : 'admin.accounts.oauth.openai.codexSessionHint') }}
|
||||
{{ t(isAgentIdentityInput ? 'admin.accounts.oauth.openai.agentIdentityHint' : 'admin.accounts.oauth.openai.codexSessionHint') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -822,14 +833,13 @@ interface Props {
|
||||
showSessionTokenOption?: boolean
|
||||
showAccessTokenOption?: boolean
|
||||
showCodexSessionImportOption?: boolean
|
||||
showAgentIdentityOption?: boolean
|
||||
showCodexPatOption?: boolean
|
||||
showSsoOption?: boolean
|
||||
showManualOption?: boolean
|
||||
initialInputMethod?: AuthInputMethod
|
||||
platform?: AccountPlatform // Platform type for different UI/text
|
||||
showProjectId?: boolean // New prop to control project ID visibility
|
||||
titleOverride?: string
|
||||
agentIdentityOnly?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -847,14 +857,13 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
showSessionTokenOption: false,
|
||||
showAccessTokenOption: false,
|
||||
showCodexSessionImportOption: false,
|
||||
showAgentIdentityOption: false,
|
||||
showCodexPatOption: false,
|
||||
showSsoOption: false,
|
||||
showManualOption: true,
|
||||
initialInputMethod: 'manual',
|
||||
platform: 'anthropic',
|
||||
showProjectId: true,
|
||||
titleOverride: '',
|
||||
agentIdentityOnly: false
|
||||
showProjectId: true
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -885,7 +894,7 @@ const getOAuthKey = (key: string) => {
|
||||
}
|
||||
|
||||
// Computed translations for current platform
|
||||
const oauthTitle = computed(() => props.titleOverride || t(getOAuthKey('title')))
|
||||
const oauthTitle = computed(() => t(getOAuthKey('title')))
|
||||
const oauthFollowSteps = computed(() => t(getOAuthKey('followSteps')))
|
||||
const oauthStep1GenerateUrl = computed(() => t(getOAuthKey('step1GenerateUrl')))
|
||||
const oauthGenerateAuthUrl = computed(() => t(getOAuthKey('generateAuthUrl')))
|
||||
@@ -905,6 +914,7 @@ const oauthImportantNotice = computed(() => {
|
||||
|
||||
// Local state
|
||||
const inputMethod = ref<AuthInputMethod>(props.initialInputMethod)
|
||||
const isAgentIdentityInput = computed(() => inputMethod.value === 'agent_identity')
|
||||
const authCodeInput = ref('')
|
||||
const sessionKeyInput = ref('')
|
||||
const refreshTokenInput = ref('')
|
||||
@@ -925,6 +935,7 @@ const methodOptionCount = computed(() => [
|
||||
props.showSessionTokenOption,
|
||||
props.showAccessTokenOption,
|
||||
props.showCodexSessionImportOption,
|
||||
props.showAgentIdentityOption,
|
||||
props.showCodexPatOption,
|
||||
props.showSsoOption
|
||||
].filter(Boolean).length)
|
||||
|
||||
@@ -67,11 +67,11 @@ const OAuthAuthorizationFlowStub = defineComponent({
|
||||
props: {
|
||||
showManualOption: Boolean,
|
||||
showCodexSessionImportOption: Boolean,
|
||||
showAgentIdentityOption: Boolean,
|
||||
showCodexPatOption: Boolean,
|
||||
initialInputMethod: String,
|
||||
agentIdentityOnly: Boolean,
|
||||
titleOverride: String,
|
||||
},
|
||||
data: () => ({ inputMethod: 'manual' }),
|
||||
emits: ['import-codex-session', 'import-codex-pat'],
|
||||
template: `
|
||||
<div>
|
||||
@@ -155,54 +155,34 @@ describe('CreateAccountModal OpenAI long-context billing', () => {
|
||||
expect(createAccountMock.mock.calls[0]?.[0]?.extra?.openai_long_context_billing_enabled).toBe(false)
|
||||
})
|
||||
|
||||
it('shows Agent Identity as a separate OpenAI account type', async () => {
|
||||
it('exposes Agent Identity in the OpenAI authorization methods', async () => {
|
||||
const wrapper = mountModal()
|
||||
await selectButtonByText(wrapper, 'OpenAI')
|
||||
await wrapper.get('[data-testid="openai-account-type-agent-identity"]').trigger('click')
|
||||
await wrapper.get('form#create-account-form input[type="text"]').setValue('Agent Identity')
|
||||
await wrapper.get('form#create-account-form input[type="text"]').setValue('OpenAI account')
|
||||
await wrapper.get('form#create-account-form').trigger('submit.prevent')
|
||||
|
||||
const flow = wrapper.getComponent(OAuthAuthorizationFlowStub)
|
||||
expect(flow.props('showManualOption')).toBe(false)
|
||||
expect(flow.props('showManualOption')).toBe(true)
|
||||
expect(flow.props('showCodexSessionImportOption')).toBe(true)
|
||||
expect(flow.props('showCodexPatOption')).toBe(false)
|
||||
expect(flow.props('initialInputMethod')).toBe('codex_session')
|
||||
expect(flow.props('agentIdentityOnly')).toBe(true)
|
||||
expect(flow.props('titleOverride')).toBe('Agent Identity')
|
||||
expect(flow.props('showAgentIdentityOption')).toBe(true)
|
||||
expect(flow.props('showCodexPatOption')).toBe(true)
|
||||
expect(flow.props('initialInputMethod')).toBe('manual')
|
||||
})
|
||||
|
||||
it.each([
|
||||
['camelCase', { authMode: 'agentIdentity', agentIdentity: { agentRuntimeId: 'runtime' } }],
|
||||
['nested identity without auth_mode', { agent_identity: { agent_runtime_id: 'runtime' } }],
|
||||
])('accepts backend-compatible %s Agent Identity imports', async (_name, content) => {
|
||||
const wrapper = mountModal()
|
||||
await selectButtonByText(wrapper, 'OpenAI')
|
||||
await wrapper.get('[data-testid="openai-account-type-agent-identity"]').trigger('click')
|
||||
await wrapper.get('form#create-account-form input[type="text"]').setValue('Agent Identity')
|
||||
await wrapper.get('form#create-account-form').trigger('submit.prevent')
|
||||
const wrapper = await openCodexImportStep()
|
||||
const flow = wrapper.getComponent(OAuthAuthorizationFlowStub)
|
||||
flow.vm.inputMethod = 'agent_identity'
|
||||
|
||||
wrapper.getComponent(OAuthAuthorizationFlowStub).vm.$emit('import-codex-session', JSON.stringify(content))
|
||||
flow.vm.$emit('import-codex-session', JSON.stringify(content))
|
||||
await flushPromises()
|
||||
|
||||
expect(importCodexSessionMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('shows Codex PAT as a separate OpenAI account type', async () => {
|
||||
const wrapper = mountModal()
|
||||
await selectButtonByText(wrapper, 'OpenAI')
|
||||
await wrapper.get('[data-testid="openai-account-type-codex-pat"]').trigger('click')
|
||||
await wrapper.get('form#create-account-form input[type="text"]').setValue('Codex PAT')
|
||||
await wrapper.get('form#create-account-form').trigger('submit.prevent')
|
||||
|
||||
const flow = wrapper.getComponent(OAuthAuthorizationFlowStub)
|
||||
expect(flow.props('showManualOption')).toBe(false)
|
||||
expect(flow.props('showCodexSessionImportOption')).toBe(false)
|
||||
expect(flow.props('showCodexPatOption')).toBe(true)
|
||||
expect(flow.props('initialInputMethod')).toBe('codex_pat')
|
||||
expect(flow.props('agentIdentityOnly')).toBe(false)
|
||||
expect(flow.props('titleOverride')).toBe('Codex PAT')
|
||||
})
|
||||
|
||||
it('sends true explicitly when OpenAI long-context billing is enabled', async () => {
|
||||
await submitApiKeyAccount('openai', true)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useAppStore } from '@/stores/app'
|
||||
import { adminAPI } from '@/api/admin'
|
||||
|
||||
export type AddMethod = 'oauth' | 'setup-token'
|
||||
export type AuthInputMethod = 'manual' | 'cookie' | 'refresh_token' | 'mobile_refresh_token' | 'session_token' | 'access_token' | 'codex_session' | 'codex_pat' | 'sso_cookie'
|
||||
export type AuthInputMethod = 'manual' | 'cookie' | 'refresh_token' | 'mobile_refresh_token' | 'session_token' | 'access_token' | 'codex_session' | 'agent_identity' | 'codex_pat' | 'sso_cookie'
|
||||
|
||||
export interface OAuthState {
|
||||
authUrl: string
|
||||
|
||||
@@ -108,8 +108,6 @@ export default {
|
||||
types: {
|
||||
oauth: 'OAuth',
|
||||
chatgptOauth: 'ChatGPT OAuth',
|
||||
agentIdentity: 'Import Codex auth.json',
|
||||
codexPat: 'Personal access token',
|
||||
responsesApi: 'Responses API',
|
||||
googleOauth: 'Google OAuth',
|
||||
codeAssist: 'Code Assist',
|
||||
@@ -837,6 +835,7 @@ export default {
|
||||
codexSessionImportFailed: 'Failed to import Codex account',
|
||||
codexSessionImportSuccess: 'Import completed: created {created}, updated {updated}, skipped {skipped}',
|
||||
codexSessionImportPartial: 'Partial success: created {created}, updated {updated}, skipped {skipped}, failed {failed}',
|
||||
agentIdentityAuth: 'Agent Identity auth.json',
|
||||
agentIdentityDesc: 'Import a Codex Agent Identity auth.json. No OAuth access or refresh token is stored.',
|
||||
agentIdentityInputLabel: 'Agent Identity auth.json',
|
||||
agentIdentityPlaceholder: 'Paste one Agent Identity auth.json object',
|
||||
|
||||
@@ -227,8 +227,6 @@ export default {
|
||||
types: {
|
||||
oauth: 'OAuth',
|
||||
chatgptOauth: 'ChatGPT OAuth',
|
||||
agentIdentity: '导入 Codex auth.json',
|
||||
codexPat: '个人访问令牌',
|
||||
responsesApi: 'Responses API',
|
||||
googleOauth: 'Google OAuth',
|
||||
codeAssist: 'Code Assist',
|
||||
@@ -924,6 +922,7 @@ export default {
|
||||
codexSessionImportFailed: 'Codex 账号导入失败',
|
||||
codexSessionImportSuccess: '导入完成:新增 {created},更新 {updated},跳过 {skipped}',
|
||||
codexSessionImportPartial: '部分成功:新增 {created},更新 {updated},跳过 {skipped},失败 {failed}',
|
||||
agentIdentityAuth: 'Agent Identity auth.json',
|
||||
agentIdentityDesc: '导入 Codex Agent Identity auth.json,不保存 OAuth access token 或 refresh token。',
|
||||
agentIdentityInputLabel: 'Agent Identity auth.json',
|
||||
agentIdentityPlaceholder: '粘贴一个 Agent Identity auth.json 对象',
|
||||
|
||||
Reference in New Issue
Block a user