diff --git a/frontend/src/components/account/EditAccountModal.vue b/frontend/src/components/account/EditAccountModal.vue
index c61ebf6050..12afc93a11 100644
--- a/frontend/src/components/account/EditAccountModal.vue
+++ b/frontend/src/components/account/EditAccountModal.vue
@@ -428,6 +428,26 @@
+
+
+
+
+
+
+ {{ t('admin.accounts.grokClientToolCache.hint') }}
+
+
+
+
+
+
([])
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
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 = {
+ ...((props.account.extra as Record) || {})
+ }
+ 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 非影子账号:
diff --git a/frontend/src/components/account/__tests__/EditAccountModal.grokUpstream.spec.ts b/frontend/src/components/account/__tests__/EditAccountModal.grokUpstream.spec.ts
index 310a370889..068d7ac1a4 100644
--- a/frontend/src/components/account/__tests__/EditAccountModal.grokUpstream.spec.ts
+++ b/frontend/src/components/account/__tests__/EditAccountModal.grokUpstream.spec.ts
@@ -67,7 +67,10 @@ const BaseDialogStub = defineComponent({
template: '
'
})
-function buildGrokOAuthAccount(credentials: Record = {}) {
+function buildGrokOAuthAccount(
+ credentials: Record = {},
+ extra: Record = {}
+) {
return {
id: 5,
name: 'Grok OAuth',
@@ -80,7 +83,7 @@ function buildGrokOAuthAccount(credentials: Record = {}) {
...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'
+ })
+ })
})
diff --git a/frontend/src/i18n/locales/en/admin/accounts.ts b/frontend/src/i18n/locales/en/admin/accounts.ts
index 206c7c5172..772161f6c8 100644
--- a/frontend/src/i18n/locales/en/admin/accounts.ts
+++ b/frontend/src/i18n/locales/en/admin/accounts.ts
@@ -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 (%)',
diff --git a/frontend/src/i18n/locales/zh/admin/accounts.ts b/frontend/src/i18n/locales/zh/admin/accounts.ts
index 48da50175d..9cfa1ee17c 100644
--- a/frontend/src/i18n/locales/zh/admin/accounts.ts
+++ b/frontend/src/i18n/locales/zh/admin/accounts.ts
@@ -734,6 +734,10 @@ export default {
official: '官方 API'
}
},
+ grokClientToolCache: {
+ title: '客户端工具缓存(可能改变自动工具选择)',
+ hint: '仅对已识别为 Free 的 Grok OAuth 账号生效,为 Codex、Trae 等客户端函数工具请求启用上游提示缓存;请确认自动工具选择行为可接受后开启。'
+ },
autoPauseOnExpired: '过期自动暂停调度',
autoPauseOnExpiredDesc: '启用后,账号过期将自动暂停调度',
autoPause5hThreshold: '5h 用量阈值(%)',