feat(admin): expose Grok client tool cache opt-in

This commit is contained in:
superman2003
2026-07-19 14:42:06 +08:00
parent 752b2eaa9b
commit 9d79b42729
4 changed files with 106 additions and 2 deletions
@@ -428,6 +428,26 @@
</div>
<!-- Grok OAuth client-tool prompt cache opt-in -->
<div
v-if="account.platform === 'grok' && account.type === 'oauth'"
class="border-t border-gray-200 pt-4 dark:border-dark-600"
>
<div class="flex items-center justify-between gap-4">
<div class="min-w-0">
<label class="input-label mb-0">{{ t('admin.accounts.grokClientToolCache.title') }}</label>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
{{ t('admin.accounts.grokClientToolCache.hint') }}
</p>
</div>
<Toggle
v-model="grokClientToolCacheEnabled"
data-testid="grok-client-tool-cache-toggle"
:aria-label="t('admin.accounts.grokClientToolCache.title')"
/>
</div>
</div>
<!-- Grok OAuth Custom Upstream URL (仅改写转发端点,OAuth 授权/刷新不受影响) -->
<div
v-if="account.platform === 'grok' && account.type === 'oauth'"
@@ -2699,6 +2719,7 @@ const allowedModels = ref<string[]>([])
const DEFAULT_POOL_MODE_RETRY_COUNT = 3
const MAX_POOL_MODE_RETRY_COUNT = 10
const DEFAULT_POOL_MODE_RETRY_STATUS_CODES = [401, 403, 429]
const GROK_CLIENT_TOOL_CACHE_EXTRA_KEY = 'grok_client_tool_cache_enabled'
const poolModeEnabled = ref(false)
const poolModeRetryCount = ref(DEFAULT_POOL_MODE_RETRY_COUNT)
const poolModeRetryStatusCodesInput = ref('')
@@ -2747,6 +2768,7 @@ const headerOverrideCapable = computed(
// Grok OAuth 自定义上游地址(仅转发端点;OAuth 授权/令牌刷新不受影响)
const grokOAuthCustomBaseUrlEnabled = ref(false)
const grokOAuthBaseUrl = ref('')
const grokClientToolCacheEnabled = ref(false)
const interceptWarmupRequests = ref(false)
const autoPauseOnExpired = ref(false)
@@ -3402,6 +3424,10 @@ const syncFormFromAccount = (newAccount: Account | null) => {
// Load Grok OAuth custom upstream URL state(存储的官方地址视同未定制)
grokOAuthCustomBaseUrlEnabled.value = false
grokOAuthBaseUrl.value = ''
grokClientToolCacheEnabled.value =
newAccount.platform === 'grok' &&
newAccount.type === 'oauth' &&
newAccount.extra?.[GROK_CLIENT_TOOL_CACHE_EXTRA_KEY] === true
if (newAccount.platform === 'grok' && newAccount.type === 'oauth' && newAccount.credentials) {
const grokCreds = newAccount.credentials as Record<string, unknown>
if (isCustomGrokBaseUrl(grokCreds.base_url)) {
@@ -4288,6 +4314,16 @@ const handleSubmit = async () => {
applyHeaderOverride(newCredentials, headerOverrideEnabled.value, headerOverrideRows.value, 'edit')
updatePayload.credentials = newCredentials
const newExtra: Record<string, unknown> = {
...((props.account.extra as Record<string, unknown>) || {})
}
if (grokClientToolCacheEnabled.value) {
newExtra[GROK_CLIENT_TOOL_CACHE_EXTRA_KEY] = true
} else {
delete newExtra[GROK_CLIENT_TOOL_CACHE_EXTRA_KEY]
}
updatePayload.extra = newExtra
}
// OpenAI: 手动覆盖订阅档位 plan_type(Plus/Pro/Free)。仅 OAuth 非影子账号:
@@ -67,7 +67,10 @@ const BaseDialogStub = defineComponent({
template: '<div v-if="show"><slot /><slot name="footer" /></div>'
})
function buildGrokOAuthAccount(credentials: Record<string, unknown> = {}) {
function buildGrokOAuthAccount(
credentials: Record<string, unknown> = {},
extra: Record<string, unknown> = {}
) {
return {
id: 5,
name: 'Grok OAuth',
@@ -80,7 +83,7 @@ function buildGrokOAuthAccount(credentials: Record<string, unknown> = {}) {
...credentials
},
credentials_status: { has_access_token: true, has_refresh_token: true },
extra: {},
extra,
proxy_id: null,
concurrency: 1,
priority: 1,
@@ -225,4 +228,61 @@ describe('EditAccountModal Grok OAuth upstream config', () => {
'x-xai-token-auth': 'xai-grok-cli'
})
})
it('shows the client-tool cache switch only for Grok OAuth accounts', () => {
const grokOAuthWrapper = mountModal(buildGrokOAuthAccount())
expect(grokOAuthWrapper.find('[data-testid="grok-client-tool-cache-toggle"]').exists()).toBe(true)
const grokAPIKeyWrapper = mountModal({
...buildGrokOAuthAccount(),
type: 'apikey',
credentials: { api_key: 'xai-test', base_url: 'https://api.x.ai/v1' }
})
expect(grokAPIKeyWrapper.find('[data-testid="grok-client-tool-cache-toggle"]').exists()).toBe(false)
const openAIOAuthWrapper = mountModal({
...buildGrokOAuthAccount(),
platform: 'openai'
})
expect(openAIOAuthWrapper.find('[data-testid="grok-client-tool-cache-toggle"]').exists()).toBe(false)
})
it('loads and disables client-tool caching while preserving unrelated extra fields', async () => {
const account = buildGrokOAuthAccount({}, {
grok_client_tool_cache_enabled: true,
custom_setting: 'keep-me'
})
updateAccountMock.mockResolvedValue(account)
const wrapper = mountModal(account)
const toggle = wrapper.get('[data-testid="grok-client-tool-cache-toggle"]')
expect(toggle.attributes('aria-checked')).toBe('true')
await toggle.trigger('click')
await wrapper.get('form#edit-account-form').trigger('submit.prevent')
await vi.waitFor(() => expect(updateAccountMock).toHaveBeenCalledTimes(1))
const payload = updateAccountMock.mock.calls[0]?.[1]
expect(payload?.extra?.custom_setting).toBe('keep-me')
expect(payload?.extra).not.toHaveProperty('grok_client_tool_cache_enabled')
})
it('enables client-tool caching while preserving unrelated extra fields', async () => {
const account = buildGrokOAuthAccount({}, { custom_setting: 'keep-me' })
updateAccountMock.mockResolvedValue(account)
const wrapper = mountModal(account)
const toggle = wrapper.get('[data-testid="grok-client-tool-cache-toggle"]')
expect(toggle.attributes('aria-checked')).toBe('false')
await toggle.trigger('click')
await wrapper.get('form#edit-account-form').trigger('submit.prevent')
await vi.waitFor(() => expect(updateAccountMock).toHaveBeenCalledTimes(1))
const payload = updateAccountMock.mock.calls[0]?.[1]
expect(payload?.extra).toMatchObject({
grok_client_tool_cache_enabled: true,
custom_setting: 'keep-me'
})
})
})
@@ -641,6 +641,10 @@ export default {
official: 'Official API'
}
},
grokClientToolCache: {
title: 'Client Tool Cache (May Change Automatic Tool Selection)',
hint: 'For detected Grok Free OAuth accounts, enable upstream prompt caching for client function tools such as Codex and Trae. Enable only after confirming the automatic tool-selection behavior is acceptable.'
},
autoPauseOnExpired: 'Auto Pause On Expired',
autoPauseOnExpiredDesc: 'When enabled, the account will auto pause scheduling after it expires',
autoPause5hThreshold: '5h Usage Threshold (%)',
@@ -734,6 +734,10 @@ export default {
official: '官方 API'
}
},
grokClientToolCache: {
title: '客户端工具缓存(可能改变自动工具选择)',
hint: '仅对已识别为 Free 的 Grok OAuth 账号生效,为 Codex、Trae 等客户端函数工具请求启用上游提示缓存;请确认自动工具选择行为可接受后开启。'
},
autoPauseOnExpired: '过期自动暂停调度',
autoPauseOnExpiredDesc: '启用后,账号过期将自动暂停调度',
autoPause5hThreshold: '5h 用量阈值(%)',