mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 12:02:27 +08:00
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:
@@ -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')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user