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 CHANGED
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "Agent Relay API",
5
- "version": "0.88.1",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay-server",
3
- "version": "0.88.1",
3
+ "version": "0.88.2",
4
4
  "description": "Lightweight HTTP message relay for inter-agent communication across machines",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -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 quotaWindowSampleTimes(provider: string, accountKey: string, fallback?: QuotaState): Map<string, number> {
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: QuotaState | undefined, incoming: QuotaState, provider: string, accountKey: string): {
216
- quota: QuotaState;
260
+ function mergeQuotaWindows(existing: StoredQuotaState | undefined, incoming: QuotaState, provider: string, accountKey: string): {
261
+ quota: StoredQuotaState;
217
262
  acceptedIncomingWindow: boolean;
218
263
  } {
219
- if (!existing) return { quota: incoming, acceptedIncomingWindow: true };
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 (incoming.updatedAt < existingUpdatedAt) return window;
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: parseJson<QuotaState>(row.quota_state, { windows: [], source: "probe", updatedAt: row.captured_at }),
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 ? parseJson<QuotaState>(row.quota_state, { windows: [], source: "probe", updatedAt: lastUpdatedAt ?? row.last_attempt_at }) : undefined;
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 ? parseJson<QuotaState>(existing.quota_state, { windows: [], source: "probe", updatedAt: existing.last_updated_at ?? 0 }) : undefined;
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
- if (input.quota && latestSnapshotFingerprint(provider, accountKey) !== quotaFingerprint(input.quota)) {
360
- recordProviderQuotaSnapshot({ provider, accountKey, quota: input.quota, sourceAgentId: input.sourceAgentId }, now);
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,