agent-relay-runner 0.77.0 → 0.77.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/src/quota.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { homedir, hostname } from "node:os";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import type { ContextProbeMetrics, ProviderQuotaError, ProviderQuotaUpdateInput, QuotaState, QuotaWindowName } from "agent-relay-sdk";
|
|
@@ -46,6 +46,7 @@ export class RunnerQuotaPoller {
|
|
|
46
46
|
private timer?: Timer;
|
|
47
47
|
private inFlight = false;
|
|
48
48
|
private active = false;
|
|
49
|
+
private activeLockDir?: string;
|
|
49
50
|
private lastLog?: { key: string; at: number };
|
|
50
51
|
|
|
51
52
|
constructor(private readonly options: {
|
|
@@ -74,6 +75,9 @@ export class RunnerQuotaPoller {
|
|
|
74
75
|
this.active = false;
|
|
75
76
|
if (this.timer) clearTimeout(this.timer);
|
|
76
77
|
this.timer = undefined;
|
|
78
|
+
const lockDir = this.activeLockDir;
|
|
79
|
+
this.activeLockDir = undefined;
|
|
80
|
+
if (lockDir) releaseQuotaLock(lockDir);
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
clear(): void {
|
|
@@ -112,6 +116,7 @@ export class RunnerQuotaPoller {
|
|
|
112
116
|
return;
|
|
113
117
|
}
|
|
114
118
|
lockDir = attempt.lockDir;
|
|
119
|
+
this.activeLockDir = lockDir;
|
|
115
120
|
const sample = await collectRunnerQuotaSample({
|
|
116
121
|
provider: this.options.provider,
|
|
117
122
|
agentId: this.options.agentId,
|
|
@@ -147,7 +152,7 @@ export class RunnerQuotaPoller {
|
|
|
147
152
|
const lastError = providerQuotaErrorFromCollectorError(error, retryAfterMs);
|
|
148
153
|
const lastAttemptAt = Date.now();
|
|
149
154
|
const retryDelayMs = retryAfterMs ?? (identity && !readSharedQuotaEntry(identity, this.options.quotaStateDir)?.lastAttemptAt ? QUOTA_FAST_RETRY_MS : QUOTA_POLL_INTERVAL_MS);
|
|
150
|
-
if (identity) {
|
|
155
|
+
if (identity && this.active) {
|
|
151
156
|
await this.reportProviderQuota({
|
|
152
157
|
provider: identity.provider,
|
|
153
158
|
accountKey: identity.accountKey,
|
|
@@ -168,7 +173,10 @@ export class RunnerQuotaPoller {
|
|
|
168
173
|
if (this.active) this.logFailure(error, retryAfterMs);
|
|
169
174
|
this.schedule(retryDelayMs);
|
|
170
175
|
} finally {
|
|
171
|
-
if (lockDir
|
|
176
|
+
if (lockDir && this.activeLockDir === lockDir) {
|
|
177
|
+
releaseQuotaLock(lockDir);
|
|
178
|
+
this.activeLockDir = undefined;
|
|
179
|
+
}
|
|
172
180
|
this.inFlight = false;
|
|
173
181
|
}
|
|
174
182
|
}
|
|
@@ -223,16 +231,15 @@ export async function resolveRunnerQuotaIdentity(input: {
|
|
|
223
231
|
const provider = input.provider.trim().toLowerCase();
|
|
224
232
|
if (provider === "claude") {
|
|
225
233
|
const credentialsPath = claudeCredentialsPathFromProcess(input.process);
|
|
226
|
-
const token = await readClaudeOAuthAccessToken(credentialsPath);
|
|
227
234
|
return {
|
|
228
235
|
provider,
|
|
229
|
-
accountKey:
|
|
236
|
+
accountKey: hostProviderAccountKey(),
|
|
230
237
|
credentialsPath,
|
|
231
238
|
};
|
|
232
239
|
}
|
|
233
240
|
return {
|
|
234
241
|
provider,
|
|
235
|
-
accountKey: codexAccountKeyFromProcess(input.process) ?? hostProviderAccountKey(
|
|
242
|
+
accountKey: codexAccountKeyFromProcess(input.process) ?? hostProviderAccountKey(),
|
|
236
243
|
};
|
|
237
244
|
}
|
|
238
245
|
|
|
@@ -383,8 +390,8 @@ function processEnvFromMeta(proc: ManagedProcess | undefined): Record<string, un
|
|
|
383
390
|
return isRecord(config?.env) ? config.env : undefined;
|
|
384
391
|
}
|
|
385
392
|
|
|
386
|
-
function hostProviderAccountKey(
|
|
387
|
-
return `host:${hostname()}
|
|
393
|
+
function hostProviderAccountKey(): string {
|
|
394
|
+
return `host:${hostname()}`;
|
|
388
395
|
}
|
|
389
396
|
|
|
390
397
|
export function beginSharedQuotaAttempt(identity: QuotaIdentity, stateDir?: string, now = Date.now()): {
|
|
@@ -449,16 +456,17 @@ function sharedQuotaKey(identity: QuotaIdentity): string {
|
|
|
449
456
|
}
|
|
450
457
|
|
|
451
458
|
function acquireQuotaLock(lockDir: string, now = Date.now()): boolean {
|
|
459
|
+
mkdirSync(dirname(lockDir), { recursive: true });
|
|
452
460
|
try {
|
|
453
461
|
mkdirSync(lockDir, { recursive: false });
|
|
454
462
|
writeFileSync(join(lockDir, "owner"), String(now));
|
|
455
463
|
return true;
|
|
456
464
|
} catch {
|
|
457
465
|
const ownerPath = join(lockDir, "owner");
|
|
466
|
+
const lockedAt = quotaLockTimestamp(lockDir, ownerPath);
|
|
467
|
+
if (lockedAt !== undefined && now - lockedAt <= QUOTA_LOCK_STALE_MS) return false;
|
|
468
|
+
rmSync(lockDir, { recursive: true, force: true });
|
|
458
469
|
try {
|
|
459
|
-
const lockedAt = Number(readFileSync(ownerPath, "utf8"));
|
|
460
|
-
if (Number.isFinite(lockedAt) && now - lockedAt <= QUOTA_LOCK_STALE_MS) return false;
|
|
461
|
-
rmSync(lockDir, { recursive: true, force: true });
|
|
462
470
|
mkdirSync(lockDir, { recursive: false });
|
|
463
471
|
writeFileSync(ownerPath, String(now));
|
|
464
472
|
return true;
|
|
@@ -472,6 +480,18 @@ function releaseQuotaLock(lockDir: string): void {
|
|
|
472
480
|
rmSync(lockDir, { recursive: true, force: true });
|
|
473
481
|
}
|
|
474
482
|
|
|
483
|
+
function quotaLockTimestamp(lockDir: string, ownerPath: string): number | undefined {
|
|
484
|
+
try {
|
|
485
|
+
const lockedAt = Number(readFileSync(ownerPath, "utf8"));
|
|
486
|
+
if (Number.isFinite(lockedAt)) return lockedAt;
|
|
487
|
+
} catch {}
|
|
488
|
+
try {
|
|
489
|
+
return statSync(lockDir).mtimeMs;
|
|
490
|
+
} catch {
|
|
491
|
+
return undefined;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
475
495
|
function isQuotaState(value: unknown): value is QuotaState {
|
|
476
496
|
return isRecord(value) && Array.isArray(value.windows) && typeof value.updatedAt === "number";
|
|
477
497
|
}
|