1b354be827
Build Electron Desktop App / Validate version (push) Failing after 42s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
- New: open-sse/services/apiKeyRotator.ts — round-robin rotation between primary API key + providerSpecificData.extraApiKeys[] - Modified: open-sse/executors/base.ts — buildHeaders() rotates key using getRotatingApiKey() when extraApiKeys configured - Modified: open-sse/handlers/chatCore.ts — injects connectionId into credentials to enable per-connection rotation index tracking - Modified: providers/[id]/page.tsx — 'Extra API Keys' UI section in EditConnectionModal: add/remove keys, persisted in providerSpecificData T08 (quota window rolling) and T13 (wildcard model routing) confirmed already implemented in accountFallback.ts and wildcardRouter.ts.
64 lines
2.2 KiB
TypeScript
64 lines
2.2 KiB
TypeScript
/**
|
|
* apiKeyRotator.ts — T07: API Key Round-Robin
|
|
*
|
|
* Rotates between a primary API key and extra API keys stored in
|
|
* providerSpecificData.extraApiKeys[]. Uses round-robin by default.
|
|
*
|
|
* Extra keys are stored as plain strings in providerSpecificData.extraApiKeys.
|
|
* Example: { extraApiKeys: ["sk-abc...", "sk-def...", "sk-ghi..."] }
|
|
*
|
|
* The in-memory rotation index resets on process restart, which is intentional —
|
|
* it ensures even distribution across restarts without persistence overhead.
|
|
*/
|
|
|
|
// In-memory round-robin index per connection
|
|
const _keyIndexes = new Map<string, number>();
|
|
|
|
/**
|
|
* Get the next API key in round-robin rotation for a given connection.
|
|
* If no extra keys are configured, returns the primary key unchanged.
|
|
*
|
|
* @param connectionId - Unique connection identifier (for index isolation)
|
|
* @param primaryKey - The main api_key from the connection
|
|
* @param extraKeys - Additional API keys from providerSpecificData.extraApiKeys
|
|
* @returns The selected API key (may be primary or one of the extras)
|
|
*/
|
|
export function getRotatingApiKey(
|
|
connectionId: string,
|
|
primaryKey: string,
|
|
extraKeys: string[] = []
|
|
): string {
|
|
const validExtras = extraKeys.filter((k) => typeof k === "string" && k.trim().length > 0);
|
|
|
|
// Only 1 key available → no rotation needed
|
|
if (validExtras.length === 0) return primaryKey;
|
|
|
|
const allKeys = [primaryKey, ...validExtras].filter(Boolean);
|
|
if (allKeys.length <= 1) return primaryKey;
|
|
|
|
const current = _keyIndexes.get(connectionId) ?? 0;
|
|
const idx = current % allKeys.length;
|
|
_keyIndexes.set(connectionId, current + 1);
|
|
|
|
return allKeys[idx];
|
|
}
|
|
|
|
/**
|
|
* Reset the rotation index for a connection.
|
|
* Call this when a key fails (401/403) to skip the bad key next time.
|
|
*
|
|
* @param connectionId - Connection to reset
|
|
*/
|
|
export function resetRotationIndex(connectionId: string): void {
|
|
_keyIndexes.delete(connectionId);
|
|
}
|
|
|
|
/**
|
|
* Get the total number of API keys available for a connection.
|
|
* Used for logging/observability.
|
|
*/
|
|
export function getApiKeyCount(primaryKey: string, extraKeys: string[] = []): number {
|
|
const validExtras = extraKeys.filter((k) => typeof k === "string" && k.trim().length > 0);
|
|
return (primaryKey ? 1 : 0) + validExtras.length;
|
|
}
|