code quality improvements and refactoring
This commit is contained in:
@@ -4,7 +4,8 @@
|
|||||||
* automatic failover, and smart cooldown for rate-limited accounts.
|
* automatic failover, and smart cooldown for rate-limited accounts.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
import { readFile, writeFile, mkdir, access } from 'fs/promises';
|
||||||
|
import { constants as fsConstants } from 'fs';
|
||||||
import { dirname } from 'path';
|
import { dirname } from 'path';
|
||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
import { homedir } from 'os';
|
import { homedir } from 'os';
|
||||||
@@ -71,8 +72,9 @@ export class AccountManager {
|
|||||||
if (this.#initialized) return;
|
if (this.#initialized) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (existsSync(this.#configPath)) {
|
// Check if config file exists using async access
|
||||||
const configData = readFileSync(this.#configPath, 'utf-8');
|
await access(this.#configPath, fsConstants.F_OK);
|
||||||
|
const configData = await readFile(this.#configPath, 'utf-8');
|
||||||
const config = JSON.parse(configData);
|
const config = JSON.parse(configData);
|
||||||
|
|
||||||
this.#accounts = (config.accounts || []).map(acc => ({
|
this.#accounts = (config.accounts || []).map(acc => ({
|
||||||
@@ -91,13 +93,13 @@ export class AccountManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[AccountManager] Loaded ${this.#accounts.length} account(s) from config`);
|
console.log(`[AccountManager] Loaded ${this.#accounts.length} account(s) from config`);
|
||||||
} else {
|
} catch (error) {
|
||||||
|
if (error.code === 'ENOENT') {
|
||||||
// No config file - use single account from Antigravity database
|
// No config file - use single account from Antigravity database
|
||||||
console.log('[AccountManager] No config file found. Using Antigravity database (single account mode)');
|
console.log('[AccountManager] No config file found. Using Antigravity database (single account mode)');
|
||||||
await this.#loadDefaultAccount();
|
} else {
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[AccountManager] Failed to load config:', error.message);
|
console.error('[AccountManager] Failed to load config:', error.message);
|
||||||
|
}
|
||||||
// Fall back to default account
|
// Fall back to default account
|
||||||
await this.#loadDefaultAccount();
|
await this.#loadDefaultAccount();
|
||||||
}
|
}
|
||||||
@@ -341,7 +343,7 @@ export class AccountManager {
|
|||||||
if (account.isInvalid) {
|
if (account.isInvalid) {
|
||||||
account.isInvalid = false;
|
account.isInvalid = false;
|
||||||
account.invalidReason = null;
|
account.invalidReason = null;
|
||||||
this.saveToDisk();
|
await this.saveToDisk();
|
||||||
}
|
}
|
||||||
console.log(`[AccountManager] Refreshed OAuth token for: ${account.email}`);
|
console.log(`[AccountManager] Refreshed OAuth token for: ${account.email}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -454,15 +456,13 @@ export class AccountManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save current state to disk
|
* Save current state to disk (async)
|
||||||
*/
|
*/
|
||||||
saveToDisk() {
|
async saveToDisk() {
|
||||||
try {
|
try {
|
||||||
// Ensure directory exists
|
// Ensure directory exists
|
||||||
const dir = dirname(this.#configPath);
|
const dir = dirname(this.#configPath);
|
||||||
if (!existsSync(dir)) {
|
await mkdir(dir, { recursive: true });
|
||||||
mkdirSync(dir, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
accounts: this.#accounts.map(acc => ({
|
accounts: this.#accounts.map(acc => ({
|
||||||
@@ -483,7 +483,7 @@ export class AccountManager {
|
|||||||
activeIndex: this.#currentIndex
|
activeIndex: this.#currentIndex
|
||||||
};
|
};
|
||||||
|
|
||||||
writeFileSync(this.#configPath, JSON.stringify(config, null, 2));
|
await writeFile(this.#configPath, JSON.stringify(config, null, 2));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[AccountManager] Failed to save config:', error.message);
|
console.error('[AccountManager] Failed to save config:', error.message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ import {
|
|||||||
ANTIGRAVITY_ENDPOINT_FALLBACKS,
|
ANTIGRAVITY_ENDPOINT_FALLBACKS,
|
||||||
ANTIGRAVITY_HEADERS,
|
ANTIGRAVITY_HEADERS,
|
||||||
AVAILABLE_MODELS,
|
AVAILABLE_MODELS,
|
||||||
MAX_RETRIES
|
MAX_RETRIES,
|
||||||
|
MAX_WAIT_BEFORE_ERROR_MS,
|
||||||
|
MIN_SIGNATURE_LENGTH
|
||||||
} from './constants.js';
|
} from './constants.js';
|
||||||
import {
|
import {
|
||||||
mapModelName,
|
mapModelName,
|
||||||
@@ -250,7 +252,7 @@ export async function sendMessage(anthropicRequest, accountManager) {
|
|||||||
const resetTime = new Date(Date.now() + waitMs).toISOString();
|
const resetTime = new Date(Date.now() + waitMs).toISOString();
|
||||||
|
|
||||||
// If wait time is too long (> 2 minutes), throw error immediately
|
// If wait time is too long (> 2 minutes), throw error immediately
|
||||||
if (waitMs > 120000) {
|
if (waitMs > MAX_WAIT_BEFORE_ERROR_MS) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`RESOURCE_EXHAUSTED: Rate limited. Quota will reset after ${formatDuration(waitMs)}. Next available: ${resetTime}`
|
`RESOURCE_EXHAUSTED: Rate limited. Quota will reset after ${formatDuration(waitMs)}. Next available: ${resetTime}`
|
||||||
);
|
);
|
||||||
@@ -448,7 +450,9 @@ async function parseThinkingSSEResponse(response, originalModel) {
|
|||||||
accumulatedText += part.text;
|
accumulatedText += part.text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) { /* skip parse errors */ }
|
} catch (e) {
|
||||||
|
console.log('[CloudCode] SSE parse warning:', e.message, 'Raw:', jsonText.slice(0, 100));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,7 +500,7 @@ export async function* sendMessageStream(anthropicRequest, accountManager) {
|
|||||||
const resetTime = new Date(Date.now() + waitMs).toISOString();
|
const resetTime = new Date(Date.now() + waitMs).toISOString();
|
||||||
|
|
||||||
// If wait time is too long (> 2 minutes), throw error immediately
|
// If wait time is too long (> 2 minutes), throw error immediately
|
||||||
if (waitMs > 120000) {
|
if (waitMs > MAX_WAIT_BEFORE_ERROR_MS) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`RESOURCE_EXHAUSTED: Rate limited. Quota will reset after ${formatDuration(waitMs)}. Next available: ${resetTime}`
|
`RESOURCE_EXHAUSTED: Rate limited. Quota will reset after ${formatDuration(waitMs)}. Next available: ${resetTime}`
|
||||||
);
|
);
|
||||||
@@ -692,7 +696,7 @@ async function* streamSSEResponse(response, originalModel) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (signature && signature.length >= 50) {
|
if (signature && signature.length >= MIN_SIGNATURE_LENGTH) {
|
||||||
currentThinkingSignature = signature;
|
currentThinkingSignature = signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,14 @@ export const ACCOUNT_CONFIG_PATH = join(
|
|||||||
export const DEFAULT_COOLDOWN_MS = 60 * 1000; // 1 minute default cooldown
|
export const DEFAULT_COOLDOWN_MS = 60 * 1000; // 1 minute default cooldown
|
||||||
export const MAX_RETRIES = 5; // Max retry attempts across accounts
|
export const MAX_RETRIES = 5; // Max retry attempts across accounts
|
||||||
|
|
||||||
|
// Rate limit wait thresholds
|
||||||
|
export const MAX_WAIT_BEFORE_ERROR_MS = 120000; // 2 minutes - throw error if wait exceeds this
|
||||||
|
|
||||||
|
// Thinking model constants
|
||||||
|
export const DEFAULT_THINKING_BUDGET = 16000; // Default thinking budget tokens
|
||||||
|
export const CLAUDE_THINKING_MAX_OUTPUT_TOKENS = 64000; // Max output tokens for thinking models
|
||||||
|
export const MIN_SIGNATURE_LENGTH = 50; // Minimum valid thinking signature length
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
ANTIGRAVITY_ENDPOINT_FALLBACKS,
|
ANTIGRAVITY_ENDPOINT_FALLBACKS,
|
||||||
ANTIGRAVITY_HEADERS,
|
ANTIGRAVITY_HEADERS,
|
||||||
@@ -93,5 +101,9 @@ export default {
|
|||||||
DEFAULT_PORT,
|
DEFAULT_PORT,
|
||||||
ACCOUNT_CONFIG_PATH,
|
ACCOUNT_CONFIG_PATH,
|
||||||
DEFAULT_COOLDOWN_MS,
|
DEFAULT_COOLDOWN_MS,
|
||||||
MAX_RETRIES
|
MAX_RETRIES,
|
||||||
|
MAX_WAIT_BEFORE_ERROR_MS,
|
||||||
|
DEFAULT_THINKING_BUDGET,
|
||||||
|
CLAUDE_THINKING_MAX_OUTPUT_TOKENS,
|
||||||
|
MIN_SIGNATURE_LENGTH
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,12 +8,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import { MODEL_MAPPINGS } from './constants.js';
|
import {
|
||||||
|
MODEL_MAPPINGS,
|
||||||
// Default thinking budget (16K tokens)
|
DEFAULT_THINKING_BUDGET,
|
||||||
const DEFAULT_THINKING_BUDGET = 16000;
|
CLAUDE_THINKING_MAX_OUTPUT_TOKENS,
|
||||||
// Claude thinking models need larger max output tokens
|
MIN_SIGNATURE_LENGTH
|
||||||
const CLAUDE_THINKING_MAX_OUTPUT_TOKENS = 64000;
|
} from './constants.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map Anthropic model name to Antigravity model name
|
* Map Anthropic model name to Antigravity model name
|
||||||
@@ -33,11 +33,11 @@ function isThinkingPart(part) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a thinking part has a valid signature (>= 50 chars)
|
* Check if a thinking part has a valid signature (>= MIN_SIGNATURE_LENGTH chars)
|
||||||
*/
|
*/
|
||||||
function hasValidSignature(part) {
|
function hasValidSignature(part) {
|
||||||
const signature = part.thought === true ? part.thoughtSignature : part.signature;
|
const signature = part.thought === true ? part.thoughtSignature : part.signature;
|
||||||
return typeof signature === 'string' && signature.length >= 50;
|
return typeof signature === 'string' && signature.length >= MIN_SIGNATURE_LENGTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -187,8 +187,8 @@ export function restoreThinkingSignatures(content) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep blocks with valid signatures (>= 50 chars), sanitized
|
// Keep blocks with valid signatures (>= MIN_SIGNATURE_LENGTH chars), sanitized
|
||||||
if (block.signature && block.signature.length >= 50) {
|
if (block.signature && block.signature.length >= MIN_SIGNATURE_LENGTH) {
|
||||||
filtered.push(sanitizeAnthropicThinkingBlock(block));
|
filtered.push(sanitizeAnthropicThinkingBlock(block));
|
||||||
}
|
}
|
||||||
// Unsigned thinking blocks are dropped
|
// Unsigned thinking blocks are dropped
|
||||||
@@ -360,7 +360,7 @@ function convertContentToParts(content, isClaudeModel = false) {
|
|||||||
parts.push({ functionResponse });
|
parts.push({ functionResponse });
|
||||||
} else if (block.type === 'thinking') {
|
} else if (block.type === 'thinking') {
|
||||||
// Handle thinking blocks - only those with valid signatures
|
// Handle thinking blocks - only those with valid signatures
|
||||||
if (block.signature && block.signature.length >= 50) {
|
if (block.signature && block.signature.length >= MIN_SIGNATURE_LENGTH) {
|
||||||
// Convert to Gemini format with signature
|
// Convert to Gemini format with signature
|
||||||
parts.push({
|
parts.push({
|
||||||
text: block.thinking,
|
text: block.thinking,
|
||||||
|
|||||||
@@ -19,13 +19,18 @@ const accountManager = new AccountManager();
|
|||||||
// Track initialization status
|
// Track initialization status
|
||||||
let isInitialized = false;
|
let isInitialized = false;
|
||||||
let initError = null;
|
let initError = null;
|
||||||
|
let initPromise = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure account manager is initialized
|
* Ensure account manager is initialized (with race condition protection)
|
||||||
*/
|
*/
|
||||||
async function ensureInitialized() {
|
async function ensureInitialized() {
|
||||||
if (isInitialized) return;
|
if (isInitialized) return;
|
||||||
|
|
||||||
|
// If initialization is already in progress, wait for it
|
||||||
|
if (initPromise) return initPromise;
|
||||||
|
|
||||||
|
initPromise = (async () => {
|
||||||
try {
|
try {
|
||||||
await accountManager.initialize();
|
await accountManager.initialize();
|
||||||
isInitialized = true;
|
isInitialized = true;
|
||||||
@@ -33,9 +38,13 @@ async function ensureInitialized() {
|
|||||||
console.log(`[Server] Account pool initialized: ${status.summary}`);
|
console.log(`[Server] Account pool initialized: ${status.summary}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
initError = error;
|
initError = error;
|
||||||
|
initPromise = null; // Allow retry on failure
|
||||||
console.error('[Server] Failed to initialize account manager:', error.message);
|
console.error('[Server] Failed to initialize account manager:', error.message);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return initPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
|
|||||||
Reference in New Issue
Block a user