fix: lint and format issues for multi-account module
This commit is contained in:
parent
c20b27f5e6
commit
c5a28b28d4
@ -8,7 +8,7 @@ import { RateLimitTracker } from "./rate-limit-tracker.js";
|
|||||||
import { HealthScorer } from "./health-scorer.js";
|
import { HealthScorer } from "./health-scorer.js";
|
||||||
import { QuotaTracker } from "./quota-tracker.js";
|
import { QuotaTracker } from "./quota-tracker.js";
|
||||||
import { createStrategy, STRATEGY_NAMES, type Strategy } from "./strategies.js";
|
import { createStrategy, STRATEGY_NAMES, type Strategy } from "./strategies.js";
|
||||||
import { DEFAULT_COOLDOWN_MS, MAX_WAIT_BEFORE_ERROR_MS } from "./constants.js";
|
import { DEFAULT_COOLDOWN_MS } from "./constants.js";
|
||||||
|
|
||||||
export interface Account {
|
export interface Account {
|
||||||
profileId: string;
|
profileId: string;
|
||||||
@ -27,15 +27,18 @@ export interface AccountManagerConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthProfileStore {
|
export interface AuthProfileStore {
|
||||||
profiles: Record<string, {
|
profiles: Record<
|
||||||
type: string;
|
string,
|
||||||
provider: string;
|
{
|
||||||
email?: string;
|
type: string;
|
||||||
access?: string;
|
provider: string;
|
||||||
refresh?: string;
|
email?: string;
|
||||||
expires?: number;
|
access?: string;
|
||||||
projectId?: string;
|
refresh?: string;
|
||||||
}>;
|
expires?: number;
|
||||||
|
projectId?: string;
|
||||||
|
}
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AccountManager {
|
export class AccountManager {
|
||||||
@ -59,10 +62,7 @@ export class AccountManager {
|
|||||||
this.strategy.init(this);
|
this.strategy.init(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
async initialize(
|
async initialize(authStore: AuthProfileStore, strategyOverride?: string): Promise<this> {
|
||||||
authStore: AuthProfileStore,
|
|
||||||
strategyOverride?: string
|
|
||||||
): Promise<this> {
|
|
||||||
this.authStore = authStore;
|
this.authStore = authStore;
|
||||||
|
|
||||||
if (strategyOverride && STRATEGY_NAMES.includes(strategyOverride.toLowerCase() as any)) {
|
if (strategyOverride && STRATEGY_NAMES.includes(strategyOverride.toLowerCase() as any)) {
|
||||||
@ -93,9 +93,7 @@ export class AccountManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getProfilesForProvider(): string[] {
|
getProfilesForProvider(): string[] {
|
||||||
return Array.from(this.accounts.keys()).filter(
|
return Array.from(this.accounts.keys()).filter((id) => !this.invalidProfiles.has(id));
|
||||||
(id) => !this.invalidProfiles.has(id)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getAccountByProfileId(profileId: string): Account | null {
|
getAccountByProfileId(profileId: string): Account | null {
|
||||||
@ -147,11 +145,7 @@ export class AccountManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
markRateLimited(profileId: string, modelId: string, cooldownMs?: number): void {
|
markRateLimited(profileId: string, modelId: string, cooldownMs?: number): void {
|
||||||
this.rateLimitTracker.markRateLimited(
|
this.rateLimitTracker.markRateLimited(profileId, modelId, cooldownMs ?? this.defaultCooldownMs);
|
||||||
profileId,
|
|
||||||
modelId,
|
|
||||||
cooldownMs ?? this.defaultCooldownMs
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
markInvalid(profileId: string, reason: string): void {
|
markInvalid(profileId: string, reason: string): void {
|
||||||
@ -236,12 +230,8 @@ export class AccountManager {
|
|||||||
getStatus() {
|
getStatus() {
|
||||||
const profiles = this.getProfilesForProvider();
|
const profiles = this.getProfilesForProvider();
|
||||||
const invalid = Array.from(this.invalidProfiles);
|
const invalid = Array.from(this.invalidProfiles);
|
||||||
const rateLimited = profiles.filter(
|
const rateLimited = profiles.filter((id) => this.rateLimitTracker.getSoonestReset(id) !== null);
|
||||||
(id) => this.rateLimitTracker.getSoonestReset(id) !== null
|
const available = profiles.filter((id) => !rateLimited.includes(id) && !invalid.includes(id));
|
||||||
);
|
|
||||||
const available = profiles.filter(
|
|
||||||
(id) => !rateLimited.includes(id) && !invalid.includes(id)
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
total: profiles.length + invalid.length,
|
total: profiles.length + invalid.length,
|
||||||
|
|||||||
@ -21,46 +21,33 @@ export class HealthScorer {
|
|||||||
|
|
||||||
// Apply time-based recovery
|
// Apply time-based recovery
|
||||||
const minutesSinceUpdate = (Date.now() - lastUpdateTime) / 60_000;
|
const minutesSinceUpdate = (Date.now() - lastUpdateTime) / 60_000;
|
||||||
const recovery = Math.floor(
|
const recovery = Math.floor(minutesSinceUpdate * HEALTH_SCORE.recoveryPerMinute);
|
||||||
minutesSinceUpdate * HEALTH_SCORE.recoveryPerMinute
|
|
||||||
);
|
|
||||||
|
|
||||||
return Math.min(HEALTH_SCORE.maxScore, baseScore + recovery);
|
return Math.min(HEALTH_SCORE.maxScore, baseScore + recovery);
|
||||||
}
|
}
|
||||||
|
|
||||||
recordSuccess(profileId: string): void {
|
recordSuccess(profileId: string): void {
|
||||||
const current = this.getScore(profileId);
|
const current = this.getScore(profileId);
|
||||||
const newScore = Math.min(
|
const newScore = Math.min(HEALTH_SCORE.maxScore, current + HEALTH_SCORE.successBonus);
|
||||||
HEALTH_SCORE.maxScore,
|
|
||||||
current + HEALTH_SCORE.successBonus
|
|
||||||
);
|
|
||||||
this.scores.set(profileId, newScore);
|
this.scores.set(profileId, newScore);
|
||||||
this.lastUpdate.set(profileId, Date.now());
|
this.lastUpdate.set(profileId, Date.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
recordFailure(profileId: string): void {
|
recordFailure(profileId: string): void {
|
||||||
const current = this.getScore(profileId);
|
const current = this.getScore(profileId);
|
||||||
const newScore = Math.max(
|
const newScore = Math.max(HEALTH_SCORE.minScore, current - HEALTH_SCORE.failurePenalty);
|
||||||
HEALTH_SCORE.minScore,
|
|
||||||
current - HEALTH_SCORE.failurePenalty
|
|
||||||
);
|
|
||||||
this.scores.set(profileId, newScore);
|
this.scores.set(profileId, newScore);
|
||||||
this.lastUpdate.set(profileId, Date.now());
|
this.lastUpdate.set(profileId, Date.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
recordRateLimit(profileId: string): void {
|
recordRateLimit(profileId: string): void {
|
||||||
const current = this.getScore(profileId);
|
const current = this.getScore(profileId);
|
||||||
const newScore = Math.max(
|
const newScore = Math.max(HEALTH_SCORE.minScore, current - HEALTH_SCORE.rateLimitPenalty);
|
||||||
HEALTH_SCORE.minScore,
|
|
||||||
current - HEALTH_SCORE.rateLimitPenalty
|
|
||||||
);
|
|
||||||
this.scores.set(profileId, newScore);
|
this.scores.set(profileId, newScore);
|
||||||
this.lastUpdate.set(profileId, Date.now());
|
this.lastUpdate.set(profileId, Date.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
getSortedByHealth(
|
getSortedByHealth(profileIds: string[]): Array<{ profileId: string; score: number }> {
|
||||||
profileIds: string[]
|
|
||||||
): Array<{ profileId: string; score: number }> {
|
|
||||||
return profileIds
|
return profileIds
|
||||||
.map((profileId) => ({
|
.map((profileId) => ({
|
||||||
profileId,
|
profileId,
|
||||||
@ -79,10 +66,7 @@ export class HealthScorer {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fromJSON(data: {
|
fromJSON(data: { scores?: Record<string, number>; lastUpdate?: Record<string, number> }): void {
|
||||||
scores?: Record<string, number>;
|
|
||||||
lastUpdate?: Record<string, number>;
|
|
||||||
}): void {
|
|
||||||
if (data?.scores) {
|
if (data?.scores) {
|
||||||
for (const [key, value] of Object.entries(data.scores)) {
|
for (const [key, value] of Object.entries(data.scores)) {
|
||||||
this.scores.set(key, value);
|
this.scores.set(key, value);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Multi-Account Module
|
* Multi-Account Module
|
||||||
*
|
*
|
||||||
* Provides multi-account load balancing for Clawdbot providers.
|
* Provides multi-account load balancing for Clawdbot providers.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@ const managers = new Map<string, AccountManager>();
|
|||||||
export async function getAccountManager(
|
export async function getAccountManager(
|
||||||
provider: string,
|
provider: string,
|
||||||
authStore: AuthProfileStore,
|
authStore: AuthProfileStore,
|
||||||
cfg?: ClawdbotConfig
|
cfg?: ClawdbotConfig,
|
||||||
): Promise<AccountManager> {
|
): Promise<AccountManager> {
|
||||||
if (managers.has(provider)) {
|
if (managers.has(provider)) {
|
||||||
return managers.get(provider)!;
|
return managers.get(provider)!;
|
||||||
@ -40,10 +40,7 @@ export async function getAccountManager(
|
|||||||
/**
|
/**
|
||||||
* Check if multi-account is enabled for a provider
|
* Check if multi-account is enabled for a provider
|
||||||
*/
|
*/
|
||||||
export function isMultiAccountEnabled(
|
export function isMultiAccountEnabled(provider: string, cfg?: ClawdbotConfig): boolean {
|
||||||
provider: string,
|
|
||||||
cfg?: ClawdbotConfig
|
|
||||||
): boolean {
|
|
||||||
const multiAccountConfig = (cfg as any)?.auth?.multiAccount;
|
const multiAccountConfig = (cfg as any)?.auth?.multiAccount;
|
||||||
if (!multiAccountConfig?.enabled) return false;
|
if (!multiAccountConfig?.enabled) return false;
|
||||||
|
|
||||||
@ -91,7 +88,7 @@ export async function resolveWithMultiAccount(params: {
|
|||||||
const minWait = manager.getMinWaitTimeMs(modelId);
|
const minWait = manager.getMinWaitTimeMs(modelId);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`MULTI_ACCOUNT_EXHAUSTED: All accounts rate limited for ${modelId}. ` +
|
`MULTI_ACCOUNT_EXHAUSTED: All accounts rate limited for ${modelId}. ` +
|
||||||
`Wait ${Math.ceil(minWait / 1000)}s.`
|
`Wait ${Math.ceil(minWait / 1000)}s.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -116,7 +113,7 @@ export async function resolveWithMultiAccount(params: {
|
|||||||
*/
|
*/
|
||||||
export function reportMultiAccountSuccess(
|
export function reportMultiAccountSuccess(
|
||||||
result: { _manager?: AccountManager; _profileId?: string } | null,
|
result: { _manager?: AccountManager; _profileId?: string } | null,
|
||||||
modelId: string
|
modelId: string,
|
||||||
): void {
|
): void {
|
||||||
if (!result?._manager || !result?._profileId) return;
|
if (!result?._manager || !result?._profileId) return;
|
||||||
result._manager.notifySuccess(result._profileId, modelId);
|
result._manager.notifySuccess(result._profileId, modelId);
|
||||||
@ -128,7 +125,7 @@ export function reportMultiAccountSuccess(
|
|||||||
export function reportMultiAccountRateLimit(
|
export function reportMultiAccountRateLimit(
|
||||||
result: { _manager?: AccountManager; _profileId?: string } | null,
|
result: { _manager?: AccountManager; _profileId?: string } | null,
|
||||||
modelId: string,
|
modelId: string,
|
||||||
cooldownMs?: number
|
cooldownMs?: number,
|
||||||
): void {
|
): void {
|
||||||
if (!result?._manager || !result?._profileId) return;
|
if (!result?._manager || !result?._profileId) return;
|
||||||
result._manager.notifyRateLimit(result._profileId, modelId, cooldownMs);
|
result._manager.notifyRateLimit(result._profileId, modelId, cooldownMs);
|
||||||
@ -139,7 +136,7 @@ export function reportMultiAccountRateLimit(
|
|||||||
*/
|
*/
|
||||||
export function reportMultiAccountFailure(
|
export function reportMultiAccountFailure(
|
||||||
result: { _manager?: AccountManager; _profileId?: string } | null,
|
result: { _manager?: AccountManager; _profileId?: string } | null,
|
||||||
modelId: string
|
modelId: string,
|
||||||
): void {
|
): void {
|
||||||
if (!result?._manager || !result?._profileId) return;
|
if (!result?._manager || !result?._profileId) return;
|
||||||
result._manager.notifyFailure(result._profileId, modelId);
|
result._manager.notifyFailure(result._profileId, modelId);
|
||||||
@ -150,7 +147,7 @@ export function reportMultiAccountFailure(
|
|||||||
*/
|
*/
|
||||||
export function reportMultiAccountAuthInvalid(
|
export function reportMultiAccountAuthInvalid(
|
||||||
result: { _manager?: AccountManager; _profileId?: string } | null,
|
result: { _manager?: AccountManager; _profileId?: string } | null,
|
||||||
reason: string
|
reason: string,
|
||||||
): void {
|
): void {
|
||||||
if (!result?._manager || !result?._profileId) return;
|
if (!result?._manager || !result?._profileId) return;
|
||||||
result._manager.markInvalid(result._profileId, reason);
|
result._manager.markInvalid(result._profileId, reason);
|
||||||
|
|||||||
@ -40,7 +40,7 @@ export function isMultiAccountEnabled(provider: string, cfg?: ClawdbotConfig): b
|
|||||||
export async function getOrCreateManager(
|
export async function getOrCreateManager(
|
||||||
provider: string,
|
provider: string,
|
||||||
store: AuthProfileStore,
|
store: AuthProfileStore,
|
||||||
cfg?: ClawdbotConfig
|
cfg?: ClawdbotConfig,
|
||||||
): Promise<AccountManager> {
|
): Promise<AccountManager> {
|
||||||
const normalized = normalizeProviderId(provider);
|
const normalized = normalizeProviderId(provider);
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ export async function selectAccountForModel(params: {
|
|||||||
const waitSec = Math.ceil(waitMs / 1000);
|
const waitSec = Math.ceil(waitMs / 1000);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`All ${manager.getAccountCount()} accounts rate-limited for ${modelId}. ` +
|
`All ${manager.getAccountCount()} accounts rate-limited for ${modelId}. ` +
|
||||||
`Shortest wait: ${waitSec}s. Try again later.`
|
`Shortest wait: ${waitSec}s. Try again later.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -112,7 +112,7 @@ export async function selectAccountForModel(params: {
|
|||||||
|
|
||||||
export function notifyMultiAccountSuccess(
|
export function notifyMultiAccountSuccess(
|
||||||
selection: MultiAccountSelection | null | undefined,
|
selection: MultiAccountSelection | null | undefined,
|
||||||
modelId: string
|
modelId: string,
|
||||||
): void {
|
): void {
|
||||||
if (!selection) return;
|
if (!selection) return;
|
||||||
selection.manager.notifySuccess(selection.profileId, modelId);
|
selection.manager.notifySuccess(selection.profileId, modelId);
|
||||||
@ -121,7 +121,7 @@ export function notifyMultiAccountSuccess(
|
|||||||
export function notifyMultiAccountRateLimit(
|
export function notifyMultiAccountRateLimit(
|
||||||
selection: MultiAccountSelection | null | undefined,
|
selection: MultiAccountSelection | null | undefined,
|
||||||
modelId: string,
|
modelId: string,
|
||||||
cooldownMs?: number
|
cooldownMs?: number,
|
||||||
): void {
|
): void {
|
||||||
if (!selection) return;
|
if (!selection) return;
|
||||||
selection.manager.notifyRateLimit(selection.profileId, modelId, cooldownMs);
|
selection.manager.notifyRateLimit(selection.profileId, modelId, cooldownMs);
|
||||||
@ -129,7 +129,7 @@ export function notifyMultiAccountRateLimit(
|
|||||||
|
|
||||||
export function notifyMultiAccountFailure(
|
export function notifyMultiAccountFailure(
|
||||||
selection: MultiAccountSelection | null | undefined,
|
selection: MultiAccountSelection | null | undefined,
|
||||||
modelId: string
|
modelId: string,
|
||||||
): void {
|
): void {
|
||||||
if (!selection) return;
|
if (!selection) return;
|
||||||
selection.manager.notifyFailure(selection.profileId, modelId);
|
selection.manager.notifyFailure(selection.profileId, modelId);
|
||||||
@ -137,16 +137,13 @@ export function notifyMultiAccountFailure(
|
|||||||
|
|
||||||
export function notifyMultiAccountInvalid(
|
export function notifyMultiAccountInvalid(
|
||||||
selection: MultiAccountSelection | null | undefined,
|
selection: MultiAccountSelection | null | undefined,
|
||||||
reason: string
|
reason: string,
|
||||||
): void {
|
): void {
|
||||||
if (!selection) return;
|
if (!selection) return;
|
||||||
selection.manager.markInvalid(selection.profileId, reason);
|
selection.manager.markInvalid(selection.profileId, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMultiAccountStatus(): Record<
|
export function getMultiAccountStatus(): Record<string, ReturnType<AccountManager["getStatus"]>> {
|
||||||
string,
|
|
||||||
ReturnType<AccountManager["getStatus"]>
|
|
||||||
> {
|
|
||||||
const status: Record<string, ReturnType<AccountManager["getStatus"]>> = {};
|
const status: Record<string, ReturnType<AccountManager["getStatus"]>> = {};
|
||||||
for (const [provider, manager] of managers) {
|
for (const [provider, manager] of managers) {
|
||||||
status[provider] = manager.getStatus();
|
status[provider] = manager.getStatus();
|
||||||
|
|||||||
@ -60,13 +60,13 @@ describe("RateLimitTracker", () => {
|
|||||||
it("checks all rate limited", () => {
|
it("checks all rate limited", () => {
|
||||||
const tracker = new RateLimitTracker();
|
const tracker = new RateLimitTracker();
|
||||||
const profiles = ["p1", "p2", "p3"];
|
const profiles = ["p1", "p2", "p3"];
|
||||||
|
|
||||||
expect(tracker.areAllRateLimited(profiles, "m1")).toBe(false);
|
expect(tracker.areAllRateLimited(profiles, "m1")).toBe(false);
|
||||||
|
|
||||||
tracker.markRateLimited("p1", "m1", 5000);
|
tracker.markRateLimited("p1", "m1", 5000);
|
||||||
tracker.markRateLimited("p2", "m1", 5000);
|
tracker.markRateLimited("p2", "m1", 5000);
|
||||||
tracker.markRateLimited("p3", "m1", 5000);
|
tracker.markRateLimited("p3", "m1", 5000);
|
||||||
|
|
||||||
expect(tracker.areAllRateLimited(profiles, "m1")).toBe(true);
|
expect(tracker.areAllRateLimited(profiles, "m1")).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -121,7 +121,7 @@ describe("AccountManager", () => {
|
|||||||
const profiles = manager.getProfilesForProvider();
|
const profiles = manager.getProfilesForProvider();
|
||||||
const first = profiles[0];
|
const first = profiles[0];
|
||||||
manager.markInvalid(first, "Token revoked");
|
manager.markInvalid(first, "Token revoked");
|
||||||
|
|
||||||
const updated = manager.getProfilesForProvider();
|
const updated = manager.getProfilesForProvider();
|
||||||
expect(updated).not.toContain(first);
|
expect(updated).not.toContain(first);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -55,11 +55,7 @@ export class QuotaTracker {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateModelQuota(
|
updateModelQuota(profileId: string, modelId: string, quota: ModelQuota): void {
|
||||||
profileId: string,
|
|
||||||
modelId: string,
|
|
||||||
quota: ModelQuota
|
|
||||||
): void {
|
|
||||||
const existing = this.getQuota(profileId) ?? {
|
const existing = this.getQuota(profileId) ?? {
|
||||||
tier: "unknown" as const,
|
tier: "unknown" as const,
|
||||||
projectId: null,
|
projectId: null,
|
||||||
|
|||||||
@ -71,7 +71,7 @@ export class RateLimitTracker {
|
|||||||
markRateLimited(
|
markRateLimited(
|
||||||
profileId: string,
|
profileId: string,
|
||||||
modelId: string,
|
modelId: string,
|
||||||
cooldownMs: number = DEFAULT_COOLDOWN_MS
|
cooldownMs: number = DEFAULT_COOLDOWN_MS,
|
||||||
): BackoffResult {
|
): BackoffResult {
|
||||||
const key = this.getKey(profileId, modelId);
|
const key = this.getKey(profileId, modelId);
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
@ -81,7 +81,7 @@ export class RateLimitTracker {
|
|||||||
if (previous && now - previous.lastAt < RATE_LIMIT_DEDUP_WINDOW_MS) {
|
if (previous && now - previous.lastAt < RATE_LIMIT_DEDUP_WINDOW_MS) {
|
||||||
const backoffDelay = Math.min(
|
const backoffDelay = Math.min(
|
||||||
FIRST_RETRY_DELAY_MS * Math.pow(2, previous.consecutive429 - 1),
|
FIRST_RETRY_DELAY_MS * Math.pow(2, previous.consecutive429 - 1),
|
||||||
60_000
|
60_000,
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
attempt: previous.consecutive429,
|
attempt: previous.consecutive429,
|
||||||
@ -97,10 +97,7 @@ export class RateLimitTracker {
|
|||||||
: 1;
|
: 1;
|
||||||
|
|
||||||
// Calculate exponential backoff
|
// Calculate exponential backoff
|
||||||
const backoffDelay = Math.min(
|
const backoffDelay = Math.min(FIRST_RETRY_DELAY_MS * Math.pow(2, attempt - 1), 60_000);
|
||||||
FIRST_RETRY_DELAY_MS * Math.pow(2, attempt - 1),
|
|
||||||
60_000
|
|
||||||
);
|
|
||||||
const effectiveCooldown = Math.max(cooldownMs, backoffDelay, MIN_BACKOFF_MS);
|
const effectiveCooldown = Math.max(cooldownMs, backoffDelay, MIN_BACKOFF_MS);
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
|
|||||||
@ -29,9 +29,7 @@ export class HybridStrategy implements Strategy {
|
|||||||
const { rateLimitTracker, healthScorer, quotaTracker } = this.manager;
|
const { rateLimitTracker, healthScorer, quotaTracker } = this.manager;
|
||||||
const profiles = this.manager.getProfilesForProvider();
|
const profiles = this.manager.getProfilesForProvider();
|
||||||
|
|
||||||
const available = profiles.filter(
|
const available = profiles.filter((id) => !rateLimitTracker.isRateLimited(id, modelId));
|
||||||
(id) => !rateLimitTracker.isRateLimited(id, modelId)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (available.length === 0) {
|
if (available.length === 0) {
|
||||||
const minWait = rateLimitTracker.getMinWaitTime(profiles, modelId);
|
const minWait = rateLimitTracker.getMinWaitTime(profiles, modelId);
|
||||||
@ -98,9 +96,7 @@ export class StickyStrategy implements Strategy {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const available = profiles.filter(
|
const available = profiles.filter((id) => !rateLimitTracker.isRateLimited(id, modelId));
|
||||||
(id) => !rateLimitTracker.isRateLimited(id, modelId)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (available.length === 0) {
|
if (available.length === 0) {
|
||||||
const minWait = rateLimitTracker.getMinWaitTime(profiles, modelId);
|
const minWait = rateLimitTracker.getMinWaitTime(profiles, modelId);
|
||||||
|
|||||||
@ -41,11 +41,7 @@ import {
|
|||||||
type FailoverReason,
|
type FailoverReason,
|
||||||
} from "../pi-embedded-helpers.js";
|
} from "../pi-embedded-helpers.js";
|
||||||
import { normalizeUsage, type UsageLike } from "../usage.js";
|
import { normalizeUsage, type UsageLike } from "../usage.js";
|
||||||
import {
|
import { isMultiAccountEnabled, getManager, getOrCreateManager } from "../multi-account/index.js";
|
||||||
isMultiAccountEnabled,
|
|
||||||
getManager,
|
|
||||||
getOrCreateManager,
|
|
||||||
} from "../multi-account/index.js";
|
|
||||||
|
|
||||||
import { compactEmbeddedPiSessionDirect } from "./compact.js";
|
import { compactEmbeddedPiSessionDirect } from "./compact.js";
|
||||||
import { resolveGlobalLane, resolveSessionLane } from "./lanes.js";
|
import { resolveGlobalLane, resolveSessionLane } from "./lanes.js";
|
||||||
@ -141,12 +137,12 @@ export async function runEmbeddedPiAgent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const authStore = ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false });
|
const authStore = ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false });
|
||||||
|
|
||||||
// Initialize multi-account manager if enabled for this provider
|
// Initialize multi-account manager if enabled for this provider
|
||||||
if (isMultiAccountEnabled(provider, params.config)) {
|
if (isMultiAccountEnabled(provider, params.config)) {
|
||||||
await getOrCreateManager(provider, authStore, params.config);
|
await getOrCreateManager(provider, authStore, params.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
const preferredProfileId = params.authProfileId?.trim();
|
const preferredProfileId = params.authProfileId?.trim();
|
||||||
let lockedProfileId = params.authProfileIdSource === "user" ? preferredProfileId : undefined;
|
let lockedProfileId = params.authProfileIdSource === "user" ? preferredProfileId : undefined;
|
||||||
if (lockedProfileId) {
|
if (lockedProfileId) {
|
||||||
@ -158,7 +154,7 @@ export async function runEmbeddedPiAgent(
|
|||||||
lockedProfileId = undefined;
|
lockedProfileId = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use multi-account intelligent ordering when enabled
|
// Use multi-account intelligent ordering when enabled
|
||||||
let profileOrder: string[];
|
let profileOrder: string[];
|
||||||
const manager = getManager(provider);
|
const manager = getManager(provider);
|
||||||
@ -170,11 +166,11 @@ export async function runEmbeddedPiAgent(
|
|||||||
provider,
|
provider,
|
||||||
preferredProfile: preferredProfileId,
|
preferredProfile: preferredProfileId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Re-order based on multi-account intelligence (health + rate limits)
|
// Re-order based on multi-account intelligence (health + rate limits)
|
||||||
const available: string[] = [];
|
const available: string[] = [];
|
||||||
const rateLimited: Array<{ profileId: string; cooldown: number }> = [];
|
const rateLimited: Array<{ profileId: string; cooldown: number }> = [];
|
||||||
|
|
||||||
for (const profileId of baseOrder) {
|
for (const profileId of baseOrder) {
|
||||||
if (manager.rateLimitTracker.isRateLimited(profileId, modelId)) {
|
if (manager.rateLimitTracker.isRateLimited(profileId, modelId)) {
|
||||||
rateLimited.push({
|
rateLimited.push({
|
||||||
@ -185,20 +181,23 @@ export async function runEmbeddedPiAgent(
|
|||||||
available.push(profileId);
|
available.push(profileId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort available by health score (highest first)
|
// Sort available by health score (highest first)
|
||||||
const healthSorted = manager.healthScorer.getSortedByHealth(available);
|
const healthSorted = manager.healthScorer.getSortedByHealth(available);
|
||||||
const sortedAvailable = healthSorted.map(h => h.profileId);
|
const sortedAvailable = healthSorted.map((h) => h.profileId);
|
||||||
|
|
||||||
// Sort rate-limited by soonest cooldown expiry
|
// Sort rate-limited by soonest cooldown expiry
|
||||||
rateLimited.sort((a, b) => a.cooldown - b.cooldown);
|
rateLimited.sort((a, b) => a.cooldown - b.cooldown);
|
||||||
const sortedRateLimited = rateLimited.map(r => r.profileId);
|
const sortedRateLimited = rateLimited.map((r) => r.profileId);
|
||||||
|
|
||||||
profileOrder = [...sortedAvailable, ...sortedRateLimited];
|
profileOrder = [...sortedAvailable, ...sortedRateLimited];
|
||||||
|
|
||||||
// Ensure preferred profile is first if specified
|
// Ensure preferred profile is first if specified
|
||||||
if (preferredProfileId && profileOrder.includes(preferredProfileId)) {
|
if (preferredProfileId && profileOrder.includes(preferredProfileId)) {
|
||||||
profileOrder = [preferredProfileId, ...profileOrder.filter(p => p !== preferredProfileId)];
|
profileOrder = [
|
||||||
|
preferredProfileId,
|
||||||
|
...profileOrder.filter((p) => p !== preferredProfileId),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fall back to standard ordering
|
// Fall back to standard ordering
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
import type { Command } from "commander";
|
import type { Command } from "commander";
|
||||||
import { defaultRuntime } from "../runtime.js";
|
import { defaultRuntime } from "../runtime.js";
|
||||||
import { formatDocsLink } from "../terminal/links.js";
|
|
||||||
import { theme } from "../terminal/theme.js";
|
import { theme } from "../terminal/theme.js";
|
||||||
import { runCommandWithRuntime } from "./cli-utils.js";
|
import { runCommandWithRuntime } from "./cli-utils.js";
|
||||||
import {
|
import {
|
||||||
@ -24,7 +23,7 @@ export function registerAccountsCli(program: Command) {
|
|||||||
.addHelpText(
|
.addHelpText(
|
||||||
"after",
|
"after",
|
||||||
() =>
|
() =>
|
||||||
`\n${theme.muted("Multi-account distributes requests across multiple OAuth accounts for higher throughput.")}\n`
|
`\n${theme.muted("Multi-account distributes requests across multiple OAuth accounts for higher throughput.")}\n`,
|
||||||
);
|
);
|
||||||
|
|
||||||
accounts
|
accounts
|
||||||
@ -32,26 +31,20 @@ export function registerAccountsCli(program: Command) {
|
|||||||
.description("List all accounts for a provider")
|
.description("List all accounts for a provider")
|
||||||
.option("-p, --provider <provider>", "Provider name", "google-antigravity")
|
.option("-p, --provider <provider>", "Provider name", "google-antigravity")
|
||||||
.option("--json", "Output as JSON")
|
.option("--json", "Output as JSON")
|
||||||
.action((options) =>
|
.action((options) => runAccountsCommand(() => accountsListCommand(defaultRuntime, options)));
|
||||||
runAccountsCommand(() => accountsListCommand(defaultRuntime, options))
|
|
||||||
);
|
|
||||||
|
|
||||||
accounts
|
accounts
|
||||||
.command("status")
|
.command("status")
|
||||||
.description("Show multi-account status (health scores, rate limits)")
|
.description("Show multi-account status (health scores, rate limits)")
|
||||||
.option("-p, --provider <provider>", "Provider name", "google-antigravity")
|
.option("-p, --provider <provider>", "Provider name", "google-antigravity")
|
||||||
.option("--json", "Output as JSON")
|
.option("--json", "Output as JSON")
|
||||||
.action((options) =>
|
.action((options) => runAccountsCommand(() => accountsStatusCommand(defaultRuntime, options)));
|
||||||
runAccountsCommand(() => accountsStatusCommand(defaultRuntime, options))
|
|
||||||
);
|
|
||||||
|
|
||||||
accounts
|
accounts
|
||||||
.command("reset")
|
.command("reset")
|
||||||
.description("Reset rate limits for all accounts")
|
.description("Reset rate limits for all accounts")
|
||||||
.option("-p, --provider <provider>", "Provider name", "google-antigravity")
|
.option("-p, --provider <provider>", "Provider name", "google-antigravity")
|
||||||
.action((options) =>
|
.action((options) => runAccountsCommand(() => accountsResetCommand(defaultRuntime, options)));
|
||||||
runAccountsCommand(() => accountsResetCommand(defaultRuntime, options))
|
|
||||||
);
|
|
||||||
|
|
||||||
return accounts;
|
return accounts;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ export interface AccountsListOptions {
|
|||||||
|
|
||||||
export async function accountsListCommand(
|
export async function accountsListCommand(
|
||||||
runtime: RuntimeEnv,
|
runtime: RuntimeEnv,
|
||||||
options: AccountsListOptions
|
options: AccountsListOptions,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const cfg = loadConfig();
|
const cfg = loadConfig();
|
||||||
const agentDir = resolveClawdbotAgentDir();
|
const agentDir = resolveClawdbotAgentDir();
|
||||||
|
|||||||
@ -11,7 +11,7 @@ export interface AccountsResetOptions {
|
|||||||
|
|
||||||
export async function accountsResetCommand(
|
export async function accountsResetCommand(
|
||||||
runtime: RuntimeEnv,
|
runtime: RuntimeEnv,
|
||||||
options: AccountsResetOptions
|
options: AccountsResetOptions,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const provider = options.provider ?? "google-antigravity";
|
const provider = options.provider ?? "google-antigravity";
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export interface AccountsStatusOptions {
|
|||||||
|
|
||||||
export async function accountsStatusCommand(
|
export async function accountsStatusCommand(
|
||||||
runtime: RuntimeEnv,
|
runtime: RuntimeEnv,
|
||||||
options: AccountsStatusOptions
|
options: AccountsStatusOptions,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const cfg = loadConfig();
|
const cfg = loadConfig();
|
||||||
const agentDir = resolveClawdbotAgentDir();
|
const agentDir = resolveClawdbotAgentDir();
|
||||||
@ -34,7 +34,7 @@ export async function accountsStatusCommand(
|
|||||||
` multiAccount:\n` +
|
` multiAccount:\n` +
|
||||||
` enabled: true\n` +
|
` enabled: true\n` +
|
||||||
` providers:\n` +
|
` providers:\n` +
|
||||||
` - ${provider}`
|
` - ${provider}`,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -69,9 +69,7 @@ export async function accountsStatusCommand(
|
|||||||
const status = acc.isInvalid
|
const status = acc.isInvalid
|
||||||
? `❌ Invalid (${acc.invalidReason})`
|
? `❌ Invalid (${acc.invalidReason})`
|
||||||
: `✅ Health: ${acc.healthScore}`;
|
: `✅ Health: ${acc.healthScore}`;
|
||||||
const lastUsed = acc.lastUsed
|
const lastUsed = acc.lastUsed ? new Date(acc.lastUsed).toLocaleTimeString() : "never";
|
||||||
? new Date(acc.lastUsed).toLocaleTimeString()
|
|
||||||
: "never";
|
|
||||||
runtime.log(` • ${acc.email}`);
|
runtime.log(` • ${acc.email}`);
|
||||||
runtime.log(` Status: ${status}`);
|
runtime.log(` Status: ${status}`);
|
||||||
runtime.log(` Last used: ${lastUsed}`);
|
runtime.log(` Last used: ${lastUsed}`);
|
||||||
|
|||||||
@ -206,11 +206,9 @@ export const ClawdbotSchema = z
|
|||||||
multiAccount: z
|
multiAccount: z
|
||||||
.object({
|
.object({
|
||||||
enabled: z.boolean().optional(),
|
enabled: z.boolean().optional(),
|
||||||
strategy: z.union([
|
strategy: z
|
||||||
z.literal("hybrid"),
|
.union([z.literal("hybrid"), z.literal("sticky"), z.literal("round-robin")])
|
||||||
z.literal("sticky"),
|
.optional(),
|
||||||
z.literal("round-robin"),
|
|
||||||
]).optional(),
|
|
||||||
providers: z.array(z.string()).optional(),
|
providers: z.array(z.string()).optional(),
|
||||||
defaultCooldownMs: z.number().positive().optional(),
|
defaultCooldownMs: z.number().positive().optional(),
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user