agent-relay-server 0.88.1 → 0.88.2
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/docs/openapi.json +1 -1
- package/package.json +1 -1
- package/src/db/provider-quotas.ts +71 -13
package/docs/openapi.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"openapi": "3.1.0",
|
|
3
3
|
"info": {
|
|
4
4
|
"title": "Agent Relay API",
|
|
5
|
-
"version": "0.88.
|
|
5
|
+
"version": "0.88.2",
|
|
6
6
|
"description": "Real-time message bus for inter-agent communication. Agent-first: this spec is designed for machine consumption — agents can self-discover the full API surface via GET /api/spec.",
|
|
7
7
|
"license": {
|
|
8
8
|
"name": "MIT",
|
package/package.json
CHANGED
|
@@ -45,6 +45,10 @@ type ProviderQuotaSnapshotRow = {
|
|
|
45
45
|
last_error?: string | null;
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
+
type StoredQuotaState = QuotaState & {
|
|
49
|
+
_windowUpdatedAt?: Record<string, number>;
|
|
50
|
+
};
|
|
51
|
+
|
|
48
52
|
function cleanKey(value: string): string {
|
|
49
53
|
return value.trim();
|
|
50
54
|
}
|
|
@@ -189,8 +193,48 @@ function quotaWindowKey(window: QuotaState["windows"][number]): string {
|
|
|
189
193
|
return `${window.name}:${window.unit}`;
|
|
190
194
|
}
|
|
191
195
|
|
|
192
|
-
function
|
|
196
|
+
function publicQuotaState(quota: StoredQuotaState): QuotaState {
|
|
197
|
+
return {
|
|
198
|
+
windows: quota.windows.map((window) => ({ ...window })),
|
|
199
|
+
source: quota.source,
|
|
200
|
+
updatedAt: quota.updatedAt,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function parseStoredQuotaState(value: string, fallback: QuotaState): StoredQuotaState {
|
|
205
|
+
const quota = parseJson<StoredQuotaState>(value, fallback);
|
|
206
|
+
return {
|
|
207
|
+
...publicQuotaState(quota),
|
|
208
|
+
...(quota._windowUpdatedAt && typeof quota._windowUpdatedAt === "object" ? { _windowUpdatedAt: quota._windowUpdatedAt } : {}),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function storedQuotaWindowSampleTimes(quota: StoredQuotaState): Map<string, number> {
|
|
193
213
|
const times = new Map<string, number>();
|
|
214
|
+
const rawTimes = quota._windowUpdatedAt && typeof quota._windowUpdatedAt === "object" ? quota._windowUpdatedAt : {};
|
|
215
|
+
for (const window of quota.windows) {
|
|
216
|
+
const key = quotaWindowKey(window);
|
|
217
|
+
const value = rawTimes[key];
|
|
218
|
+
if (typeof value === "number" && Number.isFinite(value)) times.set(key, value);
|
|
219
|
+
}
|
|
220
|
+
return times;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function quotaWithWindowSampleTimes(quota: QuotaState, times: Map<string, number>): StoredQuotaState {
|
|
224
|
+
const windowUpdatedAt: Record<string, number> = {};
|
|
225
|
+
for (const window of quota.windows) {
|
|
226
|
+
const timestamp = times.get(quotaWindowKey(window));
|
|
227
|
+
if (timestamp !== undefined) windowUpdatedAt[quotaWindowKey(window)] = timestamp;
|
|
228
|
+
}
|
|
229
|
+
return Object.keys(windowUpdatedAt).length > 0
|
|
230
|
+
? { ...quota, _windowUpdatedAt: windowUpdatedAt }
|
|
231
|
+
: quota;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function quotaWindowSampleTimes(provider: string, accountKey: string, fallback?: StoredQuotaState): Map<string, number> {
|
|
235
|
+
const times = new Map<string, number>();
|
|
236
|
+
const storedTimes = fallback ? storedQuotaWindowSampleTimes(fallback) : new Map<string, number>();
|
|
237
|
+
for (const [key, timestamp] of storedTimes) times.set(key, timestamp);
|
|
194
238
|
const rows = getDb().query(`
|
|
195
239
|
SELECT quota_state FROM quota_snapshots
|
|
196
240
|
WHERE provider = ? AND account_key = ? AND unavailable = 0
|
|
@@ -200,6 +244,7 @@ function quotaWindowSampleTimes(provider: string, accountKey: string, fallback?:
|
|
|
200
244
|
const quota = parseJson<QuotaState>(row.quota_state, { windows: [], source: "probe", updatedAt: 0 });
|
|
201
245
|
for (const window of quota.windows) {
|
|
202
246
|
const key = quotaWindowKey(window);
|
|
247
|
+
if (storedTimes.has(key)) continue;
|
|
203
248
|
times.set(key, Math.max(times.get(key) ?? 0, quota.updatedAt));
|
|
204
249
|
}
|
|
205
250
|
}
|
|
@@ -212,25 +257,36 @@ function quotaWindowSampleTimes(provider: string, accountKey: string, fallback?:
|
|
|
212
257
|
return times;
|
|
213
258
|
}
|
|
214
259
|
|
|
215
|
-
function mergeQuotaWindows(existing:
|
|
216
|
-
quota:
|
|
260
|
+
function mergeQuotaWindows(existing: StoredQuotaState | undefined, incoming: QuotaState, provider: string, accountKey: string): {
|
|
261
|
+
quota: StoredQuotaState;
|
|
217
262
|
acceptedIncomingWindow: boolean;
|
|
218
263
|
} {
|
|
219
|
-
if (!existing)
|
|
264
|
+
if (!existing) {
|
|
265
|
+
const times = new Map(incoming.windows.map((window) => [quotaWindowKey(window), incoming.updatedAt]));
|
|
266
|
+
return { quota: quotaWithWindowSampleTimes(incoming, times), acceptedIncomingWindow: true };
|
|
267
|
+
}
|
|
220
268
|
const existingTimes = quotaWindowSampleTimes(provider, accountKey, existing);
|
|
221
269
|
const incomingByKey = new Map(incoming.windows.map((window) => [quotaWindowKey(window), window]));
|
|
270
|
+
const mergedTimes = new Map<string, number>();
|
|
222
271
|
let acceptedIncomingWindow = false;
|
|
223
272
|
let updatedAt = existing.updatedAt;
|
|
224
273
|
let source = existing.source;
|
|
225
274
|
const windows = existing.windows.map((window) => {
|
|
226
275
|
const key = quotaWindowKey(window);
|
|
227
276
|
const incomingWindow = incomingByKey.get(key);
|
|
228
|
-
if (!incomingWindow) return window;
|
|
229
277
|
const existingUpdatedAt = existingTimes.get(key) ?? existing.updatedAt;
|
|
230
|
-
if (
|
|
278
|
+
if (!incomingWindow) {
|
|
279
|
+
mergedTimes.set(key, existingUpdatedAt);
|
|
280
|
+
return window;
|
|
281
|
+
}
|
|
282
|
+
if (incoming.updatedAt < existingUpdatedAt) {
|
|
283
|
+
mergedTimes.set(key, existingUpdatedAt);
|
|
284
|
+
return window;
|
|
285
|
+
}
|
|
231
286
|
acceptedIncomingWindow = true;
|
|
232
287
|
updatedAt = Math.max(updatedAt, incoming.updatedAt);
|
|
233
288
|
source = incoming.source;
|
|
289
|
+
mergedTimes.set(key, incoming.updatedAt);
|
|
234
290
|
return incomingWindow;
|
|
235
291
|
});
|
|
236
292
|
const existingKeys = new Set(existing.windows.map(quotaWindowKey));
|
|
@@ -240,8 +296,9 @@ function mergeQuotaWindows(existing: QuotaState | undefined, incoming: QuotaStat
|
|
|
240
296
|
windows.push(window);
|
|
241
297
|
updatedAt = Math.max(updatedAt, incoming.updatedAt);
|
|
242
298
|
source = incoming.source;
|
|
299
|
+
mergedTimes.set(quotaWindowKey(window), incoming.updatedAt);
|
|
243
300
|
}
|
|
244
|
-
return { quota: { windows, source, updatedAt }, acceptedIncomingWindow };
|
|
301
|
+
return { quota: quotaWithWindowSampleTimes({ windows, source, updatedAt }, mergedTimes), acceptedIncomingWindow };
|
|
245
302
|
}
|
|
246
303
|
|
|
247
304
|
function rowToSnapshot(row: ProviderQuotaSnapshotRow): ProviderQuotaSnapshot {
|
|
@@ -258,14 +315,14 @@ function rowToSnapshot(row: ProviderQuotaSnapshotRow): ProviderQuotaSnapshot {
|
|
|
258
315
|
}
|
|
259
316
|
return {
|
|
260
317
|
...base,
|
|
261
|
-
quota:
|
|
318
|
+
quota: publicQuotaState(parseStoredQuotaState(row.quota_state, { windows: [], source: "probe", updatedAt: row.captured_at })),
|
|
262
319
|
};
|
|
263
320
|
}
|
|
264
321
|
|
|
265
322
|
function rowToRecord(row: ProviderQuotaRow, history: ProviderQuotaSnapshot[], now: number): ProviderQuotaRecord {
|
|
266
323
|
const lastError = parseJson<ProviderQuotaError | undefined>(row.last_error, undefined);
|
|
267
324
|
const lastUpdatedAt = row.last_updated_at ?? undefined;
|
|
268
|
-
const quota = row.quota_state ?
|
|
325
|
+
const quota = row.quota_state ? publicQuotaState(parseStoredQuotaState(row.quota_state, { windows: [], source: "probe", updatedAt: lastUpdatedAt ?? row.last_attempt_at })) : undefined;
|
|
269
326
|
return {
|
|
270
327
|
provider: row.provider,
|
|
271
328
|
accountKey: row.account_key,
|
|
@@ -322,7 +379,7 @@ export function upsertProviderQuota(input: ProviderQuotaUpdateInput, now = Date.
|
|
|
322
379
|
const existing = getDb().query("SELECT * FROM provider_quotas WHERE provider = ? AND account_key = ?").get(provider, accountKey) as ProviderQuotaRow | undefined;
|
|
323
380
|
const sourceAgentIds = normalizeSourceAgentIds(existing?.source_agent_ids);
|
|
324
381
|
if (input.sourceAgentId && !sourceAgentIds.includes(input.sourceAgentId)) sourceAgentIds.push(input.sourceAgentId);
|
|
325
|
-
const existingQuota = existing?.quota_state ?
|
|
382
|
+
const existingQuota = existing?.quota_state ? parseStoredQuotaState(existing.quota_state, { windows: [], source: "probe", updatedAt: existing.last_updated_at ?? 0 }) : undefined;
|
|
326
383
|
const mergedQuota = input.quota ? mergeQuotaWindows(existingQuota, input.quota, provider, accountKey) : undefined;
|
|
327
384
|
const lastUpdatedAt = mergedQuota ? mergedQuota.quota.updatedAt : existing?.last_updated_at ?? undefined;
|
|
328
385
|
const lastAttemptAt = input.lastAttemptAt ?? input.quota?.updatedAt ?? now;
|
|
@@ -356,8 +413,9 @@ export function upsertProviderQuota(input: ProviderQuotaUpdateInput, now = Date.
|
|
|
356
413
|
now,
|
|
357
414
|
);
|
|
358
415
|
|
|
359
|
-
|
|
360
|
-
|
|
416
|
+
const snapshotQuota = mergedQuota?.acceptedIncomingWindow ? publicQuotaState(mergedQuota.quota) : undefined;
|
|
417
|
+
if (snapshotQuota && latestSnapshotFingerprint(provider, accountKey) !== quotaFingerprint(snapshotQuota)) {
|
|
418
|
+
recordProviderQuotaSnapshot({ provider, accountKey, quota: snapshotQuota, sourceAgentId: input.sourceAgentId }, now);
|
|
361
419
|
} else if (!input.quota && input.lastError?.type === "unavailable" && !latestSnapshotIsUnavailable(provider, accountKey)) {
|
|
362
420
|
// #609 — the provider couldn't be collected (orchestrator skip, #601). Record one explicit gap
|
|
363
421
|
// marker on the transition into unavailable (deduped while it stays unavailable) so history shows
|
|
@@ -368,7 +426,7 @@ export function upsertProviderQuota(input: ProviderQuotaUpdateInput, now = Date.
|
|
|
368
426
|
const quotaAdvisoryState = providerQuotaAdvisoryHandler?.({
|
|
369
427
|
provider,
|
|
370
428
|
accountKey,
|
|
371
|
-
previousQuota: existingQuota,
|
|
429
|
+
previousQuota: existingQuota ? publicQuotaState(existingQuota) : undefined,
|
|
372
430
|
quota: input.quota,
|
|
373
431
|
sourceAgentIds,
|
|
374
432
|
previousAdvisoryState: existing?.quota_advisory_state,
|