agent-relay-orchestrator 0.77.0 → 0.78.0
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/index.ts +4 -0
- package/src/quota-poller.ts +318 -0
- package/src/relay.ts +13 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-relay-orchestrator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.78.0",
|
|
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.53"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/bun": "latest",
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { recoverManagedAgents } from "./recovery";
|
|
|
9
9
|
import { ProviderProbeCache } from "./provider-probe";
|
|
10
10
|
import { sweepEmptyWorkspaceContainers, workspacesRoot } from "./workspace-probe";
|
|
11
11
|
import { startOrchestratorMaintenanceScheduler } from "./maintenance";
|
|
12
|
+
import { OrchestratorQuotaPoller } from "./quota-poller";
|
|
12
13
|
|
|
13
14
|
const args = process.argv.slice(2);
|
|
14
15
|
|
|
@@ -50,6 +51,7 @@ const config = loadConfig();
|
|
|
50
51
|
const probeCache = new ProviderProbeCache(config);
|
|
51
52
|
const relay = createRelayClient(config, probeCache);
|
|
52
53
|
const control = createControlHandler(config, relay);
|
|
54
|
+
const quotaPoller = new OrchestratorQuotaPoller(config, relay);
|
|
53
55
|
|
|
54
56
|
const POLL_INTERVAL_MS = 3_000;
|
|
55
57
|
const REGISTER_RETRY_MS = 5_000;
|
|
@@ -81,6 +83,7 @@ async function startup(): Promise<void> {
|
|
|
81
83
|
|
|
82
84
|
// Host-local maintenance must run where the agent processes and tmux sockets live.
|
|
83
85
|
startOrchestratorMaintenanceScheduler();
|
|
86
|
+
quotaPoller.start();
|
|
84
87
|
|
|
85
88
|
// Sweep empty workspace container dirs left behind by prior cleanups (#280).
|
|
86
89
|
const swept = sweepEmptyWorkspaceContainers(workspacesRoot(config.baseDir));
|
|
@@ -207,6 +210,7 @@ async function shutdown(): Promise<void> {
|
|
|
207
210
|
if (healthCheckTimer) clearInterval(healthCheckTimer);
|
|
208
211
|
if (guestReaperTimer) clearInterval(guestReaperTimer);
|
|
209
212
|
if (apiServer) apiServer.stop();
|
|
213
|
+
quotaPoller.stop();
|
|
210
214
|
relay.stopHeartbeatLoop();
|
|
211
215
|
process.exit(0);
|
|
212
216
|
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
QUOTA_FAILURE_LOG_INTERVAL_MS,
|
|
5
|
+
QUOTA_FAST_RETRY_MS,
|
|
6
|
+
QUOTA_POLL_INTERVAL_MS,
|
|
7
|
+
QuotaCollectionError,
|
|
8
|
+
collectClaudeQuotaSample,
|
|
9
|
+
collectCodexQuotaSample,
|
|
10
|
+
providerQuotaErrorFromCollectorError,
|
|
11
|
+
quotaRetryAfterMs,
|
|
12
|
+
resolveClaudeQuotaIdentity,
|
|
13
|
+
resolveCodexQuotaIdentityFromHome,
|
|
14
|
+
type ProviderQuotaIdentity,
|
|
15
|
+
type ProviderQuotaSample,
|
|
16
|
+
} from "agent-relay-sdk/provider-quota";
|
|
17
|
+
import type { ProviderQuotaLeaseAcquireInput, ProviderQuotaUpdateInput } from "agent-relay-sdk";
|
|
18
|
+
import { errMessage } from "agent-relay-sdk";
|
|
19
|
+
import type { OrchestratorConfig } from "./config";
|
|
20
|
+
|
|
21
|
+
const QUOTA_LEASE_TTL_MS = 90_000;
|
|
22
|
+
const QUOTA_LEASE_RENEW_MS = 30_000;
|
|
23
|
+
|
|
24
|
+
type QuotaRelay = {
|
|
25
|
+
acquireProviderQuotaLease(orchestratorId: string, input: ProviderQuotaLeaseAcquireInput): Promise<{
|
|
26
|
+
acquired: boolean;
|
|
27
|
+
lease?: { leaseToken: string; expiresAt: number };
|
|
28
|
+
retryAfterMs?: number;
|
|
29
|
+
}>;
|
|
30
|
+
releaseProviderQuotaLease(orchestratorId: string, input: ProviderQuotaLeaseAcquireInput & { leaseToken: string }): Promise<unknown>;
|
|
31
|
+
reportProviderQuota(input: ProviderQuotaUpdateInput): Promise<unknown>;
|
|
32
|
+
connected: boolean;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type QuotaCandidate = ProviderQuotaIdentity & {
|
|
36
|
+
appServerUrl?: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type QuotaPollState = {
|
|
40
|
+
leaseToken?: string;
|
|
41
|
+
leaseExpiresAt?: number;
|
|
42
|
+
nextPollAt?: number;
|
|
43
|
+
lastAttemptAt?: number;
|
|
44
|
+
lastLog?: { key: string; at: number };
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export class OrchestratorQuotaPoller {
|
|
48
|
+
private timer?: Timer;
|
|
49
|
+
private active = false;
|
|
50
|
+
private inFlight = false;
|
|
51
|
+
private readonly states = new Map<string, QuotaPollState>();
|
|
52
|
+
|
|
53
|
+
constructor(
|
|
54
|
+
private readonly config: OrchestratorConfig,
|
|
55
|
+
private readonly relay: QuotaRelay,
|
|
56
|
+
private readonly options: {
|
|
57
|
+
intervalMs?: number;
|
|
58
|
+
fetchImpl?: typeof fetch;
|
|
59
|
+
now?: () => number;
|
|
60
|
+
log?: (message: string) => void;
|
|
61
|
+
codexRateLimitsRead?: (appServerUrl: string) => Promise<unknown>;
|
|
62
|
+
} = {},
|
|
63
|
+
) {}
|
|
64
|
+
|
|
65
|
+
start(): void {
|
|
66
|
+
if (this.active) return;
|
|
67
|
+
this.active = true;
|
|
68
|
+
this.schedule(1_000);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
stop(): void {
|
|
72
|
+
this.active = false;
|
|
73
|
+
if (this.timer) clearTimeout(this.timer);
|
|
74
|
+
this.timer = undefined;
|
|
75
|
+
for (const [key, state] of this.states) {
|
|
76
|
+
if (!state.leaseToken) continue;
|
|
77
|
+
const [provider, accountKey] = splitStateKey(key);
|
|
78
|
+
void this.relay.releaseProviderQuotaLease(this.config.id, { provider, accountKey, leaseToken: state.leaseToken }).catch(() => {});
|
|
79
|
+
}
|
|
80
|
+
this.states.clear();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async tick(): Promise<void> {
|
|
84
|
+
if (this.inFlight || !this.active || !this.relay.connected) return;
|
|
85
|
+
this.inFlight = true;
|
|
86
|
+
try {
|
|
87
|
+
const candidates = await this.discoverCandidates();
|
|
88
|
+
await this.releaseRemovedCandidates(candidates);
|
|
89
|
+
for (const candidate of candidates) {
|
|
90
|
+
await this.processCandidate(candidate);
|
|
91
|
+
}
|
|
92
|
+
} finally {
|
|
93
|
+
this.inFlight = false;
|
|
94
|
+
this.schedule(this.options.intervalMs ?? QUOTA_LEASE_RENEW_MS);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private schedule(delayMs: number): void {
|
|
99
|
+
if (this.timer) clearTimeout(this.timer);
|
|
100
|
+
this.timer = undefined;
|
|
101
|
+
if (!this.active) return;
|
|
102
|
+
this.timer = setTimeout(() => {
|
|
103
|
+
this.timer = undefined;
|
|
104
|
+
void this.tick();
|
|
105
|
+
}, Math.max(1_000, delayMs));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private async discoverCandidates(): Promise<QuotaCandidate[]> {
|
|
109
|
+
const candidates: QuotaCandidate[] = [];
|
|
110
|
+
if (this.config.providers.includes("claude")) {
|
|
111
|
+
const configDir = this.config.env.CLAUDE_CONFIG_DIR || process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude");
|
|
112
|
+
candidates.push(await resolveClaudeQuotaIdentity({ configDir, host: this.config.hostname }));
|
|
113
|
+
}
|
|
114
|
+
if (this.config.providers.includes("codex")) {
|
|
115
|
+
const codexHome = this.config.env.CODEX_HOME || process.env.CODEX_HOME || join(homedir(), ".codex");
|
|
116
|
+
candidates.push({
|
|
117
|
+
...(await resolveCodexQuotaIdentityFromHome({ codexHome, host: this.config.hostname })),
|
|
118
|
+
appServerUrl: this.config.env.CODEX_APP_SERVER_URL || process.env.CODEX_APP_SERVER_URL,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return candidates;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private async releaseRemovedCandidates(candidates: QuotaCandidate[]): Promise<void> {
|
|
125
|
+
const live = new Set(candidates.map(candidateStateKey));
|
|
126
|
+
for (const [key, state] of this.states) {
|
|
127
|
+
if (live.has(key)) continue;
|
|
128
|
+
this.states.delete(key);
|
|
129
|
+
if (!state.leaseToken) continue;
|
|
130
|
+
const [provider, accountKey] = splitStateKey(key);
|
|
131
|
+
await this.relay.releaseProviderQuotaLease(this.config.id, { provider, accountKey, leaseToken: state.leaseToken }).catch(() => {});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private async processCandidate(candidate: QuotaCandidate): Promise<void> {
|
|
136
|
+
const state = this.stateFor(candidate);
|
|
137
|
+
const now = this.now();
|
|
138
|
+
if (!await this.ensureLease(candidate, state, now)) return;
|
|
139
|
+
if (state.nextPollAt !== undefined && state.nextPollAt > now) return;
|
|
140
|
+
|
|
141
|
+
const lastAttemptAt = now;
|
|
142
|
+
try {
|
|
143
|
+
const sample = await this.collect(candidate);
|
|
144
|
+
const quota = sample.quota;
|
|
145
|
+
if (!quota) throw new QuotaCollectionError("creds_not_ready", `${candidate.provider} quota source is not ready`);
|
|
146
|
+
const update: ProviderQuotaUpdateInput = {
|
|
147
|
+
provider: candidate.provider,
|
|
148
|
+
accountKey: sample.accountKey ?? candidate.accountKey,
|
|
149
|
+
quota,
|
|
150
|
+
lastAttemptAt: quota.updatedAt,
|
|
151
|
+
sourceAgentId: this.sourceAgentId(),
|
|
152
|
+
};
|
|
153
|
+
await this.relay.reportProviderQuota(update);
|
|
154
|
+
state.lastAttemptAt = update.lastAttemptAt;
|
|
155
|
+
state.nextPollAt = now + QUOTA_POLL_INTERVAL_MS;
|
|
156
|
+
} catch (error) {
|
|
157
|
+
const retryAfterMs = quotaRetryAfterMs(error);
|
|
158
|
+
const lastError = providerQuotaErrorFromCollectorError(error, retryAfterMs);
|
|
159
|
+
const retryDelayMs = retryAfterMs ?? (state.lastAttemptAt ? QUOTA_POLL_INTERVAL_MS : QUOTA_FAST_RETRY_MS);
|
|
160
|
+
state.lastAttemptAt = lastAttemptAt;
|
|
161
|
+
state.nextPollAt = now + retryDelayMs;
|
|
162
|
+
await this.relay.reportProviderQuota({
|
|
163
|
+
provider: candidate.provider,
|
|
164
|
+
accountKey: candidate.accountKey,
|
|
165
|
+
lastAttemptAt,
|
|
166
|
+
lastError,
|
|
167
|
+
sourceAgentId: this.sourceAgentId(),
|
|
168
|
+
}).catch((publishError) => this.log(`quota status publish failed: ${errMessage(publishError)}`));
|
|
169
|
+
this.logFailure(candidate, error, retryAfterMs);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private async ensureLease(candidate: QuotaCandidate, state: QuotaPollState, now: number): Promise<boolean> {
|
|
174
|
+
if (state.leaseToken && state.leaseExpiresAt && state.leaseExpiresAt - now > QUOTA_LEASE_RENEW_MS) return true;
|
|
175
|
+
const result = await this.relay.acquireProviderQuotaLease(this.config.id, {
|
|
176
|
+
provider: candidate.provider,
|
|
177
|
+
accountKey: candidate.accountKey,
|
|
178
|
+
...(state.leaseToken ? { leaseToken: state.leaseToken } : {}),
|
|
179
|
+
ttlMs: QUOTA_LEASE_TTL_MS,
|
|
180
|
+
});
|
|
181
|
+
if (!result.acquired || !result.lease) {
|
|
182
|
+
state.leaseToken = undefined;
|
|
183
|
+
state.leaseExpiresAt = undefined;
|
|
184
|
+
state.nextPollAt = now + Math.min(result.retryAfterMs ?? QUOTA_LEASE_RENEW_MS, QUOTA_LEASE_RENEW_MS);
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
state.leaseToken = result.lease.leaseToken;
|
|
188
|
+
state.leaseExpiresAt = result.lease.expiresAt;
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private async collect(candidate: QuotaCandidate): Promise<ProviderQuotaSample> {
|
|
193
|
+
if (candidate.provider === "claude") {
|
|
194
|
+
return collectClaudeQuotaSample({
|
|
195
|
+
agentId: this.sourceAgentId(),
|
|
196
|
+
credentialsPath: candidate.credentialsPath,
|
|
197
|
+
fetchImpl: this.options.fetchImpl,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if (candidate.provider === "codex") {
|
|
201
|
+
if (!candidate.appServerUrl) throw new QuotaCollectionError("creds_not_ready", "Codex app-server URL is not configured");
|
|
202
|
+
return collectCodexQuotaSample({
|
|
203
|
+
agentId: this.sourceAgentId(),
|
|
204
|
+
rateLimitsRead: () => this.options.codexRateLimitsRead
|
|
205
|
+
? this.options.codexRateLimitsRead(candidate.appServerUrl!)
|
|
206
|
+
: codexRateLimitsRead(candidate.appServerUrl!),
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
return {};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private stateFor(candidate: QuotaCandidate): QuotaPollState {
|
|
213
|
+
const key = candidateStateKey(candidate);
|
|
214
|
+
const existing = this.states.get(key);
|
|
215
|
+
if (existing) return existing;
|
|
216
|
+
const created: QuotaPollState = {};
|
|
217
|
+
this.states.set(key, created);
|
|
218
|
+
return created;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private sourceAgentId(): string {
|
|
222
|
+
return `orchestrator-${this.config.id}`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private now(): number {
|
|
226
|
+
return this.options.now?.() ?? Date.now();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private log(message: string): void {
|
|
230
|
+
(this.options.log ?? ((line) => console.error(`[orchestrator] ${line}`)))(message);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private logFailure(candidate: QuotaCandidate, error: unknown, retryAfterMs: number | undefined): void {
|
|
234
|
+
const state = this.stateFor(candidate);
|
|
235
|
+
const key = retryAfterMs !== undefined ? `retry-after:${retryAfterMs}` : errMessage(error);
|
|
236
|
+
const now = this.now();
|
|
237
|
+
if (state.lastLog?.key === key && now - state.lastLog.at < QUOTA_FAILURE_LOG_INTERVAL_MS) return;
|
|
238
|
+
state.lastLog = { key, at: now };
|
|
239
|
+
const suffix = retryAfterMs !== undefined ? `; retrying in ${Math.round(retryAfterMs / 1000)}s` : "";
|
|
240
|
+
this.log(`quota refresh failed for ${candidate.provider}/${candidate.accountKey}${suffix}: ${errMessage(error)}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function codexRateLimitsRead(appServerUrl: string): Promise<unknown> {
|
|
245
|
+
const client = new JsonRpcWebSocket(appServerUrl);
|
|
246
|
+
try {
|
|
247
|
+
await client.connect();
|
|
248
|
+
await client.request("initialize", {
|
|
249
|
+
clientInfo: { name: "agent-relay-orchestrator", title: "Agent Relay Orchestrator" },
|
|
250
|
+
capabilities: { experimentalApi: true },
|
|
251
|
+
}).catch(() => undefined);
|
|
252
|
+
return await client.request("account/rateLimits/read");
|
|
253
|
+
} finally {
|
|
254
|
+
client.close();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
class JsonRpcWebSocket {
|
|
259
|
+
private ws!: WebSocket;
|
|
260
|
+
private nextId = 1;
|
|
261
|
+
private pending = new Map<number, { resolve: (value: unknown) => void; reject: (error: unknown) => void }>();
|
|
262
|
+
|
|
263
|
+
constructor(private readonly url: string) {}
|
|
264
|
+
|
|
265
|
+
async connect(): Promise<void> {
|
|
266
|
+
await new Promise<void>((resolve, reject) => {
|
|
267
|
+
const ws = new WebSocket(this.url);
|
|
268
|
+
this.ws = ws;
|
|
269
|
+
ws.onopen = () => resolve();
|
|
270
|
+
ws.onerror = () => reject(new Error("websocket error"));
|
|
271
|
+
ws.onclose = (event) => {
|
|
272
|
+
const error = new Error(`websocket closed code=${event.code} reason=${event.reason || "(none)"}`);
|
|
273
|
+
for (const pending of this.pending.values()) pending.reject(error);
|
|
274
|
+
this.pending.clear();
|
|
275
|
+
};
|
|
276
|
+
ws.onmessage = (event) => this.handleMessage(String(event.data));
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
request(method: string, params?: unknown): Promise<unknown> {
|
|
281
|
+
const id = this.nextId++;
|
|
282
|
+
const promise = new Promise<unknown>((resolve, reject) => {
|
|
283
|
+
this.pending.set(id, { resolve, reject });
|
|
284
|
+
});
|
|
285
|
+
this.ws.send(JSON.stringify({ id, method, params }));
|
|
286
|
+
return promise;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
close(): void {
|
|
290
|
+
this.ws?.close();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private handleMessage(raw: string): void {
|
|
294
|
+
let message: unknown;
|
|
295
|
+
try {
|
|
296
|
+
message = JSON.parse(raw) as unknown;
|
|
297
|
+
} catch {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (!message || typeof message !== "object" || !("id" in message)) return;
|
|
301
|
+
const id = Number((message as { id: unknown }).id);
|
|
302
|
+
const pending = this.pending.get(id);
|
|
303
|
+
if (!pending) return;
|
|
304
|
+
this.pending.delete(id);
|
|
305
|
+
const response = message as { result?: unknown; error?: { message?: string; code?: number } };
|
|
306
|
+
if (response.error) pending.reject(new Error(`${response.error.message ?? "JSON-RPC error"} (${response.error.code ?? "unknown"})`));
|
|
307
|
+
else pending.resolve(response.result);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function candidateStateKey(candidate: Pick<QuotaCandidate, "provider" | "accountKey">): string {
|
|
312
|
+
return `${candidate.provider}\0${candidate.accountKey}`;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function splitStateKey(key: string): [string, string] {
|
|
316
|
+
const [provider, accountKey] = key.split("\0");
|
|
317
|
+
return [provider ?? "", accountKey ?? ""];
|
|
318
|
+
}
|
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, WorkspaceMetadata, WorkspaceMode, ManagedSessionExitDiagnostics as SdkManagedSessionExitDiagnostics } from "agent-relay-sdk";
|
|
5
|
+
import type { AgentLifecycle, 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 {
|
|
@@ -11,6 +11,9 @@ export interface RelayClient {
|
|
|
11
11
|
updateManagedAgents(agents: ManagedAgentReport[], exitedAgents?: ManagedSessionExitDiagnostics[]): Promise<void>;
|
|
12
12
|
pollCommands(): Promise<RelayCommand[]>;
|
|
13
13
|
updateCommand(commandId: string, status: string, result?: Record<string, unknown>, error?: string): Promise<boolean>;
|
|
14
|
+
acquireProviderQuotaLease(orchestratorId: string, input: ProviderQuotaLeaseAcquireInput): Promise<ProviderQuotaLeaseAcquireResult>;
|
|
15
|
+
releaseProviderQuotaLease(orchestratorId: string, input: ProviderQuotaLeaseAcquireInput & { leaseToken: string }): Promise<{ released: boolean }>;
|
|
16
|
+
reportProviderQuota(input: ProviderQuotaUpdateInput): Promise<unknown>;
|
|
14
17
|
setApiUrl(url: string): void;
|
|
15
18
|
startHeartbeatLoop(): void;
|
|
16
19
|
stopHeartbeatLoop(): void;
|
|
@@ -239,6 +242,15 @@ export function createRelayClient(config: OrchestratorConfig, probeCache: Provid
|
|
|
239
242
|
return null;
|
|
240
243
|
}
|
|
241
244
|
},
|
|
245
|
+
acquireProviderQuotaLease(orchestratorId: string, input: ProviderQuotaLeaseAcquireInput): Promise<ProviderQuotaLeaseAcquireResult> {
|
|
246
|
+
return http.acquireProviderQuotaLease(orchestratorId, input);
|
|
247
|
+
},
|
|
248
|
+
releaseProviderQuotaLease(orchestratorId: string, input: ProviderQuotaLeaseAcquireInput & { leaseToken: string }): Promise<{ released: boolean }> {
|
|
249
|
+
return http.releaseProviderQuotaLease(orchestratorId, input);
|
|
250
|
+
},
|
|
251
|
+
reportProviderQuota(input: ProviderQuotaUpdateInput): Promise<unknown> {
|
|
252
|
+
return http.upsertProviderQuota(input);
|
|
253
|
+
},
|
|
242
254
|
get connected() { return connected; },
|
|
243
255
|
};
|
|
244
256
|
}
|