Files
stack/apps/orchestrator/src/api/agents/agent-events.service.ts

71 lines
1.8 KiB
TypeScript

import { Injectable, Logger, OnModuleInit } from "@nestjs/common";
import { randomUUID } from "crypto";
import { ValkeyService } from "../../valkey/valkey.service";
import type { EventHandler, OrchestratorEvent } from "../../valkey/types";
type UnsubscribeFn = () => void;
@Injectable()
export class AgentEventsService implements OnModuleInit {
private readonly logger = new Logger(AgentEventsService.name);
private readonly subscribers = new Map<string, EventHandler>();
private connected = false;
constructor(private readonly valkeyService: ValkeyService) {}
async onModuleInit(): Promise<void> {
if (this.connected) return;
await this.valkeyService.subscribeToEvents(
(event) => {
this.subscribers.forEach((handler) => {
void handler(event);
});
},
(error, _raw, channel) => {
this.logger.warn(`Event stream parse/validation warning on ${channel}: ${error.message}`);
}
);
this.connected = true;
this.logger.log("Agent event stream subscription active");
}
subscribe(handler: EventHandler): UnsubscribeFn {
const id = randomUUID();
this.subscribers.set(id, handler);
return () => {
this.subscribers.delete(id);
};
}
async getInitialSnapshot(): Promise<{
type: "stream.snapshot";
timestamp: string;
agents: number;
tasks: number;
}> {
const [agents, tasks] = await Promise.all([
this.valkeyService.listAgents(),
this.valkeyService.listTasks(),
]);
return {
type: "stream.snapshot",
timestamp: new Date().toISOString(),
agents: agents.length,
tasks: tasks.length,
};
}
createHeartbeat(): OrchestratorEvent {
return {
type: "task.processing",
timestamp: new Date().toISOString(),
data: {
heartbeat: true,
},
};
}
}