feat(companion): add waitForAnyEvent runtime helper

This commit is contained in:
William Valentin
2026-02-16 19:27:25 -08:00
parent f7c6947d22
commit 717e5d60e5
5 changed files with 156 additions and 1 deletions
+74
View File
@@ -44,6 +44,10 @@ export interface CompanionRuntimeClientOptions {
export type CompanionEventHandler = (event: string, data: unknown) => void;
export type CompanionTypedEventHandler<TData = unknown> = (data: TData) => void;
export type CompanionEventPredicate<TData = unknown> = (data: TData) => boolean;
export type CompanionEventEnvelope<TData = unknown> = {
event: string;
data: TData;
};
export const COMPANION_EVENT_NAMES = {
agentStream: 'agent.stream',
@@ -475,6 +479,76 @@ export class CompanionRuntimeClient {
});
}
waitForAnyEvent<TData = unknown>(
eventNames: readonly string[],
options?: {
timeoutMs?: number;
predicate?: (event: string, data: TData) => boolean;
signal?: AbortSignal;
},
): Promise<CompanionEventEnvelope<TData>> {
if (eventNames.length === 0) {
throw new Error('eventNames must contain at least one event name');
}
const eventNameSet = new Set(eventNames);
const timeoutMs = options?.timeoutMs ?? this.requestTimeoutMs;
const predicate = options?.predicate;
const signal = options?.signal;
return new Promise<CompanionEventEnvelope<TData>>((resolve, reject) => {
let settled = false;
let abortCleanup: (() => void) | null = null;
const finish = (fn: () => void) => {
if (settled) {
return;
}
settled = true;
clearTimeout(timeout);
unsubscribe();
if (abortCleanup) {
abortCleanup();
abortCleanup = null;
}
this.pendingEventWaits.delete(cancelWait);
fn();
};
const cancelWait = (error: Error) => {
finish(() => reject(error));
};
this.pendingEventWaits.add(cancelWait);
const unsubscribe = this.subscribeEvents((event, data) => {
if (!eventNameSet.has(event)) {
return;
}
const castData = data as TData;
if (predicate && !predicate(event, castData)) {
return;
}
finish(() => resolve({ event, data: castData }));
});
const timeout = setTimeout(() => {
cancelWait(new Error(`Timed out waiting for any event in [${eventNames.join(', ')}]`));
}, timeoutMs);
if (signal) {
const onAbort = () => {
cancelWait(new Error(`Aborted while waiting for events [${eventNames.join(', ')}]`));
};
signal.addEventListener('abort', onAbort, { once: true });
abortCleanup = () => {
signal.removeEventListener('abort', onAbort);
};
if (signal.aborted) {
onAbort();
}
}
});
}
waitForAgentStream<TData = unknown>(options?: {
timeoutMs?: number;
predicate?: CompanionEventPredicate<TData>;