mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-14 00:16:38 +00:00
fix(cliproxy): refresh upstream model and quota surfaces (#1158)
* fix(cliproxy): refresh upstream model and quota surfaces * test(cliproxy): cover codex legacy model fallback
This commit is contained in:
1 parent
66865127d7
commit
50b0ffba75
7 files changed
+220
-97
No files matched your search
@@ -19,13 +19,13 @@ export const BACKEND_CONFIG = {
|
||||
repo: 'router-for-me/CLIProxyAPI',
|
||||
binaryPrefix: 'CLIProxyAPI',
|
||||
executable: 'cli-proxy-api',
|
||||
fallbackVersion: '6.8.2',
|
||||
fallbackVersion: '6.9.45',
|
||||
},
|
||||
plus: {
|
||||
repo: 'kaitranntt/CLIProxyAPIPlus',
|
||||
binaryPrefix: 'CLIProxyAPIPlus',
|
||||
executable: 'cli-proxy-api-plus',
|
||||
fallbackVersion: '6.9.36-0',
|
||||
fallbackVersion: '6.9.45-0',
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -208,4 +208,111 @@ describe('Antigravity quota failure metadata', () => {
|
||||
fs.rmSync(tempHome, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('tries the daily loadCodeAssist host before falling back to prod', async () => {
|
||||
const moduleId = Date.now() + Math.random();
|
||||
const { fetchAccountQuota } = await import(`../quota-fetcher?agy-daily-host=${moduleId}`);
|
||||
const { getProviderAuthDir } = await import(
|
||||
`../../config/config-generator?agy-config=${moduleId}`
|
||||
);
|
||||
const fs = await import('node:fs');
|
||||
const os = await import('node:os');
|
||||
const path = await import('node:path');
|
||||
|
||||
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-agy-daily-host-'));
|
||||
const originalCcsHome = process.env.CCS_HOME;
|
||||
process.env.CCS_HOME = tempHome;
|
||||
|
||||
try {
|
||||
const authDir = getProviderAuthDir('agy');
|
||||
fs.mkdirSync(authDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(authDir, 'antigravity-user@example.com.json'),
|
||||
JSON.stringify({
|
||||
type: 'antigravity',
|
||||
email: 'user@example.com',
|
||||
access_token: 'token',
|
||||
})
|
||||
);
|
||||
|
||||
const originalFetch = globalThis.fetch;
|
||||
const urls: string[] = [];
|
||||
globalThis.fetch = (async (input, init) => {
|
||||
const url = String(input);
|
||||
urls.push(url);
|
||||
|
||||
if (url === 'https://daily-cloudcode-pa.googleapis.com/v1internal:loadCodeAssist') {
|
||||
const bodyText = typeof init?.body === 'string' ? init.body : '';
|
||||
expect(init?.headers).toMatchObject({
|
||||
'X-Goog-Api-Client': 'gl-node/22.21.1',
|
||||
});
|
||||
expect(bodyText).toBe(
|
||||
JSON.stringify({
|
||||
metadata: {
|
||||
ide_name: 'antigravity',
|
||||
ide_type: 'ANTIGRAVITY',
|
||||
ide_version: '1.21.9',
|
||||
},
|
||||
})
|
||||
);
|
||||
return new Response('daily unavailable', { status: 503 });
|
||||
}
|
||||
|
||||
if (url === 'https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist') {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
cloudaicompanionProject: { id: 'project-x' },
|
||||
paidTier: { id: 'g1-pro-tier' },
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (url === 'https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels') {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
models: {
|
||||
'gemini-3-pro-high': {
|
||||
quotaInfo: {
|
||||
remainingFraction: 0.75,
|
||||
resetTime: '2026-05-01T10:00:00Z',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return new Response('unexpected url', { status: 500 });
|
||||
}) as typeof fetch;
|
||||
|
||||
try {
|
||||
const result = await fetchAccountQuota('agy', 'user@example.com');
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.tier).toBe('pro');
|
||||
expect(result.projectId).toBe('project-x');
|
||||
expect(urls).toEqual([
|
||||
'https://daily-cloudcode-pa.googleapis.com/v1internal:loadCodeAssist',
|
||||
'https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist',
|
||||
'https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels',
|
||||
]);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
} finally {
|
||||
if (originalCcsHome === undefined) {
|
||||
delete process.env.CCS_HOME;
|
||||
} else {
|
||||
process.env.CCS_HOME = originalCcsHome;
|
||||
}
|
||||
fs.rmSync(tempHome, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -82,17 +82,20 @@ export interface QuotaResult {
|
||||
}
|
||||
|
||||
/** Google Cloud Code API endpoints */
|
||||
const ANTIGRAVITY_DAILY_API_BASE = 'https://daily-cloudcode-pa.googleapis.com';
|
||||
const ANTIGRAVITY_API_BASE = 'https://cloudcode-pa.googleapis.com';
|
||||
const ANTIGRAVITY_API_VERSION = 'v1internal';
|
||||
const ANTIGRAVITY_LOADCODEASSIST_BASE_URLS = [
|
||||
ANTIGRAVITY_DAILY_API_BASE,
|
||||
ANTIGRAVITY_API_BASE,
|
||||
] as const;
|
||||
const MANAGEMENT_API_TIMEOUT_MS = 5000;
|
||||
|
||||
/** Headers for loadCodeAssist (matches CLIProxyAPI antigravity.go) */
|
||||
/** Headers for loadCodeAssist (matches current CLIProxyAPIPlus control-plane requests) */
|
||||
const LOADCODEASSIST_HEADERS = {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'google-api-nodejs-client/9.15.1',
|
||||
'X-Goog-Api-Client': 'google-cloud-sdk vscode_cloudshelleditor/0.1',
|
||||
'Client-Metadata':
|
||||
'{"ideType":"IDE_UNSPECIFIED","platform":"PLATFORM_UNSPECIFIED","pluginType":"GEMINI"}',
|
||||
'User-Agent': 'antigravity/1.21.9 darwin/arm64 google-api-nodejs-client/10.3.0',
|
||||
'X-Goog-Api-Client': 'gl-node/22.21.1',
|
||||
};
|
||||
|
||||
/** Headers for fetchAvailableModels (matches CLIProxyAPI antigravity_executor.go) */
|
||||
@@ -543,6 +546,40 @@ async function performAntigravityRequest(
|
||||
}
|
||||
}
|
||||
|
||||
async function performAntigravityRequestWithBaseUrlFallback(
|
||||
accountId: string,
|
||||
accessToken: string,
|
||||
baseUrls: readonly string[],
|
||||
apiPath: string,
|
||||
headers: Record<string, string>,
|
||||
body: string
|
||||
): Promise<ManagedResponse> {
|
||||
let lastResponse: ManagedResponse | null = null;
|
||||
|
||||
for (const baseUrl of baseUrls) {
|
||||
const response = await performAntigravityRequest(
|
||||
accountId,
|
||||
accessToken,
|
||||
`${baseUrl}/${apiPath}`,
|
||||
headers,
|
||||
body
|
||||
);
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return response;
|
||||
}
|
||||
lastResponse = response;
|
||||
}
|
||||
|
||||
return (
|
||||
lastResponse ?? {
|
||||
status: 503,
|
||||
bodyText: 'No Antigravity API endpoint available',
|
||||
json: null,
|
||||
viaManagement: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read auth data from auth file (access token, project_id, expiry status)
|
||||
*/
|
||||
@@ -616,18 +653,18 @@ function readAuthData(provider: CLIProxyProvider, accountId: string): AuthData |
|
||||
* Uses paidTier.id for accurate tier detection (g1-ultra-tier, g1-pro-tier)
|
||||
*/
|
||||
async function getProjectId(accountId: string, accessToken: string): Promise<ProjectLookupResult> {
|
||||
const url = `${ANTIGRAVITY_API_BASE}/${ANTIGRAVITY_API_VERSION}:loadCodeAssist`;
|
||||
const body = JSON.stringify({
|
||||
metadata: {
|
||||
ideType: 'IDE_UNSPECIFIED',
|
||||
platform: 'PLATFORM_UNSPECIFIED',
|
||||
pluginType: 'GEMINI',
|
||||
ide_name: 'antigravity',
|
||||
ide_type: 'ANTIGRAVITY',
|
||||
ide_version: '1.21.9',
|
||||
},
|
||||
});
|
||||
const response = await performAntigravityRequest(
|
||||
const response = await performAntigravityRequestWithBaseUrlFallback(
|
||||
accountId,
|
||||
accessToken,
|
||||
url,
|
||||
ANTIGRAVITY_LOADCODEASSIST_BASE_URLS,
|
||||
`${ANTIGRAVITY_API_VERSION}:loadCodeAssist`,
|
||||
LOADCODEASSIST_HEADERS,
|
||||
body
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user