mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 13:38:31 +00:00
Fix Antigravity
This commit is contained in:
@@ -126,12 +126,17 @@ export class AntigravityExecutor extends BaseExecutor {
|
|||||||
let lastError = null;
|
let lastError = null;
|
||||||
let lastStatus = 0;
|
let lastStatus = 0;
|
||||||
const MAX_AUTO_RETRIES = 2;
|
const MAX_AUTO_RETRIES = 2;
|
||||||
|
const retryAttemptsByUrl = {}; // Track retry attempts per URL
|
||||||
|
|
||||||
for (let urlIndex = 0; urlIndex < fallbackCount; urlIndex++) {
|
for (let urlIndex = 0; urlIndex < fallbackCount; urlIndex++) {
|
||||||
const url = this.buildUrl(model, stream, urlIndex);
|
const url = this.buildUrl(model, stream, urlIndex);
|
||||||
const headers = this.buildHeaders(credentials, stream);
|
const headers = this.buildHeaders(credentials, stream);
|
||||||
const transformedBody = this.transformRequest(model, body, stream, credentials);
|
const transformedBody = this.transformRequest(model, body, stream, credentials);
|
||||||
let retryAttempts = 0;
|
|
||||||
|
// Initialize retry counter for this URL
|
||||||
|
if (!retryAttemptsByUrl[urlIndex]) {
|
||||||
|
retryAttemptsByUrl[urlIndex] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
@@ -152,10 +157,12 @@ export class AntigravityExecutor extends BaseExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Auto retry only for 429 when retryMs is 0 or undefined
|
// Auto retry only for 429 when retryMs is 0 or undefined
|
||||||
if (response.status === 429 && (!retryMs || retryMs === 0) && retryAttempts < MAX_AUTO_RETRIES) {
|
if (response.status === 429 && (!retryMs || retryMs === 0) && retryAttemptsByUrl[urlIndex] < MAX_AUTO_RETRIES) {
|
||||||
retryAttempts++;
|
retryAttemptsByUrl[urlIndex]++;
|
||||||
log?.debug?.("RETRY", `429 auto retry ${retryAttempts}/${MAX_AUTO_RETRIES} after 1s`);
|
// Exponential backoff: 2s, 4s, 8s...
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
const backoffMs = Math.min(1000 * Math.pow(2, retryAttemptsByUrl[urlIndex]), MAX_RETRY_AFTER_MS);
|
||||||
|
log?.debug?.("RETRY", `429 auto retry ${retryAttemptsByUrl[urlIndex]}/${MAX_AUTO_RETRIES} after ${backoffMs/1000}s`);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, backoffMs));
|
||||||
urlIndex--;
|
urlIndex--;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -229,15 +229,21 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
|
|||||||
// Check provider response - return error info for fallback handling
|
// Check provider response - return error info for fallback handling
|
||||||
if (!providerResponse.ok) {
|
if (!providerResponse.ok) {
|
||||||
trackPendingRequest(model, provider, connectionId, false);
|
trackPendingRequest(model, provider, connectionId, false);
|
||||||
const { statusCode, message } = await parseUpstreamError(providerResponse);
|
const { statusCode, message, retryAfterMs } = await parseUpstreamError(providerResponse, provider);
|
||||||
appendRequestLog({ model, provider, connectionId, status: `FAILED ${statusCode}` }).catch(() => {});
|
appendRequestLog({ model, provider, connectionId, status: `FAILED ${statusCode}` }).catch(() => {});
|
||||||
const errMsg = formatProviderError(new Error(message), provider, model, statusCode);
|
const errMsg = formatProviderError(new Error(message), provider, model, statusCode);
|
||||||
console.log(`${COLORS.red}[ERROR] ${errMsg}${COLORS.reset}`);
|
console.log(`${COLORS.red}[ERROR] ${errMsg}${COLORS.reset}`);
|
||||||
|
|
||||||
|
// Log Antigravity retry time if available
|
||||||
|
if (retryAfterMs && provider === "antigravity") {
|
||||||
|
const retrySeconds = Math.ceil(retryAfterMs / 1000);
|
||||||
|
log?.debug?.("RETRY", `Antigravity quota reset in ${retrySeconds}s (${retryAfterMs}ms)`);
|
||||||
|
}
|
||||||
|
|
||||||
// Log error with full request body for debugging
|
// Log error with full request body for debugging
|
||||||
reqLogger.logError(new Error(message), finalBody || translatedBody);
|
reqLogger.logError(new Error(message), finalBody || translatedBody);
|
||||||
|
|
||||||
return createErrorResult(statusCode, errMsg);
|
return createErrorResult(statusCode, errMsg, retryAfterMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non-streaming response
|
// Non-streaming response
|
||||||
|
|||||||
@@ -1,11 +1,23 @@
|
|||||||
// Gemini helper functions for translator
|
// Gemini helper functions for translator
|
||||||
|
|
||||||
// Unsupported JSON Schema constraints that should be removed for Antigravity
|
// Unsupported JSON Schema constraints that should be removed for Antigravity
|
||||||
|
// Reference: CLIProxyAPI/internal/util/gemini_schema.go (removeUnsupportedKeywords)
|
||||||
export const UNSUPPORTED_SCHEMA_CONSTRAINTS = [
|
export const UNSUPPORTED_SCHEMA_CONSTRAINTS = [
|
||||||
|
// Basic constraints (not supported by Gemini API)
|
||||||
"minLength", "maxLength", "exclusiveMinimum", "exclusiveMaximum",
|
"minLength", "maxLength", "exclusiveMinimum", "exclusiveMaximum",
|
||||||
"pattern", "minItems", "maxItems", "format",
|
"pattern", "minItems", "maxItems", "format",
|
||||||
"default", "examples", "$schema", "const", "title",
|
// Claude rejects these in VALIDATED mode
|
||||||
"anyOf", "oneOf", "allOf", "not"
|
"default", "examples",
|
||||||
|
// JSON Schema meta keywords
|
||||||
|
"$schema", "$defs", "definitions", "const", "$ref",
|
||||||
|
// Object validation keywords (not supported)
|
||||||
|
"additionalProperties", "propertyNames", "patternProperties",
|
||||||
|
// Complex schema keywords (handled by flattenAnyOfOneOf/mergeAllOf)
|
||||||
|
"anyOf", "oneOf", "allOf", "not",
|
||||||
|
// Dependency keywords (not supported)
|
||||||
|
"dependencies", "dependentSchemas", "dependentRequired",
|
||||||
|
// Other unsupported keywords
|
||||||
|
"title", "if", "then", "else", "contentMediaType", "contentEncoding"
|
||||||
];
|
];
|
||||||
|
|
||||||
// Default safety settings
|
// Default safety settings
|
||||||
|
|||||||
+60
-6
@@ -78,13 +78,51 @@ export async function writeStreamError(writer, statusCode, message) {
|
|||||||
await writer.write(encoder.encode(`data: ${JSON.stringify(errorBody)}\n\n`));
|
await writer.write(encoder.encode(`data: ${JSON.stringify(errorBody)}\n\n`));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse Antigravity error message to extract retry time
|
||||||
|
* Example: "You have exhausted your capacity on this model. Your quota will reset after 2h7m23s."
|
||||||
|
* @param {string} message - Error message
|
||||||
|
* @returns {number|null} Retry time in milliseconds, or null if not found
|
||||||
|
*/
|
||||||
|
export function parseAntigravityRetryTime(message) {
|
||||||
|
if (typeof message !== "string") return null;
|
||||||
|
|
||||||
|
// Match patterns like: 2h7m23s, 5m30s, 45s, 1h20m, etc.
|
||||||
|
const match = message.match(/reset after (\d+h)?(\d+m)?(\d+s)?/i);
|
||||||
|
if (!match) return null;
|
||||||
|
|
||||||
|
let totalMs = 0;
|
||||||
|
|
||||||
|
// Extract hours
|
||||||
|
if (match[1]) {
|
||||||
|
const hours = parseInt(match[1]);
|
||||||
|
totalMs += hours * 60 * 60 * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract minutes
|
||||||
|
if (match[2]) {
|
||||||
|
const minutes = parseInt(match[2]);
|
||||||
|
totalMs += minutes * 60 * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract seconds
|
||||||
|
if (match[3]) {
|
||||||
|
const seconds = parseInt(match[3]);
|
||||||
|
totalMs += seconds * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalMs > 0 ? totalMs : null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse upstream provider error response
|
* Parse upstream provider error response
|
||||||
* @param {Response} response - Fetch response from provider
|
* @param {Response} response - Fetch response from provider
|
||||||
* @returns {Promise<{statusCode: number, message: string}>}
|
* @param {string} provider - Provider name (for Antigravity-specific parsing)
|
||||||
|
* @returns {Promise<{statusCode: number, message: string, retryAfterMs: number|null}>}
|
||||||
*/
|
*/
|
||||||
export async function parseUpstreamError(response) {
|
export async function parseUpstreamError(response, provider = null) {
|
||||||
let message = "";
|
let message = "";
|
||||||
|
let retryAfterMs = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
@@ -100,9 +138,17 @@ export async function parseUpstreamError(response) {
|
|||||||
message = `Upstream error: ${response.status}`;
|
message = `Upstream error: ${response.status}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const messageStr = typeof message === "string" ? message : JSON.stringify(message);
|
||||||
|
|
||||||
|
// Parse Antigravity-specific retry time from error message
|
||||||
|
if (provider === "antigravity" && response.status === 429) {
|
||||||
|
retryAfterMs = parseAntigravityRetryTime(messageStr);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode: response.status,
|
statusCode: response.status,
|
||||||
message: typeof message === "string" ? message : JSON.stringify(message)
|
message: messageStr,
|
||||||
|
retryAfterMs
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,15 +156,23 @@ export async function parseUpstreamError(response) {
|
|||||||
* Create error result for chatCore handler
|
* Create error result for chatCore handler
|
||||||
* @param {number} statusCode - HTTP status code
|
* @param {number} statusCode - HTTP status code
|
||||||
* @param {string} message - Error message
|
* @param {string} message - Error message
|
||||||
* @returns {{ success: false, status: number, error: string, response: Response }}
|
* @param {number|null} retryAfterMs - Optional retry-after time in milliseconds
|
||||||
|
* @returns {{ success: false, status: number, error: string, response: Response, retryAfterMs?: number }}
|
||||||
*/
|
*/
|
||||||
export function createErrorResult(statusCode, message) {
|
export function createErrorResult(statusCode, message, retryAfterMs = null) {
|
||||||
return {
|
const result = {
|
||||||
success: false,
|
success: false,
|
||||||
status: statusCode,
|
status: statusCode,
|
||||||
error: message,
|
error: message,
|
||||||
response: errorResponse(statusCode, message)
|
response: errorResponse(statusCode, message)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add retryAfterMs if available (for Antigravity quota errors)
|
||||||
|
if (retryAfterMs) {
|
||||||
|
result.retryAfterMs = retryAfterMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user