From 65493df95afba3f3b27841b6250f293ea36e4f13 Mon Sep 17 00:00:00 2001 From: wucm667 Date: Fri, 8 May 2026 17:31:36 +0800 Subject: [PATCH] fix(ccswitch): add codex model to import deeplink --- .../utils/__tests__/ccswitchImport.spec.ts | 67 +++++++++++++++++ frontend/src/utils/ccswitchImport.ts | 72 +++++++++++++++++++ frontend/src/views/user/KeysView.vue | 50 ++++--------- 3 files changed, 151 insertions(+), 38 deletions(-) create mode 100644 frontend/src/utils/__tests__/ccswitchImport.spec.ts create mode 100644 frontend/src/utils/ccswitchImport.ts diff --git a/frontend/src/utils/__tests__/ccswitchImport.spec.ts b/frontend/src/utils/__tests__/ccswitchImport.spec.ts new file mode 100644 index 00000000..4e0eb1b2 --- /dev/null +++ b/frontend/src/utils/__tests__/ccswitchImport.spec.ts @@ -0,0 +1,67 @@ +import { describe, expect, it } from 'vitest' +import { + OPENAI_CC_SWITCH_CODEX_MODEL, + buildCcSwitchImportDeeplink +} from '@/utils/ccswitchImport' +import type { GroupPlatform } from '@/types' + +function paramsFromDeeplink(deeplink: string): URLSearchParams { + const query = deeplink.split('?')[1] || '' + return new URLSearchParams(query) +} + +describe('ccswitchImport utils', () => { + const baseInput = { + baseUrl: 'https://api.example.com', + providerName: 'Sub2API', + apiKey: 'sk-test', + usageScript: 'return true' + } + + it('adds the Codex model parameter for OpenAI imports', () => { + const params = paramsFromDeeplink( + buildCcSwitchImportDeeplink({ + ...baseInput, + platform: 'openai', + clientType: 'claude' + }) + ) + + expect(params.get('resource')).toBe('provider') + expect(params.get('app')).toBe('codex') + expect(params.get('endpoint')).toBe(baseInput.baseUrl) + expect(params.get('model')).toBe(OPENAI_CC_SWITCH_CODEX_MODEL) + expect(atob(params.get('usageScript') || '')).toBe(baseInput.usageScript) + }) + + it.each([ + { platform: 'anthropic' as GroupPlatform, clientType: 'claude' as const, app: 'claude' }, + { platform: 'gemini' as GroupPlatform, clientType: 'gemini' as const, app: 'gemini' } + ])('does not add a model parameter for $platform imports', ({ platform, clientType, app }) => { + const params = paramsFromDeeplink( + buildCcSwitchImportDeeplink({ + ...baseInput, + platform, + clientType + }) + ) + + expect(params.get('app')).toBe(app) + expect(params.get('endpoint')).toBe(baseInput.baseUrl) + expect(params.has('model')).toBe(false) + }) + + it('keeps Antigravity imports on the selected client endpoint without a model parameter', () => { + const params = paramsFromDeeplink( + buildCcSwitchImportDeeplink({ + ...baseInput, + platform: 'antigravity', + clientType: 'gemini' + }) + ) + + expect(params.get('app')).toBe('gemini') + expect(params.get('endpoint')).toBe(`${baseInput.baseUrl}/antigravity`) + expect(params.has('model')).toBe(false) + }) +}) diff --git a/frontend/src/utils/ccswitchImport.ts b/frontend/src/utils/ccswitchImport.ts new file mode 100644 index 00000000..79411d2f --- /dev/null +++ b/frontend/src/utils/ccswitchImport.ts @@ -0,0 +1,72 @@ +import type { GroupPlatform } from '@/types' + +export const OPENAI_CC_SWITCH_CODEX_MODEL = 'gpt-5.4' + +export type CcSwitchClientType = 'claude' | 'gemini' + +export interface CcSwitchImportConfig { + app: string + endpoint: string + model?: string +} + +export interface CcSwitchImportDeeplinkInput { + baseUrl: string + platform?: GroupPlatform | null + clientType: CcSwitchClientType + providerName: string + apiKey: string + usageScript: string +} + +export function resolveCcSwitchImportConfig( + platform: GroupPlatform | undefined | null, + clientType: CcSwitchClientType, + baseUrl: string +): CcSwitchImportConfig { + switch (platform || 'anthropic') { + case 'antigravity': + return { + app: clientType === 'gemini' ? 'gemini' : 'claude', + endpoint: `${baseUrl}/antigravity` + } + case 'openai': + return { + app: 'codex', + endpoint: baseUrl, + model: OPENAI_CC_SWITCH_CODEX_MODEL + } + case 'gemini': + return { + app: 'gemini', + endpoint: baseUrl + } + default: + return { + app: 'claude', + endpoint: baseUrl + } + } +} + +export function buildCcSwitchImportDeeplink(input: CcSwitchImportDeeplinkInput): string { + const config = resolveCcSwitchImportConfig(input.platform, input.clientType, input.baseUrl) + const entries: [string, string][] = [ + ['resource', 'provider'], + ['app', config.app], + ['name', input.providerName], + ['homepage', input.baseUrl], + ['endpoint', config.endpoint], + ['apiKey', input.apiKey], + ['configFormat', 'json'], + ['usageEnabled', 'true'], + ['usageScript', btoa(input.usageScript)], + ['usageAutoInterval', '30'] + ] + + if (config.model) { + entries.splice(2, 0, ['model', config.model]) + } + + return `ccswitch://v1/import?${new URLSearchParams(entries).toString()}` +} diff --git a/frontend/src/views/user/KeysView.vue b/frontend/src/views/user/KeysView.vue index cf29e4bd..7ca0a1c7 100644 --- a/frontend/src/views/user/KeysView.vue +++ b/frontend/src/views/user/KeysView.vue @@ -1073,6 +1073,10 @@ import type { Column } from '@/components/common/types' import type { BatchApiKeyUsageStats } from '@/api/usage' import { formatDateTime } from '@/utils/format' import { maskApiKey } from '@/utils/maskApiKey' +import { + buildCcSwitchImportDeeplink, + type CcSwitchClientType +} from '@/utils/ccswitchImport' // Helper to format date for datetime-local input const formatDateTimeLocal = (isoDate: string): string => { @@ -1700,34 +1704,10 @@ const importToCcswitch = (row: ApiKey) => { executeCcsImport(row, platform === 'gemini' ? 'gemini' : 'claude') } -const executeCcsImport = (row: ApiKey, clientType: 'claude' | 'gemini') => { +const executeCcsImport = (row: ApiKey, clientType: CcSwitchClientType) => { const baseUrl = publicSettings.value?.api_base_url || window.location.origin const platform = row.group?.platform || 'anthropic' - // Determine app name and endpoint based on platform and client type - let app: string - let endpoint: string - - if (platform === 'antigravity') { - // Antigravity always uses /antigravity suffix - app = clientType === 'gemini' ? 'gemini' : 'claude' - endpoint = `${baseUrl}/antigravity` - } else { - switch (platform) { - case 'openai': - app = 'codex' - endpoint = baseUrl - break - case 'gemini': - app = 'gemini' - endpoint = baseUrl - break - default: // anthropic - app = 'claude' - endpoint = baseUrl - } - } - const usageScript = `({ request: { url: "{{baseUrl}}/v1/usage", @@ -1745,20 +1725,14 @@ const executeCcsImport = (row: ApiKey, clientType: 'claude' | 'gemini') => { } })` const providerName = (publicSettings.value?.site_name || 'sub2api').trim() || 'sub2api' - - const params = new URLSearchParams({ - resource: 'provider', - app: app, - name: providerName, - homepage: baseUrl, - endpoint: endpoint, + const deeplink = buildCcSwitchImportDeeplink({ + baseUrl, + platform, + clientType, + providerName, apiKey: row.key, - configFormat: 'json', - usageEnabled: 'true', - usageScript: btoa(usageScript), - usageAutoInterval: '30' + usageScript }) - const deeplink = `ccswitch://v1/import?${params.toString()}` try { window.open(deeplink, '_self') @@ -1775,7 +1749,7 @@ const executeCcsImport = (row: ApiKey, clientType: 'claude' | 'gemini') => { } } -const handleCcsClientSelect = (clientType: 'claude' | 'gemini') => { +const handleCcsClientSelect = (clientType: CcSwitchClientType) => { if (pendingCcsRow.value) { executeCcsImport(pendingCcsRow.value, clientType) }