import type { BusEvent, JobsResponse, LimitsResponse, LimitWindow, } from "./api/types"; import { panelCache } from "./panel/cache"; import { browserHost } from "./panel/host"; import { ConversationIndex } from "./panel/index.svelte"; import { session } from "./session.svelte"; const TAPE_SIZE = 120; const QUIET = new Set(["stream", "hello"]); const JOBS_KEY = "jobs"; const cache = panelCache("beaver.admin"); // Gateway-wide live state for the admin: the conversation index, the last // non-chatty events as a tape, and the quota windows updated the moment // the SDK reports them. class Gateway extends ConversationIndex { tape = $state([]); limits = $state(null); // The scheduler snapshot: what was last seen, shown at once, then refreshed. jobs = $state(cache.get(JOBS_KEY) ?? null); constructor() { super(() => session.client, browserHost().cache ?? null); } async refreshJobs(): Promise { const client = this.client(); if (!client) { return; } this.jobs = await client.jobs(); cache.set(JOBS_KEY, this.jobs); } override async load(): Promise { const client = this.client(); if (!client) { return; } const [, limits] = await Promise.all([ super.load(), client.limits().catch(() => null), ]); if (limits) { this.limits = limits; } this.refreshJobs().catch(() => undefined); } async refreshLimits(): Promise { const client = this.client(); if (client) { this.limits = await client.limits(); } } override apply(event: BusEvent): void { if (!QUIET.has(event.type)) { this.tape.unshift(event); if (this.tape.length > TAPE_SIZE) { this.tape.length = TAPE_SIZE; } } if (event.type === "rate_limit") { this.applyLimit(event); return; } if (event.type.startsWith("job.") || event.type.startsWith("schedule.")) { this.refreshJobs().catch(() => undefined); } super.apply(event); } private applyLimit(event: BusEvent): void { if (!this.limits || typeof event.window !== "string") { this.refreshLimits().catch(() => undefined); return; } const current = this.limits.windows.find((w) => w.window === event.window); const patch = { overage_status: event.overage_status ?? null, resets_at: event.resets_at, status: event.status, ts: event.ts, utilization: event.utilization, } as Partial; if (current) { Object.assign(current, patch); } else { this.refreshLimits().catch(() => undefined); } } } export const gateway = new Gateway();