@llmtap/collector 0.1.0
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.
- package/dist/index.d.mts +45 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +1709 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1664 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/db.ts","../src/seed.ts","../src/schemas.ts","../src/events.ts","../src/otlp-forwarder.ts","../src/routes/ingest.ts","../src/routes/traces.ts","../src/routes/stats.ts","../src/routes/sse.ts","../src/routes/sessions.ts","../src/routes/db-info.ts","../src/routes/insights.ts","../src/routes/replay.ts","../src/routes/otlp.ts"],"sourcesContent":["import Fastify from \"fastify\";\nimport cors from \"@fastify/cors\";\nimport fastifyStatic from \"@fastify/static\";\nimport { z } from \"zod\";\nimport { DEFAULT_COLLECTOR_PORT } from \"@llmtap/shared\";\nimport { getDb, closeDb, resetDb, startRetentionSchedule, enforceRetention } from \"./db.js\";\nimport { seedDemoData } from \"./seed.js\";\nimport { registerIngestRoute } from \"./routes/ingest.js\";\nimport { registerTraceRoutes } from \"./routes/traces.js\";\nimport { registerStatsRoute } from \"./routes/stats.js\";\nimport { registerSSERoute } from \"./routes/sse.js\";\nimport { registerSessionsRoute } from \"./routes/sessions.js\";\nimport { registerDbInfoRoute } from \"./routes/db-info.js\";\nimport { registerInsightsRoute } from \"./routes/insights.js\";\nimport { registerReplayRoute } from \"./routes/replay.js\";\nimport { registerOtlpExportRoute } from \"./routes/otlp.js\";\nimport { initOtlpForwarder, getOtlpEndpoint } from \"./otlp-forwarder.js\";\n\nexport interface CollectorOptions {\n port?: number;\n host?: string;\n dashboardPath?: string;\n quiet?: boolean;\n demo?: boolean;\n /** Data retention in days. 0 = keep forever. */\n retentionDays?: number;\n}\n\n// Per-IP rate limiter keyed by \"METHOD:pathname\"\ninterface RateState { count: number; windowStart: number }\nconst rateLimitConfigs: Record<string, { max: number; windowMs: number }> = {\n \"POST:/v1/spans\": { max: 300, windowMs: 60_000 },\n \"POST:/v1/reset\": { max: 5, windowMs: 60_000 },\n \"POST:/v1/replay\": { max: 30, windowMs: 60_000 },\n \"POST:/v1/retention\": { max: 10, windowMs: 60_000 },\n \"POST:/v1/export/otlp/forward\": { max: 20, windowMs: 60_000 },\n \"GET:/v1/insights\": { max: 60, windowMs: 60_000 },\n \"GET:/v1/export/otlp\": { max: 30, windowMs: 60_000 },\n};\nconst rateLimitByIP = new Map<string, RateState>();\n\nconst RetentionSchema = z.object({\n retentionDays: z.number().min(0).max(3650),\n});\n\nconst ResetSchema = z.object({\n confirm: z.literal(true),\n});\n\nexport async function createServer(options: CollectorOptions = {}) {\n const port = options.port ?? DEFAULT_COLLECTOR_PORT;\n const host = options.host ?? \"127.0.0.1\";\n\n const app = Fastify({\n logger: !options.quiet\n ? {\n transport: {\n target: \"pino-pretty\",\n options: { translateTime: \"HH:MM:ss\", ignore: \"pid,hostname\" },\n },\n }\n : false,\n // Limit request body to 2MB to prevent abuse\n bodyLimit: 2 * 1024 * 1024,\n });\n\n // CORS: only allow localhost origins (dashboard runs on same host)\n await app.register(cors, {\n origin: (origin, cb) => {\n // Allow requests with no origin (curl, SDK, server-to-server)\n if (!origin || /^https?:\\/\\/(localhost|127\\.0\\.0\\.1)(:\\d+)?$/.test(origin)) {\n cb(null, true);\n } else {\n cb(new Error(\"Not allowed by CORS\"), false);\n }\n },\n methods: [\"GET\", \"POST\", \"OPTIONS\"],\n });\n\n // Per-IP rate limiter for all sensitive endpoints\n app.addHook(\"onRequest\", async (request, reply) => {\n const pathname = request.url.split(\"?\")[0].replace(/\\/+$/, \"\");\n const key = `${request.method}:${pathname}`;\n const cfg = rateLimitConfigs[key];\n if (!cfg) return;\n\n const ip = request.ip;\n const ipKey = `${ip}:${key}`;\n const now = Date.now();\n let state = rateLimitByIP.get(ipKey);\n if (!state || now - state.windowStart > cfg.windowMs) {\n state = { count: 0, windowStart: now };\n rateLimitByIP.set(ipKey, state);\n }\n state.count++;\n if (state.count > cfg.max) {\n return reply.status(429).send({\n error: \"Rate limit exceeded\",\n retryAfterMs: cfg.windowMs - (now - state.windowStart),\n });\n }\n });\n\n // Periodically clean up stale rate limit entries\n const rateLimitCleanup = setInterval(() => {\n const now = Date.now();\n for (const [k, v] of rateLimitByIP) {\n if (now - v.windowStart > 120_000) rateLimitByIP.delete(k);\n }\n }, 60_000);\n rateLimitCleanup.unref();\n\n // Serve dashboard static files if path provided\n if (options.dashboardPath) {\n await app.register(fastifyStatic, {\n root: options.dashboardPath,\n prefix: \"/\",\n wildcard: true,\n });\n\n // SPA fallback: serve index.html for non-API routes\n app.setNotFoundHandler(async (_request, reply) => {\n return reply.sendFile(\"index.html\");\n });\n }\n\n // Initialize database\n getDb();\n\n // Demo data is opt-in so a real install starts clean.\n if (options.demo) {\n seedDemoData();\n }\n\n // Start retention enforcement if configured\n if (options.retentionDays && options.retentionDays > 0) {\n startRetentionSchedule(options.retentionDays);\n }\n\n // Initialize OTLP auto-forwarding if OTEL_EXPORTER_OTLP_ENDPOINT is set\n initOtlpForwarder();\n\n // Register API routes\n await registerIngestRoute(app);\n await registerTraceRoutes(app);\n await registerStatsRoute(app);\n await registerSSERoute(app);\n await registerSessionsRoute(app);\n await registerDbInfoRoute(app);\n await registerInsightsRoute(app);\n await registerReplayRoute(app);\n await registerOtlpExportRoute(app);\n\n // Health check\n app.get(\"/health\", async () => ({ status: \"ok\" }));\n\n // Reset endpoint (requires explicit confirmation)\n app.post(\"/v1/reset\", async (request, reply) => {\n const parsed = ResetSchema.safeParse(request.body);\n if (!parsed.success) {\n return reply.status(400).send({ error: \"Must send { confirm: true } to reset database\" });\n }\n resetDb();\n return reply.send({ status: \"ok\", message: \"Data cleared\" });\n });\n\n // Retention endpoint (for dashboard Settings page)\n app.post(\"/v1/retention\", async (request, reply) => {\n const parsed = RetentionSchema.safeParse(request.body);\n if (!parsed.success) {\n return reply.status(400).send({ error: \"Validation failed\", details: parsed.error.issues });\n }\n const deleted = enforceRetention(parsed.data.retentionDays);\n return reply.send({\n status: \"ok\",\n retentionDays: parsed.data.retentionDays,\n deletedSpans: deleted,\n });\n });\n\n // Graceful shutdown\n let isShuttingDown = false;\n const shutdown = async () => {\n if (isShuttingDown) return;\n isShuttingDown = true;\n clearInterval(rateLimitCleanup);\n await app.close();\n closeDb();\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n app.addHook(\"onClose\", async () => {\n process.off(\"SIGINT\", shutdown);\n process.off(\"SIGTERM\", shutdown);\n });\n\n return { app, port, host };\n}\n\nexport async function startServer(\n options: CollectorOptions = {}\n): Promise<string> {\n const { app, port, host } = await createServer(options);\n const address = await app.listen({ port, host });\n return address;\n}\n","import Database from \"better-sqlite3\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport fs from \"node:fs\";\nimport { DB_DIR_NAME, DB_FILE_NAME } from \"@llmtap/shared\";\n\nlet db: Database.Database | null = null;\nlet retentionCheckInterval: ReturnType<typeof setInterval> | null = null;\n\n// ---------- Migration system ----------\n\ninterface Migration {\n version: number;\n description: string;\n up: (db: Database.Database) => void;\n}\n\nconst migrations: Migration[] = [\n {\n version: 1,\n description: \"Initial schema — spans table and indexes\",\n up(db) {\n db.exec(`\n CREATE TABLE IF NOT EXISTS spans (\n spanId TEXT PRIMARY KEY,\n traceId TEXT NOT NULL,\n parentSpanId TEXT,\n name TEXT NOT NULL,\n operationName TEXT NOT NULL,\n providerName TEXT NOT NULL,\n startTime INTEGER NOT NULL,\n endTime INTEGER,\n duration INTEGER,\n requestModel TEXT NOT NULL,\n responseModel TEXT,\n inputTokens INTEGER DEFAULT 0,\n outputTokens INTEGER DEFAULT 0,\n totalTokens INTEGER DEFAULT 0,\n inputCost REAL DEFAULT 0,\n outputCost REAL DEFAULT 0,\n totalCost REAL DEFAULT 0,\n temperature REAL,\n maxTokens INTEGER,\n topP REAL,\n inputMessages TEXT,\n outputMessages TEXT,\n toolCalls TEXT,\n status TEXT NOT NULL DEFAULT 'ok',\n errorType TEXT,\n errorMessage TEXT,\n tags TEXT,\n sessionId TEXT,\n userId TEXT,\n createdAt INTEGER DEFAULT (strftime('%s','now') * 1000)\n );\n\n CREATE INDEX IF NOT EXISTS idx_spans_traceId ON spans(traceId);\n CREATE INDEX IF NOT EXISTS idx_spans_startTime ON spans(startTime);\n CREATE INDEX IF NOT EXISTS idx_spans_providerName ON spans(providerName);\n CREATE INDEX IF NOT EXISTS idx_spans_requestModel ON spans(requestModel);\n CREATE INDEX IF NOT EXISTS idx_spans_status ON spans(status);\n `);\n },\n },\n {\n version: 2,\n description: \"Add sessionId index for session queries\",\n up(db) {\n db.exec(\n `CREATE INDEX IF NOT EXISTS idx_spans_sessionId ON spans(sessionId);`\n );\n },\n },\n];\n\nfunction ensureMigrationsTable(db: Database.Database): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS _migrations (\n version INTEGER PRIMARY KEY,\n description TEXT NOT NULL,\n appliedAt INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)\n );\n `);\n}\n\nfunction getAppliedVersion(db: Database.Database): number {\n const row = db\n .prepare(\"SELECT MAX(version) as maxVer FROM _migrations\")\n .get() as { maxVer: number | null };\n return row.maxVer ?? 0;\n}\n\nfunction runMigrations(db: Database.Database): void {\n ensureMigrationsTable(db);\n const currentVersion = getAppliedVersion(db);\n\n const insertMigration = db.prepare(\n \"INSERT INTO _migrations (version, description) VALUES (@version, @description)\"\n );\n\n const applyPending = db.transaction(() => {\n for (const migration of migrations) {\n if (migration.version <= currentVersion) continue;\n migration.up(db);\n insertMigration.run({\n version: migration.version,\n description: migration.description,\n });\n }\n });\n\n applyPending();\n}\n\n// ---------- Database lifecycle ----------\n\n/** Get or create the SQLite database */\nexport function getDb(): Database.Database {\n if (db) return db;\n\n const dbDir = process.env.LLMTAP_DB_DIR\n ? path.resolve(process.env.LLMTAP_DB_DIR)\n : path.join(os.homedir(), DB_DIR_NAME);\n if (!fs.existsSync(dbDir)) {\n fs.mkdirSync(dbDir, { recursive: true });\n }\n\n const dbPath = process.env.LLMTAP_DB_PATH\n ? path.resolve(process.env.LLMTAP_DB_PATH)\n : path.join(dbDir, DB_FILE_NAME);\n db = new Database(dbPath);\n\n // Enable WAL for better performance\n db.pragma(\"journal_mode = WAL\");\n db.pragma(\"foreign_keys = ON\");\n // Wait up to 5s when DB is locked by another writer (prevents SQLITE_BUSY)\n db.pragma(\"busy_timeout = 5000\");\n\n runMigrations(db);\n return db;\n}\n\n/** Close the database connection */\nexport function closeDb(): void {\n if (retentionCheckInterval) {\n clearInterval(retentionCheckInterval);\n retentionCheckInterval = null;\n }\n if (db) {\n // Checkpoint WAL before closing to consolidate changes\n try {\n db.pragma(\"wal_checkpoint(TRUNCATE)\");\n } catch {\n /* best effort */\n }\n db.close();\n db = null;\n }\n}\n\n/** Reset the database (delete all data) */\nexport function resetDb(): void {\n const d = getDb();\n d.exec(\"DELETE FROM spans\");\n d.exec(\"VACUUM\");\n}\n\n// ---------- Data retention ----------\n\n/**\n * Delete spans older than `retentionDays` and reclaim disk space.\n * Returns the number of rows deleted.\n */\nexport function enforceRetention(retentionDays: number): number {\n if (retentionDays <= 0) return 0;\n\n const d = getDb();\n const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000;\n\n const result = d\n .prepare(\"DELETE FROM spans WHERE startTime < @cutoff\")\n .run({ cutoff });\n\n if (result.changes > 0) {\n d.exec(\"VACUUM\");\n }\n\n return result.changes;\n}\n\n/**\n * Start periodic retention enforcement (runs every hour).\n * Also runs once immediately on call.\n */\nexport function startRetentionSchedule(retentionDays: number): void {\n if (retentionDays <= 0) return;\n\n // Run immediately on startup\n enforceRetention(retentionDays);\n\n // Then run every hour\n retentionCheckInterval = setInterval(\n () => enforceRetention(retentionDays),\n 60 * 60 * 1000\n );\n // Don't block process exit\n if (retentionCheckInterval.unref) {\n retentionCheckInterval.unref();\n }\n}\n","import { getDb } from \"./db.js\";\n\ninterface SeedSpan {\n spanId: string;\n parentSpanId?: string;\n name: string;\n operationName: string;\n providerName: string;\n requestModel: string;\n responseModel: string;\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n inputCost: number;\n outputCost: number;\n totalCost: number;\n status: string;\n startOffset: number;\n duration: number;\n inputMessages: string;\n outputMessages?: string;\n toolCalls?: string;\n temperature?: number;\n errorType?: string;\n errorMessage?: string;\n}\n\ninterface SeedTrace {\n traceId: string;\n name: string;\n spans: SeedSpan[];\n}\n\n/** Generate realistic demo data so the dashboard looks alive on first visit */\nexport function seedDemoData(): void {\n const db = getDb();\n\n // Check if data already exists\n const count = db.prepare(\"SELECT COUNT(*) as c FROM spans\").get() as { c: number };\n if (count.c > 0) return;\n\n const now = Date.now();\n const hour = 3600_000;\n\n // 6 traces spread across the last 24 hours with realistic LLM interactions\n const traces: SeedTrace[] = [\n {\n traceId: \"tr_demo_chatbot_session_001\",\n name: \"customer-support-chat\",\n spans: [\n {\n spanId: \"sp_demo_001a\",\n name: \"classify-intent\",\n operationName: \"chat.completions.create\",\n providerName: \"openai\",\n requestModel: \"gpt-4o-mini\",\n responseModel: \"gpt-4o-mini-2024-07-18\",\n inputTokens: 245,\n outputTokens: 32,\n totalTokens: 277,\n inputCost: 0.0000368,\n outputCost: 0.0000192,\n totalCost: 0.0000560,\n status: \"ok\",\n startOffset: -22 * hour,\n duration: 420,\n inputMessages: JSON.stringify([\n { role: \"system\", content: \"You are an intent classifier. Classify the user message into: billing, technical, general, urgent.\" },\n { role: \"user\", content: \"I can't access my account and I have a deadline in 2 hours!\" }\n ]),\n outputMessages: JSON.stringify([\n { role: \"assistant\", content: \"urgent\" }\n ]),\n temperature: 0.0,\n },\n {\n spanId: \"sp_demo_001b\",\n parentSpanId: \"sp_demo_001a\",\n name: \"generate-response\",\n operationName: \"chat.completions.create\",\n providerName: \"openai\",\n requestModel: \"gpt-4o\",\n responseModel: \"gpt-4o-2024-08-06\",\n inputTokens: 580,\n outputTokens: 245,\n totalTokens: 825,\n inputCost: 0.00145,\n outputCost: 0.002450,\n totalCost: 0.003900,\n status: \"ok\",\n startOffset: -22 * hour + 450,\n duration: 1800,\n inputMessages: JSON.stringify([\n { role: \"system\", content: \"You are a helpful customer support agent. The user has an urgent account access issue.\" },\n { role: \"user\", content: \"I can't access my account and I have a deadline in 2 hours!\" }\n ]),\n outputMessages: JSON.stringify([\n { role: \"assistant\", content: \"I understand the urgency. Let me help you regain access to your account right away.\\n\\n1. First, try resetting your password at our recovery page\\n2. If that doesn't work, I can verify your identity and unlock your account manually\\n3. For your deadline, I can also temporarily extend any submission windows\\n\\nWhich would you like to try first?\" }\n ]),\n temperature: 0.7,\n }\n ]\n },\n {\n traceId: \"tr_demo_code_review_002\",\n name: \"ai-code-review\",\n spans: [\n {\n spanId: \"sp_demo_002a\",\n name: \"analyze-diff\",\n operationName: \"messages.create\",\n providerName: \"anthropic\",\n requestModel: \"claude-sonnet-4-20250514\",\n responseModel: \"claude-sonnet-4-20250514\",\n inputTokens: 1850,\n outputTokens: 620,\n totalTokens: 2470,\n inputCost: 0.005550,\n outputCost: 0.009300,\n totalCost: 0.014850,\n status: \"ok\",\n startOffset: -18 * hour,\n duration: 3200,\n inputMessages: JSON.stringify([\n { role: \"system\", content: \"You are a senior code reviewer. Analyze pull requests for bugs, security issues, and best practices.\" },\n { role: \"user\", content: \"Review this PR diff:\\n```diff\\n+async function getUserData(userId) {\\n+ const query = `SELECT * FROM users WHERE id = '${userId}'`;\\n+ return db.query(query);\\n+}\\n```\" }\n ]),\n outputMessages: JSON.stringify([\n { role: \"assistant\", content: \"**Critical: SQL Injection Vulnerability**\\n\\nThe code directly interpolates `userId` into the SQL query string, creating a SQL injection attack vector.\\n\\n**Fix:** Use parameterized queries:\\n```javascript\\nasync function getUserData(userId) {\\n return db.query('SELECT * FROM users WHERE id = $1', [userId]);\\n}\\n```\\n\\n**Additional recommendations:**\\n- Add input validation for userId format\\n- Consider selecting specific columns instead of `SELECT *`\\n- Add TypeScript types for the return value\" }\n ]),\n temperature: 0.3,\n }\n ]\n },\n {\n traceId: \"tr_demo_rag_pipeline_003\",\n name: \"rag-document-qa\",\n spans: [\n {\n spanId: \"sp_demo_003a\",\n name: \"embed-query\",\n operationName: \"chat.completions.create\",\n providerName: \"openai\",\n requestModel: \"gpt-4o-mini\",\n responseModel: \"gpt-4o-mini-2024-07-18\",\n inputTokens: 45,\n outputTokens: 12,\n totalTokens: 57,\n inputCost: 0.0000068,\n outputCost: 0.0000072,\n totalCost: 0.0000140,\n status: \"ok\",\n startOffset: -12 * hour,\n duration: 180,\n inputMessages: JSON.stringify([\n { role: \"user\", content: \"Rewrite as a search query: What are the company's Q3 revenue numbers?\" }\n ]),\n outputMessages: JSON.stringify([\n { role: \"assistant\", content: \"Q3 quarterly revenue financial results earnings\" }\n ]),\n temperature: 0.0,\n },\n {\n spanId: \"sp_demo_003b\",\n parentSpanId: \"sp_demo_003a\",\n name: \"synthesize-answer\",\n operationName: \"chat.completions.create\",\n providerName: \"openai\",\n requestModel: \"gpt-4o\",\n responseModel: \"gpt-4o-2024-08-06\",\n inputTokens: 2100,\n outputTokens: 380,\n totalTokens: 2480,\n inputCost: 0.005250,\n outputCost: 0.003800,\n totalCost: 0.009050,\n status: \"ok\",\n startOffset: -12 * hour + 500,\n duration: 2800,\n inputMessages: JSON.stringify([\n { role: \"system\", content: \"Answer the user's question using only the provided context. Cite sources.\" },\n { role: \"user\", content: \"What are the company's Q3 revenue numbers?\\n\\nContext:\\n[Doc 1] Q3 2024 Earnings Report: Total revenue reached $4.2B, up 32% YoY...\\n[Doc 2] Segment breakdown: Cloud services $2.1B, Enterprise $1.4B, Consumer $0.7B...\" }\n ]),\n outputMessages: JSON.stringify([\n { role: \"assistant\", content: \"Based on the Q3 2024 Earnings Report, the company's Q3 revenue numbers are:\\n\\n- **Total Revenue:** $4.2 billion (up 32% year-over-year) [Doc 1]\\n- **Cloud Services:** $2.1 billion [Doc 2]\\n- **Enterprise:** $1.4 billion [Doc 2]\\n- **Consumer:** $0.7 billion [Doc 2]\\n\\nCloud services represents the largest segment at 50% of total revenue.\" }\n ]),\n temperature: 0.2,\n }\n ]\n },\n {\n traceId: \"tr_demo_agent_tools_004\",\n name: \"multi-step-agent\",\n spans: [\n {\n spanId: \"sp_demo_004a\",\n name: \"agent-planning\",\n operationName: \"chat.completions.create\",\n providerName: \"openai\",\n requestModel: \"gpt-4o\",\n responseModel: \"gpt-4o-2024-08-06\",\n inputTokens: 890,\n outputTokens: 156,\n totalTokens: 1046,\n inputCost: 0.002225,\n outputCost: 0.001560,\n totalCost: 0.003785,\n status: \"ok\",\n startOffset: -8 * hour,\n duration: 1200,\n toolCalls: JSON.stringify([\n { id: \"call_abc123\", name: \"get_weather\", arguments: '{\"location\": \"San Francisco, CA\"}', result: '{\"temp\": 62, \"condition\": \"foggy\", \"humidity\": 78}' },\n { id: \"call_def456\", name: \"get_calendar\", arguments: '{\"date\": \"today\"}', result: '{\"events\": [{\"time\": \"2pm\", \"title\": \"Team standup\"}, {\"time\": \"4pm\", \"title\": \"Design review\"}]}' }\n ]),\n inputMessages: JSON.stringify([\n { role: \"system\", content: \"You are a personal assistant with access to weather and calendar tools.\" },\n { role: \"user\", content: \"What's my afternoon looking like? Should I bring an umbrella?\" }\n ]),\n outputMessages: JSON.stringify([\n { role: \"assistant\", content: \"Here's your afternoon:\\n\\n**Schedule:**\\n- 2:00 PM - Team standup\\n- 4:00 PM - Design review\\n\\n**Weather:** 62\\u00b0F and foggy with 78% humidity. No rain expected, but a light jacket would be smart. No umbrella needed!\" }\n ]),\n temperature: 0.5,\n }\n ]\n },\n {\n traceId: \"tr_demo_error_005\",\n name: \"content-moderation\",\n spans: [\n {\n spanId: \"sp_demo_005a\",\n name: \"moderate-content\",\n operationName: \"messages.create\",\n providerName: \"anthropic\",\n requestModel: \"claude-sonnet-4-20250514\",\n responseModel: \"claude-sonnet-4-20250514\",\n inputTokens: 320,\n outputTokens: 0,\n totalTokens: 320,\n inputCost: 0.000960,\n outputCost: 0,\n totalCost: 0.000960,\n status: \"error\",\n errorType: \"RateLimitError\",\n errorMessage: \"Rate limit exceeded: Too many requests. Please retry after 30 seconds.\",\n startOffset: -4 * hour,\n duration: 250,\n inputMessages: JSON.stringify([\n { role: \"user\", content: \"Check this user-generated content for policy violations...\" }\n ]),\n temperature: 0.0,\n }\n ]\n },\n {\n traceId: \"tr_demo_summarize_006\",\n name: \"document-summarizer\",\n spans: [\n {\n spanId: \"sp_demo_006a\",\n name: \"chunk-summarize-1\",\n operationName: \"messages.create\",\n providerName: \"anthropic\",\n requestModel: \"claude-sonnet-4-20250514\",\n responseModel: \"claude-sonnet-4-20250514\",\n inputTokens: 3200,\n outputTokens: 450,\n totalTokens: 3650,\n inputCost: 0.009600,\n outputCost: 0.006750,\n totalCost: 0.016350,\n status: \"ok\",\n startOffset: -2 * hour,\n duration: 2400,\n inputMessages: JSON.stringify([\n { role: \"system\", content: \"Summarize the following document chunk concisely.\" },\n { role: \"user\", content: \"Chapter 1: The integration of large language models into enterprise workflows has fundamentally changed how organizations approach knowledge management...\" }\n ]),\n outputMessages: JSON.stringify([\n { role: \"assistant\", content: \"This chapter discusses how LLM adoption in enterprises has transformed knowledge management, shifting from static document repositories to dynamic, queryable knowledge systems. Key themes: reduced onboarding time (40% avg), improved cross-team knowledge sharing, and new challenges around data privacy and model governance.\" }\n ]),\n temperature: 0.3,\n },\n {\n spanId: \"sp_demo_006b\",\n parentSpanId: \"sp_demo_006a\",\n name: \"chunk-summarize-2\",\n operationName: \"messages.create\",\n providerName: \"anthropic\",\n requestModel: \"claude-sonnet-4-20250514\",\n responseModel: \"claude-sonnet-4-20250514\",\n inputTokens: 2800,\n outputTokens: 380,\n totalTokens: 3180,\n inputCost: 0.008400,\n outputCost: 0.005700,\n totalCost: 0.014100,\n status: \"ok\",\n startOffset: -2 * hour + 2500,\n duration: 2100,\n inputMessages: JSON.stringify([\n { role: \"system\", content: \"Summarize the following document chunk concisely.\" },\n { role: \"user\", content: \"Chapter 2: Implementation best practices for LLM-powered systems require careful consideration of prompt engineering, evaluation frameworks, and cost optimization strategies...\" }\n ]),\n outputMessages: JSON.stringify([\n { role: \"assistant\", content: \"Chapter 2 covers LLM implementation best practices: structured prompt templates, A/B evaluation frameworks comparing model outputs, and cost optimization through model routing (using smaller models for simple tasks). Recommends starting with a pilot program before enterprise-wide rollout.\" }\n ]),\n temperature: 0.3,\n },\n {\n spanId: \"sp_demo_006c\",\n parentSpanId: \"sp_demo_006a\",\n name: \"final-synthesis\",\n operationName: \"messages.create\",\n providerName: \"anthropic\",\n requestModel: \"claude-sonnet-4-20250514\",\n responseModel: \"claude-sonnet-4-20250514\",\n inputTokens: 950,\n outputTokens: 280,\n totalTokens: 1230,\n inputCost: 0.002850,\n outputCost: 0.004200,\n totalCost: 0.007050,\n status: \"ok\",\n startOffset: -2 * hour + 5000,\n duration: 1500,\n inputMessages: JSON.stringify([\n { role: \"system\", content: \"Combine these chapter summaries into a cohesive executive summary.\" },\n { role: \"user\", content: \"[Summary 1] LLM adoption transforms enterprise knowledge management...\\n[Summary 2] Implementation requires prompt engineering, evaluation, cost optimization...\" }\n ]),\n outputMessages: JSON.stringify([\n { role: \"assistant\", content: \"**Executive Summary**\\n\\nEnterprise LLM adoption is transforming knowledge management from static repositories to dynamic, queryable systems, yielding 40% faster onboarding and improved cross-team collaboration. Successful implementation requires structured prompt templates, rigorous A/B evaluation, and intelligent cost optimization through model routing. Organizations should begin with pilot programs, prioritizing data privacy and model governance frameworks before scaling enterprise-wide.\" }\n ]),\n temperature: 0.4,\n }\n ]\n }\n ];\n\n const insertStmt = db.prepare(`\n INSERT INTO spans (\n spanId, traceId, parentSpanId, name, operationName, providerName,\n startTime, endTime, duration, requestModel, responseModel,\n inputTokens, outputTokens, totalTokens, inputCost, outputCost, totalCost,\n temperature, inputMessages, outputMessages, toolCalls,\n status, errorType, errorMessage\n ) VALUES (\n @spanId, @traceId, @parentSpanId, @name, @operationName, @providerName,\n @startTime, @endTime, @duration, @requestModel, @responseModel,\n @inputTokens, @outputTokens, @totalTokens, @inputCost, @outputCost, @totalCost,\n @temperature, @inputMessages, @outputMessages, @toolCalls,\n @status, @errorType, @errorMessage\n )\n `);\n\n const insertAll = db.transaction(() => {\n for (const trace of traces) {\n for (const span of trace.spans) {\n const startTime = now + span.startOffset;\n const endTime = startTime + span.duration;\n insertStmt.run({\n spanId: span.spanId,\n traceId: trace.traceId,\n parentSpanId: span.parentSpanId ?? null,\n name: span.name,\n operationName: span.operationName,\n providerName: span.providerName,\n startTime,\n endTime,\n duration: span.duration,\n requestModel: span.requestModel,\n responseModel: span.responseModel,\n inputTokens: span.inputTokens,\n outputTokens: span.outputTokens,\n totalTokens: span.totalTokens,\n inputCost: span.inputCost,\n outputCost: span.outputCost,\n totalCost: span.totalCost,\n temperature: span.temperature ?? null,\n inputMessages: span.inputMessages ?? null,\n outputMessages: span.outputMessages ?? null,\n toolCalls: span.toolCalls ?? null,\n status: span.status,\n errorType: span.errorType ?? null,\n errorMessage: span.errorMessage ?? null,\n });\n }\n }\n });\n\n insertAll();\n}\n","import { z } from \"zod\";\n\n// Max lengths to prevent abuse (100MB error messages, etc.)\nconst MAX_ID_LEN = 256;\nconst MAX_SHORT_STRING = 512;\nconst MAX_CONTENT_STRING = 100_000; // ~100KB per message content\nconst MAX_ERROR_MESSAGE = 10_000;\nconst MAX_TOOL_ARGS = 200_000; // tool calls can have large JSON\n\nconst MessageSchema = z.object({\n role: z.enum([\"system\", \"user\", \"assistant\", \"tool\"]),\n content: z.string().max(MAX_CONTENT_STRING).nullable(),\n name: z.string().max(MAX_SHORT_STRING).optional(),\n toolCallId: z.string().max(MAX_ID_LEN).optional(),\n});\n\nconst ToolCallSchema = z.object({\n id: z.string().max(MAX_ID_LEN),\n name: z.string().max(MAX_SHORT_STRING),\n arguments: z.string().max(MAX_TOOL_ARGS),\n result: z.string().max(MAX_TOOL_ARGS).optional(),\n duration: z.number().nonnegative().optional(),\n});\n\nexport const SpanInputSchema = z.object({\n spanId: z.string().min(1).max(MAX_ID_LEN),\n traceId: z.string().min(1).max(MAX_ID_LEN),\n parentSpanId: z.string().min(1).max(MAX_ID_LEN).optional(),\n name: z.string().min(1).max(MAX_SHORT_STRING),\n operationName: z.string().min(1).max(MAX_SHORT_STRING),\n providerName: z.string().min(1).max(MAX_SHORT_STRING),\n startTime: z.number().nonnegative(),\n endTime: z.number().nonnegative().optional(),\n duration: z.number().nonnegative().optional(),\n requestModel: z.string().min(1).max(MAX_SHORT_STRING),\n responseModel: z.string().max(MAX_SHORT_STRING).optional(),\n inputTokens: z.number().int().nonnegative().optional(),\n outputTokens: z.number().int().nonnegative().optional(),\n totalTokens: z.number().int().nonnegative().optional(),\n inputCost: z.number().nonnegative().optional(),\n outputCost: z.number().nonnegative().optional(),\n totalCost: z.number().nonnegative().optional(),\n temperature: z.number().min(0).max(10).optional(),\n maxTokens: z.number().int().nonnegative().optional(),\n topP: z.number().min(0).max(1).optional(),\n inputMessages: z.array(MessageSchema).max(500).optional(),\n outputMessages: z.array(MessageSchema).max(500).optional(),\n toolCalls: z.array(ToolCallSchema).max(200).optional(),\n status: z.enum([\"ok\", \"error\"]),\n errorType: z.string().max(MAX_SHORT_STRING).optional(),\n errorMessage: z.string().max(MAX_ERROR_MESSAGE).optional(),\n tags: z.record(z.string().max(MAX_SHORT_STRING)).optional(),\n sessionId: z.string().max(MAX_ID_LEN).optional(),\n userId: z.string().max(MAX_ID_LEN).optional(),\n});\n\nexport const IngestRequestSchema = z.object({\n spans: z.array(SpanInputSchema).min(1).max(500),\n});\n\n// Query parameter schemas for GET routes\nexport const TracesQuerySchema = z.object({\n limit: z.coerce.number().int().min(1).max(200).default(50),\n offset: z.coerce.number().int().min(0).default(0),\n status: z.enum([\"ok\", \"error\"]).optional(),\n provider: z.string().max(MAX_SHORT_STRING).optional(),\n q: z.string().max(MAX_SHORT_STRING).optional(),\n periodHours: z.coerce.number().int().min(1).max(8760).optional(), // max 1 year\n});\n\nexport const StatsQuerySchema = z.object({\n period: z.coerce.number().int().min(1).max(8760).default(24),\n});\n\nexport const SessionsQuerySchema = z.object({\n periodHours: z.coerce.number().int().min(1).max(8760).default(168),\n limit: z.coerce.number().int().min(1).max(200).default(50),\n offset: z.coerce.number().int().min(0).default(0),\n});\n\nexport type ValidatedSpanInput = z.infer<typeof SpanInputSchema>;\n","import { EventEmitter } from \"node:events\";\n\n/** Global event bus for SSE real-time updates */\nconst eventBus = new EventEmitter();\neventBus.setMaxListeners(100);\n\nexport type SpanEvent = {\n type: \"span\";\n data: unknown;\n};\n\nexport function emitSpanEvent(data: unknown): void {\n eventBus.emit(\"span\", { type: \"span\", data });\n}\n\nexport function onSpanEvent(\n handler: (event: SpanEvent) => void\n): () => void {\n eventBus.on(\"span\", handler);\n return () => eventBus.off(\"span\", handler);\n}\n","import { spansToOtlp } from \"@llmtap/shared\";\r\nimport type { SpanInput } from \"@llmtap/shared\";\r\n\r\n/**\r\n * Automatic OTLP forwarding when OTEL_EXPORTER_OTLP_ENDPOINT is set.\r\n *\r\n * Batches spans and forwards them asynchronously so ingest latency\r\n * is not affected. Follows the OpenTelemetry env var conventions:\r\n *\r\n * OTEL_EXPORTER_OTLP_ENDPOINT — Base URL (e.g. http://localhost:4318)\r\n * OTEL_EXPORTER_OTLP_HEADERS — Comma-separated key=value pairs\r\n * OTEL_SERVICE_NAME — service.name resource attribute\r\n */\r\n\r\nlet endpoint: string | null = null;\r\nlet headers: Record<string, string> = {};\r\nlet serviceName = \"llmtap\";\r\nlet buffer: SpanInput[] = [];\r\nlet flushTimer: ReturnType<typeof setTimeout> | null = null;\r\nconst FLUSH_INTERVAL_MS = 2000;\r\nconst MAX_BATCH = 100;\r\n\r\nexport function initOtlpForwarder(): boolean {\r\n const rawEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;\r\n if (!rawEndpoint) return false;\r\n\r\n // OTLP/HTTP traces endpoint: append /v1/traces if not already there\r\n endpoint = rawEndpoint.replace(/\\/+$/, \"\");\r\n if (!endpoint.endsWith(\"/v1/traces\")) {\r\n endpoint += \"/v1/traces\";\r\n }\r\n\r\n // Parse OTEL_EXPORTER_OTLP_HEADERS (comma-separated key=value)\r\n const rawHeaders = process.env.OTEL_EXPORTER_OTLP_HEADERS;\r\n if (rawHeaders) {\r\n for (const pair of rawHeaders.split(\",\")) {\r\n const eq = pair.indexOf(\"=\");\r\n if (eq > 0) {\r\n headers[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();\r\n }\r\n }\r\n }\r\n\r\n serviceName = process.env.OTEL_SERVICE_NAME ?? \"llmtap\";\r\n return true;\r\n}\r\n\r\nexport function forwardSpans(spans: SpanInput[]): void {\r\n if (!endpoint) return;\r\n\r\n buffer.push(...spans);\r\n\r\n // Flush immediately if batch is large enough\r\n if (buffer.length >= MAX_BATCH) {\r\n flushOtlpBuffer();\r\n return;\r\n }\r\n\r\n // Otherwise debounce\r\n if (!flushTimer) {\r\n flushTimer = setTimeout(flushOtlpBuffer, FLUSH_INTERVAL_MS);\r\n }\r\n}\r\n\r\nfunction flushOtlpBuffer(): void {\r\n if (flushTimer) {\r\n clearTimeout(flushTimer);\r\n flushTimer = null;\r\n }\r\n\r\n if (!endpoint || buffer.length === 0) return;\r\n\r\n const batch = buffer.splice(0, MAX_BATCH);\r\n\r\n // Cast SpanInput to Span shape (they're structurally compatible)\r\n const otlp = spansToOtlp(batch as Parameters<typeof spansToOtlp>[0], serviceName);\r\n\r\n // Fire and forget — don't block ingest\r\n fetch(endpoint, {\r\n method: \"POST\",\r\n headers: {\r\n \"Content-Type\": \"application/json\",\r\n ...headers,\r\n },\r\n body: JSON.stringify(otlp),\r\n signal: AbortSignal.timeout(10000),\r\n }).catch(() => {\r\n // Silently drop on failure — OTLP forwarding is best-effort\r\n });\r\n\r\n // If there are remaining spans, schedule another flush\r\n if (buffer.length > 0) {\r\n flushTimer = setTimeout(flushOtlpBuffer, FLUSH_INTERVAL_MS);\r\n }\r\n}\r\n\r\nexport function getOtlpEndpoint(): string | null {\r\n return endpoint;\r\n}\r\n","import type { FastifyInstance } from \"fastify\";\nimport { getDb } from \"../db.js\";\nimport { IngestRequestSchema } from \"../schemas.js\";\nimport { emitSpanEvent } from \"../events.js\";\nimport { forwardSpans } from \"../otlp-forwarder.js\";\nimport { ROUTES } from \"@llmtap/shared\";\n\nexport async function registerIngestRoute(\n app: FastifyInstance\n): Promise<void> {\n app.post(ROUTES.INGEST_SPANS, async (request, reply) => {\n const parsed = IngestRequestSchema.safeParse(request.body);\n if (!parsed.success) {\n return reply.status(400).send({\n error: \"Validation failed\",\n details: parsed.error.issues,\n });\n }\n\n const db = getDb();\n const insert = db.prepare(`\n INSERT OR REPLACE INTO spans (\n spanId, traceId, parentSpanId, name, operationName, providerName,\n startTime, endTime, duration, requestModel, responseModel,\n inputTokens, outputTokens, totalTokens,\n inputCost, outputCost, totalCost,\n temperature, maxTokens, topP,\n inputMessages, outputMessages, toolCalls,\n status, errorType, errorMessage,\n tags, sessionId, userId\n ) VALUES (\n @spanId, @traceId, @parentSpanId, @name, @operationName, @providerName,\n @startTime, @endTime, @duration, @requestModel, @responseModel,\n @inputTokens, @outputTokens, @totalTokens,\n @inputCost, @outputCost, @totalCost,\n @temperature, @maxTokens, @topP,\n @inputMessages, @outputMessages, @toolCalls,\n @status, @errorType, @errorMessage,\n @tags, @sessionId, @userId\n )\n `);\n\n const insertMany = db.transaction((spans: typeof parsed.data.spans) => {\n for (const span of spans) {\n insert.run({\n spanId: span.spanId,\n traceId: span.traceId,\n parentSpanId: span.parentSpanId ?? null,\n name: span.name,\n operationName: span.operationName,\n providerName: span.providerName,\n startTime: span.startTime,\n endTime: span.endTime ?? null,\n duration: span.duration ?? null,\n requestModel: span.requestModel,\n responseModel: span.responseModel ?? null,\n inputTokens: span.inputTokens ?? 0,\n outputTokens: span.outputTokens ?? 0,\n totalTokens: span.totalTokens ?? 0,\n inputCost: span.inputCost ?? 0,\n outputCost: span.outputCost ?? 0,\n totalCost: span.totalCost ?? 0,\n temperature: span.temperature ?? null,\n maxTokens: span.maxTokens ?? null,\n topP: span.topP ?? null,\n inputMessages: span.inputMessages\n ? JSON.stringify(span.inputMessages)\n : null,\n outputMessages: span.outputMessages\n ? JSON.stringify(span.outputMessages)\n : null,\n toolCalls: span.toolCalls\n ? JSON.stringify(span.toolCalls)\n : null,\n status: span.status,\n errorType: span.errorType ?? null,\n errorMessage: span.errorMessage ?? null,\n tags: span.tags ? JSON.stringify(span.tags) : null,\n sessionId: span.sessionId ?? null,\n userId: span.userId ?? null,\n });\n }\n });\n\n insertMany(parsed.data.spans);\n\n // Emit SSE events for each span\n for (const span of parsed.data.spans) {\n emitSpanEvent(span);\n }\n\n // Forward to OTLP endpoint if configured (async, best-effort)\n forwardSpans(parsed.data.spans);\n\n return reply.status(200).send({\n accepted: parsed.data.spans.length,\n });\n });\n}\n","import type { FastifyInstance } from \"fastify\";\nimport { getDb } from \"../db.js\";\nimport { ROUTES } from \"@llmtap/shared\";\nimport { TracesQuerySchema } from \"../schemas.js\";\n\ninterface TraceRow {\n traceId: string;\n name: string;\n startTime: number;\n endTime: number | null;\n status: string;\n spanCount: number;\n totalTokens: number;\n totalCost: number;\n totalDuration: number | null;\n}\n\ninterface SpanRow {\n spanId: string;\n traceId: string;\n parentSpanId: string | null;\n name: string;\n operationName: string;\n providerName: string;\n startTime: number;\n endTime: number | null;\n duration: number | null;\n requestModel: string;\n responseModel: string | null;\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n inputCost: number;\n outputCost: number;\n totalCost: number;\n temperature: number | null;\n maxTokens: number | null;\n topP: number | null;\n inputMessages: string | null;\n outputMessages: string | null;\n toolCalls: string | null;\n status: string;\n errorType: string | null;\n errorMessage: string | null;\n tags: string | null;\n sessionId: string | null;\n userId: string | null;\n}\n\nfunction safeJsonParse(val: string | null): unknown {\n if (!val) return undefined;\n try { return JSON.parse(val); } catch { return undefined; }\n}\n\nfunction parseSpanRow(row: SpanRow) {\n return {\n ...row,\n inputMessages: safeJsonParse(row.inputMessages),\n outputMessages: safeJsonParse(row.outputMessages),\n toolCalls: safeJsonParse(row.toolCalls),\n tags: safeJsonParse(row.tags),\n parentSpanId: row.parentSpanId ?? undefined,\n responseModel: row.responseModel ?? undefined,\n endTime: row.endTime ?? undefined,\n duration: row.duration ?? undefined,\n temperature: row.temperature ?? undefined,\n maxTokens: row.maxTokens ?? undefined,\n topP: row.topP ?? undefined,\n errorType: row.errorType ?? undefined,\n errorMessage: row.errorMessage ?? undefined,\n sessionId: row.sessionId ?? undefined,\n userId: row.userId ?? undefined,\n };\n}\n\nexport async function registerTraceRoutes(\n app: FastifyInstance\n): Promise<void> {\n // List traces\n app.get(ROUTES.LIST_TRACES, async (request, reply) => {\n const parsed = TracesQuerySchema.safeParse(request.query);\n if (!parsed.success) {\n return reply.status(400).send({ error: \"Invalid query parameters\", details: parsed.error.flatten() });\n }\n const { limit, offset, status, provider, q, periodHours } = parsed.data;\n\n const db = getDb();\n\n const whereConditions: string[] = [];\n const havingConditions: string[] = [];\n const params: Record<string, unknown> = { limit, offset };\n\n if (status) {\n havingConditions.push(\"status = @status\");\n params.status = status;\n }\n\n if (provider) {\n whereConditions.push(\"providerName = @provider\");\n params.provider = provider;\n }\n\n if (q) {\n const escaped = q.replace(/[%_]/g, \"\\\\$&\");\n whereConditions.push(`\n (\n name LIKE @search ESCAPE '\\\\' OR\n providerName LIKE @search ESCAPE '\\\\' OR\n requestModel LIKE @search ESCAPE '\\\\' OR\n COALESCE(responseModel, '') LIKE @search ESCAPE '\\\\' OR\n COALESCE(errorMessage, '') LIKE @search ESCAPE '\\\\' OR\n COALESCE(inputMessages, '') LIKE @search ESCAPE '\\\\' OR\n COALESCE(outputMessages, '') LIKE @search ESCAPE '\\\\'\n )\n `);\n params.search = `%${escaped}%`;\n }\n\n if (periodHours) {\n whereConditions.push(\"startTime >= @since\");\n params.since = Date.now() - periodHours * 60 * 60 * 1000;\n }\n\n const whereClause =\n whereConditions.length > 0 ? `WHERE ${whereConditions.join(\" AND \")}` : \"\";\n const havingClause =\n havingConditions.length > 0\n ? `HAVING ${havingConditions.join(\" AND \")}`\n : \"\";\n\n const rows = db\n .prepare(\n `\n SELECT\n traceId,\n MIN(name) as name,\n MIN(startTime) as startTime,\n MAX(endTime) as endTime,\n CASE WHEN SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) > 0\n THEN 'error' ELSE 'ok' END as status,\n COUNT(*) as spanCount,\n SUM(totalTokens) as totalTokens,\n SUM(totalCost) as totalCost,\n MAX(endTime) - MIN(startTime) as totalDuration\n FROM spans\n ${whereClause}\n GROUP BY traceId\n ${havingClause}\n ORDER BY startTime DESC\n LIMIT @limit OFFSET @offset\n `\n )\n .all(params) as TraceRow[];\n\n const totalRow = db\n .prepare(\n `\n SELECT COUNT(*) as total\n FROM (\n SELECT traceId\n FROM spans\n ${whereClause}\n GROUP BY traceId\n ${havingClause}\n ) grouped_traces\n `\n )\n .get(params) as { total: number };\n\n return reply.send({\n traces: rows.map((r) => ({\n ...r,\n endTime: r.endTime ?? undefined,\n totalDuration: r.totalDuration ?? undefined,\n })),\n total: totalRow.total,\n });\n });\n\n // Get spans for a trace\n app.get(\"/v1/traces/:traceId/spans\", async (request, reply) => {\n const { traceId } = request.params as { traceId: string };\n const db = getDb();\n\n const rows = db\n .prepare(\n `SELECT * FROM spans WHERE traceId = @traceId ORDER BY startTime ASC`\n )\n .all({ traceId }) as SpanRow[];\n\n return reply.send({\n spans: rows.map(parseSpanRow),\n });\n });\n}\n","import type { FastifyInstance } from \"fastify\";\nimport { getDb } from \"../db.js\";\nimport { ROUTES } from \"@llmtap/shared\";\nimport { StatsQuerySchema } from \"../schemas.js\";\n\ninterface StatsRow {\n totalTraces: number;\n totalSpans: number;\n totalTokens: number;\n totalCost: number;\n avgDuration: number;\n errorCount: number;\n}\n\ninterface ProviderStatsRow {\n provider: string;\n spanCount: number;\n totalTokens: number;\n totalCost: number;\n avgDuration: number;\n}\n\ninterface ModelStatsRow {\n model: string;\n provider: string;\n spanCount: number;\n totalTokens: number;\n totalCost: number;\n avgDuration: number;\n}\n\ninterface CostRow {\n bucket: number;\n cost: number;\n tokens: number;\n spans: number;\n}\n\nexport async function registerStatsRoute(\n app: FastifyInstance\n): Promise<void> {\n app.get(ROUTES.GET_STATS, async (request, reply) => {\n const parsed = StatsQuerySchema.safeParse(request.query);\n if (!parsed.success) {\n return reply.status(400).send({ error: \"Invalid query parameters\", details: parsed.error.flatten() });\n }\n const periodHours = parsed.data.period;\n const since = Date.now() - periodHours * 60 * 60 * 1000;\n\n const db = getDb();\n\n // Aggregate stats\n const stats = db\n .prepare(\n `\n SELECT\n COUNT(DISTINCT traceId) as totalTraces,\n COUNT(*) as totalSpans,\n COALESCE(SUM(totalTokens), 0) as totalTokens,\n COALESCE(SUM(totalCost), 0) as totalCost,\n COALESCE(AVG(duration), 0) as avgDuration,\n SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) as errorCount\n FROM spans\n WHERE startTime >= @since\n `\n )\n .get({ since }) as StatsRow;\n\n // By provider\n const byProvider = db\n .prepare(\n `\n SELECT\n providerName as provider,\n COUNT(*) as spanCount,\n COALESCE(SUM(totalTokens), 0) as totalTokens,\n COALESCE(SUM(totalCost), 0) as totalCost,\n COALESCE(AVG(duration), 0) as avgDuration\n FROM spans\n WHERE startTime >= @since\n GROUP BY providerName\n ORDER BY totalCost DESC\n `\n )\n .all({ since }) as ProviderStatsRow[];\n\n // By model\n const byModel = db\n .prepare(\n `\n SELECT\n requestModel as model,\n providerName as provider,\n COUNT(*) as spanCount,\n COALESCE(SUM(totalTokens), 0) as totalTokens,\n COALESCE(SUM(totalCost), 0) as totalCost,\n COALESCE(AVG(duration), 0) as avgDuration\n FROM spans\n WHERE startTime >= @since\n GROUP BY requestModel, providerName\n ORDER BY totalCost DESC\n `\n )\n .all({ since }) as ModelStatsRow[];\n\n // Cost over time (hourly buckets)\n const costOverTime = db\n .prepare(\n `\n SELECT\n (startTime / 3600000) * 3600000 as bucket,\n COALESCE(SUM(totalCost), 0) as cost,\n COALESCE(SUM(totalTokens), 0) as tokens,\n COUNT(*) as spans\n FROM spans\n WHERE startTime >= @since\n GROUP BY bucket\n ORDER BY bucket ASC\n `\n )\n .all({ since }) as CostRow[];\n\n return reply.send({\n period: `${periodHours}h`,\n ...stats,\n errorRate:\n stats.totalSpans > 0\n ? stats.errorCount / stats.totalSpans\n : 0,\n byProvider,\n byModel,\n costOverTime: costOverTime.map((r) => ({\n timestamp: r.bucket,\n cost: r.cost,\n tokens: r.tokens,\n spans: r.spans,\n })),\n });\n });\n}\n","import type { FastifyInstance } from \"fastify\";\nimport { ROUTES } from \"@llmtap/shared\";\nimport { onSpanEvent } from \"../events.js\";\n\nconst MAX_SSE_CONNECTIONS = 50;\nlet sseConnectionCount = 0;\n\nexport async function registerSSERoute(\n app: FastifyInstance\n): Promise<void> {\n app.get(ROUTES.SSE_STREAM, async (request, reply) => {\n if (sseConnectionCount >= MAX_SSE_CONNECTIONS) {\n return reply.status(503).send({ error: \"Too many SSE connections\" });\n }\n sseConnectionCount++;\n\n reply.raw.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n\n // Send initial heartbeat\n reply.raw.write(\"event: connected\\ndata: {}\\n\\n\");\n\n // Set up heartbeat interval\n const heartbeat = setInterval(() => {\n reply.raw.write(\":heartbeat\\n\\n\");\n }, 15000);\n\n // Subscribe to span events\n const unsubscribe = onSpanEvent((event) => {\n reply.raw.write(\n `event: ${event.type}\\ndata: ${JSON.stringify(event.data)}\\n\\n`\n );\n });\n\n // Cleanup on disconnect\n request.raw.on(\"close\", () => {\n sseConnectionCount--;\n clearInterval(heartbeat);\n unsubscribe();\n });\n });\n}\n","import type { FastifyInstance } from \"fastify\";\nimport { getDb } from \"../db.js\";\nimport { ROUTES } from \"@llmtap/shared\";\nimport { SessionsQuerySchema } from \"../schemas.js\";\n\ninterface SessionRow {\n sessionId: string;\n traceCount: number;\n spanCount: number;\n totalTokens: number;\n totalCost: number;\n firstSeen: number;\n lastSeen: number;\n errorCount: number;\n}\n\nexport async function registerSessionsRoute(\n app: FastifyInstance\n): Promise<void> {\n app.get(ROUTES.GET_SESSIONS, async (request, reply) => {\n const parsed = SessionsQuerySchema.safeParse(request.query);\n if (!parsed.success) {\n return reply.status(400).send({ error: \"Invalid query parameters\", details: parsed.error.flatten() });\n }\n const { periodHours, limit, offset } = parsed.data;\n const since = Date.now() - periodHours * 60 * 60 * 1000;\n\n const db = getDb();\n\n const rows = db\n .prepare(\n `\n SELECT\n sessionId,\n COUNT(DISTINCT traceId) as traceCount,\n COUNT(*) as spanCount,\n COALESCE(SUM(totalTokens), 0) as totalTokens,\n COALESCE(SUM(totalCost), 0) as totalCost,\n MIN(startTime) as firstSeen,\n MAX(startTime) as lastSeen,\n SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) as errorCount\n FROM spans\n WHERE sessionId IS NOT NULL AND sessionId != '' AND startTime >= @since\n GROUP BY sessionId\n ORDER BY lastSeen DESC\n LIMIT @limit OFFSET @offset\n `\n )\n .all({ since, limit, offset }) as SessionRow[];\n\n const totalRow = db\n .prepare(\n `\n SELECT COUNT(DISTINCT sessionId) as total\n FROM spans\n WHERE sessionId IS NOT NULL AND sessionId != '' AND startTime >= @since\n `\n )\n .get({ since }) as { total: number };\n\n return reply.send({ sessions: rows, total: totalRow.total });\n });\n}\n","import type { FastifyInstance } from \"fastify\";\r\nimport path from \"node:path\";\r\nimport fs from \"node:fs\";\r\nimport os from \"node:os\";\r\nimport { getDb } from \"../db.js\";\r\nimport { DB_DIR_NAME, DB_FILE_NAME, ROUTES } from \"@llmtap/shared\";\r\n\r\nexport async function registerDbInfoRoute(\r\n app: FastifyInstance\r\n): Promise<void> {\r\n app.get(ROUTES.GET_DB_INFO, async (_request, reply) => {\r\n const db = getDb();\r\n\r\n const dbDir = process.env.LLMTAP_DB_DIR\r\n ? path.resolve(process.env.LLMTAP_DB_DIR)\r\n : path.join(os.homedir(), DB_DIR_NAME);\r\n const dbPath = process.env.LLMTAP_DB_PATH\r\n ? path.resolve(process.env.LLMTAP_DB_PATH)\r\n : path.join(dbDir, DB_FILE_NAME);\r\n\r\n let sizeBytes = 0;\r\n try {\r\n const stat = fs.statSync(dbPath);\r\n sizeBytes = stat.size;\r\n } catch {\r\n /* db file may not exist yet */\r\n }\r\n\r\n const spanCount = (\r\n db.prepare(\"SELECT COUNT(*) as count FROM spans\").get() as {\r\n count: number;\r\n }\r\n ).count;\r\n\r\n const traceCount = (\r\n db\r\n .prepare(\"SELECT COUNT(DISTINCT traceId) as count FROM spans\")\r\n .get() as { count: number }\r\n ).count;\r\n\r\n const oldestSpan = (\r\n db\r\n .prepare(\"SELECT MIN(startTime) as oldest FROM spans\")\r\n .get() as { oldest: number | null }\r\n ).oldest;\r\n\r\n const newestSpan = (\r\n db\r\n .prepare(\"SELECT MAX(startTime) as newest FROM spans\")\r\n .get() as { newest: number | null }\r\n ).newest;\r\n\r\n return reply.send({\r\n path: dbPath,\r\n sizeBytes,\r\n spanCount,\r\n traceCount,\r\n oldestSpan,\r\n newestSpan,\r\n walMode: (db.pragma(\"journal_mode\") as { journal_mode: string }[])[0]\r\n ?.journal_mode,\r\n });\r\n });\r\n}\r\n","import type { FastifyInstance } from \"fastify\";\r\nimport { getDb } from \"../db.js\";\r\n\r\ninterface Insight {\r\n id: string;\r\n type: \"cost_anomaly\" | \"error_pattern\" | \"model_recommendation\" | \"token_waste\";\r\n severity: \"info\" | \"warning\" | \"critical\";\r\n title: string;\r\n description: string;\r\n metric?: string;\r\n}\r\n\r\ninterface CostAnomalyRow {\r\n traceId: string;\r\n name: string;\r\n totalCost: number;\r\n avgCost: number;\r\n}\r\n\r\ninterface ErrorPatternRow {\r\n errorType: string;\r\n errorCount: number;\r\n latestTime: number;\r\n}\r\n\r\ninterface ModelUsageRow {\r\n model: string;\r\n provider: string;\r\n spanCount: number;\r\n avgTokens: number;\r\n totalCost: number;\r\n avgCost: number;\r\n}\r\n\r\ninterface TokenWasteRow {\r\n name: string;\r\n model: string;\r\n avgInputTokens: number;\r\n avgOutputTokens: number;\r\n spanCount: number;\r\n inputRatio: number;\r\n}\r\n\r\nlet insightsCache: { data: { insights: Insight[] }; timestamp: number } | null = null;\r\nconst INSIGHTS_CACHE_TTL_MS = 30_000; // 30 seconds\r\n\r\nexport async function registerInsightsRoute(\r\n app: FastifyInstance\r\n): Promise<void> {\r\n app.get(\"/v1/insights\", async (_request, reply) => {\r\n const now = Date.now();\r\n if (insightsCache && (now - insightsCache.timestamp) < INSIGHTS_CACHE_TTL_MS) {\r\n return reply.send(insightsCache.data);\r\n }\r\n\r\n const db = getDb();\r\n const insights: Insight[] = [];\r\n const since24h = Date.now() - 24 * 60 * 60 * 1000;\r\n const since7d = Date.now() - 7 * 24 * 60 * 60 * 1000;\r\n\r\n // 1. Cost anomalies: traces that cost >5x the average\r\n try {\r\n const anomalies = db\r\n .prepare(\r\n `\r\n WITH avg_cost AS (\r\n SELECT AVG(trace_cost) as avgCost\r\n FROM (\r\n SELECT traceId, SUM(totalCost) as trace_cost\r\n FROM spans\r\n WHERE startTime >= @since\r\n GROUP BY traceId\r\n )\r\n )\r\n SELECT\r\n s.traceId,\r\n MIN(s.name) as name,\r\n SUM(s.totalCost) as totalCost,\r\n (SELECT avgCost FROM avg_cost) as avgCost\r\n FROM spans s\r\n WHERE s.startTime >= @since\r\n GROUP BY s.traceId\r\n HAVING totalCost > (SELECT avgCost FROM avg_cost) * 5 AND totalCost > 0.01\r\n ORDER BY totalCost DESC\r\n LIMIT 3\r\n `\r\n )\r\n .all({ since: since24h }) as CostAnomalyRow[];\r\n\r\n for (const a of anomalies) {\r\n const multiplier = a.avgCost > 0 ? Math.round(a.totalCost / a.avgCost) : 0;\r\n insights.push({\r\n id: `cost_anomaly_${a.traceId}`,\r\n type: \"cost_anomaly\",\r\n severity: multiplier > 20 ? \"critical\" : \"warning\",\r\n title: `High cost trace detected`,\r\n description: `\"${a.name}\" cost $${a.totalCost.toFixed(4)} — ${multiplier}x your average trace cost of $${a.avgCost.toFixed(4)}.`,\r\n metric: `${multiplier}x avg`,\r\n });\r\n }\r\n } catch { /* ignore query errors */ }\r\n\r\n // 2. Error patterns: recurring errors\r\n try {\r\n const errors = db\r\n .prepare(\r\n `\r\n SELECT\r\n COALESCE(errorType, 'Unknown') as errorType,\r\n COUNT(*) as errorCount,\r\n MAX(startTime) as latestTime\r\n FROM spans\r\n WHERE status = 'error' AND startTime >= @since\r\n GROUP BY errorType\r\n HAVING errorCount >= 3\r\n ORDER BY errorCount DESC\r\n LIMIT 3\r\n `\r\n )\r\n .all({ since: since7d }) as ErrorPatternRow[];\r\n\r\n for (const e of errors) {\r\n insights.push({\r\n id: `error_pattern_${e.errorType}`,\r\n type: \"error_pattern\",\r\n severity: e.errorCount > 20 ? \"critical\" : \"warning\",\r\n title: `Recurring errors: ${e.errorType}`,\r\n description: `${e.errorCount} \"${e.errorType}\" errors in the last 7 days.`,\r\n metric: `${e.errorCount} errors`,\r\n });\r\n }\r\n } catch { /* ignore */ }\r\n\r\n // 3. Model recommendations: if an expensive model is used for simple (low-token) tasks\r\n try {\r\n const usage = db\r\n .prepare(\r\n `\r\n SELECT\r\n requestModel as model,\r\n providerName as provider,\r\n COUNT(*) as spanCount,\r\n AVG(totalTokens) as avgTokens,\r\n SUM(totalCost) as totalCost,\r\n AVG(totalCost) as avgCost\r\n FROM spans\r\n WHERE startTime >= @since AND status = 'ok'\r\n GROUP BY requestModel, providerName\r\n ORDER BY totalCost DESC\r\n `\r\n )\r\n .all({ since: since7d }) as ModelUsageRow[];\r\n\r\n // Find expensive models used for low-token tasks (likely could use a cheaper model)\r\n for (const u of usage) {\r\n const isExpensive =\r\n u.model.includes(\"gpt-4o\") && !u.model.includes(\"mini\") ||\r\n u.model.includes(\"gpt-4-\") ||\r\n u.model.includes(\"claude-3-opus\") ||\r\n u.model.includes(\"claude-opus\") ||\r\n u.model.includes(\"claude-4\") && !u.model.includes(\"haiku\");\r\n const isLowToken = u.avgTokens < 500;\r\n\r\n if (isExpensive && isLowToken && u.spanCount >= 5 && u.totalCost > 0.05) {\r\n insights.push({\r\n id: `model_rec_${u.model}`,\r\n type: \"model_recommendation\",\r\n severity: \"info\",\r\n title: `Consider a lighter model for \"${u.model}\"`,\r\n description: `${u.spanCount} calls averaging ${Math.round(u.avgTokens)} tokens — a smaller model could save ~$${(u.totalCost * 0.7).toFixed(2)}.`,\r\n metric: `$${u.totalCost.toFixed(2)} spent`,\r\n });\r\n }\r\n }\r\n } catch { /* ignore */ }\r\n\r\n // 4. Token waste: system prompts that are disproportionately large compared to output\r\n try {\r\n const wasteRows = db\r\n .prepare(\r\n `\r\n SELECT\r\n name,\r\n requestModel as model,\r\n AVG(inputTokens) as avgInputTokens,\r\n AVG(outputTokens) as avgOutputTokens,\r\n COUNT(*) as spanCount,\r\n CASE WHEN AVG(outputTokens) > 0\r\n THEN CAST(AVG(inputTokens) AS REAL) / AVG(outputTokens)\r\n ELSE 0\r\n END as inputRatio\r\n FROM spans\r\n WHERE startTime >= @since AND status = 'ok' AND inputTokens > 0\r\n GROUP BY name, requestModel\r\n HAVING inputRatio > 10 AND avgInputTokens > 1000 AND spanCount >= 3\r\n ORDER BY inputRatio DESC\r\n LIMIT 3\r\n `\r\n )\r\n .all({ since: since7d }) as TokenWasteRow[];\r\n\r\n for (const w of wasteRows) {\r\n insights.push({\r\n id: `token_waste_${w.name}_${w.model}`,\r\n type: \"token_waste\",\r\n severity: \"info\",\r\n title: `High input-to-output token ratio`,\r\n description: `\"${w.name}\" uses ~${Math.round(w.avgInputTokens)} input tokens to generate ~${Math.round(w.avgOutputTokens)} output tokens (${Math.round(w.inputRatio)}:1 ratio). Consider compressing the system prompt.`,\r\n metric: `${Math.round(w.inputRatio)}:1 ratio`,\r\n });\r\n }\r\n } catch { /* ignore */ }\r\n\r\n const result = { insights };\r\n insightsCache = { data: result, timestamp: Date.now() };\r\n return reply.send(result);\r\n });\r\n}\r\n","import type { FastifyInstance } from \"fastify\";\nimport { z } from \"zod\";\nimport { getDb } from \"../db.js\";\n\nconst ReplaySchema = z.object({\n spanId: z.string().min(1).max(256),\n apiKey: z.string().min(1).max(512),\n});\n\ninterface SpanRow {\n providerName: string;\n requestModel: string;\n inputMessages: string | null;\n temperature: number | null;\n maxTokens: number | null;\n topP: number | null;\n}\n\nexport async function registerReplayRoute(\n app: FastifyInstance\n): Promise<void> {\n app.post(\"/v1/replay\", async (request, reply) => {\n const parsed = ReplaySchema.safeParse(request.body);\n if (!parsed.success) {\n return reply.status(400).send({\n error: \"Validation failed\",\n details: parsed.error.issues,\n });\n }\n const { spanId, apiKey } = parsed.data;\n\n const db = getDb();\n const span = db\n .prepare(\n `SELECT providerName, requestModel, inputMessages, temperature, maxTokens, topP\n FROM spans WHERE spanId = ?`\n )\n .get(spanId) as SpanRow | undefined;\n\n if (!span) {\n return reply.status(404).send({ error: \"Span not found\" });\n }\n\n if (!span.inputMessages) {\n return reply.status(400).send({\n error: \"Span has no input messages to replay\",\n });\n }\n\n let messages: unknown[];\n try {\n messages = JSON.parse(span.inputMessages);\n } catch {\n return reply.status(400).send({ error: \"Failed to parse input messages\" });\n }\n\n const startTime = Date.now();\n\n try {\n if (span.providerName === \"anthropic\") {\n const result = await replayAnthropic(\n apiKey,\n span.requestModel,\n messages,\n span.temperature,\n span.maxTokens\n );\n return reply.send({\n ...result,\n duration: Date.now() - startTime,\n provider: \"anthropic\",\n model: span.requestModel,\n });\n }\n\n // Default to OpenAI-compatible format\n const result = await replayOpenAI(\n apiKey,\n span.requestModel,\n messages,\n span.temperature,\n span.maxTokens,\n span.topP,\n span.providerName\n );\n return reply.send({\n ...result,\n duration: Date.now() - startTime,\n provider: span.providerName,\n model: span.requestModel,\n });\n } catch (err) {\n return reply.status(502).send({\n error: \"Replay failed\",\n message: err instanceof Error ? err.message : String(err),\n duration: Date.now() - startTime,\n });\n }\n });\n}\n\nasync function replayOpenAI(\n apiKey: string,\n model: string,\n messages: unknown[],\n temperature: number | null,\n maxTokens: number | null,\n topP: number | null,\n provider: string\n): Promise<{\n content: string;\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n responseModel: string;\n}> {\n const baseUrls: Record<string, string> = {\n openai: \"https://api.openai.com/v1\",\n deepseek: \"https://api.deepseek.com/v1\",\n groq: \"https://api.groq.com/openai/v1\",\n together: \"https://api.together.xyz/v1\",\n fireworks: \"https://api.fireworks.ai/inference/v1\",\n openrouter: \"https://openrouter.ai/api/v1\",\n xai: \"https://api.x.ai/v1\",\n };\n\n const baseUrl = baseUrls[provider] ?? baseUrls.openai;\n\n const reqBody: Record<string, unknown> = { model, messages };\n if (temperature !== null) reqBody.temperature = temperature;\n if (maxTokens !== null) reqBody.max_tokens = maxTokens;\n if (topP !== null) reqBody.top_p = topP;\n\n const res = await fetch(`${baseUrl}/chat/completions`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(reqBody),\n signal: AbortSignal.timeout(60_000),\n });\n\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(`API returned HTTP ${res.status}: ${text.slice(0, 500)}`);\n }\n\n const data = (await res.json()) as {\n choices: { message: { content: string } }[];\n usage: { prompt_tokens: number; completion_tokens: number; total_tokens: number };\n model: string;\n };\n\n const choice = data.choices?.[0];\n return {\n content: choice?.message?.content ?? \"\",\n inputTokens: data.usage?.prompt_tokens ?? 0,\n outputTokens: data.usage?.completion_tokens ?? 0,\n totalTokens: data.usage?.total_tokens ?? 0,\n responseModel: data.model ?? model,\n };\n}\n\nasync function replayAnthropic(\n apiKey: string,\n model: string,\n rawMessages: unknown[],\n temperature: number | null,\n maxTokens: number | null\n): Promise<{\n content: string;\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n responseModel: string;\n}> {\n // Anthropic requires system messages to be separate\n const messages = rawMessages as { role: string; content: unknown }[];\n const systemMsg = messages.find((m) => m.role === \"system\");\n const nonSystemMessages = messages.filter((m) => m.role !== \"system\");\n\n const reqBody: Record<string, unknown> = {\n model,\n messages: nonSystemMessages,\n max_tokens: maxTokens ?? 4096,\n };\n if (systemMsg) reqBody.system = systemMsg.content;\n if (temperature !== null) reqBody.temperature = temperature;\n\n const res = await fetch(\"https://api.anthropic.com/v1/messages\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": apiKey,\n \"anthropic-version\": \"2023-06-01\",\n },\n body: JSON.stringify(reqBody),\n signal: AbortSignal.timeout(60_000),\n });\n\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(`API returned HTTP ${res.status}: ${text.slice(0, 500)}`);\n }\n\n const data = (await res.json()) as {\n content: { type: string; text: string }[];\n usage: { input_tokens: number; output_tokens: number };\n model: string;\n };\n\n const textContent = data.content\n ?.filter((c) => c.type === \"text\")\n .map((c) => c.text)\n .join(\"\") ?? \"\";\n\n const inputTokens = data.usage?.input_tokens ?? 0;\n const outputTokens = data.usage?.output_tokens ?? 0;\n\n return {\n content: textContent,\n inputTokens,\n outputTokens,\n totalTokens: inputTokens + outputTokens,\n responseModel: data.model ?? model,\n };\n}\n","import type { FastifyInstance } from \"fastify\";\r\nimport { z } from \"zod\";\r\nimport { spansToOtlp } from \"@llmtap/shared\";\r\nimport type { Span } from \"@llmtap/shared\";\r\nimport { getDb } from \"../db.js\";\r\n\r\ninterface SpanRow {\r\n spanId: string;\r\n traceId: string;\r\n parentSpanId: string | null;\r\n name: string;\r\n operationName: string;\r\n providerName: string;\r\n startTime: number;\r\n endTime: number | null;\r\n duration: number | null;\r\n requestModel: string;\r\n responseModel: string | null;\r\n inputTokens: number;\r\n outputTokens: number;\r\n totalTokens: number;\r\n inputCost: number;\r\n outputCost: number;\r\n totalCost: number;\r\n temperature: number | null;\r\n maxTokens: number | null;\r\n topP: number | null;\r\n inputMessages: string | null;\r\n outputMessages: string | null;\r\n toolCalls: string | null;\r\n status: \"ok\" | \"error\";\r\n errorType: string | null;\r\n errorMessage: string | null;\r\n tags: string | null;\r\n sessionId: string | null;\r\n userId: string | null;\r\n}\r\n\r\nfunction safeParse(val: string | null): unknown {\r\n if (!val) return undefined;\r\n try { return JSON.parse(val); } catch { return undefined; }\r\n}\r\n\r\nfunction rowToSpan(row: SpanRow): Span {\r\n return {\r\n spanId: row.spanId,\r\n traceId: row.traceId,\r\n parentSpanId: row.parentSpanId ?? undefined,\r\n name: row.name,\r\n operationName: row.operationName,\r\n providerName: row.providerName,\r\n startTime: row.startTime,\r\n endTime: row.endTime ?? undefined,\r\n duration: row.duration ?? undefined,\r\n requestModel: row.requestModel,\r\n responseModel: row.responseModel ?? undefined,\r\n inputTokens: row.inputTokens,\r\n outputTokens: row.outputTokens,\r\n totalTokens: row.totalTokens,\r\n inputCost: row.inputCost,\r\n outputCost: row.outputCost,\r\n totalCost: row.totalCost,\r\n temperature: row.temperature ?? undefined,\r\n maxTokens: row.maxTokens ?? undefined,\r\n topP: row.topP ?? undefined,\r\n inputMessages: safeParse(row.inputMessages) as Span[\"inputMessages\"],\r\n outputMessages: safeParse(row.outputMessages) as Span[\"outputMessages\"],\r\n toolCalls: safeParse(row.toolCalls) as Span[\"toolCalls\"],\r\n status: row.status,\r\n errorType: row.errorType ?? undefined,\r\n errorMessage: row.errorMessage ?? undefined,\r\n tags: safeParse(row.tags) as Record<string, string> | undefined,\r\n sessionId: row.sessionId ?? undefined,\r\n userId: row.userId ?? undefined,\r\n };\r\n}\r\n\r\n// Headers that must not be overridden by user input\r\nconst BLOCKED_HEADERS = new Set([\r\n \"host\", \"content-length\", \"transfer-encoding\", \"connection\",\r\n \"keep-alive\", \"upgrade\", \"proxy-authorization\", \"te\", \"trailer\",\r\n]);\r\n\r\nfunction sanitizeHeaders(\r\n userHeaders: Record<string, string> | undefined\r\n): Record<string, string> {\r\n const result: Record<string, string> = { \"Content-Type\": \"application/json\" };\r\n if (!userHeaders) return result;\r\n for (const [key, value] of Object.entries(userHeaders)) {\r\n if (typeof key === \"string\" && typeof value === \"string\" && !BLOCKED_HEADERS.has(key.toLowerCase())) {\r\n result[key] = value;\r\n }\r\n }\r\n return result;\r\n}\r\n\r\nconst OtlpExportQuerySchema = z.object({\r\n limit: z.coerce.number().int().min(1).max(5000).default(1000),\r\n periodHours: z.coerce.number().int().min(0).max(8760).default(0),\r\n traceId: z.string().max(256).optional(),\r\n service: z.string().max(256).default(\"llmtap\"),\r\n});\r\n\r\nconst OtlpForwardSchema = z.object({\r\n endpoint: z.string().min(1).max(2048).url().refine(\r\n (url) => {\r\n try {\r\n const parsed = new URL(url);\r\n return parsed.protocol === \"http:\" || parsed.protocol === \"https:\";\r\n } catch {\r\n return false;\r\n }\r\n },\r\n { message: \"endpoint must use http or https protocol\" }\r\n ),\r\n headers: z.record(z.string().max(4096)).optional(),\r\n limit: z.number().int().min(1).max(5000).optional(),\r\n periodHours: z.number().int().min(0).max(8760).optional(),\r\n service: z.string().max(256).optional(),\r\n});\r\n\r\nexport async function registerOtlpExportRoute(app: FastifyInstance): Promise<void> {\r\n /**\r\n * GET /v1/export/otlp\r\n *\r\n * Export spans as OTLP JSON. Accepts query params:\r\n * ?limit=1000 Max spans to export (default 1000, max 5000)\r\n * ?periodHours=24 Only spans from the last N hours\r\n * ?traceId=abc Only spans from a specific trace\r\n * ?service=myapp service.name resource attribute (default \"llmtap\")\r\n */\r\n app.get(\"/v1/export/otlp\", async (request, reply) => {\r\n const parsed = OtlpExportQuerySchema.safeParse(request.query);\r\n if (!parsed.success) {\r\n return reply.status(400).send({ error: \"Validation failed\", details: parsed.error.issues });\r\n }\r\n const { limit, periodHours, traceId, service: serviceName } = parsed.data;\r\n\r\n const db = getDb();\r\n const conditions: string[] = [];\r\n const params: unknown[] = [];\r\n\r\n if (periodHours > 0) {\r\n conditions.push(\"startTime >= ?\");\r\n params.push(Date.now() - periodHours * 3600_000);\r\n }\r\n if (traceId) {\r\n conditions.push(\"traceId = ?\");\r\n params.push(traceId);\r\n }\r\n\r\n const where = conditions.length > 0 ? `WHERE ${conditions.join(\" AND \")}` : \"\";\r\n params.push(limit);\r\n\r\n const rows = db\r\n .prepare(`SELECT * FROM spans ${where} ORDER BY startTime DESC LIMIT ?`)\r\n .all(...params) as SpanRow[];\r\n\r\n const spans = rows.map(rowToSpan);\r\n const otlp = spansToOtlp(spans, serviceName);\r\n\r\n return reply\r\n .header(\"Content-Type\", \"application/json\")\r\n .send(otlp);\r\n });\r\n\r\n /**\r\n * POST /v1/export/otlp/forward\r\n *\r\n * Forward stored spans to an external OTLP endpoint.\r\n * Body: { endpoint: \"http://localhost:4318/v1/traces\", headers?: {}, limit?: 1000, periodHours?: 24 }\r\n */\r\n app.post(\"/v1/export/otlp/forward\", async (request, reply) => {\r\n const parsed = OtlpForwardSchema.safeParse(request.body);\r\n if (!parsed.success) {\r\n return reply.status(400).send({ error: \"Validation failed\", details: parsed.error.issues });\r\n }\r\n const body = parsed.data;\r\n\r\n const limit = body.limit ?? 1000;\r\n const periodHours = body.periodHours ?? 0;\r\n const serviceName = body.service ?? \"llmtap\";\r\n\r\n const db = getDb();\r\n const conditions: string[] = [];\r\n const params: unknown[] = [];\r\n\r\n if (periodHours > 0) {\r\n conditions.push(\"startTime >= ?\");\r\n params.push(Date.now() - periodHours * 3600_000);\r\n }\r\n\r\n const where = conditions.length > 0 ? `WHERE ${conditions.join(\" AND \")}` : \"\";\r\n params.push(limit);\r\n\r\n const rows = db\r\n .prepare(`SELECT * FROM spans ${where} ORDER BY startTime DESC LIMIT ?`)\r\n .all(...params) as SpanRow[];\r\n\r\n const spans = rows.map(rowToSpan);\r\n const otlp = spansToOtlp(spans, serviceName);\r\n\r\n try {\r\n const res = await fetch(body.endpoint, {\r\n method: \"POST\",\r\n headers: sanitizeHeaders(body.headers),\r\n body: JSON.stringify(otlp),\r\n signal: AbortSignal.timeout(30000),\r\n });\r\n\r\n if (!res.ok) {\r\n const text = await res.text().catch(() => \"\");\r\n return reply.status(502).send({\r\n error: \"OTLP endpoint returned error\",\r\n status: res.status,\r\n body: text.slice(0, 500),\r\n });\r\n }\r\n\r\n return reply.send({\r\n status: \"ok\",\r\n spanCount: spans.length,\r\n endpoint: body.endpoint,\r\n });\r\n } catch (err) {\r\n return reply.status(502).send({\r\n error: \"Failed to reach OTLP endpoint\",\r\n message: err instanceof Error ? err.message : String(err),\r\n });\r\n }\r\n });\r\n}\r\n"],"mappings":"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","names":["z","db","db","db","ROUTES","ROUTES","db","ROUTES","ROUTES","db","ROUTES","ROUTES","ROUTES","ROUTES","db","path","fs","os","DB_DIR_NAME","DB_FILE_NAME","ROUTES","db","path","os","fs","db","z","z","db","result","z","spansToOtlp","z","serviceName","db","spansToOtlp","z"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@llmtap/collector",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "LLMTap Collector - Local ingestion server and storage",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@fastify/cors": "^10.0.0",
|
|
20
|
+
"@fastify/static": "^8.0.0",
|
|
21
|
+
"better-sqlite3": "^11.0.0",
|
|
22
|
+
"fastify": "^5.2.0",
|
|
23
|
+
"pino-pretty": "^13.1.3",
|
|
24
|
+
"zod": "^3.24.0",
|
|
25
|
+
"@llmtap/shared": "0.1.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/better-sqlite3": "^7.6.0",
|
|
29
|
+
"@types/node": "^25.4.0",
|
|
30
|
+
"rimraf": "^6.0.0",
|
|
31
|
+
"tsup": "^8.4.0",
|
|
32
|
+
"tsx": "^4.19.0",
|
|
33
|
+
"typescript": "^5.7.0",
|
|
34
|
+
"vitest": "^3.0.0"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"llm",
|
|
38
|
+
"ai",
|
|
39
|
+
"collector",
|
|
40
|
+
"observability",
|
|
41
|
+
"tracing",
|
|
42
|
+
"sqlite",
|
|
43
|
+
"fastify"
|
|
44
|
+
],
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "https://github.com/llmtap/llmtap",
|
|
49
|
+
"directory": "packages/collector"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsup",
|
|
53
|
+
"dev": "tsx watch src/index.ts",
|
|
54
|
+
"test": "vitest run",
|
|
55
|
+
"clean": "rimraf dist"
|
|
56
|
+
}
|
|
57
|
+
}
|