mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 12:02:27 +08:00
Merge pull request #4590 from superman2003/fix/grok-client-cache-codex-trae
fix(grok): cache client tools from Codex, Trae, and Claude Desktop
This commit is contained in:
@@ -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,9 @@ const headerOverrideCapable = computed(
|
||||
// Grok OAuth 自定义上游地址(仅转发端点;OAuth 授权/令牌刷新不受影响)
|
||||
const grokOAuthCustomBaseUrlEnabled = ref(false)
|
||||
const grokOAuthBaseUrl = ref('')
|
||||
// Grok Free OAuth accounts use client-tool prompt caching by default. Keep an
|
||||
// explicit false in the account extra as the opt-out signal.
|
||||
const grokClientToolCacheEnabled = ref(true)
|
||||
|
||||
const interceptWarmupRequests = ref(false)
|
||||
const autoPauseOnExpired = ref(false)
|
||||
@@ -3402,6 +3426,14 @@ const syncFormFromAccount = (newAccount: Account | null) => {
|
||||
// Load Grok OAuth custom upstream URL state(存储的官方地址视同未定制)
|
||||
grokOAuthCustomBaseUrlEnabled.value = false
|
||||
grokOAuthBaseUrl.value = ''
|
||||
const grokClientToolCacheSetting =
|
||||
newAccount.platform === 'grok' && newAccount.type === 'oauth'
|
||||
? newAccount.extra?.[GROK_CLIENT_TOOL_CACHE_EXTRA_KEY]
|
||||
: undefined
|
||||
grokClientToolCacheEnabled.value =
|
||||
newAccount.platform === 'grok' &&
|
||||
newAccount.type === 'oauth' &&
|
||||
(grokClientToolCacheSetting === undefined || grokClientToolCacheSetting === 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 +4320,14 @@ 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>) || {})
|
||||
}
|
||||
// Persist both states so a disabled account remains opted out when the
|
||||
// backend applies the default-enabled policy to missing values.
|
||||
newExtra[GROK_CLIENT_TOOL_CACHE_EXTRA_KEY] = grokClientToolCacheEnabled.value
|
||||
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,99 @@ 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?.grok_client_tool_cache_enabled).toBe(false)
|
||||
})
|
||||
|
||||
it('defaults client-tool caching on when the setting is missing and persists explicit true', 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('true')
|
||||
|
||||
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'
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps an explicit false opt-out when saving an untouched account', async () => {
|
||||
const account = buildGrokOAuthAccount(
|
||||
{},
|
||||
{
|
||||
grok_client_tool_cache_enabled: false,
|
||||
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 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: false,
|
||||
custom_setting: 'keep-me'
|
||||
})
|
||||
})
|
||||
|
||||
it('shows malformed cache settings as disabled so the UI matches the fail-closed backend', () => {
|
||||
const account = buildGrokOAuthAccount(
|
||||
{},
|
||||
{
|
||||
grok_client_tool_cache_enabled: 'true',
|
||||
custom_setting: 'keep-me'
|
||||
}
|
||||
)
|
||||
|
||||
const wrapper = mountModal(account)
|
||||
expect(
|
||||
wrapper.get('[data-testid="grok-client-tool-cache-toggle"]').attributes('aria-checked')
|
||||
).toBe('false')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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, this is enabled by default for client function tools such as Codex and Trae. Turn it off to opt out if the automatic tool-selection behavior is not 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 用量阈值(%)',
|
||||
|
||||
Reference in New Issue
Block a user