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 { parseResetTime } from './rate-limit-parser.js';
|
||||||
import { buildCloudCodeRequest, buildHeaders } from './request-builder.js';
|
import { buildCloudCodeRequest, buildHeaders } from './request-builder.js';
|
||||||
import { parseThinkingSSEResponse } from './sse-parser.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
|
* 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
|
* @returns {Promise<Object>} Anthropic-format response object
|
||||||
* @throws {Error} If max retries exceeded or no accounts available
|
* @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 model = anthropicRequest.model;
|
||||||
const isThinking = isThinkingModel(model);
|
const isThinking = isThinkingModel(model);
|
||||||
|
|
||||||
@@ -76,6 +77,16 @@ export async function sendMessage(anthropicRequest, accountManager) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!account) {
|
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');
|
throw new Error('No accounts available');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { logger } from '../utils/logger.js';
|
|||||||
import { parseResetTime } from './rate-limit-parser.js';
|
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';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -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.)
|
* @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
|
* @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;
|
const model = anthropicRequest.model;
|
||||||
|
|
||||||
// Retry loop with account failover
|
// Retry loop with account failover
|
||||||
@@ -74,6 +75,17 @@ export async function* sendMessageStream(anthropicRequest, accountManager) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!account) {
|
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');
|
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
|
// Parse command line arguments
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
const isDebug = args.includes('--debug') || process.env.DEBUG === 'true';
|
const isDebug = args.includes('--debug') || process.env.DEBUG === 'true';
|
||||||
|
const isFallbackEnabled = args.includes('--fallback') || process.env.FALLBACK === 'true';
|
||||||
|
|
||||||
// Initialize logger
|
// Initialize logger
|
||||||
logger.setDebug(isDebug);
|
logger.setDebug(isDebug);
|
||||||
@@ -20,6 +21,13 @@ if (isDebug) {
|
|||||||
logger.debug('Debug mode enabled');
|
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;
|
const PORT = process.env.PORT || DEFAULT_PORT;
|
||||||
|
|
||||||
// Home directory for account storage
|
// Home directory for account storage
|
||||||
@@ -40,14 +48,22 @@ app.listen(PORT, () => {
|
|||||||
if (!isDebug) {
|
if (!isDebug) {
|
||||||
controlSection += '║ --debug Enable debug logging ║\n';
|
controlSection += '║ --debug Enable debug logging ║\n';
|
||||||
}
|
}
|
||||||
|
if (!isFallbackEnabled) {
|
||||||
|
controlSection += '║ --fallback Enable model fallback on quota exhaust ║\n';
|
||||||
|
}
|
||||||
controlSection += '║ Ctrl+C Stop server ║';
|
controlSection += '║ Ctrl+C Stop server ║';
|
||||||
|
|
||||||
// Build status section if debug mode is active
|
// Build status section if any modes are active
|
||||||
let statusSection = '';
|
let statusSection = '';
|
||||||
if (isDebug) {
|
if (isDebug || isFallbackEnabled) {
|
||||||
statusSection = '║ ║\n';
|
statusSection = '║ ║\n';
|
||||||
statusSection += '║ Active Modes: ║\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(`
|
logger.log(`
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ import { AccountManager } from './account-manager/index.js';
|
|||||||
import { formatDuration } from './utils/helpers.js';
|
import { formatDuration } from './utils/helpers.js';
|
||||||
import { logger } from './utils/logger.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();
|
const app = express();
|
||||||
|
|
||||||
// Initialize account manager (will be fully initialized on first request or startup)
|
// Initialize account manager (will be fully initialized on first request or startup)
|
||||||
@@ -595,7 +599,7 @@ app.post('/v1/messages', async (req, res) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Use the streaming generator with account manager
|
// 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`);
|
res.write(`event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`);
|
||||||
// Flush after each event for real-time streaming
|
// Flush after each event for real-time streaming
|
||||||
if (res.flush) res.flush();
|
if (res.flush) res.flush();
|
||||||
@@ -616,7 +620,7 @@ app.post('/v1/messages', async (req, res) => {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Handle non-streaming response
|
// Handle non-streaming response
|
||||||
const response = await sendMessage(request, accountManager);
|
const response = await sendMessage(request, accountManager, FALLBACK_ENABLED);
|
||||||
res.json(response);
|
res.json(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user