fix(auth): serialize profile stats updates
This commit is contained in:
@@ -107,16 +107,16 @@ function syncAuthProfileStore(
|
|||||||
target.usageStats = source.usageStats;
|
target.usageStats = source.usageStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateAuthProfileStoreWithLock(params: {
|
async function updateAuthProfileStoreWithLock(params: {
|
||||||
agentDir?: string;
|
agentDir?: string;
|
||||||
updater: (store: AuthProfileStore) => boolean;
|
updater: (store: AuthProfileStore) => boolean;
|
||||||
}): AuthProfileStore | null {
|
}): Promise<AuthProfileStore | null> {
|
||||||
const authPath = resolveAuthStorePath(params.agentDir);
|
const authPath = resolveAuthStorePath(params.agentDir);
|
||||||
ensureAuthStoreFile(authPath);
|
ensureAuthStoreFile(authPath);
|
||||||
|
|
||||||
let release: (() => void) | undefined;
|
let release: (() => Promise<void>) | undefined;
|
||||||
try {
|
try {
|
||||||
release = lockfile.lockSync(authPath, AUTH_STORE_LOCK_OPTIONS);
|
release = await lockfile.lock(authPath, AUTH_STORE_LOCK_OPTIONS);
|
||||||
const store = ensureAuthProfileStore(params.agentDir);
|
const store = ensureAuthProfileStore(params.agentDir);
|
||||||
const shouldSave = params.updater(store);
|
const shouldSave = params.updater(store);
|
||||||
if (shouldSave) {
|
if (shouldSave) {
|
||||||
@@ -128,17 +128,13 @@ function updateAuthProfileStoreWithLock(params: {
|
|||||||
} finally {
|
} finally {
|
||||||
if (release) {
|
if (release) {
|
||||||
try {
|
try {
|
||||||
release();
|
await release();
|
||||||
} catch {
|
|
||||||
try {
|
|
||||||
lockfile.unlockSync(authPath);
|
|
||||||
} catch {
|
} catch {
|
||||||
// ignore unlock errors
|
// ignore unlock errors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function buildOAuthApiKey(
|
function buildOAuthApiKey(
|
||||||
provider: OAuthProvider,
|
provider: OAuthProvider,
|
||||||
@@ -403,13 +399,13 @@ export function isProfileInCooldown(
|
|||||||
* Mark a profile as successfully used. Resets error count and updates lastUsed.
|
* Mark a profile as successfully used. Resets error count and updates lastUsed.
|
||||||
* Uses store lock to avoid overwriting concurrent usage updates.
|
* Uses store lock to avoid overwriting concurrent usage updates.
|
||||||
*/
|
*/
|
||||||
export function markAuthProfileUsed(params: {
|
export async function markAuthProfileUsed(params: {
|
||||||
store: AuthProfileStore;
|
store: AuthProfileStore;
|
||||||
profileId: string;
|
profileId: string;
|
||||||
agentDir?: string;
|
agentDir?: string;
|
||||||
}): void {
|
}): Promise<void> {
|
||||||
const { store, profileId, agentDir } = params;
|
const { store, profileId, agentDir } = params;
|
||||||
const updated = updateAuthProfileStoreWithLock({
|
const updated = await updateAuthProfileStoreWithLock({
|
||||||
agentDir,
|
agentDir,
|
||||||
updater: (freshStore) => {
|
updater: (freshStore) => {
|
||||||
if (!freshStore.profiles[profileId]) return false;
|
if (!freshStore.profiles[profileId]) return false;
|
||||||
@@ -452,13 +448,13 @@ export function calculateAuthProfileCooldownMs(errorCount: number): number {
|
|||||||
* Cooldown times: 1min, 5min, 25min, max 1 hour.
|
* Cooldown times: 1min, 5min, 25min, max 1 hour.
|
||||||
* Uses store lock to avoid overwriting concurrent usage updates.
|
* Uses store lock to avoid overwriting concurrent usage updates.
|
||||||
*/
|
*/
|
||||||
export function markAuthProfileCooldown(params: {
|
export async function markAuthProfileCooldown(params: {
|
||||||
store: AuthProfileStore;
|
store: AuthProfileStore;
|
||||||
profileId: string;
|
profileId: string;
|
||||||
agentDir?: string;
|
agentDir?: string;
|
||||||
}): void {
|
}): Promise<void> {
|
||||||
const { store, profileId, agentDir } = params;
|
const { store, profileId, agentDir } = params;
|
||||||
const updated = updateAuthProfileStoreWithLock({
|
const updated = await updateAuthProfileStoreWithLock({
|
||||||
agentDir,
|
agentDir,
|
||||||
updater: (freshStore) => {
|
updater: (freshStore) => {
|
||||||
if (!freshStore.profiles[profileId]) return false;
|
if (!freshStore.profiles[profileId]) return false;
|
||||||
@@ -503,13 +499,13 @@ export function markAuthProfileCooldown(params: {
|
|||||||
* Clear cooldown for a profile (e.g., manual reset).
|
* Clear cooldown for a profile (e.g., manual reset).
|
||||||
* Uses store lock to avoid overwriting concurrent usage updates.
|
* Uses store lock to avoid overwriting concurrent usage updates.
|
||||||
*/
|
*/
|
||||||
export function clearAuthProfileCooldown(params: {
|
export async function clearAuthProfileCooldown(params: {
|
||||||
store: AuthProfileStore;
|
store: AuthProfileStore;
|
||||||
profileId: string;
|
profileId: string;
|
||||||
agentDir?: string;
|
agentDir?: string;
|
||||||
}): void {
|
}): Promise<void> {
|
||||||
const { store, profileId, agentDir } = params;
|
const { store, profileId, agentDir } = params;
|
||||||
const updated = updateAuthProfileStoreWithLock({
|
const updated = await updateAuthProfileStoreWithLock({
|
||||||
agentDir,
|
agentDir,
|
||||||
updater: (freshStore) => {
|
updater: (freshStore) => {
|
||||||
if (!freshStore.usageStats?.[profileId]) return false;
|
if (!freshStore.usageStats?.[profileId]) return false;
|
||||||
@@ -693,14 +689,14 @@ export async function resolveApiKeyForProfile(params: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function markAuthProfileGood(params: {
|
export async function markAuthProfileGood(params: {
|
||||||
store: AuthProfileStore;
|
store: AuthProfileStore;
|
||||||
provider: string;
|
provider: string;
|
||||||
profileId: string;
|
profileId: string;
|
||||||
agentDir?: string;
|
agentDir?: string;
|
||||||
}): void {
|
}): Promise<void> {
|
||||||
const { store, provider, profileId, agentDir } = params;
|
const { store, provider, profileId, agentDir } = params;
|
||||||
const updated = updateAuthProfileStoreWithLock({
|
const updated = await updateAuthProfileStoreWithLock({
|
||||||
agentDir,
|
agentDir,
|
||||||
updater: (freshStore) => {
|
updater: (freshStore) => {
|
||||||
const profile = freshStore.profiles[profileId];
|
const profile = freshStore.profiles[profileId];
|
||||||
|
|||||||
@@ -1000,7 +1000,7 @@ export async function runEmbeddedPiAgent(params: {
|
|||||||
if (shouldRotate) {
|
if (shouldRotate) {
|
||||||
// Mark current profile for cooldown before rotating
|
// Mark current profile for cooldown before rotating
|
||||||
if (lastProfileId) {
|
if (lastProfileId) {
|
||||||
markAuthProfileCooldown({
|
await markAuthProfileCooldown({
|
||||||
store: authStore,
|
store: authStore,
|
||||||
profileId: lastProfileId,
|
profileId: lastProfileId,
|
||||||
});
|
});
|
||||||
@@ -1091,13 +1091,16 @@ export async function runEmbeddedPiAgent(params: {
|
|||||||
`embedded run done: runId=${params.runId} sessionId=${params.sessionId} durationMs=${Date.now() - started} aborted=${aborted}`,
|
`embedded run done: runId=${params.runId} sessionId=${params.sessionId} durationMs=${Date.now() - started} aborted=${aborted}`,
|
||||||
);
|
);
|
||||||
if (lastProfileId) {
|
if (lastProfileId) {
|
||||||
markAuthProfileGood({
|
await markAuthProfileGood({
|
||||||
store: authStore,
|
store: authStore,
|
||||||
provider,
|
provider,
|
||||||
profileId: lastProfileId,
|
profileId: lastProfileId,
|
||||||
});
|
});
|
||||||
// Track usage for round-robin rotation
|
// Track usage for round-robin rotation
|
||||||
markAuthProfileUsed({ store: authStore, profileId: lastProfileId });
|
await markAuthProfileUsed({
|
||||||
|
store: authStore,
|
||||||
|
profileId: lastProfileId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
payloads: payloads.length ? payloads : undefined,
|
payloads: payloads.length ? payloads : undefined,
|
||||||
|
|||||||
Reference in New Issue
Block a user