feat: add multi-key auth profile rotation for model providers

This commit is contained in:
William Valentin
2026-02-18 10:29:54 -08:00
parent f341149ac7
commit 8e3cd2e0ba
10 changed files with 271 additions and 39 deletions
+64
View File
@@ -0,0 +1,64 @@
import type { ChatRequest, ChatResponse, ChatStreamEvent, ModelClient } from './types.js';
/**
* Model client wrapper that rotates across equivalent auth profiles (e.g. API keys).
* Sticky-by-success behavior: keep using the last successful profile until it fails.
*/
export class RotatingModelClient implements ModelClient {
private readonly clients: ModelClient[];
private currentIndex = 0;
constructor(clients: ModelClient[]) {
if (clients.length === 0) {
throw new Error('RotatingModelClient requires at least one client');
}
this.clients = clients;
}
async chat(request: ChatRequest): Promise<ChatResponse> {
const start = this.currentIndex;
const errors: Error[] = [];
for (let offset = 0; offset < this.clients.length; offset += 1) {
const index = (start + offset) % this.clients.length;
const client = this.clients[index];
try {
const response = await client.chat(request);
this.currentIndex = index;
return response;
} catch (error) {
errors.push(error instanceof Error ? error : new Error(String(error)));
}
}
throw new Error(`All auth profiles failed: ${errors.map((e) => e.message).join(', ')}`);
}
async *chatStream(request: ChatRequest): AsyncIterable<ChatStreamEvent> {
const start = this.currentIndex;
for (let offset = 0; offset < this.clients.length; offset += 1) {
const index = (start + offset) % this.clients.length;
const client = this.clients[index];
if (!client.chatStream) {
continue;
}
let failed = false;
for await (const event of client.chatStream(request)) {
if (event.type === 'error') {
failed = true;
break;
}
yield event;
}
if (!failed) {
this.currentIndex = index;
return;
}
}
yield { type: 'error', error: new Error('All auth profiles failed for streaming') };
}
}