Files
hermes-agent/ui-tui/src/lib/petPolling.ts
T
2026-08-15 00:32:53 -07:00

74 lines
1.7 KiB
TypeScript

import type { GatewayClient } from '../gatewayClient.js'
import { asRpcResult } from './rpc.js'
export interface PetMetaResult {
enabled?: boolean
scale?: number
slug?: string
spritesheetRevision?: string
}
interface PetUpdate<TCells> {
cells: TCells | null
meta: PetMetaResult
}
type PetGateway = Pick<GatewayClient, 'request'>
/**
* Suppress overlapping cosmetic polls so a slow gateway can never accumulate
* a queue of pet requests. Returning false tells callers that an existing
* probe is still in flight.
*/
export function createPetSingleFlight() {
let active = false
return async (operation: () => Promise<void>): Promise<boolean> => {
if (active) {
return false
}
active = true
try {
await operation()
return true
} finally {
active = false
}
}
}
/**
* Probe cheap pet metadata on the gateway reader thread, then request the
* expensive frame payload only when the active selection/state is not cached.
* This deliberately bypasses the transcript-logging RPC wrapper: pet display
* is cosmetic, so an unavailable gateway must not print an error.
*/
export async function requestPetUpdate<TCells>(
gateway: PetGateway,
state: string,
graphics: boolean,
needsCells: (meta: PetMetaResult) => boolean
): Promise<PetUpdate<TCells> | null> {
try {
const meta = asRpcResult(await gateway.request('pet.info.meta')) as PetMetaResult | null
if (!meta) {
return null
}
if (!meta.enabled || !needsCells(meta)) {
return { cells: null, meta }
}
const cells = asRpcResult(await gateway.request('pet.cells', { graphics, state })) as TCells | null
return { cells, meta }
} catch {
return null
}
}