agent-relay-orchestrator 0.78.3 → 0.78.4
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 +2 -2
- package/src/quota-poller.ts +39 -6
- package/src/relay.ts +5 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-relay-orchestrator",
|
|
3
|
-
"version": "0.78.
|
|
3
|
+
"version": "0.78.4",
|
|
4
4
|
"description": "Agent Relay orchestrator — manages agent lifecycle across hosts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"test": "bun test"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"agent-relay-sdk": "0.2.
|
|
19
|
+
"agent-relay-sdk": "0.2.56"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/bun": "latest",
|
package/src/quota-poller.ts
CHANGED
|
@@ -3,13 +3,14 @@ import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
|
3
3
|
import { createServer } from "node:net";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import {
|
|
6
|
+
DEFAULT_PROVIDER_QUOTA_CONFIG,
|
|
6
7
|
QUOTA_FAILURE_LOG_INTERVAL_MS,
|
|
7
8
|
QUOTA_FAST_RETRY_MS,
|
|
8
|
-
QUOTA_POLL_INTERVAL_MS,
|
|
9
9
|
QuotaCollectionError,
|
|
10
10
|
collectClaudeQuotaSample,
|
|
11
11
|
collectCodexQuotaSample,
|
|
12
12
|
credentialAccountKey,
|
|
13
|
+
normalizeProviderQuotaConfig,
|
|
13
14
|
providerQuotaErrorFromCollectorError,
|
|
14
15
|
quotaRetryAfterMs,
|
|
15
16
|
readClaudeOAuthAccessToken,
|
|
@@ -18,7 +19,7 @@ import {
|
|
|
18
19
|
type ProviderQuotaIdentity,
|
|
19
20
|
type ProviderQuotaSample,
|
|
20
21
|
} from "agent-relay-sdk/provider-quota";
|
|
21
|
-
import type { ProviderQuotaLeaseAcquireInput, ProviderQuotaUpdateInput } from "agent-relay-sdk";
|
|
22
|
+
import type { ProviderQuotaConfig, ProviderQuotaConfigMap, ProviderQuotaLeaseAcquireInput, ProviderQuotaUpdateInput } from "agent-relay-sdk";
|
|
22
23
|
import { errMessage } from "agent-relay-sdk";
|
|
23
24
|
import { codexCommandFromEnv, providerHomeRootFromEnv, type OrchestratorConfig } from "./config";
|
|
24
25
|
|
|
@@ -43,6 +44,7 @@ type QuotaRelay = {
|
|
|
43
44
|
}>;
|
|
44
45
|
releaseProviderQuotaLease(orchestratorId: string, input: ProviderQuotaLeaseAcquireInput & { leaseToken: string }): Promise<unknown>;
|
|
45
46
|
reportProviderQuota(input: ProviderQuotaUpdateInput): Promise<unknown>;
|
|
47
|
+
getProviderQuotaConfig(): Promise<ProviderQuotaConfigMap>;
|
|
46
48
|
connected: boolean;
|
|
47
49
|
};
|
|
48
50
|
|
|
@@ -69,6 +71,10 @@ export class OrchestratorQuotaPoller {
|
|
|
69
71
|
private inFlight = false;
|
|
70
72
|
private readonly states = new Map<string, QuotaPollState>();
|
|
71
73
|
private readonly logStates = new Map<string, { key: string; at: number }>();
|
|
74
|
+
// Per-provider quota config (#605), refreshed each tick. Empty until the first
|
|
75
|
+
// successful fetch — configFor() falls back to defaults so a fresh/empty config
|
|
76
|
+
// (or an older relay without the endpoint) preserves today's behavior.
|
|
77
|
+
private quotaConfig: ProviderQuotaConfigMap = {};
|
|
72
78
|
|
|
73
79
|
constructor(
|
|
74
80
|
private readonly config: OrchestratorConfig,
|
|
@@ -105,6 +111,7 @@ export class OrchestratorQuotaPoller {
|
|
|
105
111
|
if (this.inFlight || !this.active || !this.relay.connected) return;
|
|
106
112
|
this.inFlight = true;
|
|
107
113
|
try {
|
|
114
|
+
await this.refreshQuotaConfig();
|
|
108
115
|
const { candidates, skips } = await this.discoverCandidates();
|
|
109
116
|
await this.releaseRemovedCandidates(candidates);
|
|
110
117
|
for (const candidate of candidates) {
|
|
@@ -129,15 +136,34 @@ export class OrchestratorQuotaPoller {
|
|
|
129
136
|
}, Math.max(1_000, delayMs));
|
|
130
137
|
}
|
|
131
138
|
|
|
139
|
+
// Refresh per-provider quota config (#605). Best-effort: on failure we keep the
|
|
140
|
+
// last known config (defaults for any unset provider), so a transient relay blip
|
|
141
|
+
// never silently stops collection.
|
|
142
|
+
private async refreshQuotaConfig(): Promise<void> {
|
|
143
|
+
try {
|
|
144
|
+
this.quotaConfig = await this.relay.getProviderQuotaConfig();
|
|
145
|
+
} catch {
|
|
146
|
+
// keep prior config; configFor() defaults any missing provider.
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private configFor(provider: string): ProviderQuotaConfig {
|
|
151
|
+
const stored = this.quotaConfig[provider];
|
|
152
|
+
return stored ? normalizeProviderQuotaConfig(stored) : { ...DEFAULT_PROVIDER_QUOTA_CONFIG };
|
|
153
|
+
}
|
|
154
|
+
|
|
132
155
|
private async discoverCandidates(): Promise<QuotaDiscovery> {
|
|
133
156
|
const candidates: QuotaCandidate[] = [];
|
|
134
157
|
const skips: ProviderSkip[] = [];
|
|
135
|
-
|
|
158
|
+
// A disabled provider (#605) is collected from at all: no discovery → no
|
|
159
|
+
// polling/API calls, leases released by releaseRemovedCandidates, and no
|
|
160
|
+
// skip-marker row (disabled is intentional, not a credential failure).
|
|
161
|
+
if (this.config.providers.includes("claude") && this.configFor("claude").enabled) {
|
|
136
162
|
const found = await this.discoverClaudeCandidates();
|
|
137
163
|
candidates.push(...found.candidates);
|
|
138
164
|
if (found.skipReason) skips.push({ provider: "claude", reason: found.skipReason });
|
|
139
165
|
}
|
|
140
|
-
if (this.config.providers.includes("codex")) {
|
|
166
|
+
if (this.config.providers.includes("codex") && this.configFor("codex").enabled) {
|
|
141
167
|
const found = await this.discoverCodexCandidates();
|
|
142
168
|
candidates.push(...found.candidates);
|
|
143
169
|
if (found.skipReason) skips.push({ provider: "codex", reason: found.skipReason });
|
|
@@ -226,6 +252,9 @@ export class OrchestratorQuotaPoller {
|
|
|
226
252
|
if (!await this.ensureLease(candidate, state, now)) return;
|
|
227
253
|
if (state.nextPollAt !== undefined && state.nextPollAt > now) return;
|
|
228
254
|
|
|
255
|
+
// Per-provider cadence (#605): the configured interval governs the gap between
|
|
256
|
+
// successful polls and the post-failure retry once a first attempt has landed.
|
|
257
|
+
const pollIntervalMs = this.configFor(candidate.provider).pollIntervalMs;
|
|
229
258
|
const lastAttemptAt = now;
|
|
230
259
|
try {
|
|
231
260
|
const sample = await this.collect(candidate);
|
|
@@ -240,13 +269,17 @@ export class OrchestratorQuotaPoller {
|
|
|
240
269
|
};
|
|
241
270
|
await this.relay.reportProviderQuota(update);
|
|
242
271
|
state.lastAttemptAt = update.lastAttemptAt;
|
|
243
|
-
state.nextPollAt = now +
|
|
272
|
+
state.nextPollAt = now + pollIntervalMs;
|
|
244
273
|
} catch (error) {
|
|
245
274
|
const retryAfterMs = quotaRetryAfterMs(error);
|
|
246
275
|
const lastError = providerQuotaErrorFromCollectorError(error, retryAfterMs);
|
|
247
|
-
const retryDelayMs = retryAfterMs ?? (state.lastAttemptAt ?
|
|
276
|
+
const retryDelayMs = retryAfterMs ?? (state.lastAttemptAt ? pollIntervalMs : QUOTA_FAST_RETRY_MS);
|
|
248
277
|
state.lastAttemptAt = lastAttemptAt;
|
|
249
278
|
state.nextPollAt = now + retryDelayMs;
|
|
279
|
+
if (candidate.provider === "claude" && retryAfterMs !== undefined) {
|
|
280
|
+
this.logFailure(candidate, error, retryAfterMs);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
250
283
|
await this.relay.reportProviderQuota({
|
|
251
284
|
provider: candidate.provider,
|
|
252
285
|
accountKey: candidate.accountKey,
|
package/src/relay.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { OrchestratorConfig } from "./config";
|
|
|
2
2
|
import type { ProviderProbeCache } from "./provider-probe";
|
|
3
3
|
import { detectSelfSupervision } from "./self-supervision";
|
|
4
4
|
import { GIT_SHA, ORCHESTRATOR_PROTOCOL_VERSION, VERSION, runtimeMetadata } from "./version";
|
|
5
|
-
import type { AgentLifecycle, ProviderQuotaLeaseAcquireInput, ProviderQuotaLeaseAcquireResult, ProviderQuotaUpdateInput, WorkspaceMetadata, WorkspaceMode, ManagedSessionExitDiagnostics as SdkManagedSessionExitDiagnostics } from "agent-relay-sdk";
|
|
5
|
+
import type { AgentLifecycle, ProviderQuotaConfigMap, ProviderQuotaLeaseAcquireInput, ProviderQuotaLeaseAcquireResult, ProviderQuotaUpdateInput, WorkspaceMetadata, WorkspaceMode, ManagedSessionExitDiagnostics as SdkManagedSessionExitDiagnostics } from "agent-relay-sdk";
|
|
6
6
|
import { ReconnectionManager, RelayHttpClient } from "agent-relay-sdk";
|
|
7
7
|
|
|
8
8
|
export interface RelayClient {
|
|
@@ -14,6 +14,7 @@ export interface RelayClient {
|
|
|
14
14
|
acquireProviderQuotaLease(orchestratorId: string, input: ProviderQuotaLeaseAcquireInput): Promise<ProviderQuotaLeaseAcquireResult>;
|
|
15
15
|
releaseProviderQuotaLease(orchestratorId: string, input: ProviderQuotaLeaseAcquireInput & { leaseToken: string }): Promise<{ released: boolean }>;
|
|
16
16
|
reportProviderQuota(input: ProviderQuotaUpdateInput): Promise<unknown>;
|
|
17
|
+
getProviderQuotaConfig(): Promise<ProviderQuotaConfigMap>;
|
|
17
18
|
setApiUrl(url: string): void;
|
|
18
19
|
startHeartbeatLoop(): void;
|
|
19
20
|
stopHeartbeatLoop(): void;
|
|
@@ -251,6 +252,9 @@ export function createRelayClient(config: OrchestratorConfig, probeCache: Provid
|
|
|
251
252
|
reportProviderQuota(input: ProviderQuotaUpdateInput): Promise<unknown> {
|
|
252
253
|
return http.upsertProviderQuota(input);
|
|
253
254
|
},
|
|
255
|
+
getProviderQuotaConfig(): Promise<ProviderQuotaConfigMap> {
|
|
256
|
+
return http.getProviderQuotaConfig();
|
|
257
|
+
},
|
|
254
258
|
get connected() { return connected; },
|
|
255
259
|
};
|
|
256
260
|
}
|