fallback changes from PR #35
This commit is contained in:
@@ -18,6 +18,7 @@ import { logger } from '../utils/logger.js';
|
||||
import { parseResetTime } from './rate-limit-parser.js';
|
||||
import { buildCloudCodeRequest, buildHeaders } from './request-builder.js';
|
||||
import { parseThinkingSSEResponse } from './sse-parser.js';
|
||||
import { getFallbackModel } from '../fallback-config.js';
|
||||
|
||||
/**
|
||||
* Send a non-streaming request to Cloud Code with multi-account support
|
||||
@@ -32,7 +33,7 @@ import { parseThinkingSSEResponse } from './sse-parser.js';
|
||||
* @returns {Promise<Object>} Anthropic-format response object
|
||||
* @throws {Error} If max retries exceeded or no accounts available
|
||||
*/
|
||||
export async function sendMessage(anthropicRequest, accountManager) {
|
||||
export async function sendMessage(anthropicRequest, accountManager, fallbackEnabled = false) {
|
||||
const model = anthropicRequest.model;
|
||||
const isThinking = isThinkingModel(model);
|
||||
|
||||
@@ -76,6 +77,16 @@ export async function sendMessage(anthropicRequest, accountManager) {
|
||||
}
|
||||
|
||||
if (!account) {
|
||||
// Check if fallback is enabled and available
|
||||
if (fallbackEnabled) {
|
||||
const fallbackModel = getFallbackModel(model);
|
||||
if (fallbackModel) {
|
||||
logger.warn(`[CloudCode] All accounts exhausted for ${model}. Attempting fallback to ${fallbackModel}`);
|
||||
// Retry with fallback model
|
||||
const fallbackRequest = { ...anthropicRequest, model: fallbackModel };
|
||||
return await sendMessage(fallbackRequest, accountManager, false); // Disable fallback for recursive call
|
||||
}
|
||||
}
|
||||
throw new Error('No accounts available');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { logger } from '../utils/logger.js';
|
||||
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';
|
||||
|
||||
|
||||
/**
|
||||
@@ -31,7 +32,7 @@ import { streamSSEResponse } from './sse-streamer.js';
|
||||
* @yields {Object} Anthropic-format SSE events (message_start, content_block_start, content_block_delta, etc.)
|
||||
* @throws {Error} If max retries exceeded or no accounts available
|
||||
*/
|
||||
export async function* sendMessageStream(anthropicRequest, accountManager) {
|
||||
export async function* sendMessageStream(anthropicRequest, accountManager, fallbackEnabled = false) {
|
||||
const model = anthropicRequest.model;
|
||||
|
||||
// Retry loop with account failover
|
||||
@@ -74,6 +75,17 @@ export async function* sendMessageStream(anthropicRequest, accountManager) {
|
||||
}
|
||||
|
||||
if (!account) {
|
||||
// Check if fallback is enabled and available
|
||||
if (fallbackEnabled) {
|
||||
const fallbackModel = getFallbackModel(model);
|
||||
if (fallbackModel) {
|
||||
logger.warn(`[CloudCode] All accounts exhausted for ${model}. Attempting fallback to ${fallbackModel} (streaming)`);
|
||||
// Retry with fallback model
|
||||
const fallbackRequest = { ...anthropicRequest, model: fallbackModel };
|
||||
yield* sendMessageStream(fallbackRequest, accountManager, false); // Disable fallback for recursive call
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new Error('No accounts available');
|
||||
}
|
||||
}
|
||||
|
||||
36
src/fallback-config.js
Normal file
36
src/fallback-config.js
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Model Fallback Configuration
|
||||
*
|
||||
* Defines fallback mappings for when a model's quota is exhausted across all accounts.
|
||||
* Enables graceful degradation to alternative models with similar capabilities.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Model fallback mapping
|
||||
* Maps primary model ID to fallback model ID
|
||||
*/
|
||||
export const MODEL_FALLBACK_MAP = {
|
||||
'gemini-3-pro-high': 'claude-sonnet-4-5-thinking',
|
||||
'gemini-3-pro-low': 'claude-sonnet-4-5',
|
||||
'claude-opus-4-5-thinking': 'gemini-3-pro-high',
|
||||
'claude-sonnet-4-5-thinking': 'gemini-3-pro-high',
|
||||
'claude-sonnet-4-5': 'gemini-3-pro-low'
|
||||
};
|
||||
|
||||
/**
|
||||
* Get fallback model for a given model ID
|
||||
* @param {string} model - Primary model ID
|
||||
* @returns {string|null} Fallback model ID or null if no fallback exists
|
||||
*/
|
||||
export function getFallbackModel(model) {
|
||||
return MODEL_FALLBACK_MAP[model] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a model has a fallback configured
|
||||
* @param {string} model - Model ID to check
|
||||
* @returns {boolean} True if fallback exists
|
||||
*/
|
||||
export function hasFallback(model) {
|
||||
return model in MODEL_FALLBACK_MAP;
|
||||
}
|
||||
22
src/index.js
22
src/index.js
@@ -12,6 +12,7 @@ import os from 'os';
|
||||
// Parse command line arguments
|
||||
const args = process.argv.slice(2);
|
||||
const isDebug = args.includes('--debug') || process.env.DEBUG === 'true';
|
||||
const isFallbackEnabled = args.includes('--fallback') || process.env.FALLBACK === 'true';
|
||||
|
||||
// Initialize logger
|
||||
logger.setDebug(isDebug);
|
||||
@@ -20,6 +21,13 @@ if (isDebug) {
|
||||
logger.debug('Debug mode enabled');
|
||||
}
|
||||
|
||||
if (isFallbackEnabled) {
|
||||
logger.info('Model fallback mode enabled');
|
||||
}
|
||||
|
||||
// Export fallback flag for server to use
|
||||
export const FALLBACK_ENABLED = isFallbackEnabled;
|
||||
|
||||
const PORT = process.env.PORT || DEFAULT_PORT;
|
||||
|
||||
// Home directory for account storage
|
||||
@@ -40,14 +48,22 @@ app.listen(PORT, () => {
|
||||
if (!isDebug) {
|
||||
controlSection += '║ --debug Enable debug logging ║\n';
|
||||
}
|
||||
if (!isFallbackEnabled) {
|
||||
controlSection += '║ --fallback Enable model fallback on quota exhaust ║\n';
|
||||
}
|
||||
controlSection += '║ Ctrl+C Stop server ║';
|
||||
|
||||
// Build status section if debug mode is active
|
||||
// Build status section if any modes are active
|
||||
let statusSection = '';
|
||||
if (isDebug) {
|
||||
if (isDebug || isFallbackEnabled) {
|
||||
statusSection = '║ ║\n';
|
||||
statusSection += '║ Active Modes: ║\n';
|
||||
statusSection += '║ ✓ Debug mode enabled ║\n';
|
||||
if (isDebug) {
|
||||
statusSection += '║ ✓ Debug mode enabled ║\n';
|
||||
}
|
||||
if (isFallbackEnabled) {
|
||||
statusSection += '║ ✓ Model fallback enabled ║\n';
|
||||
}
|
||||
}
|
||||
|
||||
logger.log(`
|
||||
|
||||
@@ -13,6 +13,10 @@ import { AccountManager } from './account-manager/index.js';
|
||||
import { formatDuration } from './utils/helpers.js';
|
||||
import { logger } from './utils/logger.js';
|
||||
|
||||
// Parse fallback flag directly from command line args to avoid circular dependency
|
||||
const args = process.argv.slice(2);
|
||||
const FALLBACK_ENABLED = args.includes('--fallback') || process.env.FALLBACK === 'true';
|
||||
|
||||
const app = express();
|
||||
|
||||
// Initialize account manager (will be fully initialized on first request or startup)
|
||||
@@ -595,7 +599,7 @@ app.post('/v1/messages', async (req, res) => {
|
||||
|
||||
try {
|
||||
// Use the streaming generator with account manager
|
||||
for await (const event of sendMessageStream(request, accountManager)) {
|
||||
for await (const event of sendMessageStream(request, accountManager, FALLBACK_ENABLED)) {
|
||||
res.write(`event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`);
|
||||
// Flush after each event for real-time streaming
|
||||
if (res.flush) res.flush();
|
||||
@@ -616,7 +620,7 @@ app.post('/v1/messages', async (req, res) => {
|
||||
|
||||
} else {
|
||||
// Handle non-streaming response
|
||||
const response = await sendMessage(request, accountManager);
|
||||
const response = await sendMessage(request, accountManager, FALLBACK_ENABLED);
|
||||
res.json(response);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user