vectra-js 0.9.3 → 0.9.5

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,184 @@
1
+ document.addEventListener('DOMContentLoaded', () => {
2
+ const urlParams = new URLSearchParams(window.location.search);
3
+ const traceId = urlParams.get('id');
4
+
5
+ if (!traceId) {
6
+ alert('No trace ID provided');
7
+ window.location.href = '/dashboard/';
8
+ return;
9
+ }
10
+
11
+ loadTraceDetails(traceId);
12
+ });
13
+
14
+ async function loadTraceDetails(traceId) {
15
+ try {
16
+ const res = await fetch(`/api/observability/traces/${traceId}`);
17
+ if (!res.ok) throw new Error('Failed to fetch trace details');
18
+
19
+ const trace = await res.json();
20
+ if (!trace || trace.length === 0) {
21
+ document.getElementById('loading').innerHTML = '<div class="text-red-500">Trace not found</div>';
22
+ return;
23
+ }
24
+
25
+ renderTrace(trace);
26
+ } catch (e) {
27
+ console.error(e);
28
+ document.getElementById('loading').innerHTML = `<div class="text-red-500">Error: ${e.message}</div>`;
29
+ }
30
+ }
31
+
32
+ function renderTrace(trace) {
33
+ const content = document.getElementById('trace-content');
34
+ const loading = document.getElementById('loading');
35
+
36
+ // Sort spans by start time
37
+ trace.sort((a, b) => a.start_time - b.start_time);
38
+
39
+ const root = trace.find(s => !s.parent_span_id) || trace[0];
40
+ const startTime = root.start_time;
41
+ const endTime = Math.max(...trace.map(s => s.end_time));
42
+ const totalDuration = endTime - startTime;
43
+
44
+ // Header Info
45
+ document.getElementById('header-trace-id').textContent = root.trace_id;
46
+ document.getElementById('trace-timestamp').textContent = new Date(root.start_time).toLocaleString();
47
+ document.getElementById('trace-duration').textContent = `${totalDuration}ms`;
48
+
49
+ const modelInfo = root.model_name ? `${root.provider}/${root.model_name}` : (root.provider || '-');
50
+ document.getElementById('trace-model').textContent = modelInfo;
51
+
52
+ const hasError = trace.some(s => s.error && s.error !== '{}');
53
+ const statusEl = document.getElementById('trace-status');
54
+ if (hasError) {
55
+ statusEl.className = 'px-3 py-1 rounded-full text-sm font-semibold bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400';
56
+ statusEl.textContent = 'Error';
57
+ } else {
58
+ statusEl.className = 'px-3 py-1 rounded-full text-sm font-semibold bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400';
59
+ statusEl.textContent = 'Success';
60
+ }
61
+
62
+ // Render Timeline
63
+ const timelineContainer = document.getElementById('timeline-container');
64
+ timelineContainer.innerHTML = '';
65
+
66
+ trace.forEach(span => {
67
+ const left = ((span.start_time - startTime) / totalDuration) * 100;
68
+ const width = Math.max(((span.end_time - span.start_time) / totalDuration) * 100, 0.5); // min width 0.5%
69
+
70
+ const row = document.createElement('div');
71
+ row.className = 'relative group h-8 flex items-center';
72
+ row.innerHTML = `
73
+ <div class="w-1/4 min-w-[150px] text-xs font-medium text-slate-600 dark:text-slate-400 truncate pr-4" title="${span.name}">
74
+ ${span.name}
75
+ </div>
76
+ <div class="flex-1 h-full relative">
77
+ <div class="absolute top-1/2 -translate-y-1/2 h-4 bg-brand-500/20 dark:bg-brand-500/10 rounded-sm border border-brand-500/40 dark:border-brand-500/20 hover:bg-brand-500/40 dark:hover:bg-brand-500/30 transition-colors cursor-pointer"
78
+ style="left: ${left}%; width: ${width}%"
79
+ onclick="scrollToSpan('${span.span_id}')">
80
+ </div>
81
+ <div class="absolute top-1/2 -translate-y-1/2 text-[10px] text-slate-400 dark:text-slate-500 ml-2 pointer-events-none" style="left: ${left + width}%">
82
+ ${span.end_time - span.start_time}ms
83
+ </div>
84
+ </div>
85
+ `;
86
+ timelineContainer.appendChild(row);
87
+ });
88
+
89
+ // Render Detailed Spans
90
+ const spansList = document.getElementById('spans-list');
91
+ spansList.innerHTML = '';
92
+
93
+ trace.forEach(span => {
94
+ const card = document.createElement('div');
95
+ card.id = `span-${span.span_id}`;
96
+ card.className = 'bg-white dark:bg-dark-900 rounded-lg border border-slate-200 dark:border-white/5 shadow-sm overflow-hidden transition-colors duration-200';
97
+
98
+ const isError = span.error && span.error !== '{}';
99
+ const borderColor = isError ? 'border-l-4 border-l-red-500' : 'border-l-4 border-l-brand-500';
100
+
101
+ card.innerHTML = `
102
+ <div class="px-6 py-4 bg-slate-50 dark:bg-white/5 border-b border-slate-100 dark:border-white/5 flex justify-between items-center ${borderColor}">
103
+ <div>
104
+ <h4 class="text-sm font-bold text-slate-900 dark:text-white font-mono">${span.name}</h4>
105
+ <div class="text-xs text-slate-500 dark:text-slate-400 mt-1 font-mono">${span.span_id}</div>
106
+ </div>
107
+ <div class="text-right">
108
+ <div class="text-sm font-semibold text-slate-700 dark:text-slate-200">${span.end_time - span.start_time}ms</div>
109
+ <div class="text-xs text-slate-400 dark:text-slate-500">${new Date(span.start_time).toLocaleTimeString()}</div>
110
+ </div>
111
+ </div>
112
+ <div class="p-6 space-y-4">
113
+ ${renderJSONSection('Input', span.input)}
114
+ ${renderJSONSection('Output', span.output)}
115
+ ${renderJSONSection('Attributes', span.attributes)}
116
+ ${isError ? renderJSONSection('Error', span.error, true) : ''}
117
+ </div>
118
+ `;
119
+ spansList.appendChild(card);
120
+ });
121
+
122
+ loading.classList.add('hidden');
123
+ content.classList.remove('hidden');
124
+ lucide.createIcons();
125
+ }
126
+
127
+ function renderJSONSection(title, data, isError = false) {
128
+ if (!data || data === '{}') return '';
129
+
130
+ let parsed = data;
131
+ if (typeof data === 'string') {
132
+ try {
133
+ parsed = JSON.parse(data);
134
+ } catch (e) {
135
+ parsed = data; // Keep as string if parsing fails
136
+ }
137
+ }
138
+
139
+ if (Object.keys(parsed).length === 0) return '';
140
+
141
+ const jsonHtml = syntaxHighlight(parsed);
142
+ const bgClass = isError ? 'bg-red-50 dark:bg-red-900/10' : 'bg-slate-900 dark:bg-black';
143
+ const textClass = isError ? 'text-red-900 dark:text-red-200' : 'text-slate-50 dark:text-slate-300';
144
+
145
+ return `
146
+ <div>
147
+ <h5 class="text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-2">${title}</h5>
148
+ <div class="${bgClass} rounded-lg p-4 overflow-x-auto">
149
+ <pre class="text-xs font-mono ${textClass}"><code>${jsonHtml}</code></pre>
150
+ </div>
151
+ </div>
152
+ `;
153
+ }
154
+
155
+ function syntaxHighlight(json) {
156
+ if (typeof json !== 'string') {
157
+ json = JSON.stringify(json, null, 2);
158
+ }
159
+ json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
160
+ return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
161
+ let cls = 'text-purple-300'; // number
162
+ if (/^"/.test(match)) {
163
+ if (/:$/.test(match)) {
164
+ cls = 'text-indigo-300'; // key
165
+ } else {
166
+ cls = 'text-green-300'; // string
167
+ }
168
+ } else if (/true|false/.test(match)) {
169
+ cls = 'text-blue-300'; // boolean
170
+ } else if (/null/.test(match)) {
171
+ cls = 'text-slate-400'; // null
172
+ }
173
+ return '<span class="' + cls + '">' + match + '</span>';
174
+ });
175
+ }
176
+
177
+ function scrollToSpan(spanId) {
178
+ const el = document.getElementById(`span-${spanId}`);
179
+ if (el) {
180
+ el.scrollIntoView({ behavior: 'smooth', block: 'center' });
181
+ el.classList.add('ring-2', 'ring-brand-500', 'ring-offset-2');
182
+ setTimeout(() => el.classList.remove('ring-2', 'ring-brand-500', 'ring-offset-2'), 2000);
183
+ }
184
+ }
@@ -0,0 +1,239 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Trace Details - Vectra Dashboard</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script>
9
+ tailwind.config = {
10
+ darkMode: 'class',
11
+ theme: {
12
+ extend: {
13
+ colors: {
14
+ brand: {
15
+ 50: '#f5f3ff',
16
+ 100: '#ede9fe',
17
+ 200: '#ddd6fe',
18
+ 300: '#c4b5fd',
19
+ 400: '#a78bfa',
20
+ 500: '#8b5cf6',
21
+ 600: '#7c3aed',
22
+ 700: '#6d28d9',
23
+ 800: '#5b21b6',
24
+ 900: '#4c1d95',
25
+ 950: '#2e1065',
26
+ },
27
+ dark: {
28
+ 950: '#020204',
29
+ 900: '#08080C',
30
+ 850: '#0F0F16',
31
+ 800: '#14141F',
32
+ 700: '#1C1C2E',
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
38
+
39
+ // Check for saved theme preference or use system preference
40
+ if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
41
+ document.documentElement.classList.add('dark');
42
+ } else {
43
+ document.documentElement.classList.remove('dark');
44
+ }
45
+ </script>
46
+ <script src="https://unpkg.com/lucide@latest"></script>
47
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
48
+ <style>
49
+ body { font-family: 'Inter', sans-serif; }
50
+ .sidebar-link.active { background-color: #f3f4f6; color: #111827; }
51
+ .dark .sidebar-link.active { background-color: rgba(255, 255, 255, 0.1); color: #ffffff; }
52
+ .sidebar-link:hover { background-color: #f9fafb; color: #111827; }
53
+ .dark .sidebar-link:hover { background-color: rgba(255, 255, 255, 0.05); color: #ffffff; }
54
+
55
+ /* JSON Syntax Highlighting */
56
+ .json-key { color: #7c3aed; } /* violet-600 */
57
+ .dark .json-key { color: #a78bfa; } /* violet-400 */
58
+ .json-string { color: #059669; } /* emerald-600 */
59
+ .dark .json-string { color: #34d399; } /* emerald-400 */
60
+ .json-number { color: #d97706; } /* amber-600 */
61
+ .dark .json-number { color: #fbbf24; } /* amber-400 */
62
+ .json-boolean { color: #dc2626; } /* red-600 */
63
+ .dark .json-boolean { color: #f87171; } /* red-400 */
64
+ .json-null { color: #9ca3af; } /* gray-400 */
65
+ .dark .json-null { color: #9ca3af; } /* gray-400 */
66
+
67
+ /* Custom scrollbar for dark mode compatibility */
68
+ ::-webkit-scrollbar {
69
+ width: 8px;
70
+ height: 8px;
71
+ }
72
+ ::-webkit-scrollbar-track {
73
+ background: transparent;
74
+ }
75
+ ::-webkit-scrollbar-thumb {
76
+ background: #cbd5e1;
77
+ border-radius: 4px;
78
+ }
79
+ .dark ::-webkit-scrollbar-thumb {
80
+ background: #334155;
81
+ }
82
+ ::-webkit-scrollbar-thumb:hover {
83
+ background: #94a3b8;
84
+ }
85
+ .dark ::-webkit-scrollbar-thumb:hover {
86
+ background: #475569;
87
+ }
88
+ </style>
89
+ </head>
90
+ <body class="bg-gray-50 text-slate-800 dark:bg-dark-950 dark:text-white h-screen overflow-hidden flex transition-colors duration-200">
91
+
92
+ <!-- Sidebar -->
93
+ <aside class="w-64 bg-white border-r border-gray-200 dark:bg-dark-900 dark:border-white/5 flex-col hidden md:flex transition-colors duration-200">
94
+ <!-- Logo -->
95
+ <div class="h-16 flex items-center px-6 border-b border-gray-100 dark:border-white/5">
96
+ <div class="flex items-center gap-2">
97
+ <div class="bg-brand-600 p-1.5 rounded-lg text-white">
98
+ <i data-lucide="layers" class="w-5 h-5"></i>
99
+ </div>
100
+ <span class="text-xl font-bold text-slate-900 dark:text-white tracking-tight">Vectra</span>
101
+ </div>
102
+ </div>
103
+
104
+ <!-- Navigation -->
105
+ <nav class="flex-1 px-4 py-6 space-y-1 overflow-y-auto">
106
+ <div class="mb-6">
107
+ <p class="px-2 text-xs font-semibold text-gray-400 dark:text-gray-500 uppercase tracking-wider mb-2">Menu</p>
108
+ <a href="/dashboard/" class="w-full flex items-center px-3 py-2 text-sm font-medium rounded-lg text-gray-600 dark:text-gray-400 sidebar-link group transition-colors">
109
+ <i data-lucide="layout-dashboard" class="w-5 h-5 mr-3 text-gray-400 dark:text-gray-500 group-hover:text-gray-500 dark:group-hover:text-gray-300"></i>
110
+ Dashboard
111
+ </a>
112
+ <a href="/dashboard/?view=traces" class="w-full flex items-center px-3 py-2 text-sm font-medium rounded-lg text-slate-900 dark:text-white bg-gray-100 dark:bg-white/10 sidebar-link active group transition-colors">
113
+ <i data-lucide="activity" class="w-5 h-5 mr-3 text-gray-500 dark:text-gray-400"></i>
114
+ Traces
115
+ </a>
116
+ <a href="/dashboard/?view=sessions" class="w-full flex items-center px-3 py-2 text-sm font-medium rounded-lg text-gray-600 dark:text-gray-400 sidebar-link group transition-colors">
117
+ <i data-lucide="users" class="w-5 h-5 mr-3 text-gray-400 dark:text-gray-500 group-hover:text-gray-500 dark:group-hover:text-gray-300"></i>
118
+ Sessions
119
+ </a>
120
+ </div>
121
+
122
+ <div>
123
+ <p class="px-2 text-xs font-semibold text-gray-400 dark:text-gray-500 uppercase tracking-wider mb-2">Management</p>
124
+ <a href="#" class="w-full flex items-center px-3 py-2 text-sm font-medium rounded-lg text-gray-600 dark:text-gray-400 sidebar-link group transition-colors">
125
+ <i data-lucide="settings" class="w-5 h-5 mr-3 text-gray-400 dark:text-gray-500 group-hover:text-gray-500 dark:group-hover:text-gray-300"></i>
126
+ Settings
127
+ </a>
128
+ <a href="#" class="w-full flex items-center px-3 py-2 text-sm font-medium rounded-lg text-gray-600 dark:text-gray-400 sidebar-link group transition-colors">
129
+ <i data-lucide="book-open" class="w-5 h-5 mr-3 text-gray-400 dark:text-gray-500 group-hover:text-gray-500 dark:group-hover:text-gray-300"></i>
130
+ Documentation
131
+ </a>
132
+ </div>
133
+ </nav>
134
+
135
+ <!-- User Profile (Bottom) -->
136
+ <div class="p-4 border-t border-gray-100 dark:border-white/5">
137
+ <div class="flex items-center gap-3">
138
+ <div class="w-8 h-8 rounded-full bg-brand-100 dark:bg-brand-900/50 flex items-center justify-center text-brand-600 dark:text-brand-400 font-bold text-xs border border-brand-200 dark:border-brand-500/20">
139
+ AD
140
+ </div>
141
+ <div>
142
+ <p class="text-sm font-medium text-gray-900 dark:text-white">Admin User</p>
143
+ <p class="text-xs text-gray-500">admin@vectra.ai</p>
144
+ </div>
145
+ <button onclick="toggleTheme()" class="ml-auto p-1.5 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 rounded-lg hover:bg-gray-100 dark:hover:bg-white/5 transition-colors">
146
+ <i data-lucide="moon" class="w-4 h-4 hidden dark:block"></i>
147
+ <i data-lucide="sun" class="w-4 h-4 block dark:hidden"></i>
148
+ </button>
149
+ </div>
150
+ </div>
151
+ </aside>
152
+
153
+ <!-- Main Content -->
154
+ <div class="flex-1 flex flex-col min-w-0 overflow-hidden bg-gray-50 dark:bg-dark-950 transition-colors duration-200">
155
+
156
+ <!-- Top Header -->
157
+ <header class="bg-white dark:bg-dark-900 border-b border-gray-200 dark:border-white/5 h-16 flex items-center justify-between px-6 lg:px-8 transition-colors duration-200">
158
+ <div class="flex items-center flex-1">
159
+ <div class="flex items-center text-sm text-gray-500 dark:text-gray-400">
160
+ <a href="/dashboard/" class="hover:text-gray-900 dark:hover:text-white transition-colors">Traces</a>
161
+ <i data-lucide="chevron-right" class="w-4 h-4 mx-2"></i>
162
+ <span class="font-medium text-gray-900 dark:text-white" id="header-trace-id">Loading...</span>
163
+ </div>
164
+ </div>
165
+ <div class="ml-4 flex items-center gap-4">
166
+ <button class="p-2 text-gray-400 hover:text-gray-500 dark:hover:text-white transition-colors">
167
+ <i data-lucide="bell" class="w-6 h-6"></i>
168
+ </button>
169
+ </div>
170
+ </header>
171
+
172
+ <!-- Main Scrollable Area -->
173
+ <main class="flex-1 overflow-y-auto p-6 lg:p-8" id="main-content">
174
+
175
+ <!-- Loading State -->
176
+ <div id="loading" class="flex flex-col justify-center items-center h-64">
177
+ <div class="animate-spin rounded-full h-8 w-8 border-b-2 border-brand-600 dark:border-brand-500 mb-4"></div>
178
+ <p class="text-sm text-gray-500 dark:text-gray-400">Loading trace details...</p>
179
+ </div>
180
+
181
+ <!-- Trace Content (Hidden initially) -->
182
+ <div id="trace-content" class="hidden space-y-8 max-w-7xl mx-auto">
183
+
184
+ <!-- Header Card -->
185
+ <div class="bg-white dark:bg-dark-900 rounded-xl shadow-sm border border-gray-100 dark:border-white/5 p-6 transition-colors duration-200">
186
+ <div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6 gap-4">
187
+ <div>
188
+ <h1 class="text-2xl font-bold text-gray-900 dark:text-white mb-1">Trace Summary</h1>
189
+ <div class="flex items-center space-x-6 text-sm text-gray-500 dark:text-gray-400">
190
+ <span class="flex items-center"><i data-lucide="calendar" class="w-4 h-4 mr-2 text-gray-400 dark:text-gray-500"></i> <span id="trace-timestamp">-</span></span>
191
+ <span class="flex items-center"><i data-lucide="clock" class="w-4 h-4 mr-2 text-gray-400 dark:text-gray-500"></i> <span id="trace-duration">-</span></span>
192
+ <span class="flex items-center"><i data-lucide="cpu" class="w-4 h-4 mr-2 text-gray-400 dark:text-gray-500"></i> <span id="trace-model">-</span></span>
193
+ </div>
194
+ </div>
195
+ <div id="trace-status" class="px-4 py-1.5 rounded-full text-sm font-semibold bg-gray-100 text-gray-600 border border-gray-200 dark:bg-white/10 dark:text-gray-300 dark:border-white/10">
196
+ Unknown
197
+ </div>
198
+ </div>
199
+
200
+ <!-- Timeline / Gantt -->
201
+ <div class="border-t border-gray-100 dark:border-white/5 pt-6">
202
+ <h3 class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-4">Execution Timeline</h3>
203
+ <div id="timeline-container" class="space-y-3 relative">
204
+ <!-- Spans injected here -->
205
+ </div>
206
+ </div>
207
+ </div>
208
+
209
+ <!-- Detailed Spans List -->
210
+ <div class="space-y-6">
211
+ <div class="flex items-center justify-between">
212
+ <h3 class="text-lg font-bold text-gray-900 dark:text-white">Span Details</h3>
213
+ <div class="text-sm text-gray-500 dark:text-gray-400">Detailed breakdown of operations</div>
214
+ </div>
215
+ <div id="spans-list" class="space-y-6">
216
+ <!-- Span Cards injected here -->
217
+ </div>
218
+ </div>
219
+
220
+ </div>
221
+ </main>
222
+ </div>
223
+
224
+ <script src="trace-script.js"></script>
225
+ <script>
226
+ lucide.createIcons();
227
+
228
+ function toggleTheme() {
229
+ if (document.documentElement.classList.contains('dark')) {
230
+ document.documentElement.classList.remove('dark');
231
+ localStorage.setItem('color-theme', 'light');
232
+ } else {
233
+ document.documentElement.classList.add('dark');
234
+ localStorage.setItem('color-theme', 'dark');
235
+ }
236
+ }
237
+ </script>
238
+ </body>
239
+ </html>
@@ -0,0 +1,226 @@
1
+ const { v4: uuidv4 } = require('uuid');
2
+ const path = require('path');
3
+
4
+ class SQLiteLogger {
5
+ constructor(config) {
6
+ this.enabled = config.enabled;
7
+ if (!this.enabled) return;
8
+
9
+ this.projectId = config.projectId;
10
+ this.trackMetrics = config.trackMetrics;
11
+ this.trackTraces = config.trackTraces;
12
+ this.trackLogs = config.trackLogs;
13
+ this.sessionTracking = config.sessionTracking;
14
+
15
+ try {
16
+ const rawPath = config.sqlitePath || 'vectra-observability.db';
17
+ const dbPath = path.isAbsolute(rawPath) ? rawPath : path.resolve(process.cwd(), rawPath);
18
+ // Ensure directory exists
19
+ const dbDir = path.dirname(dbPath);
20
+ console.log(`[SQLiteLogger] dbPath: ${dbPath}, dbDir: ${dbDir}`);
21
+
22
+ const fs = require('fs');
23
+ if (!fs.existsSync(dbDir)) {
24
+ console.log(`[SQLiteLogger] Creating directory: ${dbDir}`);
25
+ fs.mkdirSync(dbDir, { recursive: true });
26
+ } else {
27
+ console.log(`[SQLiteLogger] Directory exists: ${dbDir}`);
28
+ }
29
+
30
+ const sqlite3 = require('sqlite3').verbose();
31
+ this.db = new sqlite3.Database(dbPath, (err) => {
32
+ if (err) {
33
+ console.error('Failed to connect to SQLite database:', err);
34
+ throw err;
35
+ }
36
+ });
37
+ this.initializeSchema();
38
+ } catch (error) {
39
+ console.error('Failed to initialize SQLite logger:', error);
40
+ throw error;
41
+ }
42
+ }
43
+
44
+ initializeSchema() {
45
+ this.db.serialize(() => {
46
+ this.db.run(`
47
+ CREATE TABLE IF NOT EXISTS traces (
48
+ id TEXT PRIMARY KEY,
49
+ project_id TEXT,
50
+ trace_id TEXT,
51
+ span_id TEXT,
52
+ parent_span_id TEXT,
53
+ name TEXT,
54
+ start_time INTEGER,
55
+ end_time INTEGER,
56
+ duration INTEGER,
57
+ status TEXT,
58
+ attributes TEXT, -- JSON
59
+ input TEXT, -- JSON
60
+ output TEXT, -- JSON
61
+ error TEXT, -- JSON
62
+ provider TEXT,
63
+ model_name TEXT
64
+ )
65
+ `);
66
+
67
+ // Attempt to add columns if they don't exist (migration)
68
+ this.db.run(`ALTER TABLE traces ADD COLUMN provider TEXT`, () => {});
69
+ this.db.run(`ALTER TABLE traces ADD COLUMN model_name TEXT`, () => {});
70
+
71
+ this.db.run(`
72
+ CREATE TABLE IF NOT EXISTS metrics (
73
+ id TEXT PRIMARY KEY,
74
+ project_id TEXT,
75
+ name TEXT,
76
+ value REAL,
77
+ timestamp INTEGER,
78
+ tags TEXT -- JSON
79
+ )
80
+ `);
81
+
82
+ this.db.run(`
83
+ CREATE TABLE IF NOT EXISTS logs (
84
+ id TEXT PRIMARY KEY,
85
+ project_id TEXT,
86
+ level TEXT,
87
+ message TEXT,
88
+ timestamp INTEGER,
89
+ context TEXT -- JSON
90
+ )
91
+ `);
92
+
93
+ this.db.run(`
94
+ CREATE TABLE IF NOT EXISTS sessions (
95
+ id TEXT PRIMARY KEY,
96
+ project_id TEXT,
97
+ session_id TEXT,
98
+ user_id TEXT,
99
+ start_time INTEGER,
100
+ last_activity_time INTEGER,
101
+ metadata TEXT -- JSON
102
+ )
103
+ `);
104
+ });
105
+ }
106
+
107
+ logTrace(trace) {
108
+ if (!this.enabled || !this.trackTraces) return;
109
+ try {
110
+ const stmt = this.db.prepare(`
111
+ INSERT INTO traces (id, project_id, trace_id, span_id, parent_span_id, name, start_time, end_time, duration, status, attributes, input, output, error, provider, model_name)
112
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
113
+ `);
114
+ stmt.run(
115
+ uuidv4(),
116
+ this.projectId,
117
+ trace.traceId,
118
+ trace.spanId,
119
+ trace.parentSpanId || null,
120
+ trace.name,
121
+ trace.startTime,
122
+ trace.endTime,
123
+ trace.duration,
124
+ trace.status,
125
+ JSON.stringify(trace.attributes || {}),
126
+ JSON.stringify(trace.input || {}),
127
+ JSON.stringify(trace.output || {}),
128
+ JSON.stringify(trace.error || {}),
129
+ trace.provider || null,
130
+ trace.modelName || null
131
+ );
132
+ stmt.finalize();
133
+ } catch (error) {
134
+ console.error('Failed to log trace:', error);
135
+ }
136
+ }
137
+
138
+ logMetric(nameOrObj, value, tags = {}) {
139
+ if (!this.enabled || !this.trackMetrics) return;
140
+
141
+ let metricName = nameOrObj;
142
+ let metricValue = value;
143
+ let metricTags = tags;
144
+
145
+ if (typeof nameOrObj === 'object' && nameOrObj !== null) {
146
+ metricName = nameOrObj.name;
147
+ metricValue = nameOrObj.value;
148
+ metricTags = nameOrObj.tags || {};
149
+ }
150
+
151
+ try {
152
+ const stmt = this.db.prepare(`
153
+ INSERT INTO metrics (id, project_id, name, value, timestamp, tags)
154
+ VALUES (?, ?, ?, ?, ?, ?)
155
+ `);
156
+ stmt.run(
157
+ uuidv4(),
158
+ this.projectId,
159
+ metricName,
160
+ metricValue,
161
+ Date.now(),
162
+ JSON.stringify(metricTags)
163
+ );
164
+ stmt.finalize();
165
+ } catch (error) {
166
+ console.error('Failed to log metric:', error);
167
+ }
168
+ }
169
+
170
+ log(level, message, context = {}) {
171
+ if (!this.enabled || !this.trackLogs) return;
172
+ try {
173
+ const stmt = this.db.prepare(`
174
+ INSERT INTO logs (id, project_id, level, message, timestamp, context)
175
+ VALUES (?, ?, ?, ?, ?, ?)
176
+ `);
177
+ stmt.run(
178
+ uuidv4(),
179
+ this.projectId,
180
+ level,
181
+ message,
182
+ Date.now(),
183
+ JSON.stringify(context)
184
+ );
185
+ stmt.finalize();
186
+ } catch (error) {
187
+ console.error('Failed to log message:', error);
188
+ }
189
+ }
190
+
191
+ logSession(sessionId, userId, metadata = {}) {
192
+ if (!this.enabled || !this.sessionTracking) return;
193
+ try {
194
+ // Check if session exists (upsert logic if needed, but here simple insert/update)
195
+ // For simplicity, we just insert or ignore, or update last_activity
196
+ // Since sqlite3 doesn't support UPSERT in older versions easily without ON CONFLICT, let's try INSERT OR REPLACE
197
+ const stmt = this.db.prepare(`
198
+ INSERT OR REPLACE INTO sessions (id, project_id, session_id, user_id, start_time, last_activity_time, metadata)
199
+ VALUES (
200
+ COALESCE((SELECT id FROM sessions WHERE session_id = ?), ?),
201
+ ?, ?, ?,
202
+ COALESCE((SELECT start_time FROM sessions WHERE session_id = ?), ?),
203
+ ?, ?
204
+ )
205
+ `);
206
+
207
+ const now = Date.now();
208
+ const newId = uuidv4();
209
+
210
+ stmt.run(
211
+ sessionId, newId,
212
+ this.projectId,
213
+ sessionId,
214
+ userId,
215
+ sessionId, now,
216
+ now,
217
+ JSON.stringify(metadata)
218
+ );
219
+ stmt.finalize();
220
+ } catch (error) {
221
+ console.error('Failed to log session:', error);
222
+ }
223
+ }
224
+ }
225
+
226
+ module.exports = SQLiteLogger;
package/src/processor.js CHANGED
@@ -39,7 +39,7 @@ class DocumentProcessor {
39
39
  async process(text) {
40
40
  return this.config.strategy === ChunkingStrategy.AGENTIC
41
41
  ? this.agenticSplit(text)
42
- : this.recursiveSplit(text);
42
+ : this.recursiveSplit(text);
43
43
  }
44
44
 
45
45
  recursiveSplit(text) {