feat: add sticky round-robin routing strategy

Implements a "sticky" round-robin strategy that uses the same provider
account for a configurable number of consecutive calls (default 3)
before switching to the next one. This optimizes for prompt caching
by reducing organization/account rotation. Adds a configuration input
to the Profile settings page.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Catalin Stanciu
2026-01-09 17:45:32 +07:00
committed by decolua
co-authored by Claude Sonnet 4.5
parent f2abcc6585
commit 4f292aae63
3 changed files with 84 additions and 14 deletions
+37 -10
View File
@@ -33,17 +33,44 @@ export async function getProviderCredentials(provider, excludeConnectionId = nul
let connection;
if (strategy === "round-robin") {
// Sort by lastUsed (nulls first) to pick the least recently used
const sorted = [...availableConnections].sort((a, b) => {
if (!a.lastUsedAt && !b.lastUsedAt) return (a.priority || 999) - (b.priority || 999);
if (!a.lastUsedAt) return -1;
if (!b.lastUsedAt) return 1;
return new Date(a.lastUsedAt) - new Date(b.lastUsedAt);
});
connection = sorted[0];
const stickyLimit = settings.stickyRoundRobinLimit || 3;
// Update lastUsedAt asynchronously
updateProviderConnection(connection.id, { lastUsedAt: new Date().toISOString() }).catch(() => {});
// Sort by lastUsed (most recent first) to find current candidate
const byRecency = [...availableConnections].sort((a, b) => {
if (!a.lastUsedAt && !b.lastUsedAt) return (a.priority || 999) - (b.priority || 999);
if (!a.lastUsedAt) return 1;
if (!b.lastUsedAt) return -1;
return new Date(b.lastUsedAt) - new Date(a.lastUsedAt);
});
const current = byRecency[0];
const currentCount = current?.consecutiveUseCount || 0;
if (current && current.lastUsedAt && currentCount < stickyLimit) {
// Stay with current account
connection = current;
// Update lastUsedAt and increment count
updateProviderConnection(connection.id, {
lastUsedAt: new Date().toISOString(),
consecutiveUseCount: (connection.consecutiveUseCount || 0) + 1
}).catch(() => {});
} else {
// Pick the least recently used (excluding current if possible)
const sortedByOldest = [...availableConnections].sort((a, b) => {
if (!a.lastUsedAt && !b.lastUsedAt) return (a.priority || 999) - (b.priority || 999);
if (!a.lastUsedAt) return -1;
if (!b.lastUsedAt) return 1;
return new Date(a.lastUsedAt) - new Date(b.lastUsedAt);
});
connection = sortedByOldest[0];
// Update lastUsedAt and reset count to 1
updateProviderConnection(connection.id, {
lastUsedAt: new Date().toISOString(),
consecutiveUseCount: 1
}).catch(() => {});
}
} else {
// Default: fill-first (already sorted by priority in getProviderConnections)
connection = availableConnections[0];