@timmeck/brain-core 2.36.37 → 2.36.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/llm/middleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Structured LLM Output — Typed ContentBlocks statt nur `{ text: string }`
3
+ *
4
+ * Inspiriert von LangChain's structured output parsing.
5
+ * Ermöglicht es, LLM-Antworten in typisierte Blöcke zu zerlegen:
6
+ * Text, Reasoning, Tool Calls, Citations, JSON.
7
+ */
8
+ export interface TextBlock {
9
+ type: 'text';
10
+ content: string;
11
+ }
12
+ export interface ReasoningBlock {
13
+ type: 'reasoning';
14
+ content: string;
15
+ }
16
+ export interface ToolCallBlock {
17
+ type: 'tool_call';
18
+ toolName: string;
19
+ args: Record<string, unknown>;
20
+ }
21
+ export interface CitationBlock {
22
+ type: 'citation';
23
+ source: string;
24
+ quote: string;
25
+ }
26
+ export interface JsonBlock {
27
+ type: 'json';
28
+ data: unknown;
29
+ }
30
+ export type ContentBlock = TextBlock | ReasoningBlock | ToolCallBlock | CitationBlock | JsonBlock;
31
+ export interface StructuredLLMResponse {
32
+ /** Full raw text (for backwards-compat) */
33
+ text: string;
34
+ /** Parsed content blocks */
35
+ blocks: ContentBlock[];
36
+ /** Token usage */
37
+ tokensUsed: number;
38
+ inputTokens: number;
39
+ outputTokens: number;
40
+ cached: boolean;
41
+ model: string;
42
+ durationMs: number;
43
+ provider: string;
44
+ }
45
+ /**
46
+ * Parse raw LLM output into structured ContentBlocks.
47
+ *
48
+ * Erkennt:
49
+ * - <thinking>...</thinking> → ReasoningBlock
50
+ * - <tool_call>{"name":"...","args":{...}}</tool_call> → ToolCallBlock
51
+ * - <citation source="...">...</citation> → CitationBlock
52
+ * - ```json ... ``` → JsonBlock
53
+ * - Alles andere → TextBlock
54
+ */
55
+ export declare function parseStructuredOutput(raw: string): ContentBlock[];
56
+ /**
57
+ * Extrahiert JSON aus einer LLM-Antwort.
58
+ * Versucht zuerst die gesamte Antwort als JSON zu parsen,
59
+ * dann sucht es nach ```json Code-Blöcken.
60
+ */
61
+ export declare function extractJson<T = unknown>(raw: string): T | null;
62
+ /**
63
+ * Validate parsed JSON against a simple schema (key presence check).
64
+ * Returns the data if valid, null otherwise.
65
+ */
66
+ export declare function validateJsonSchema<T = unknown>(data: unknown, requiredKeys: string[]): T | null;
67
+ /** Get all blocks of a specific type */
68
+ export declare function getBlocks<T extends ContentBlock['type']>(blocks: ContentBlock[], type: T): Extract<ContentBlock, {
69
+ type: T;
70
+ }>[];
71
+ /** Get combined text from all TextBlocks */
72
+ export declare function getTextContent(blocks: ContentBlock[]): string;
73
+ /** Get all tool calls */
74
+ export declare function getToolCalls(blocks: ContentBlock[]): ToolCallBlock[];
75
+ /** Check if response contains reasoning */
76
+ export declare function hasReasoning(blocks: ContentBlock[]): boolean;
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Structured LLM Output — Typed ContentBlocks statt nur `{ text: string }`
3
+ *
4
+ * Inspiriert von LangChain's structured output parsing.
5
+ * Ermöglicht es, LLM-Antworten in typisierte Blöcke zu zerlegen:
6
+ * Text, Reasoning, Tool Calls, Citations, JSON.
7
+ */
8
+ // ── Parsing ─────────────────────────────────────────────
9
+ /**
10
+ * Parse raw LLM output into structured ContentBlocks.
11
+ *
12
+ * Erkennt:
13
+ * - <thinking>...</thinking> → ReasoningBlock
14
+ * - <tool_call>{"name":"...","args":{...}}</tool_call> → ToolCallBlock
15
+ * - <citation source="...">...</citation> → CitationBlock
16
+ * - ```json ... ``` → JsonBlock
17
+ * - Alles andere → TextBlock
18
+ */
19
+ export function parseStructuredOutput(raw) {
20
+ if (!raw || raw.trim().length === 0)
21
+ return [];
22
+ const blocks = [];
23
+ let remaining = raw;
24
+ // Regex patterns for structured sections
25
+ const patterns = [
26
+ {
27
+ // <thinking>...</thinking>
28
+ regex: /<thinking>([\s\S]*?)<\/thinking>/,
29
+ handler: (m) => ({ type: 'reasoning', content: m[1].trim() }),
30
+ },
31
+ {
32
+ // <tool_call>{"name":"x","args":{...}}</tool_call>
33
+ regex: /<tool_call>([\s\S]*?)<\/tool_call>/,
34
+ handler: (m) => {
35
+ try {
36
+ const parsed = JSON.parse(m[1].trim());
37
+ return {
38
+ type: 'tool_call',
39
+ toolName: parsed.name ?? parsed.tool ?? 'unknown',
40
+ args: parsed.args ?? parsed.arguments ?? parsed.input ?? {},
41
+ };
42
+ }
43
+ catch {
44
+ return { type: 'text', content: m[0] };
45
+ }
46
+ },
47
+ },
48
+ {
49
+ // <citation source="...">...</citation>
50
+ regex: /<citation\s+source="([^"]*)">([\s\S]*?)<\/citation>/,
51
+ handler: (m) => ({ type: 'citation', source: m[1], quote: m[2].trim() }),
52
+ },
53
+ {
54
+ // ```json ... ```
55
+ regex: /```json\s*\n([\s\S]*?)\n```/,
56
+ handler: (m) => {
57
+ try {
58
+ return { type: 'json', data: JSON.parse(m[1].trim()) };
59
+ }
60
+ catch {
61
+ return { type: 'text', content: m[0] };
62
+ }
63
+ },
64
+ },
65
+ ];
66
+ while (remaining.length > 0) {
67
+ // Find the earliest matching pattern
68
+ let earliest = null;
69
+ for (const p of patterns) {
70
+ const match = remaining.match(p.regex);
71
+ if (match && match.index !== undefined) {
72
+ if (!earliest || match.index < earliest.index) {
73
+ earliest = { index: match.index, match, handler: p.handler };
74
+ }
75
+ }
76
+ }
77
+ if (!earliest) {
78
+ // No more patterns found — rest is text
79
+ const trimmed = remaining.trim();
80
+ if (trimmed.length > 0) {
81
+ blocks.push({ type: 'text', content: trimmed });
82
+ }
83
+ break;
84
+ }
85
+ // Text before the match
86
+ const before = remaining.slice(0, earliest.index).trim();
87
+ if (before.length > 0) {
88
+ blocks.push({ type: 'text', content: before });
89
+ }
90
+ // The matched block
91
+ blocks.push(earliest.handler(earliest.match));
92
+ // Continue after the match
93
+ remaining = remaining.slice(earliest.index + earliest.match[0].length);
94
+ }
95
+ return blocks;
96
+ }
97
+ // ── JSON Mode Helper ────────────────────────────────────
98
+ /**
99
+ * Extrahiert JSON aus einer LLM-Antwort.
100
+ * Versucht zuerst die gesamte Antwort als JSON zu parsen,
101
+ * dann sucht es nach ```json Code-Blöcken.
102
+ */
103
+ export function extractJson(raw) {
104
+ // Try full string
105
+ try {
106
+ return JSON.parse(raw.trim());
107
+ }
108
+ catch {
109
+ // Try code block
110
+ const match = raw.match(/```(?:json)?\s*\n([\s\S]*?)\n```/);
111
+ if (match) {
112
+ try {
113
+ return JSON.parse(match[1].trim());
114
+ }
115
+ catch {
116
+ return null;
117
+ }
118
+ }
119
+ return null;
120
+ }
121
+ }
122
+ /**
123
+ * Validate parsed JSON against a simple schema (key presence check).
124
+ * Returns the data if valid, null otherwise.
125
+ */
126
+ export function validateJsonSchema(data, requiredKeys) {
127
+ if (!data || typeof data !== 'object')
128
+ return null;
129
+ const obj = data;
130
+ for (const key of requiredKeys) {
131
+ if (!(key in obj))
132
+ return null;
133
+ }
134
+ return data;
135
+ }
136
+ // ── Block Helpers ────────────────────────────────────────
137
+ /** Get all blocks of a specific type */
138
+ export function getBlocks(blocks, type) {
139
+ return blocks.filter((b) => b.type === type);
140
+ }
141
+ /** Get combined text from all TextBlocks */
142
+ export function getTextContent(blocks) {
143
+ return getBlocks(blocks, 'text').map(b => b.content).join('\n\n');
144
+ }
145
+ /** Get all tool calls */
146
+ export function getToolCalls(blocks) {
147
+ return getBlocks(blocks, 'tool_call');
148
+ }
149
+ /** Check if response contains reasoning */
150
+ export function hasReasoning(blocks) {
151
+ return blocks.some(b => b.type === 'reasoning');
152
+ }
153
+ //# sourceMappingURL=structured-output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structured-output.js","sourceRoot":"","sources":["../../src/llm/structured-output.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { TraceCollector, runTraceMigration } from './trace-collector.js';
2
+ export type { Trace, Span, TraceTree, TraceStats, TraceListOptions, TraceCollectorStatus, } from './trace-collector.js';
@@ -0,0 +1,2 @@
1
+ export { TraceCollector, runTraceMigration } from './trace-collector.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/observability/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Trace Collector — Observability für das Brain Ecosystem
3
+ *
4
+ * Inspiriert von OpenTelemetry / LangSmith Tracing.
5
+ * Erfasst Spans (Arbeitseinheiten) in hierarchischen Traces,
6
+ * speichert sie in SQLite und berechnet Latenz-Statistiken.
7
+ *
8
+ * Usage:
9
+ * ```typescript
10
+ * const traceId = collector.startTrace('research-cycle');
11
+ * const spanId = collector.startSpan(traceId, 'llm-call', { template: 'explain' });
12
+ * // ... do work ...
13
+ * collector.endSpan(spanId, { tokens: 150, cost: 0.001 });
14
+ * collector.endTrace(traceId);
15
+ * ```
16
+ */
17
+ import type Database from 'better-sqlite3';
18
+ export interface Trace {
19
+ id: string;
20
+ name: string;
21
+ status: 'running' | 'completed' | 'error';
22
+ startedAt: number;
23
+ endedAt: number | null;
24
+ durationMs: number | null;
25
+ spanCount: number;
26
+ totalTokens: number;
27
+ totalCost: number;
28
+ metadata: Record<string, unknown>;
29
+ error?: string;
30
+ }
31
+ export interface Span {
32
+ id: string;
33
+ traceId: string;
34
+ parentSpanId: string | null;
35
+ name: string;
36
+ status: 'running' | 'completed' | 'error';
37
+ startedAt: number;
38
+ endedAt: number | null;
39
+ durationMs: number | null;
40
+ tokens: number;
41
+ cost: number;
42
+ metadata: Record<string, unknown>;
43
+ error?: string;
44
+ }
45
+ export interface TraceTree {
46
+ trace: Trace;
47
+ spans: Span[];
48
+ }
49
+ export interface TraceStats {
50
+ totalTraces: number;
51
+ totalSpans: number;
52
+ totalTokens: number;
53
+ totalCost: number;
54
+ avgDurationMs: number;
55
+ p50DurationMs: number;
56
+ p99DurationMs: number;
57
+ tracesByName: Record<string, number>;
58
+ activeTraces: number;
59
+ }
60
+ export interface TraceListOptions {
61
+ limit?: number;
62
+ offset?: number;
63
+ name?: string;
64
+ status?: 'running' | 'completed' | 'error';
65
+ since?: number;
66
+ }
67
+ export interface TraceCollectorStatus {
68
+ totalTraces: number;
69
+ activeTraces: number;
70
+ totalSpans: number;
71
+ totalTokens: number;
72
+ totalCost: number;
73
+ }
74
+ export declare function runTraceMigration(db: Database.Database): void;
75
+ export declare class TraceCollector {
76
+ private db;
77
+ private readonly log;
78
+ private stmtInsertTrace;
79
+ private stmtInsertSpan;
80
+ private stmtEndTrace;
81
+ private stmtEndSpan;
82
+ private stmtUpdateTraceStats;
83
+ constructor(db: Database.Database);
84
+ /** Start a new trace. Returns trace ID. */
85
+ startTrace(name: string, metadata?: Record<string, unknown>): string;
86
+ /** End a trace (complete or error). */
87
+ endTrace(traceId: string, error?: string): void;
88
+ /** Start a span within a trace. Returns span ID. */
89
+ startSpan(traceId: string, name: string, options?: {
90
+ parentSpanId?: string;
91
+ metadata?: Record<string, unknown>;
92
+ }): string;
93
+ /** End a span with optional results. */
94
+ endSpan(spanId: string, result?: {
95
+ tokens?: number;
96
+ cost?: number;
97
+ error?: string;
98
+ }): void;
99
+ /** Get a single trace with all its spans. */
100
+ getTrace(traceId: string): TraceTree | null;
101
+ /** List traces with optional filters. */
102
+ listTraces(options?: TraceListOptions): Trace[];
103
+ /** Get aggregate statistics. */
104
+ getStats(): TraceStats;
105
+ /** Get collector status (lightweight). */
106
+ getStatus(): TraceCollectorStatus;
107
+ /** Delete traces older than maxAgeDays. Returns number pruned. */
108
+ prune(maxAgeDays?: number): number;
109
+ private mapTrace;
110
+ private mapSpan;
111
+ }
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Trace Collector — Observability für das Brain Ecosystem
3
+ *
4
+ * Inspiriert von OpenTelemetry / LangSmith Tracing.
5
+ * Erfasst Spans (Arbeitseinheiten) in hierarchischen Traces,
6
+ * speichert sie in SQLite und berechnet Latenz-Statistiken.
7
+ *
8
+ * Usage:
9
+ * ```typescript
10
+ * const traceId = collector.startTrace('research-cycle');
11
+ * const spanId = collector.startSpan(traceId, 'llm-call', { template: 'explain' });
12
+ * // ... do work ...
13
+ * collector.endSpan(spanId, { tokens: 150, cost: 0.001 });
14
+ * collector.endTrace(traceId);
15
+ * ```
16
+ */
17
+ import { randomUUID } from 'node:crypto';
18
+ import { getLogger } from '../utils/logger.js';
19
+ // ── Migration ───────────────────────────────────────────
20
+ export function runTraceMigration(db) {
21
+ db.exec(`
22
+ CREATE TABLE IF NOT EXISTS workflow_traces (
23
+ id TEXT PRIMARY KEY,
24
+ name TEXT NOT NULL,
25
+ status TEXT NOT NULL DEFAULT 'running',
26
+ started_at INTEGER NOT NULL,
27
+ ended_at INTEGER,
28
+ duration_ms INTEGER,
29
+ span_count INTEGER NOT NULL DEFAULT 0,
30
+ total_tokens INTEGER NOT NULL DEFAULT 0,
31
+ total_cost REAL NOT NULL DEFAULT 0,
32
+ metadata TEXT DEFAULT '{}',
33
+ error TEXT,
34
+ created_at TEXT DEFAULT (datetime('now'))
35
+ );
36
+ CREATE INDEX IF NOT EXISTS idx_traces_name ON workflow_traces(name);
37
+ CREATE INDEX IF NOT EXISTS idx_traces_status ON workflow_traces(status);
38
+ CREATE INDEX IF NOT EXISTS idx_traces_started ON workflow_traces(started_at);
39
+
40
+ CREATE TABLE IF NOT EXISTS trace_spans (
41
+ id TEXT PRIMARY KEY,
42
+ trace_id TEXT NOT NULL,
43
+ parent_span_id TEXT,
44
+ name TEXT NOT NULL,
45
+ status TEXT NOT NULL DEFAULT 'running',
46
+ started_at INTEGER NOT NULL,
47
+ ended_at INTEGER,
48
+ duration_ms INTEGER,
49
+ tokens INTEGER NOT NULL DEFAULT 0,
50
+ cost REAL NOT NULL DEFAULT 0,
51
+ metadata TEXT DEFAULT '{}',
52
+ error TEXT,
53
+ FOREIGN KEY (trace_id) REFERENCES workflow_traces(id) ON DELETE CASCADE
54
+ );
55
+ CREATE INDEX IF NOT EXISTS idx_spans_trace ON trace_spans(trace_id);
56
+ CREATE INDEX IF NOT EXISTS idx_spans_parent ON trace_spans(parent_span_id);
57
+ `);
58
+ }
59
+ // ── Collector ───────────────────────────────────────────
60
+ export class TraceCollector {
61
+ db;
62
+ log = getLogger();
63
+ stmtInsertTrace;
64
+ stmtInsertSpan;
65
+ stmtEndTrace;
66
+ stmtEndSpan;
67
+ stmtUpdateTraceStats;
68
+ constructor(db) {
69
+ this.db = db;
70
+ runTraceMigration(db);
71
+ this.stmtInsertTrace = db.prepare(`INSERT INTO workflow_traces (id, name, status, started_at, metadata)
72
+ VALUES (?, ?, 'running', ?, ?)`);
73
+ this.stmtInsertSpan = db.prepare(`INSERT INTO trace_spans (id, trace_id, parent_span_id, name, status, started_at, metadata)
74
+ VALUES (?, ?, ?, ?, 'running', ?, ?)`);
75
+ this.stmtEndTrace = db.prepare(`UPDATE workflow_traces SET status = ?, ended_at = ?, duration_ms = ?, error = ?
76
+ WHERE id = ?`);
77
+ this.stmtEndSpan = db.prepare(`UPDATE trace_spans SET status = ?, ended_at = ?, duration_ms = ?, tokens = ?, cost = ?, error = ?
78
+ WHERE id = ?`);
79
+ this.stmtUpdateTraceStats = db.prepare(`UPDATE workflow_traces SET
80
+ span_count = (SELECT COUNT(*) FROM trace_spans WHERE trace_id = ?),
81
+ total_tokens = (SELECT COALESCE(SUM(tokens), 0) FROM trace_spans WHERE trace_id = ?),
82
+ total_cost = (SELECT COALESCE(SUM(cost), 0) FROM trace_spans WHERE trace_id = ?)
83
+ WHERE id = ?`);
84
+ this.log.debug('[TraceCollector] Initialized');
85
+ }
86
+ // ── Trace Lifecycle ─────────────────────────────────
87
+ /** Start a new trace. Returns trace ID. */
88
+ startTrace(name, metadata = {}) {
89
+ const id = randomUUID();
90
+ const now = Date.now();
91
+ try {
92
+ this.stmtInsertTrace.run(id, name, now, JSON.stringify(metadata));
93
+ }
94
+ catch (e) {
95
+ this.log.warn(`[TraceCollector] Failed to start trace: ${e.message}`);
96
+ }
97
+ return id;
98
+ }
99
+ /** End a trace (complete or error). */
100
+ endTrace(traceId, error) {
101
+ const now = Date.now();
102
+ try {
103
+ const trace = this.db.prepare('SELECT started_at FROM workflow_traces WHERE id = ?').get(traceId);
104
+ const duration = trace ? now - trace.started_at : 0;
105
+ const status = error ? 'error' : 'completed';
106
+ // Update aggregate stats from spans
107
+ this.stmtUpdateTraceStats.run(traceId, traceId, traceId, traceId);
108
+ this.stmtEndTrace.run(status, now, duration, error ?? null, traceId);
109
+ }
110
+ catch (e) {
111
+ this.log.warn(`[TraceCollector] Failed to end trace: ${e.message}`);
112
+ }
113
+ }
114
+ // ── Span Lifecycle ──────────────────────────────────
115
+ /** Start a span within a trace. Returns span ID. */
116
+ startSpan(traceId, name, options) {
117
+ const id = randomUUID();
118
+ const now = Date.now();
119
+ try {
120
+ this.stmtInsertSpan.run(id, traceId, options?.parentSpanId ?? null, name, now, JSON.stringify(options?.metadata ?? {}));
121
+ }
122
+ catch (e) {
123
+ this.log.warn(`[TraceCollector] Failed to start span: ${e.message}`);
124
+ }
125
+ return id;
126
+ }
127
+ /** End a span with optional results. */
128
+ endSpan(spanId, result) {
129
+ const now = Date.now();
130
+ try {
131
+ const span = this.db.prepare('SELECT started_at FROM trace_spans WHERE id = ?').get(spanId);
132
+ const duration = span ? now - span.started_at : 0;
133
+ const status = result?.error ? 'error' : 'completed';
134
+ this.stmtEndSpan.run(status, now, duration, result?.tokens ?? 0, result?.cost ?? 0, result?.error ?? null, spanId);
135
+ }
136
+ catch (e) {
137
+ this.log.warn(`[TraceCollector] Failed to end span: ${e.message}`);
138
+ }
139
+ }
140
+ // ── Queries ─────────────────────────────────────────
141
+ /** Get a single trace with all its spans. */
142
+ getTrace(traceId) {
143
+ try {
144
+ const row = this.db.prepare('SELECT * FROM workflow_traces WHERE id = ?').get(traceId);
145
+ if (!row)
146
+ return null;
147
+ const spans = this.db.prepare('SELECT * FROM trace_spans WHERE trace_id = ? ORDER BY started_at').all(traceId);
148
+ return {
149
+ trace: this.mapTrace(row),
150
+ spans: spans.map(s => this.mapSpan(s)),
151
+ };
152
+ }
153
+ catch {
154
+ return null;
155
+ }
156
+ }
157
+ /** List traces with optional filters. */
158
+ listTraces(options = {}) {
159
+ const { limit = 50, offset = 0, name, status, since } = options;
160
+ try {
161
+ const conditions = [];
162
+ const params = [];
163
+ if (name) {
164
+ conditions.push('name = ?');
165
+ params.push(name);
166
+ }
167
+ if (status) {
168
+ conditions.push('status = ?');
169
+ params.push(status);
170
+ }
171
+ if (since) {
172
+ conditions.push('started_at >= ?');
173
+ params.push(since);
174
+ }
175
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
176
+ const sql = `SELECT * FROM workflow_traces ${where} ORDER BY started_at DESC LIMIT ? OFFSET ?`;
177
+ params.push(limit, offset);
178
+ const rows = this.db.prepare(sql).all(...params);
179
+ return rows.map(r => this.mapTrace(r));
180
+ }
181
+ catch {
182
+ return [];
183
+ }
184
+ }
185
+ /** Get aggregate statistics. */
186
+ getStats() {
187
+ try {
188
+ const totals = this.db.prepare(`
189
+ SELECT
190
+ COUNT(*) as total_traces,
191
+ COALESCE(SUM(span_count), 0) as total_spans,
192
+ COALESCE(SUM(total_tokens), 0) as total_tokens,
193
+ COALESCE(SUM(total_cost), 0) as total_cost,
194
+ COALESCE(AVG(duration_ms), 0) as avg_duration
195
+ FROM workflow_traces
196
+ WHERE status != 'running'
197
+ `).get();
198
+ const active = this.db.prepare("SELECT COUNT(*) as count FROM workflow_traces WHERE status = 'running'").get();
199
+ // P50 / P99 from completed traces
200
+ const durations = this.db.prepare("SELECT duration_ms FROM workflow_traces WHERE status = 'completed' AND duration_ms IS NOT NULL ORDER BY duration_ms").all();
201
+ const p50 = durations.length > 0
202
+ ? durations[Math.floor(durations.length * 0.5)]?.duration_ms ?? 0
203
+ : 0;
204
+ const p99 = durations.length > 0
205
+ ? durations[Math.floor(durations.length * 0.99)]?.duration_ms ?? 0
206
+ : 0;
207
+ // Traces by name
208
+ const byName = this.db.prepare('SELECT name, COUNT(*) as count FROM workflow_traces GROUP BY name').all();
209
+ const tracesByName = {};
210
+ for (const row of byName) {
211
+ tracesByName[row.name] = row.count;
212
+ }
213
+ return {
214
+ totalTraces: totals.total_traces ?? 0,
215
+ totalSpans: totals.total_spans ?? 0,
216
+ totalTokens: totals.total_tokens ?? 0,
217
+ totalCost: totals.total_cost ?? 0,
218
+ avgDurationMs: Math.round(totals.avg_duration ?? 0),
219
+ p50DurationMs: p50,
220
+ p99DurationMs: p99,
221
+ tracesByName,
222
+ activeTraces: active.count,
223
+ };
224
+ }
225
+ catch {
226
+ return {
227
+ totalTraces: 0, totalSpans: 0, totalTokens: 0, totalCost: 0,
228
+ avgDurationMs: 0, p50DurationMs: 0, p99DurationMs: 0,
229
+ tracesByName: {}, activeTraces: 0,
230
+ };
231
+ }
232
+ }
233
+ /** Get collector status (lightweight). */
234
+ getStatus() {
235
+ try {
236
+ const row = this.db.prepare(`
237
+ SELECT
238
+ COUNT(*) as total,
239
+ SUM(CASE WHEN status = 'running' THEN 1 ELSE 0 END) as active,
240
+ COALESCE(SUM(span_count), 0) as spans,
241
+ COALESCE(SUM(total_tokens), 0) as tokens,
242
+ COALESCE(SUM(total_cost), 0) as cost
243
+ FROM workflow_traces
244
+ `).get();
245
+ return {
246
+ totalTraces: row.total ?? 0,
247
+ activeTraces: row.active ?? 0,
248
+ totalSpans: row.spans ?? 0,
249
+ totalTokens: row.tokens ?? 0,
250
+ totalCost: row.cost ?? 0,
251
+ };
252
+ }
253
+ catch {
254
+ return { totalTraces: 0, activeTraces: 0, totalSpans: 0, totalTokens: 0, totalCost: 0 };
255
+ }
256
+ }
257
+ /** Delete traces older than maxAgeDays. Returns number pruned. */
258
+ prune(maxAgeDays = 30) {
259
+ try {
260
+ const cutoff = Date.now() - maxAgeDays * 86_400_000;
261
+ // Delete spans first (FK constraint)
262
+ this.db.prepare('DELETE FROM trace_spans WHERE trace_id IN (SELECT id FROM workflow_traces WHERE started_at < ? AND status != ?)').run(cutoff, 'running');
263
+ const result = this.db.prepare('DELETE FROM workflow_traces WHERE started_at < ? AND status != ?').run(cutoff, 'running');
264
+ return result.changes;
265
+ }
266
+ catch {
267
+ return 0;
268
+ }
269
+ }
270
+ // ── Private ─────────────────────────────────────────
271
+ mapTrace(row) {
272
+ return {
273
+ id: row.id,
274
+ name: row.name,
275
+ status: row.status,
276
+ startedAt: row.started_at,
277
+ endedAt: row.ended_at,
278
+ durationMs: row.duration_ms,
279
+ spanCount: row.span_count,
280
+ totalTokens: row.total_tokens,
281
+ totalCost: row.total_cost,
282
+ metadata: JSON.parse(row.metadata || '{}'),
283
+ error: row.error,
284
+ };
285
+ }
286
+ mapSpan(row) {
287
+ return {
288
+ id: row.id,
289
+ traceId: row.trace_id,
290
+ parentSpanId: row.parent_span_id,
291
+ name: row.name,
292
+ status: row.status,
293
+ startedAt: row.started_at,
294
+ endedAt: row.ended_at,
295
+ durationMs: row.duration_ms,
296
+ tokens: row.tokens,
297
+ cost: row.cost,
298
+ metadata: JSON.parse(row.metadata || '{}'),
299
+ error: row.error,
300
+ };
301
+ }
302
+ }
303
+ //# sourceMappingURL=trace-collector.js.map