feat: add diagnostics events and otel exporter
This commit is contained in:
28
src/infra/diagnostic-events.test.ts
Normal file
28
src/infra/diagnostic-events.test.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import {
|
||||
emitDiagnosticEvent,
|
||||
onDiagnosticEvent,
|
||||
resetDiagnosticEventsForTest,
|
||||
} from "./diagnostic-events.js";
|
||||
|
||||
describe("diagnostic-events", () => {
|
||||
test("emits monotonic seq", async () => {
|
||||
resetDiagnosticEventsForTest();
|
||||
const seqs: number[] = [];
|
||||
const stop = onDiagnosticEvent((evt) => seqs.push(evt.seq));
|
||||
|
||||
emitDiagnosticEvent({
|
||||
type: "model.usage",
|
||||
usage: { total: 1 },
|
||||
});
|
||||
emitDiagnosticEvent({
|
||||
type: "model.usage",
|
||||
usage: { total: 2 },
|
||||
});
|
||||
|
||||
stop();
|
||||
|
||||
expect(seqs).toEqual([1, 2]);
|
||||
});
|
||||
});
|
||||
60
src/infra/diagnostic-events.ts
Normal file
60
src/infra/diagnostic-events.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
|
||||
export type DiagnosticUsageEvent = {
|
||||
type: "model.usage";
|
||||
ts: number;
|
||||
seq: number;
|
||||
sessionKey?: string;
|
||||
sessionId?: string;
|
||||
channel?: string;
|
||||
provider?: string;
|
||||
model?: string;
|
||||
usage: {
|
||||
input?: number;
|
||||
output?: number;
|
||||
cacheRead?: number;
|
||||
cacheWrite?: number;
|
||||
promptTokens?: number;
|
||||
total?: number;
|
||||
};
|
||||
context?: {
|
||||
limit?: number;
|
||||
used?: number;
|
||||
};
|
||||
costUsd?: number;
|
||||
durationMs?: number;
|
||||
};
|
||||
|
||||
export type DiagnosticEventPayload = DiagnosticUsageEvent;
|
||||
|
||||
let seq = 0;
|
||||
const listeners = new Set<(evt: DiagnosticEventPayload) => void>();
|
||||
|
||||
export function isDiagnosticsEnabled(config?: ClawdbotConfig): boolean {
|
||||
return config?.diagnostics?.enabled === true;
|
||||
}
|
||||
|
||||
export function emitDiagnosticEvent(event: Omit<DiagnosticEventPayload, "seq" | "ts">) {
|
||||
const enriched: DiagnosticEventPayload = {
|
||||
...event,
|
||||
seq: (seq += 1),
|
||||
ts: Date.now(),
|
||||
};
|
||||
for (const listener of listeners) {
|
||||
try {
|
||||
listener(enriched);
|
||||
} catch {
|
||||
// Ignore listener failures.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function onDiagnosticEvent(listener: (evt: DiagnosticEventPayload) => void): () => void {
|
||||
listeners.add(listener);
|
||||
return () => listeners.delete(listener);
|
||||
}
|
||||
|
||||
export function resetDiagnosticEventsForTest(): void {
|
||||
seq = 0;
|
||||
listeners.clear();
|
||||
}
|
||||
Reference in New Issue
Block a user