Merge pull request #7549 from helianthus1/fix/ccswitch-usage-v1-path

fix(ccswitch): stop usage query from requesting /v1/v1/usage
This commit is contained in:
Wesley Liddick
2026-09-28 09:49:47 +08:00
committed by GitHub
3 changed files with 63 additions and 16 deletions
@@ -1,5 +1,6 @@
import { describe, expect, it } from 'vitest'
import {
CC_SWITCH_USAGE_SCRIPT,
GROK_CC_SWITCH_MODEL,
OPENAI_CC_SWITCH_CODEX_MODEL,
buildCcSwitchImportDeeplink
@@ -100,3 +101,38 @@ describe('ccswitchImport utils', () => {
expect(params.has('model')).toBe(false)
})
})
describe('CC Switch usage script', () => {
// Mirrors CC Switch: substitute the template vars as text, evaluate, read request.url.
function usageUrlFor(baseUrl: string): string {
const script = CC_SWITCH_USAGE_SCRIPT.split('{{baseUrl}}').join(baseUrl).split('{{apiKey}}').join('sk-test')
// eslint-disable-next-line no-new-func
const config = new Function(`return ${script}`)() as { request: { url: string } }
return config.request.url
}
it.each([
'https://api.example.com',
'https://api.example.com/',
'https://api.example.com/v1',
'https://api.example.com/v1/'
])('queries exactly one /v1/usage for base URL %s', (baseUrl) => {
expect(usageUrlFor(baseUrl)).toBe('https://api.example.com/v1/usage')
})
it('works against the endpoint every platform import stores', () => {
for (const platform of ['anthropic', 'openai', 'grok', 'gemini'] as GroupPlatform[]) {
const endpoint = paramsFromDeeplink(
buildCcSwitchImportDeeplink({
baseUrl: 'https://api.example.com',
platform,
clientType: platform === 'gemini' ? 'gemini' : 'claude',
providerName: 'Sub2API',
apiKey: 'sk-test',
usageScript: CC_SWITCH_USAGE_SCRIPT
})
).get('endpoint') as string
expect(usageUrlFor(endpoint)).toBe('https://api.example.com/v1/usage')
}
})
})
+25
View File
@@ -20,6 +20,31 @@ export interface CcSwitchImportDeeplinkInput {
usageScript: string
}
/**
* Balance query CC Switch runs against the imported provider. CC Switch fills
* `{{baseUrl}}` with the provider's base URL as stored — Codex and Grok imports
* carry a trailing `/v1` (see `withV1Endpoint`), Claude ones do not, and users
* may edit it either way afterwards — then evaluates the script, so the URL
* strips an existing `/v1` instead of blindly appending one (`/v1/v1/usage`
* is a 404 and CC Switch shows "query failed").
*/
export const CC_SWITCH_USAGE_SCRIPT = `({
request: {
url: "{{baseUrl}}".replace(/\\/+$/, "").replace(/\\/v1$/, "") + "/v1/usage",
method: "GET",
headers: { "Authorization": "Bearer {{apiKey}}" }
},
extractor: function(response) {
const remaining = response?.remaining ?? response?.quota?.remaining ?? response?.balance;
const unit = response?.unit ?? response?.quota?.unit ?? "USD";
return {
isValid: response?.is_active ?? response?.isValid ?? true,
remaining,
unit
};
}
})`
function withV1Endpoint(baseUrl: string): string {
const normalizedBaseUrl = baseUrl.replace(/\/+$/, '')
return normalizedBaseUrl.endsWith('/v1') ? normalizedBaseUrl : `${normalizedBaseUrl}/v1`
+2 -16
View File
@@ -1231,6 +1231,7 @@ import PlatformIcon from '@/components/common/PlatformIcon.vue'
import { platformBadgeLightClass } from '@/utils/platformColors'
import { KEY_GROUP_PROVIDERS, KEY_GROUP_PROVIDER_ICONS, getKeyGroupProvider, type KeyGroupProvider } from '@/utils/keyGroupProviders'
import {
CC_SWITCH_USAGE_SCRIPT,
buildCcSwitchImportDeeplink,
type CcSwitchClientType
} from '@/utils/ccswitchImport'
@@ -2025,22 +2026,7 @@ const executeCcsImport = (row: ApiKey, clientType: CcSwitchClientType) => {
const baseUrl = publicSettings.value?.api_base_url || window.location.origin
const platform = row.group?.platform || 'anthropic'
const usageScript = `({
request: {
url: "{{baseUrl}}/v1/usage",
method: "GET",
headers: { "Authorization": "Bearer {{apiKey}}" }
},
extractor: function(response) {
const remaining = response?.remaining ?? response?.quota?.remaining ?? response?.balance;
const unit = response?.unit ?? response?.quota?.unit ?? "USD";
return {
isValid: response?.is_active ?? response?.isValid ?? true,
remaining,
unit
};
}
})`
const usageScript = CC_SWITCH_USAGE_SCRIPT
const providerName = (publicSettings.value?.site_name || 'sub2api').trim() || 'sub2api'
const deeplink = buildCcSwitchImportDeeplink({
baseUrl,