import { Plugin } from "@opencode-ai/plugin"; const INGEST_URL = process.env.AGENTMON_INGEST_URL || "http://localhost:8080"; let buffer: any[] = []; let flushTimer: NodeJS.Timeout | null = null; const BATCH_SIZE = 10; const FLUSH_MS = 2000; let currentSessionId: string | undefined; let currentRunId: string | undefined; function enqueue(event: any) { buffer.push(event); if (buffer.length >= BATCH_SIZE) { flush(); } else if (!flushTimer) { flushTimer = setTimeout(flush, FLUSH_MS); } } async function flush() { if (flushTimer) { clearTimeout(flushTimer); flushTimer = null; } if (buffer.length === 0) return; const batch = buffer.splice(0, BATCH_SIZE); try { await fetch(`${INGEST_URL}/v1/events`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(batch), }); } catch (e) { console.debug("[agentmon] failed to flush events"); } } function buildEnvelope(type: string, data: any) { return { schema: { name: "agentmon.event", version: 1 }, event: { id: crypto.randomUUID(), type, ts: new Date().toISOString(), source: { framework: "opencode", client_id: "opencode", host: require("os").hostname(), }, }, ...(data.correlation && { correlation: data.correlation }), ...(data.payload && { payload: data.payload }), ...(data.attributes && { attributes: data.attributes }), }; } export const agentmon: Plugin = async (input) => { return { "tool.execute.before": async (data: any) => { if (!currentSessionId) { currentSessionId = crypto.randomUUID(); currentRunId = crypto.randomUUID(); enqueue(buildEnvelope("session.start", { correlation: { session_id: currentSessionId }, })); enqueue(buildEnvelope("run.start", { correlation: { session_id: currentSessionId, run_id: currentRunId }, })); } const spanId = crypto.randomUUID(); enqueue(buildEnvelope("span.start", { correlation: { session_id: currentSessionId, run_id: currentRunId, span_id: spanId }, attributes: { span_kind: "tool", name: data.tool }, payload: { input: (JSON.stringify(data.input) ?? "").substring(0, 200) }, })); }, "tool.execute.after": async (data: any) => { if (!currentSessionId || !currentRunId) return; enqueue(buildEnvelope("span.end", { correlation: { session_id: currentSessionId, run_id: currentRunId }, attributes: { span_kind: "tool", name: data.tool }, payload: { status: data.error ? "error" : "success", result_preview: data.result ? JSON.stringify(data.result).substring(0, 500) : undefined, duration_ms: data.duration, }, })); }, "chat.message": async (data: any) => { if (!currentSessionId) { currentSessionId = data.sessionID || crypto.randomUUID(); currentRunId = crypto.randomUUID(); enqueue(buildEnvelope("session.start", { correlation: { session_id: currentSessionId }, })); enqueue(buildEnvelope("run.start", { correlation: { session_id: currentSessionId, run_id: currentRunId }, })); } }, "experimental.session.compacting": async (data: any) => { if (currentSessionId && currentRunId) { enqueue(buildEnvelope("span.start", { correlation: { session_id: currentSessionId, run_id: currentRunId }, attributes: { span_kind: "internal", name: "context_compaction" }, })); } }, }; };