import { realpath } from "node:fs/promises"; import type { EventStore } from "../events/event-store.js"; import type { IndexedSkill, SkillIndex } from "./skill-catalog.js"; export type SkillEventInferenceOptions = { sessionId: string; runId: string; eventStore: EventStore; skillIndex: SkillIndex; }; export class SkillEventInference { readonly #sessionId: string; readonly #runId: string; readonly #eventStore: EventStore; readonly #skillIndex: SkillIndex; readonly #pending = new Map(); public constructor(options: SkillEventInferenceOptions) { this.#sessionId = options.sessionId; this.#runId = options.runId; this.#eventStore = options.eventStore; this.#skillIndex = options.skillIndex; } public async onReadRequested(input: { toolCallId: string; path: string }): Promise { let canonicalFilePath: string; try { canonicalFilePath = await realpath(input.path); } catch { return; } const skill = this.#skillIndex.find(canonicalFilePath); if (skill === undefined) return; this.#pending.set(input.toolCallId, skill); this.#eventStore.append(this.#sessionId, { type: "skill.requested", runId: this.#runId, payload: { name: skill.name, source: skill.source }, }); } public onReadCompleted(input: { toolCallId: string; success: boolean }): void { const skill = this.#pending.get(input.toolCallId); this.#pending.delete(input.toolCallId); if (skill === undefined || !input.success) return; this.#eventStore.append(this.#sessionId, { type: "skill.loaded", runId: this.#runId, payload: { name: skill.name, source: skill.source }, }); } }