Files
agentmon/hooks/opencode/plugin.ts
T
2026-03-20 11:17:26 -07:00

123 lines
3.6 KiB
TypeScript

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" },
}));
}
},
};
};