From 05cd80ebb59a311655c4d1aca782a0bd455aaaec Mon Sep 17 00:00:00 2001 From: BrunoMarc Date: Wed, 7 Jan 2026 18:16:09 -0300 Subject: [PATCH] fix: address code review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- src/cloudcode/streaming-handler.js | 41 +++++++++++++++++++++++------- src/constants.js | 2 ++ 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/cloudcode/streaming-handler.js b/src/cloudcode/streaming-handler.js index 8a6a1de..6729c80 100644 --- a/src/cloudcode/streaming-handler.js +++ b/src/cloudcode/streaming-handler.js @@ -8,6 +8,7 @@ import { ANTIGRAVITY_ENDPOINT_FALLBACKS, MAX_RETRIES, + MAX_EMPTY_RESPONSE_RETRIES, MAX_WAIT_BEFORE_ERROR_MS } from '../constants.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 { streamSSEResponse } from './sse-streamer.js'; import { getFallbackModel } from '../fallback-config.js'; - -// Maximum retries for empty responses before giving up -const MAX_EMPTY_RETRIES = 2; +import crypto from 'crypto'; /** * 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 currentResponse = response; - while (emptyRetries <= MAX_EMPTY_RETRIES) { + while (emptyRetries <= MAX_EMPTY_RESPONSE_RETRIES) { try { yield* streamSSEResponse(currentResponse, anthropicRequest.model); logger.debug('[CloudCode] Stream completed'); return; } catch (streamError) { - if (isEmptyResponseError(streamError) && emptyRetries < MAX_EMPTY_RETRIES) { + if (isEmptyResponseError(streamError) && emptyRetries < MAX_EMPTY_RESPONSE_RETRIES) { 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 currentResponse = await fetch(url, { @@ -166,15 +165,38 @@ export async function* sendMessageStream(anthropicRequest, accountManager, fallb body: JSON.stringify(payload) }); + // Handle specific error codes on retry 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; } // After max retries, emit fallback message 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); return; } @@ -245,7 +267,8 @@ export async function* sendMessageStream(anthropicRequest, accountManager, fallb * @yields {Object} Anthropic-format SSE events for empty response fallback */ 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 { type: 'message_start', diff --git a/src/constants.js b/src/constants.js index b2f52d7..b0e9ff2 100644 --- a/src/constants.js +++ b/src/constants.js @@ -76,6 +76,7 @@ export const ANTIGRAVITY_DB_PATH = getAntigravityDbPath(); export const DEFAULT_COOLDOWN_MS = 10 * 1000; // 10 second default cooldown 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 // Rate limit wait thresholds @@ -166,6 +167,7 @@ export default { ANTIGRAVITY_DB_PATH, DEFAULT_COOLDOWN_MS, MAX_RETRIES, + MAX_EMPTY_RESPONSE_RETRIES, MAX_ACCOUNTS, MAX_WAIT_BEFORE_ERROR_MS, MIN_SIGNATURE_LENGTH,