feat(proxy): improve quota fallback and proxy parity handling

This commit is contained in:
Draculabo committed 2026-04-11 16:47:57 +08:00
1 parent 388fbcb2a9
commit 4fa0a3c63d
9 files changed
+350 -66

No files matched your search

+5 -21
View File
@@ -370,18 +370,6 @@ function detectsNetworkingTool(tools?: (Tool | GeminiToolDeclaration)[]): boolea
return false;
}
/**
* Inject Google Search Tool
* Adds googleSearch tool to the request
*/
function supportsMixedTools(mappedModel?: string): boolean {
if (!mappedModel) {
return false;
}
const modelLower = mappedModel.toLowerCase();
return modelLower.includes('gemini-3');
}
function injectGoogleSearchTool(body: { tools?: GeminiToolDeclaration[] }, mappedModel?: string) {
if (!body.tools) {
body.tools = [];
@@ -389,9 +377,9 @@ function injectGoogleSearchTool(body: { tools?: GeminiToolDeclaration[] }, mappe
const toolsArr = body.tools;
const hasFunctions = toolsArr.some((t) => t.functionDeclarations);
if (hasFunctions && !supportsMixedTools(mappedModel)) {
if (hasFunctions) {
logger.info(
'Skipping googleSearch injection due to existing functionDeclarations on old model',
`[Claude-Request] Skipping googleSearch injection for ${mappedModel ?? 'unknown-model'} because functionDeclarations are present (v1internal incompatible)`,
);
return;
}
@@ -631,13 +619,9 @@ function buildTools(
if (functionDeclarations.length > 0) {
toolList.push({ functionDeclarations });
if (hasGoogleSearch) {
if (supportsMixedTools(mappedModel)) {
toolList.push({ googleSearch: {} });
} else {
logger.info(
`[Claude-Request] Skipping googleSearch injection for ${mappedModel} due to existing functionDeclarations on old model`,
);
}
logger.info(
`[Claude-Request] Skipping googleSearch injection for ${mappedModel} because functionDeclarations are present (v1internal incompatible)`,
);
}
} else if (hasGoogleSearch) {
toolList.push({ googleSearch: {} });
+6 -3
View File
@@ -59,6 +59,10 @@ const CLAUDE_TO_GEMINI: Record<string, string> = {
'gemini-3.1-pro-preview': 'gemini-3.1-pro-high',
'gemini-3.1-pro': 'gemini-3.1-pro-high',
'gemini-3.0-pro': 'gemini-3.1-pro-high',
'gemini-3-pro': 'gemini-3-pro-preview',
'gemini-3-pro-preview': 'gemini-3-pro-preview',
'gemini-3-pro-low': 'gemini-3-pro-low',
'gemini-3-pro-high': 'gemini-3-pro-high',
'gemini-2.5-flash': 'gemini-3-flash',
'gemini-2.5-pro': 'gemini-3.1-pro-high',
'gemini-2.0-flash': 'gemini-3-flash',
@@ -79,11 +83,10 @@ export const MODEL_LIST_CREATED_AT = 1770652800;
export const MODEL_LIST_OWNER = 'antigravity';
const GEMINI_MODEL_ALIASES: Record<string, string> = {
'gemini-3.1-pro': 'gemini-3.1-pro-high',
'gemini-3.0-pro': 'gemini-3.1-pro-high',
'gemini-3.1-pro-preview': 'gemini-3.1-pro-high',
'gemini-3-pro-image-preview': 'gemini-3-pro-image',
'gemini-3-flash-preview': 'gemini-3-flash',
'gemini-3.1-pro-preview': 'gemini-3.1-pro-high',
'gemini-3.1-pro': 'gemini-3.1-pro-high',
};
export function getSupportedModels(): string[] {
+38 -14
View File
@@ -3,7 +3,11 @@ import { QuotaData, LoadProjectResponse, QuotaApiResponse } from './types';
import { logger } from '../../utils/logger';
// Constants
const QUOTA_API_URL = 'https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels';
const QUOTA_API_ENDPOINTS = [
'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:fetchAvailableModels',
'https://daily-cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels',
'https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels',
] as const;
const CLOUD_CODE_BASE_URL = 'https://cloudcode-pa.googleapis.com';
const USER_AGENT = 'antigravity/1.11.3 Darwin/arm64'; // Keeping the same UA as source
@@ -83,15 +87,16 @@ export class QuotaService {
const finalProjectId = projectId;
const client = this.createClient();
const payload = { project: finalProjectId };
const url = QUOTA_API_URL;
const maxRetries = 3;
const payload = finalProjectId ? { project: finalProjectId } : {};
let lastError: Error | null = null;
logger.info(`Sending quota request to ${url}`);
for (let endpointIndex = 0; endpointIndex < QUOTA_API_ENDPOINTS.length; endpointIndex++) {
const endpoint = QUOTA_API_ENDPOINTS[endpointIndex];
const hasNextEndpoint = endpointIndex + 1 < QUOTA_API_ENDPOINTS.length;
logger.info(`Sending quota request to ${endpoint}`);
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await client.post<QuotaApiResponse>(url, payload, {
const response = await client.post<QuotaApiResponse>(endpoint, payload, {
headers: {
Authorization: `Bearer ${accessToken}`,
'User-Agent': USER_AGENT,
@@ -127,8 +132,14 @@ export class QuotaService {
}
}
return { quotaData, projectId: projectId };
if (endpointIndex > 0) {
logger.info(`Quota API fallback succeeded at endpoint #${endpointIndex + 1}`);
}
return { quotaData, projectId };
} catch (error: any) {
let shouldFallback = true;
if (axios.isAxiosError(error)) {
const status = error.response?.status;
let text = '';
@@ -147,23 +158,36 @@ export class QuotaService {
isForbidden: true,
subscriptionTier: subscriptionTier,
},
projectId: projectId,
projectId,
};
}
logger.warn(`API Error: ${status} - ${text} (Attempt ${attempt}/${maxRetries})`);
if (hasNextEndpoint && (status === 429 || (typeof status === 'number' && status >= 500))) {
logger.warn(
`Quota API ${endpoint} returned ${status}, falling back to next endpoint`,
);
lastError = new Error(`HTTP ${status} - ${text}`);
await new Promise((r) => setTimeout(r, 1000));
continue;
}
logger.warn(`API Error: ${status} - ${text}`);
lastError = new Error(`HTTP ${status} - ${text}`);
shouldFallback = typeof status !== 'number';
} else {
logger.warn(`Request Failed: ${error.message} (Attempt ${attempt}/${maxRetries})`);
logger.warn(`Request Failed at ${endpoint}: ${error.message}`);
lastError = error instanceof Error ? error : new Error(String(error));
}
if (attempt < maxRetries) {
if (hasNextEndpoint && shouldFallback) {
logger.warn(`Quota API request failed at ${endpoint}, falling back to next endpoint`);
await new Promise((r) => setTimeout(r, 1000));
} else {
throw new Error(`Quota query failed: ${error.message}`);
throw lastError ?? new Error(`Quota query failed: ${error.message}`);
}
}
}
throw new Error('Unknown error in fetchQuota'); // Should not reach here
throw lastError ?? new Error('Unknown error in fetchQuota');
}
}