fix: address code review feedback

- Move MAX_EMPTY_RESPONSE_RETRIES to constants.js for consistency
- Handle 429/401/5xx errors properly during retry fetch
- Use proper message ID format (crypto.randomBytes) instead of Date.now()
- Add crypto import for UUID generation

Code review by: Gemini 3 Pro Preview + Claude Opus 4.5

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
BrunoMarc
2026-01-07 18:16:09 -03:00
parent 49480847b6
commit 05cd80ebb5
2 changed files with 34 additions and 9 deletions

View File

@@ -8,6 +8,7 @@
import { import {
ANTIGRAVITY_ENDPOINT_FALLBACKS, ANTIGRAVITY_ENDPOINT_FALLBACKS,
MAX_RETRIES, MAX_RETRIES,
MAX_EMPTY_RESPONSE_RETRIES,
MAX_WAIT_BEFORE_ERROR_MS MAX_WAIT_BEFORE_ERROR_MS
} from '../constants.js'; } from '../constants.js';
import { isRateLimitError, isAuthError, isEmptyResponseError } from '../errors.js'; import { isRateLimitError, isAuthError, isEmptyResponseError } from '../errors.js';
@@ -17,9 +18,7 @@ import { parseResetTime } from './rate-limit-parser.js';
import { buildCloudCodeRequest, buildHeaders } from './request-builder.js'; import { buildCloudCodeRequest, buildHeaders } from './request-builder.js';
import { streamSSEResponse } from './sse-streamer.js'; import { streamSSEResponse } from './sse-streamer.js';
import { getFallbackModel } from '../fallback-config.js'; import { getFallbackModel } from '../fallback-config.js';
import crypto from 'crypto';
// Maximum retries for empty responses before giving up
const MAX_EMPTY_RETRIES = 2;
/** /**
* Send a streaming request to Cloud Code with multi-account support * Send a streaming request to Cloud Code with multi-account support
@@ -149,15 +148,15 @@ export async function* sendMessageStream(anthropicRequest, accountManager, fallb
let emptyRetries = 0; let emptyRetries = 0;
let currentResponse = response; let currentResponse = response;
while (emptyRetries <= MAX_EMPTY_RETRIES) { while (emptyRetries <= MAX_EMPTY_RESPONSE_RETRIES) {
try { try {
yield* streamSSEResponse(currentResponse, anthropicRequest.model); yield* streamSSEResponse(currentResponse, anthropicRequest.model);
logger.debug('[CloudCode] Stream completed'); logger.debug('[CloudCode] Stream completed');
return; return;
} catch (streamError) { } catch (streamError) {
if (isEmptyResponseError(streamError) && emptyRetries < MAX_EMPTY_RETRIES) { if (isEmptyResponseError(streamError) && emptyRetries < MAX_EMPTY_RESPONSE_RETRIES) {
emptyRetries++; emptyRetries++;
logger.warn(`[CloudCode] Empty response, retry ${emptyRetries}/${MAX_EMPTY_RETRIES}...`); logger.warn(`[CloudCode] Empty response, retry ${emptyRetries}/${MAX_EMPTY_RESPONSE_RETRIES}...`);
// Refetch the response // Refetch the response
currentResponse = await fetch(url, { currentResponse = await fetch(url, {
@@ -166,15 +165,38 @@ export async function* sendMessageStream(anthropicRequest, accountManager, fallb
body: JSON.stringify(payload) body: JSON.stringify(payload)
}); });
// Handle specific error codes on retry
if (!currentResponse.ok) { if (!currentResponse.ok) {
throw new Error(`Empty response retry failed: ${currentResponse.status}`); const retryErrorText = await currentResponse.text();
// Re-throw rate limit errors to trigger account switch
if (currentResponse.status === 429) {
const resetMs = parseResetTime(currentResponse, retryErrorText);
throw new Error(`Rate limited during retry: ${retryErrorText}`);
}
// Re-throw auth errors for proper handling
if (currentResponse.status === 401) {
accountManager.clearTokenCache(account.email);
accountManager.clearProjectCache(account.email);
throw new Error(`Auth error during retry: ${retryErrorText}`);
}
// For 5xx errors, continue to next retry attempt
if (currentResponse.status >= 500) {
logger.warn(`[CloudCode] Retry got ${currentResponse.status}, continuing...`);
await sleep(1000);
continue;
}
throw new Error(`Empty response retry failed: ${currentResponse.status} - ${retryErrorText}`);
} }
continue; continue;
} }
// After max retries, emit fallback message // After max retries, emit fallback message
if (isEmptyResponseError(streamError)) { if (isEmptyResponseError(streamError)) {
logger.error(`[CloudCode] Empty response after ${MAX_EMPTY_RETRIES} retries`); logger.error(`[CloudCode] Empty response after ${MAX_EMPTY_RESPONSE_RETRIES} retries`);
yield* emitEmptyResponseFallback(anthropicRequest.model); yield* emitEmptyResponseFallback(anthropicRequest.model);
return; return;
} }
@@ -245,7 +267,8 @@ export async function* sendMessageStream(anthropicRequest, accountManager, fallb
* @yields {Object} Anthropic-format SSE events for empty response fallback * @yields {Object} Anthropic-format SSE events for empty response fallback
*/ */
function* emitEmptyResponseFallback(model) { function* emitEmptyResponseFallback(model) {
const messageId = `msg_${Date.now()}_empty`; // Use proper message ID format consistent with Anthropic API
const messageId = `msg_${crypto.randomBytes(16).toString('hex')}`;
yield { yield {
type: 'message_start', type: 'message_start',

View File

@@ -76,6 +76,7 @@ export const ANTIGRAVITY_DB_PATH = getAntigravityDbPath();
export const DEFAULT_COOLDOWN_MS = 10 * 1000; // 10 second default cooldown export const DEFAULT_COOLDOWN_MS = 10 * 1000; // 10 second default cooldown
export const MAX_RETRIES = 5; // Max retry attempts across accounts export const MAX_RETRIES = 5; // Max retry attempts across accounts
export const MAX_EMPTY_RESPONSE_RETRIES = 2; // Max retries for empty API responses
export const MAX_ACCOUNTS = 10; // Maximum number of accounts allowed export const MAX_ACCOUNTS = 10; // Maximum number of accounts allowed
// Rate limit wait thresholds // Rate limit wait thresholds
@@ -166,6 +167,7 @@ export default {
ANTIGRAVITY_DB_PATH, ANTIGRAVITY_DB_PATH,
DEFAULT_COOLDOWN_MS, DEFAULT_COOLDOWN_MS,
MAX_RETRIES, MAX_RETRIES,
MAX_EMPTY_RESPONSE_RETRIES,
MAX_ACCOUNTS, MAX_ACCOUNTS,
MAX_WAIT_BEFORE_ERROR_MS, MAX_WAIT_BEFORE_ERROR_MS,
MIN_SIGNATURE_LENGTH, MIN_SIGNATURE_LENGTH,