feat(proxy): improve quota fallback and proxy parity handling
This commit is contained in:
1 parent
388fbcb2a9
commit
4fa0a3c63d
9 files changed
+350
-66
No files matched your search
@@ -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: {} });
|
||||
|
||||
@@ -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[] {
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user