@agimon-ai/log-sink-mcp 0.2.13 → 0.3.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/cli.cjs +11 -11
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +22 -22
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{stdio-D5FY3NXS.cjs → stdio-D-S2Mhwk.cjs} +17 -17
- package/dist/stdio-D-S2Mhwk.cjs.map +1 -0
- package/dist/stdio-DSDgdLCE.mjs +245 -0
- package/dist/stdio-DSDgdLCE.mjs.map +1 -0
- package/package.json +3 -3
- package/dist/stdio-7vqC2Ouf.mjs +0 -245
- package/dist/stdio-7vqC2Ouf.mjs.map +0 -1
- package/dist/stdio-D5FY3NXS.cjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"stdio-7vqC2Ouf.mjs","names":["HttpServerHealthCheck","HttpServerManager","healthCheck: HttpServerHealthCheck","repoRoot?: string","registryPath?: string","distCliPath: string | undefined","srcCliPath: string | undefined","command: string","args: string[]","resolve","EmbeddingService","storage: LogStorageService","modelBytes: Uint8Array","tokenizerJson: string","embeddedRows: IndexedEmbeddingRow[]","asRows","LogQueryService","storage: LogStorageService","params: Array<string | number>","timeline: TraceTimelineEntry[]","filters: {\n level?: LogLevel | LogLevel[];\n service?: string | string[];\n traceId?: string;\n }","LogRetentionService","storage: LogStorageService","asRows","LogSearchService","storage: LogStorageService","params: Array<string | number>","asRows","LogStorageService","params: Array<string | number>","SemanticSearchService","storage: LogStorageService","searchService: LogSearchService","embeddings: EmbeddingService","params: Array<string | number>","timeRange: { start: Date; end: Date } | undefined","stat","filters: any","container: Container"],"sources":["../src/services/HttpServerHealthCheck.ts","../src/types/container.types.ts","../src/utils/resolveWorkspaceRoot.ts","../src/services/HttpServerManager.ts","../src/services/EmbeddingService.ts","../src/services/LogQueryService.ts","../src/services/LogRetentionService.ts","../src/services/LogSearchService.ts","../src/services/LogStorageService.ts","../src/services/SemanticSearchService.ts","../src/container/index.ts","../src/tools/AnalyzeErrorsTool.ts","../src/tools/ClearLogsTool.ts","../src/tools/GetLogStatsTool.ts","../src/tools/GetServicesTool.ts","../src/tools/GetTraceTimelineTool.ts","../src/tools/QueryLogsTool.ts","../src/tools/SearchLogsTool.ts","../src/server/index.ts","../src/transports/stdio.ts"],"sourcesContent":["/**\n * HttpServerHealthCheck\n *\n * Service for checking HTTP server health status using native fetch.\n * Pings /health endpoint to determine if server is running.\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Single responsibility principle\n * - Dependency injection with InversifyJS\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n * - Document complex logic with comments\n * - Return typed results\n *\n * AVOID:\n * - Mixing concerns (keep focused on single domain)\n * - Direct tool implementation (services should be tool-agnostic)\n * - Synchronous blocking operations\n * - Missing error handling\n */\n\nimport { injectable } from 'inversify';\nimport 'reflect-metadata';\n\n/**\n * Health check result\n */\nexport interface HealthCheckResult {\n /**\n * Whether the server is healthy and responding\n */\n healthy: boolean;\n\n /**\n * Port number if server is healthy\n */\n port?: number;\n\n /**\n * Service name from health response (e.g., \"log-sink-mcp\")\n */\n serviceName?: string;\n\n /**\n * Error message if health check failed\n */\n error?: string;\n}\n\n/**\n * HTTP Server Health Check Service\n *\n * Checks if HTTP server is running and healthy by pinging the /health endpoint\n */\n@injectable()\nexport class HttpServerHealthCheck {\n private readonly defaultTimeout = 5000; // 5 seconds\n\n /**\n * Check if HTTP server is running and healthy\n *\n * @param port - Port number to check\n * @param timeout - Timeout in milliseconds (default: 5000)\n * @returns Health check result\n */\n async check(port: number, timeout: number = this.defaultTimeout): Promise<HealthCheckResult> {\n try {\n const url = `http://localhost:${port}/health`;\n\n // Use AbortController for timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetch(url, {\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n clearTimeout(timeoutId);\n\n // Check if response is OK (status 200-299)\n if (!response.ok) {\n return {\n healthy: false,\n error: `HTTP error ${response.status}: ${response.statusText}`,\n };\n }\n\n // Parse response body\n const data = (await response.json()) as {\n status?: string;\n service?: string;\n serviceName?: string;\n };\n\n // Verify response structure\n if (data.status === 'healthy') {\n return {\n healthy: true,\n port,\n serviceName: data.serviceName ?? data.service,\n };\n }\n\n return {\n healthy: false,\n error: `Unexpected health status: ${data.status || 'unknown'}`,\n };\n } catch (fetchError) {\n clearTimeout(timeoutId);\n\n // Handle different error types\n if (fetchError instanceof Error) {\n // AbortError means timeout\n if (fetchError.name === 'AbortError') {\n return {\n healthy: false,\n error: `Health check timed out after ${timeout}ms`,\n };\n }\n\n // ECONNREFUSED or other network errors\n if ('code' in fetchError && fetchError.code === 'ECONNREFUSED') {\n return {\n healthy: false,\n error: 'Connection refused - server not running',\n };\n }\n\n return {\n healthy: false,\n error: `Network error: ${fetchError.message}`,\n };\n }\n\n return {\n healthy: false,\n error: `Unknown error: ${String(fetchError)}`,\n };\n }\n } catch (error) {\n // Catch-all for unexpected errors\n return {\n healthy: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n }\n}\n","/**\n * Dependency Injection Container Types\n *\n * DESIGN PATTERNS:\n * - Dependency Injection with InversifyJS\n * - Symbol-based service identifiers\n *\n * CODING STANDARDS:\n * - Use symbols for DI identifiers to avoid naming collisions\n * - Group symbols by service category\n * - Export TYPES constant for centralized access\n *\n * AVOID:\n * - String-based identifiers (use symbols)\n * - Hardcoded type values\n */\n\n/**\n * InversifyJS DI symbols for service identification\n */\nexport const TYPES = {\n // Core Services\n LogStorageService: Symbol.for('LogStorageService'),\n EmbeddingService: Symbol.for('EmbeddingService'),\n LogQueryService: Symbol.for('LogQueryService'),\n LogSearchService: Symbol.for('LogSearchService'),\n SemanticSearchService: Symbol.for('SemanticSearchService'),\n LogRetentionService: Symbol.for('LogRetentionService'),\n\n // HTTP Server Management\n HttpServerHealthCheck: Symbol.for('HttpServerHealthCheck'),\n HttpServerManager: Symbol.for('HttpServerManager'),\n\n // Infrastructure\n Database: Symbol.for('Database'),\n Logger: Symbol.for('Logger'),\n\n // Servers\n HttpServer: Symbol.for('HttpServer'),\n McpServer: Symbol.for('McpServer'),\n} as const;\n\n/**\n * Type for TYPES constant keys\n */\nexport type TypeKey = keyof typeof TYPES;\n\n/**\n * Type for TYPES constant values\n */\nexport type TypeSymbol = (typeof TYPES)[TypeKey];\n","import { existsSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { basename, dirname, join, resolve } from 'node:path';\n\nconst WORKSPACE_MARKERS = ['pnpm-workspace.yaml', 'nx.json', '.git'];\n\nexport function resolveWorkspaceRoot(startPath = process.cwd()): string {\n let current = resolve(startPath);\n\n while (true) {\n for (const marker of WORKSPACE_MARKERS) {\n if (existsSync(join(current, marker))) {\n return current;\n }\n }\n\n const parent = dirname(current);\n if (parent === current) {\n return process.cwd();\n }\n\n current = parent;\n }\n}\n\nexport function resolveDefaultDbPath(startPath?: string): string {\n const workspaceRoot = resolveWorkspaceRoot(startPath);\n const worktreeName = basename(workspaceRoot);\n return join(tmpdir(), 'log-sink-mcp', worktreeName, 'session.db');\n}\n","/**\n * HttpServerManager\n *\n * Lifecycle management service for singleton HTTP server coordination.\n * Handles server discovery, startup, shutdown, and health monitoring.\n */\n\nimport { spawn } from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { inject, injectable, unmanaged } from 'inversify';\nimport 'reflect-metadata';\nimport { DEFAULT_PORT_RANGE, PortRegistryService } from '@agimon-ai/foundation-port-registry';\nimport { ProcessRegistryService } from '@agimon-ai/foundation-process-registry';\nimport { TYPES } from '../types/container.types.js';\nimport { resolveDefaultDbPath, resolveWorkspaceRoot } from '../utils/resolveWorkspaceRoot.js';\nimport type { HttpServerHealthCheck } from './HttpServerHealthCheck.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nfunction resolveSiblingRegistryPath(registryPath: string | undefined, fileName: string): string | undefined {\n if (!registryPath) {\n return undefined;\n }\n\n const resolved = path.resolve(registryPath);\n if (path.extname(resolved) === '.json') {\n return path.join(path.dirname(resolved), fileName);\n }\n\n return path.join(resolved, fileName);\n}\n\ninterface ServiceRegistration {\n port: number;\n pid?: number;\n environment: string;\n host?: string;\n metadata?: {\n healthCheckUrl?: string;\n dbPath?: string;\n };\n}\n\n/**\n * HTTP Server status\n */\nexport interface HttpServerStatus {\n /**\n * Whether the server is running\n */\n running: boolean;\n\n /**\n * Port number if running\n */\n port?: number;\n\n /**\n * Process ID if running\n */\n pid?: number;\n\n /**\n * Error message if not running\n */\n error?: string;\n}\n\n/**\n * HTTP Server Manager Service\n *\n * Manages singleton HTTP server lifecycle with service discovery and health checks\n */\n@injectable()\nexport class HttpServerManager {\n private readonly repositoryPath: string;\n private readonly environment: string;\n private readonly serviceName = 'log-sink-mcp-http';\n private readonly serviceType = 'tool' as const;\n private readonly host = '127.0.0.1';\n private readonly registryPath?: string;\n private readonly portRegistry: PortRegistryService;\n private readonly processRegistry: ProcessRegistryService;\n\n constructor(\n @inject(TYPES.HttpServerHealthCheck) private healthCheck: HttpServerHealthCheck,\n @unmanaged() repoRoot?: string,\n @unmanaged() registryPath?: string,\n ) {\n this.repositoryPath = resolveWorkspaceRoot(repoRoot ? path.resolve(repoRoot) : process.cwd());\n this.environment = process.env.NODE_ENV || 'development';\n this.registryPath = registryPath || process.env.PORT_REGISTRY_PATH;\n this.portRegistry = new PortRegistryService(this.registryPath);\n this.processRegistry = new ProcessRegistryService(resolveSiblingRegistryPath(this.registryPath, 'processes.json'));\n }\n\n /**\n * Read service registration from the global registry\n */\n private async getRegistration(): Promise<ServiceRegistration | null> {\n const result = await this.portRegistry.getPort({\n repositoryPath: this.repositoryPath,\n serviceName: this.serviceName,\n serviceType: this.serviceType,\n environment: this.environment,\n });\n\n if (!result.success || !result.record) {\n return null;\n }\n\n return {\n port: result.record.port,\n pid: result.record.pid,\n environment: result.record.environment ?? this.environment,\n host: result.record.host,\n metadata: result.record.metadata,\n };\n }\n\n /**\n * Remove service registration\n */\n private async releaseService(pid?: number): Promise<void> {\n const response = await this.portRegistry.releasePort({\n repositoryPath: this.repositoryPath,\n serviceName: this.serviceName,\n serviceType: this.serviceType,\n environment: this.environment,\n ...(pid ? { pid } : {}),\n });\n\n if (!response.success && !response.error?.includes('No matching registry entry')) {\n throw new Error(response.error || 'Failed to release registry entry');\n }\n }\n\n /**\n * Remove process registration.\n */\n private async releaseProcess(pid?: number): Promise<void> {\n const response = await this.processRegistry.releaseProcess({\n repositoryPath: this.repositoryPath,\n serviceName: this.serviceName,\n serviceType: this.serviceType,\n environment: this.environment,\n ...(pid ? { pid } : {}),\n kill: false,\n releasePort: false,\n });\n\n if (!response.success && !response.error?.includes('No matching process entry')) {\n throw new Error(response.error || 'Failed to release process entry');\n }\n }\n\n /**\n * Find an available port in registry\n */\n private async findAvailablePort(startPort: number): Promise<number> {\n const result = await this.portRegistry.findAvailablePort({\n repositoryPath: this.repositoryPath,\n serviceName: this.serviceName,\n serviceType: this.serviceType,\n environment: this.environment,\n host: this.host,\n preferredPort: startPort,\n });\n\n if (!result.success || !result.port) {\n throw new Error(`No available port found from ${startPort}: ${result.error}`);\n }\n\n return result.port;\n }\n\n /**\n * Check if a file exists\n */\n private async fileExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Start HTTP server as background process\n */\n private async startHttpServer(port: number, dbPath: string): Promise<number> {\n // Resolve CLI path: bundled output lives in dist/ alongside this file,\n // source runs from src/services/ (two levels below package root)\n const distCandidates = [\n path.resolve(__dirname, 'cli.mjs'),\n path.resolve(__dirname, '..', 'dist', 'cli.mjs'),\n path.resolve(__dirname, '..', '..', 'dist', 'cli.mjs'),\n ];\n const srcCandidates = [\n path.resolve(__dirname, '..', 'cli.ts'),\n path.resolve(__dirname, '..', '..', 'src', 'cli.ts'),\n ];\n\n let distCliPath: string | undefined;\n for (const candidate of distCandidates) {\n if (await this.fileExists(candidate)) {\n distCliPath = candidate;\n break;\n }\n }\n\n let srcCliPath: string | undefined;\n if (!distCliPath) {\n for (const candidate of srcCandidates) {\n if (await this.fileExists(candidate)) {\n srcCliPath = candidate;\n break;\n }\n }\n }\n\n let command: string;\n let args: string[];\n\n if (distCliPath) {\n command = process.execPath;\n args = [distCliPath, 'http-serve', '--port', port.toString()];\n } else if (srcCliPath) {\n command = process.execPath;\n args = ['--import', 'tsx', srcCliPath, 'http-serve', '--port', port.toString()];\n } else {\n throw new Error(`Cannot find log-sink MCP CLI in any expected location`);\n }\n\n const absoluteDbPath = path.resolve(dbPath);\n await fs.mkdir(path.dirname(absoluteDbPath), { recursive: true });\n\n const child = spawn(command, [...args, '--db-path', absoluteDbPath], {\n detached: true,\n stdio: 'ignore',\n env: {\n ...process.env,\n ...(this.registryPath ? { PORT_REGISTRY_PATH: this.registryPath } : {}),\n NODE_ENV: this.environment,\n },\n });\n\n child.unref();\n\n const pid = child.pid;\n if (!pid) {\n throw new Error('Failed to spawn HTTP server - no PID returned');\n }\n\n return pid;\n }\n\n private async waitForRegisteredService(\n port: number,\n pid: number,\n attempts = 10,\n delayMs = 250,\n ): Promise<ServiceRegistration | null> {\n for (let attempt = 0; attempt < attempts; attempt += 1) {\n const registration = await this.getRegistration();\n if (registration?.pid === pid && registration.port === port) {\n return registration;\n }\n\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n\n return null;\n }\n\n private async waitForServerStartup(delayMs = 4000): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n\n /**\n * Kill a process by PID\n */\n private async killProcess(pid: number): Promise<void> {\n try {\n process.kill(pid, 0);\n process.kill(pid, 'SIGTERM');\n\n await new Promise((resolve) => setTimeout(resolve, 1000));\n\n try {\n process.kill(pid, 0);\n process.kill(pid, 'SIGKILL');\n } catch {\n // Process already exited\n }\n } catch {\n // Process doesn't exist\n }\n }\n\n /**\n * Ensure HTTP server is running (discover or start)\n */\n async ensureRunning(port = DEFAULT_PORT_RANGE.min, dbPath = resolveDefaultDbPath()): Promise<HttpServerStatus> {\n try {\n const existing = await this.getRegistration();\n if (existing) {\n const healthResult = await this.healthCheck.check(existing.port);\n\n if (healthResult.healthy && healthResult.serviceName === this.serviceName) {\n return {\n running: true,\n port: existing.port,\n pid: existing.pid,\n };\n }\n\n if (existing.pid) {\n await this.killProcess(existing.pid);\n }\n await this.releaseProcess(existing.pid);\n await this.releaseService(existing.pid);\n }\n\n let nextPort = port;\n for (let attempt = 0; attempt < 3; attempt += 1) {\n const availablePort = await this.findAvailablePort(nextPort);\n const pid = await this.startHttpServer(availablePort, dbPath);\n await this.waitForServerStartup();\n\n const healthResult = await this.healthCheck.check(availablePort);\n if (!healthResult.healthy || healthResult.serviceName !== this.serviceName) {\n await this.killProcess(pid);\n await this.releaseProcess(pid);\n await this.releaseService(pid);\n nextPort = availablePort + 1;\n continue;\n }\n\n const registration = await this.waitForRegisteredService(availablePort, pid);\n if (!registration) {\n await this.killProcess(pid);\n await this.releaseProcess(pid);\n await this.releaseService(pid);\n nextPort = availablePort + 1;\n continue;\n }\n\n return {\n running: true,\n port: availablePort,\n pid,\n };\n }\n\n return {\n running: false,\n error: 'Failed to start log-sink-mcp HTTP server after retries',\n };\n } catch (error) {\n return {\n running: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n }\n\n /**\n * Stop HTTP server and clean up\n */\n async stop(): Promise<boolean> {\n try {\n const registration = await this.getRegistration();\n if (!registration) {\n return false;\n }\n\n try {\n if (registration.pid) {\n process.kill(registration.pid, 'SIGTERM');\n }\n\n await new Promise((resolve) => setTimeout(resolve, 1000));\n\n if (registration.pid) {\n try {\n process.kill(registration.pid, 0);\n process.kill(registration.pid, 'SIGKILL');\n } catch {\n // Process already dead\n }\n }\n } catch {\n // Already dead or inaccessible\n }\n\n await this.releaseProcess(registration.pid);\n await this.releaseService(registration.pid);\n return true;\n } catch (error) {\n throw new Error(`Failed to stop HTTP server: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n /**\n * Get current HTTP server status\n */\n async getStatus(): Promise<HttpServerStatus> {\n try {\n const registration = await this.getRegistration();\n\n if (!registration) {\n return {\n running: false,\n error: 'No HTTP server registered',\n };\n }\n\n const healthResult = await this.healthCheck.check(registration.port);\n\n if (healthResult.healthy && healthResult.serviceName === this.serviceName) {\n return {\n running: true,\n port: registration.port,\n pid: registration.pid,\n };\n }\n\n return {\n running: false,\n port: registration.port,\n pid: registration.pid,\n error: healthResult.error,\n };\n } catch (error) {\n return {\n running: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n }\n}\n","/**\n * EmbeddingService\n *\n * Generates local embeddings for log text and writes them into SQLite.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport Module, { createRequire } from 'node:module';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport type { Log } from '../models/schema.js';\nimport { TYPES } from '../types/container.types.js';\nimport type { LogStorageService } from './LogStorageService.js';\n\ntype LoaderModule = typeof import('ruvector-onnx-embeddings-wasm/loader.js');\n\ninterface Embedder {\n embedOne(text: string): Float32Array;\n}\n\ninterface WasmEmbedderConfig {\n setMaxLength(len: number): WasmEmbedderConfig;\n setNormalize(normalize: boolean): WasmEmbedderConfig;\n setPooling(pooling: number): WasmEmbedderConfig;\n}\n\ninterface WasmModule {\n WasmEmbedderConfig: new () => WasmEmbedderConfig;\n WasmEmbedder: {\n withConfig(modelBytes: Uint8Array, tokenizerJson: string, config: WasmEmbedderConfig): Embedder;\n };\n}\n\ninterface ModelConfig {\n name: string;\n dimension: number;\n maxLength: number;\n size: string;\n description: string;\n model: string;\n tokenizer: string;\n}\n\ninterface IndexedEmbeddingRow {\n log: Log;\n embedding: number[];\n}\n\n/**\n * Load the ruvector WASM binding as CJS.\n *\n * The upstream package declares `\"type\": \"module\"` but the generated WASM\n * binding JS uses CJS globals (`module`, `exports`, `require`, `__dirname`).\n * Node's ESM loader rejects it, so we compile it manually as CJS via\n * `Module._compile` which provides the correct CJS wrapper.\n */\nfunction loadWasmModuleAsCjs(): Record<string, unknown> {\n // Resolve the main entry (which has a \"require\" condition) to find the package directory\n const esmRequire = createRequire(import.meta.url);\n const mainPath = esmRequire.resolve('ruvector-onnx-embeddings-wasm');\n const pkgDir = dirname(mainPath);\n const wasmJsPath = join(pkgDir, 'ruvector_onnx_embeddings_wasm.js');\n\n const code = readFileSync(wasmJsPath, 'utf-8');\n const m = new Module(wasmJsPath) as Module & { _compile: (code: string, filename: string) => void };\n m.filename = wasmJsPath;\n m.paths = (Module as unknown as { _nodeModulePaths: (dir: string) => string[] })._nodeModulePaths(pkgDir);\n m._compile(code, wasmJsPath);\n return m.exports as Record<string, unknown>;\n}\n\n@injectable()\nexport class EmbeddingService {\n private static readonly DEFAULT_MODEL = 'all-MiniLM-L6-v2';\n private static readonly VECTOR_DIMENSION = 384;\n private static readonly CACHE_DIR = join(homedir(), '.log-sink-mcp');\n\n private embedderPromise: Promise<Embedder> | null = null;\n private modelUnavailable = false;\n private loaderPromise: Promise<LoaderModule | null> | null = null;\n\n constructor(@inject(TYPES.LogStorageService) private storage: LogStorageService) {}\n\n private get modelName(): string {\n return process.env.LOG_SINK_EMBEDDING_MODEL ?? EmbeddingService.DEFAULT_MODEL;\n }\n\n private async loadLoader(): Promise<LoaderModule | null> {\n if (!this.loaderPromise) {\n this.loaderPromise = import('ruvector-onnx-embeddings-wasm/loader.js').catch((error: unknown) => {\n const code =\n error && typeof error === 'object' && 'code' in error ? String((error as { code?: unknown }).code) : '';\n const message = error instanceof Error ? error.message : String(error);\n if (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND' || message.includes('ruvector-onnx')) {\n return null;\n }\n\n throw error;\n });\n }\n\n return this.loaderPromise;\n }\n\n private async getEmbedder(): Promise<Embedder> {\n if (this.modelUnavailable) {\n throw new Error('Embedding model is not available locally');\n }\n\n if (!this.embedderPromise) {\n const loader = await this.loadLoader();\n if (!loader) {\n this.modelUnavailable = true;\n throw new Error('Embedding model support is disabled because ruvector-onnx-embeddings-wasm is not installed');\n }\n\n await mkdir(EmbeddingService.CACHE_DIR, { recursive: true });\n\n // The WASM binding JS file uses CJS (module.exports, require, __dirname)\n // but the package declares \"type\": \"module\", so Node's ESM loader rejects it.\n // Load the CJS module via Module._compile to provide proper CJS globals.\n const wasmModule = loadWasmModuleAsCjs() as unknown as WasmModule;\n\n this.embedderPromise = this.createEmbedderWithCache(loader, wasmModule).catch((error) => {\n this.embedderPromise = null;\n this.modelUnavailable = true;\n throw error;\n });\n }\n\n return this.embedderPromise;\n }\n\n /**\n * Create an embedder, downloading and caching model files on disk.\n *\n * The upstream `ModelLoader` only caches in the browser Cache API which is\n * unavailable in Node.js. We cache the ONNX model and tokenizer under\n * `~/.log-sink-mcp/<model>/` so subsequent runs skip the download.\n */\n private async createEmbedderWithCache(loader: LoaderModule, wasmModule: WasmModule): Promise<Embedder> {\n const modelName = this.modelName;\n const modelConfig = (loader as unknown as { MODELS: Record<string, ModelConfig> }).MODELS[modelName];\n if (!modelConfig) {\n throw new Error(`Unknown embedding model: ${modelName}`);\n }\n\n const cacheDir = join(EmbeddingService.CACHE_DIR, modelName);\n const modelPath = join(cacheDir, 'model.onnx');\n const tokenizerPath = join(cacheDir, 'tokenizer.json');\n\n let modelBytes: Uint8Array;\n let tokenizerJson: string;\n\n if (existsSync(modelPath) && existsSync(tokenizerPath)) {\n [modelBytes, tokenizerJson] = await Promise.all([\n readFile(modelPath).then((buf) => new Uint8Array(buf)),\n readFile(tokenizerPath, 'utf-8'),\n ]);\n } else {\n await mkdir(cacheDir, { recursive: true });\n\n const [modelBuf, tokenizer] = await Promise.all([\n fetch(modelConfig.model).then((r) => {\n if (!r.ok) throw new Error(`Failed to download model: ${r.status}`);\n return r.arrayBuffer();\n }),\n fetch(modelConfig.tokenizer).then((r) => {\n if (!r.ok) throw new Error(`Failed to download tokenizer: ${r.status}`);\n return r.text();\n }),\n ]);\n\n modelBytes = new Uint8Array(modelBuf);\n tokenizerJson = tokenizer;\n\n await Promise.all([writeFile(modelPath, modelBytes), writeFile(tokenizerPath, tokenizerJson)]);\n }\n\n const config = new wasmModule.WasmEmbedderConfig()\n .setMaxLength(modelConfig.maxLength)\n .setNormalize(true)\n .setPooling(0);\n\n return wasmModule.WasmEmbedder.withConfig(modelBytes, tokenizerJson, config);\n }\n\n async isAvailable(): Promise<boolean> {\n if (this.modelUnavailable) {\n return false;\n }\n\n try {\n await this.getEmbedder();\n return true;\n } catch {\n return false;\n }\n }\n\n buildEmbeddingText(log: Log): string {\n const metadata =\n log.metadata && typeof log.metadata === 'object' ? JSON.stringify(log.metadata) : (log.metadata ?? '');\n\n return [\n `service: ${log.service}`,\n `level: ${log.level}`,\n `message: ${log.message}`,\n log.errorType ? `errorType: ${log.errorType}` : null,\n log.errorMessage ? `errorMessage: ${log.errorMessage}` : null,\n metadata ? `metadata: ${metadata}` : null,\n ]\n .filter(Boolean)\n .join('\\n');\n }\n\n private normalizeVector(vector: number[]): number[] {\n const norm = Math.sqrt(vector.reduce((sum, value) => sum + value * value, 0));\n if (norm === 0) {\n return vector;\n }\n\n return vector.map((value) => value / norm);\n }\n\n private toVector(output: unknown): number[] {\n if (output instanceof Float32Array || output instanceof Float64Array) {\n return Array.from(output);\n }\n\n if (Array.isArray(output) && output.length > 0 && typeof output[0] === 'number') {\n return output as number[];\n }\n\n if (Array.isArray(output) && output.length > 0 && Array.isArray(output[0])) {\n return output[0] as number[];\n }\n\n throw new Error('Unexpected embedding output shape');\n }\n\n async embedText(text: string): Promise<number[]> {\n const embedder = await this.getEmbedder();\n const vector = this.normalizeVector(this.toVector(embedder.embedOne(text)));\n\n if (vector.length !== EmbeddingService.VECTOR_DIMENSION) {\n throw new Error(`Unexpected embedding dimension ${vector.length}, expected ${EmbeddingService.VECTOR_DIMENSION}`);\n }\n\n return vector;\n }\n\n async indexLogs(logs: Log[]): Promise<void> {\n if (logs.length === 0) {\n return;\n }\n\n try {\n const embeddedRows: IndexedEmbeddingRow[] = [];\n for (const log of logs) {\n embeddedRows.push({\n log,\n embedding: await this.embedText(this.buildEmbeddingText(log)),\n });\n }\n\n const sqlite = this.storage.getSqliteClient();\n const upsert = sqlite.prepare(`\n INSERT INTO log_embeddings (log_id, embedding, service, level, created_at)\n VALUES (?, vec_f32(?), ?, ?, ?)\n ON CONFLICT(log_id) DO UPDATE SET\n embedding = excluded.embedding,\n service = excluded.service,\n level = excluded.level,\n created_at = excluded.created_at\n `);\n\n sqlite.exec('BEGIN');\n try {\n for (const row of embeddedRows) {\n upsert.run(\n row.log.id,\n new Uint8Array(new Float32Array(row.embedding).buffer),\n row.log.service,\n row.log.level,\n Math.floor(row.log.createdAt.getTime() / 1000),\n );\n }\n sqlite.exec('COMMIT');\n } catch (error) {\n sqlite.exec('ROLLBACK');\n throw error;\n }\n } catch (error) {\n console.warn(`Skipping semantic embedding indexing: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n}\n","/**\n * LogQueryService\n *\n * Service for advanced log querying, filtering, and aggregation.\n * Provides specialized query methods for log analysis and statistics.\n */\n\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata';\nimport type { Log } from '../models/schema.js';\nimport { TYPES } from '../types/container.types.js';\nimport type { LogAggregation, LogLevel, TraceTimelineEntry } from '../types/log.types.js';\nimport type { LogStorageService } from './LogStorageService.js';\n\ninterface RawLogRow {\n id: string;\n timestamp: number | bigint;\n level: string;\n message: string;\n trace_id: string | null;\n span_id: string | null;\n parent_span_id: string | null;\n service: string;\n hostname: string | null;\n pid: number | null;\n metadata: string | null;\n error_type: string | null;\n error_message: string | null;\n error_stack: string | null;\n created_at: number | bigint;\n}\n\ninterface RawCountRow {\n count: number | bigint;\n}\n\nfunction asRows<T>(rows: unknown): T[] {\n return rows as unknown as T[];\n}\n\n@injectable()\nexport class LogQueryService {\n constructor(@inject(TYPES.LogStorageService) private storage: LogStorageService) {}\n\n /**\n * Get SQLite client from storage service, ensuring initialization.\n */\n private async getSqlite() {\n await this.storage.ensureInitialized();\n return this.storage.getSqliteClient();\n }\n\n private mapRow(row: RawLogRow): Log {\n return {\n id: row.id,\n timestamp: new Date(Number(row.timestamp) * 1000),\n level: row.level,\n message: row.message,\n traceId: row.trace_id,\n spanId: row.span_id,\n parentSpanId: row.parent_span_id,\n service: row.service,\n hostname: row.hostname,\n pid: row.pid,\n metadata: row.metadata ? JSON.parse(row.metadata) : null,\n errorType: row.error_type,\n errorMessage: row.error_message,\n errorStack: row.error_stack,\n createdAt: new Date(Number(row.created_at) * 1000),\n };\n }\n\n private buildWhereClause(filters: {\n level?: LogLevel | LogLevel[];\n service?: string | string[];\n traceId?: string;\n startTime?: Date;\n endTime?: Date;\n }) {\n let whereSql = 'WHERE 1 = 1';\n const params: Array<string | number> = [];\n\n if (filters.level) {\n const levels = Array.isArray(filters.level) ? filters.level : [filters.level];\n whereSql += ` AND level IN (${levels.map(() => '?').join(',')})`;\n params.push(...levels);\n }\n\n if (filters.service) {\n const services = Array.isArray(filters.service) ? filters.service : [filters.service];\n whereSql += ` AND service IN (${services.map(() => '?').join(',')})`;\n params.push(...services);\n }\n\n if (filters.traceId) {\n whereSql += ' AND trace_id = ?';\n params.push(filters.traceId);\n }\n\n if (filters.startTime) {\n whereSql += ' AND timestamp >= ?';\n params.push(Math.floor(filters.startTime.getTime() / 1000));\n }\n\n if (filters.endTime) {\n whereSql += ' AND timestamp <= ?';\n params.push(Math.floor(filters.endTime.getTime() / 1000));\n }\n\n return { whereSql, params };\n }\n\n async filterByLevel(level: LogLevel | LogLevel[], limit = 100): Promise<Log[]> {\n try {\n const sqlite = await this.getSqlite();\n const { whereSql, params } = this.buildWhereClause({ level });\n const rows = sqlite\n .prepare(\n `\n SELECT\n id, timestamp, level, message, trace_id, span_id, parent_span_id,\n service, hostname, pid, metadata, error_type, error_message,\n error_stack, created_at\n FROM logs\n ${whereSql}\n ORDER BY timestamp DESC\n LIMIT ?\n `,\n )\n .all(...params, limit);\n\n return asRows<RawLogRow>(rows).map((row) => this.mapRow(row));\n } catch (error) {\n throw new Error(`Failed to filter by level: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n async filterByTrace(traceId: string): Promise<Log[]> {\n try {\n const sqlite = await this.getSqlite();\n const rows = sqlite\n .prepare(\n `\n SELECT\n id, timestamp, level, message, trace_id, span_id, parent_span_id,\n service, hostname, pid, metadata, error_type, error_message,\n error_stack, created_at\n FROM logs\n WHERE trace_id = ?\n ORDER BY timestamp ASC\n `,\n )\n .all(traceId);\n\n return asRows<RawLogRow>(rows).map((row) => this.mapRow(row));\n } catch (error) {\n throw new Error(`Failed to filter by trace: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n async filterByTimeRange(start: Date, end: Date): Promise<Log[]> {\n try {\n const sqlite = await this.getSqlite();\n const rows = sqlite\n .prepare(\n `\n SELECT\n id, timestamp, level, message, trace_id, span_id, parent_span_id,\n service, hostname, pid, metadata, error_type, error_message,\n error_stack, created_at\n FROM logs\n WHERE timestamp >= ? AND timestamp <= ?\n ORDER BY timestamp ASC\n `,\n )\n .all(Math.floor(start.getTime() / 1000), Math.floor(end.getTime() / 1000));\n\n return asRows<RawLogRow>(rows).map((row) => this.mapRow(row));\n } catch (error) {\n throw new Error(`Failed to filter by time range: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n async filterByService(service: string | string[]): Promise<Log[]> {\n try {\n const sqlite = await this.getSqlite();\n const services = Array.isArray(service) ? service : [service];\n const rows = sqlite\n .prepare(\n `\n SELECT\n id, timestamp, level, message, trace_id, span_id, parent_span_id,\n service, hostname, pid, metadata, error_type, error_message,\n error_stack, created_at\n FROM logs\n WHERE service IN (${services.map(() => '?').join(',')})\n ORDER BY timestamp ASC\n `,\n )\n .all(...services);\n\n return asRows<RawLogRow>(rows).map((row) => this.mapRow(row));\n } catch (error) {\n throw new Error(`Failed to filter by service: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n async getTraceTimeline(traceId: string): Promise<TraceTimelineEntry[]> {\n try {\n const logEntries = await this.filterByTrace(traceId);\n const timeline: TraceTimelineEntry[] = [];\n\n for (let i = 0; i < logEntries.length; i += 1) {\n const entry = logEntries[i];\n const nextEntry = logEntries[i + 1];\n\n timeline.push({\n timestamp: entry.timestamp,\n service: entry.service,\n spanId: entry.spanId ?? null,\n parentSpanId: entry.parentSpanId ?? null,\n level: entry.level as LogLevel,\n message: entry.message,\n duration: nextEntry ? nextEntry.timestamp.getTime() - entry.timestamp.getTime() : undefined,\n });\n }\n\n return timeline;\n } catch (error) {\n throw new Error(`Failed to get trace timeline: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n async getStatistics(\n timeRange?: { start: Date; end: Date },\n groupBy: 'level' | 'service' | 'both' = 'both',\n ): Promise<LogAggregation[]> {\n try {\n const sqlite = await this.getSqlite();\n let whereSql = '';\n const params: Array<string | number> = [];\n\n if (timeRange) {\n whereSql = 'WHERE timestamp >= ? AND timestamp <= ?';\n params.push(Math.floor(timeRange.start.getTime() / 1000), Math.floor(timeRange.end.getTime() / 1000));\n }\n\n let selectSql = 'service, level, COUNT(*) as count';\n let groupSql = 'GROUP BY service, level';\n\n if (groupBy === 'level') {\n selectSql = `'' as service, level, COUNT(*) as count`;\n groupSql = 'GROUP BY level';\n } else if (groupBy === 'service') {\n selectSql = `service, '' as level, COUNT(*) as count`;\n groupSql = 'GROUP BY service';\n }\n\n const rows = sqlite\n .prepare(\n `\n SELECT ${selectSql}\n FROM logs\n ${whereSql}\n ${groupSql}\n `,\n )\n .all(...params);\n\n return asRows<{ service: string; level: string; count: number | bigint }>(rows).map((row) => ({\n service: row.service,\n level: row.level as LogLevel,\n count: Number(row.count),\n }));\n } catch (error) {\n throw new Error(`Failed to get statistics: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n async getActiveServices(): Promise<string[]> {\n try {\n const sqlite = await this.getSqlite();\n const rows = asRows<{ service: string }>(\n sqlite.prepare('SELECT DISTINCT service FROM logs ORDER BY service ASC').all(),\n );\n\n return rows.map((row) => row.service);\n } catch (error) {\n throw new Error(`Failed to get active services: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n async countByLevel(level: LogLevel | LogLevel[]): Promise<number> {\n try {\n const sqlite = await this.getSqlite();\n const levels = Array.isArray(level) ? level : [level];\n const rows = sqlite\n .prepare(\n `\n SELECT COUNT(*) as count\n FROM logs\n WHERE level IN (${levels.map(() => '?').join(',')})\n `,\n )\n .all(...levels);\n\n return Number(asRows<RawCountRow>(rows)[0]?.count ?? 0);\n } catch (error) {\n throw new Error(`Failed to count by level: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n async countByService(service: string | string[]): Promise<number> {\n try {\n const sqlite = await this.getSqlite();\n const services = Array.isArray(service) ? service : [service];\n const rows = sqlite\n .prepare(\n `\n SELECT COUNT(*) as count\n FROM logs\n WHERE service IN (${services.map(() => '?').join(',')})\n `,\n )\n .all(...services);\n\n return Number(asRows<RawCountRow>(rows)[0]?.count ?? 0);\n } catch (error) {\n throw new Error(`Failed to count by service: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n async countByTrace(traceId: string): Promise<number> {\n try {\n const sqlite = await this.getSqlite();\n const rows = asRows<RawCountRow>(\n sqlite.prepare('SELECT COUNT(*) as count FROM logs WHERE trace_id = ?').all(traceId),\n );\n\n return Number(rows[0]?.count ?? 0);\n } catch (error) {\n throw new Error(`Failed to count by trace: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n async queryLogs(options: {\n level?: LogLevel | LogLevel[];\n service?: string;\n traceId?: string;\n limit?: number;\n offset?: number;\n }): Promise<{ logs: Log[]; total: number }> {\n try {\n const sqlite = await this.getSqlite();\n const { level, service, traceId, limit = 25, offset = 0 } = options;\n const filters: {\n level?: LogLevel | LogLevel[];\n service?: string | string[];\n traceId?: string;\n } = {};\n\n if (level) {\n filters.level = level;\n }\n\n if (service) {\n filters.service = service;\n }\n\n if (traceId) {\n filters.traceId = traceId;\n }\n\n const { whereSql, params } = this.buildWhereClause(filters);\n\n const totalRows = asRows<RawCountRow>(\n sqlite\n .prepare(\n `\n SELECT COUNT(*) as count\n FROM logs\n ${whereSql}\n `,\n )\n .all(...params),\n );\n\n const rows = asRows<RawLogRow>(\n sqlite\n .prepare(\n `\n SELECT\n id, timestamp, level, message, trace_id, span_id, parent_span_id,\n service, hostname, pid, metadata, error_type, error_message,\n error_stack, created_at\n FROM logs\n ${whereSql}\n ORDER BY timestamp DESC\n LIMIT ?\n OFFSET ?\n `,\n )\n .all(...params, limit, offset),\n );\n\n return {\n logs: rows.map((row) => this.mapRow(row)),\n total: Number(totalRows[0]?.count ?? 0),\n };\n } catch (error) {\n throw new Error(`Failed to query logs: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n}\n","/**\n * LogRetentionService\n *\n * Service for log retention and cleanup operations.\n * Handles TTL-based cleanup and manual clearing of logs.\n */\n\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata';\nimport { TYPES } from '../types/container.types.js';\nimport type { LogStorageService } from './LogStorageService.js';\n\nexport interface DatabaseSizeRetentionConfig {\n maxBytes: number;\n targetBytes?: number;\n intervalMs: number;\n batchSize?: number;\n}\n\nexport interface DatabaseSizeRetentionResult {\n sizeBeforeBytes: number | null;\n sizeAfterBytes: number | null;\n deletedEntries: number;\n iterations: number;\n trimmed: boolean;\n}\n\n@injectable()\nexport class LogRetentionService {\n private sizeRetentionTimer: ReturnType<typeof setInterval> | null = null;\n private sizeRetentionRunPromise: Promise<void> | null = null;\n\n constructor(@inject(TYPES.LogStorageService) private storage: LogStorageService) {}\n\n /**\n * Get SQLite client from storage service, ensuring initialization.\n */\n private async getSqlite() {\n await this.storage.ensureInitialized();\n return this.storage.getSqliteClient();\n }\n\n /**\n * Clean up old logs based on retention period\n *\n * @param retentionDays - Number of days to retain logs\n * @returns Number of deleted logs\n */\n async cleanupOldLogs(retentionDays: number): Promise<number> {\n try {\n const sqlite = await this.getSqlite();\n\n const cutoffDate = new Date();\n cutoffDate.setDate(cutoffDate.getDate() - retentionDays);\n\n const result = sqlite\n .prepare('DELETE FROM logs WHERE timestamp <= ?')\n .run(Math.floor(cutoffDate.getTime() / 1000));\n return Number(result.changes ?? 0);\n } catch (error) {\n throw new Error(`Failed to cleanup old logs: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n /**\n * Clear all logs from the database\n *\n * @returns Number of deleted logs\n */\n async clearAllLogs(): Promise<number> {\n try {\n return await this.storage.deleteAllLogs();\n } catch (error) {\n throw new Error(`Failed to clear all logs: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n /**\n * Clean up logs when the database exceeds a size threshold.\n */\n async cleanupOversizedDatabase(config: DatabaseSizeRetentionConfig): Promise<DatabaseSizeRetentionResult> {\n try {\n if (!Number.isFinite(config.maxBytes) || config.maxBytes <= 0) {\n throw new Error('maxBytes must be a positive number');\n }\n\n if (!Number.isFinite(config.intervalMs) || config.intervalMs <= 0) {\n throw new Error('intervalMs must be a positive number');\n }\n\n const batchSize = config.batchSize ?? 1000;\n const targetBytes = Math.min(config.targetBytes ?? Math.floor(config.maxBytes * 0.9), config.maxBytes);\n\n const sizeBeforeBytes = await this.storage.getDatabaseSizeBytes();\n if (sizeBeforeBytes === null || sizeBeforeBytes <= config.maxBytes) {\n return {\n sizeBeforeBytes,\n sizeAfterBytes: sizeBeforeBytes,\n deletedEntries: 0,\n iterations: 0,\n trimmed: false,\n };\n }\n\n let currentSize = sizeBeforeBytes;\n let deletedEntries = 0;\n let iterations = 0;\n\n while (currentSize > targetBytes) {\n const deleted = await this.storage.deleteOldestLogs(batchSize);\n if (deleted === 0) {\n break;\n }\n\n deletedEntries += deleted;\n iterations += 1;\n\n await this.storage.compactDatabase();\n const refreshedSize = await this.storage.getDatabaseSizeBytes();\n if (refreshedSize !== null) {\n currentSize = refreshedSize;\n }\n }\n\n return {\n sizeBeforeBytes,\n sizeAfterBytes: currentSize,\n deletedEntries,\n iterations,\n trimmed: deletedEntries > 0,\n };\n } catch (error) {\n throw new Error(\n `Failed to cleanup oversized database: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n /**\n * Start a periodic monitor that trims the database when it exceeds the threshold.\n */\n startSizeRetentionMonitor(config: DatabaseSizeRetentionConfig): () => void {\n this.stopSizeRetentionMonitor();\n\n const run = async () => {\n if (this.sizeRetentionRunPromise) {\n return;\n }\n\n this.sizeRetentionRunPromise = (async () => {\n try {\n await this.cleanupOversizedDatabase(config);\n } catch (error) {\n console.warn(\n `Failed to run periodic database retention: ${error instanceof Error ? error.message : String(error)}`,\n );\n } finally {\n this.sizeRetentionRunPromise = null;\n }\n })();\n\n await this.sizeRetentionRunPromise;\n };\n\n void run();\n this.sizeRetentionTimer = setInterval(() => {\n void run();\n }, config.intervalMs);\n this.sizeRetentionTimer.unref?.();\n\n return () => {\n this.stopSizeRetentionMonitor();\n };\n }\n\n /**\n * Stop the periodic size retention monitor.\n */\n stopSizeRetentionMonitor(): void {\n if (this.sizeRetentionTimer) {\n clearInterval(this.sizeRetentionTimer);\n this.sizeRetentionTimer = null;\n }\n }\n}\n","/**\n * LogSearchService\n *\n * Service for full-text search on logs using SQLite FTS5.\n * Provides search capabilities with ranking and filtering.\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Single responsibility principle\n * - Dependency injection with InversifyJS\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n * - Document complex logic with comments\n *\n * AVOID:\n * - Mixing concerns (keep focused on single domain)\n * - Direct tool implementation (services should be tool-agnostic)\n */\n\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata';\nimport type { Log } from '../models/schema.js';\nimport { TYPES } from '../types/container.types.js';\nimport type { SearchOptions } from '../types/log.types.js';\nimport type { LogStorageService } from './LogStorageService.js';\n\n/**\n * Search result with relevance score\n */\nexport interface SearchResult extends Log {\n rank: number;\n}\n\ninterface SearchQueryFilters extends Omit<SearchOptions, 'query'> {\n offset?: number;\n}\n\ninterface SearchResultRow {\n id: string;\n timestamp: number;\n level: string;\n message: string;\n traceId: string | null;\n spanId: string | null;\n parentSpanId: string | null;\n service: string;\n hostname: string | null;\n pid: number | null;\n metadata: string | null;\n errorType: string | null;\n errorMessage: string | null;\n errorStack: string | null;\n createdAt: number;\n rank: number;\n}\n\nfunction asRows<T>(rows: unknown): T[] {\n return rows as unknown as T[];\n}\n\n@injectable()\nexport class LogSearchService {\n constructor(@inject(TYPES.LogStorageService) private storage: LogStorageService) {}\n\n /**\n * Get raw SQLite connection for FTS5 queries, ensuring initialization\n */\n private async getSqlite() {\n await this.storage.ensureInitialized();\n return this.storage.getSqliteClient();\n }\n\n private buildSearchSql(query: string, filters: SearchQueryFilters = {}) {\n const sanitizedQuery = query.replace(/[^\\w\\s\\-\"*]/g, '').trim();\n if (!sanitizedQuery) {\n throw new Error('Search query is empty after sanitization');\n }\n\n let whereSql = 'WHERE logs_search MATCH ?';\n const params: Array<string | number> = [sanitizedQuery];\n\n if (filters.service) {\n const services = Array.isArray(filters.service) ? filters.service : [filters.service];\n whereSql += ` AND logs.service IN (${services.map(() => '?').join(',')})`;\n params.push(...services);\n }\n\n if (filters.level) {\n const levels = Array.isArray(filters.level) ? filters.level : [filters.level];\n whereSql += ` AND logs.level IN (${levels.map(() => '?').join(',')})`;\n params.push(...levels);\n }\n\n if (filters.startTime) {\n whereSql += ' AND logs.timestamp >= ?';\n params.push(Math.floor(filters.startTime.getTime() / 1000));\n }\n\n if (filters.endTime) {\n whereSql += ' AND logs.timestamp <= ?';\n params.push(Math.floor(filters.endTime.getTime() / 1000));\n }\n\n return { whereSql, params };\n }\n\n private mapSearchRow(row: SearchResultRow): SearchResult {\n return {\n id: row.id,\n timestamp: new Date(row.timestamp * 1000),\n level: row.level,\n message: row.message,\n traceId: row.traceId,\n spanId: row.spanId,\n parentSpanId: row.parentSpanId,\n service: row.service,\n hostname: row.hostname,\n pid: row.pid,\n metadata: row.metadata ? JSON.parse(row.metadata) : null,\n errorType: row.errorType,\n errorMessage: row.errorMessage,\n errorStack: row.errorStack,\n createdAt: new Date(row.createdAt * 1000),\n rank: row.rank,\n };\n }\n\n /**\n * Search logs using FTS5 full-text search\n *\n * @param query - FTS5 search query (supports phrase queries, prefix matching, boolean operators)\n * @param filters - Optional filters to apply\n * @param limit - Maximum number of results\n * @returns Array of matching logs with relevance ranking\n */\n async searchLogs(query: string, filters: Omit<SearchOptions, 'query'> = {}, limit = 100): Promise<SearchResult[]> {\n const { results } = await this.searchLogsPaginated(query, { ...filters, offset: 0 }, limit);\n return results;\n }\n\n /**\n * Search logs with exact total count and pagination support.\n */\n async searchLogsPaginated(\n query: string,\n filters: SearchQueryFilters = {},\n limit = 100,\n ): Promise<{ results: SearchResult[]; total: number }> {\n try {\n const sqlite = await this.getSqlite();\n const { whereSql, params } = this.buildSearchSql(query, filters);\n const offset = filters.offset ?? 0;\n\n const countSql = `\n SELECT COUNT(*) as total\n FROM logs_search\n JOIN logs ON logs.rowid = logs_search.rowid\n ${whereSql}\n `;\n\n const dataSql = `\n SELECT\n logs.id as id,\n logs.timestamp as timestamp,\n logs.level as level,\n logs.message as message,\n logs.trace_id as traceId,\n logs.span_id as spanId,\n logs.parent_span_id as parentSpanId,\n logs.service as service,\n logs.hostname as hostname,\n logs.pid as pid,\n logs.metadata as metadata,\n logs.error_type as errorType,\n logs.error_message as errorMessage,\n logs.error_stack as errorStack,\n logs.created_at as createdAt,\n logs_search.rank as rank\n FROM logs_search\n JOIN logs ON logs.rowid = logs_search.rowid\n ${whereSql}\n ORDER BY rank\n LIMIT ?\n OFFSET ?\n `;\n\n const countResult = sqlite.prepare(countSql).get(...params) as unknown as { total: number };\n const rows = asRows<SearchResultRow>(sqlite.prepare(dataSql).all(...params, limit, offset));\n\n return {\n results: rows.map((row) => this.mapSearchRow(row)),\n total: Number(countResult.total ?? 0),\n };\n } catch (error) {\n throw new Error(`Failed to search logs: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n /**\n * Rank search results by relevance\n *\n * @param results - Search results to rank\n * @returns Results sorted by rank (best match first)\n */\n rankResults(results: SearchResult[]): SearchResult[] {\n // FTS5 already provides ranking via the rank column\n // Lower rank values = better matches (negative numbers)\n return results.sort((a, b) => a.rank - b.rank);\n }\n}\n","/**\n * LogStorageService\n *\n * Service for database operations on logs using Drizzle ORM.\n * Handles CRUD operations and database initialization.\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Single responsibility principle\n * - Dependency injection with InversifyJS\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n * - Document complex logic with comments\n *\n * AVOID:\n * - Mixing concerns (keep focused on single domain)\n * - Direct tool implementation (services should be tool-agnostic)\n */\n\nimport { mkdir, stat } from 'node:fs/promises';\nimport { DatabaseSync } from 'node:sqlite';\nimport { dirname } from 'node:path';\nimport { injectable } from 'inversify';\nimport { resolveDefaultDbPath } from '../utils/resolveWorkspaceRoot.js';\nimport 'reflect-metadata';\nimport { ulid } from 'ulidx';\nimport * as sqliteVec from 'sqlite-vec';\nimport type { Log, NewLog } from '../models/schema.js';\nimport type { QueryFilters } from '../types/log.types.js';\n\ninterface LogRow {\n id: string;\n timestamp: number | bigint;\n level: string;\n message: string;\n trace_id: string | null;\n span_id: string | null;\n parent_span_id: string | null;\n service: string;\n hostname: string | null;\n pid: number | null;\n metadata: string | null;\n error_type: string | null;\n error_message: string | null;\n error_stack: string | null;\n created_at: number | bigint;\n}\n\ninterface LogCountRow {\n count: number | bigint;\n}\n\nfunction asRows<T>(rows: unknown): T[] {\n return rows as unknown as T[];\n}\n\n@injectable()\nexport class LogStorageService {\n private sqlite: DatabaseSync | null = null;\n private embeddingWriter: ((logs: Log[]) => Promise<void>) | null = null;\n private databasePath: string | null = null;\n private useInMemory = false;\n\n setEmbeddingWriter(writer: (logs: Log[]) => Promise<void>): void {\n this.embeddingWriter = writer;\n }\n\n /**\n * Initialize the database with SQLite\n *\n * @param dbPath - Path to the SQLite database file\n * @param inMemory - Whether to use an in-memory database\n */\n async initializeDatabase(dbPath: string, inMemory = false): Promise<void> {\n try {\n const effectivePath = inMemory ? ':memory:' : dbPath;\n this.databasePath = effectivePath;\n this.useInMemory = inMemory;\n\n // Ensure parent directory exists for file-based databases\n if (!inMemory) {\n const dir = dirname(effectivePath);\n await mkdir(dir, { recursive: true });\n }\n\n // Create SQLite connection\n this.sqlite = new DatabaseSync(effectivePath, { allowExtension: true });\n sqliteVec.load(this.sqlite);\n\n // Enable WAL mode for better concurrent access\n if (!inMemory) {\n this.sqlite.exec('PRAGMA journal_mode = WAL');\n }\n this.sqlite.exec('PRAGMA foreign_keys = ON');\n\n // Initialize Drizzle ORM with schema\n // Run migrations inline - create logs table with indexes\n this.sqlite.exec(`\n CREATE TABLE IF NOT EXISTS logs (\n id text PRIMARY KEY NOT NULL,\n timestamp integer NOT NULL,\n level text NOT NULL,\n message text NOT NULL,\n trace_id text,\n span_id text,\n parent_span_id text,\n service text NOT NULL,\n hostname text,\n pid integer,\n metadata text,\n error_type text,\n error_message text,\n error_stack text,\n created_at integer NOT NULL\n )\n `);\n\n this.sqlite.exec('CREATE INDEX IF NOT EXISTS logs_timestamp_idx ON logs (timestamp)');\n this.sqlite.exec('CREATE INDEX IF NOT EXISTS logs_level_idx ON logs (level)');\n this.sqlite.exec('CREATE INDEX IF NOT EXISTS logs_trace_id_idx ON logs (trace_id)');\n this.sqlite.exec('CREATE INDEX IF NOT EXISTS logs_span_id_idx ON logs (span_id)');\n this.sqlite.exec('CREATE INDEX IF NOT EXISTS logs_parent_span_id_idx ON logs (parent_span_id)');\n this.sqlite.exec('CREATE INDEX IF NOT EXISTS logs_service_idx ON logs (service)');\n this.sqlite.exec('CREATE INDEX IF NOT EXISTS logs_error_type_idx ON logs (error_type)');\n\n this.sqlite.exec(`\n CREATE TABLE IF NOT EXISTS log_embeddings (\n log_id text PRIMARY KEY NOT NULL,\n embedding text NOT NULL,\n service text NOT NULL,\n level text NOT NULL,\n created_at integer NOT NULL,\n FOREIGN KEY (log_id) REFERENCES logs(id) ON DELETE CASCADE\n ) STRICT\n `);\n\n this.sqlite.exec('CREATE INDEX IF NOT EXISTS log_embeddings_service_idx ON log_embeddings (service)');\n this.sqlite.exec('CREATE INDEX IF NOT EXISTS log_embeddings_level_idx ON log_embeddings (level)');\n this.sqlite.exec('CREATE INDEX IF NOT EXISTS log_embeddings_created_at_idx ON log_embeddings (created_at)');\n\n // FTS5 virtual table for full-text search across log messages\n this.sqlite.exec(`\n CREATE VIRTUAL TABLE IF NOT EXISTS logs_search USING fts5(\n id UNINDEXED,\n message,\n error_message,\n content='logs',\n content_rowid='rowid'\n )\n `);\n\n // Triggers to keep FTS5 index in sync with logs table\n this.sqlite.exec(`\n CREATE TRIGGER IF NOT EXISTS logs_ai AFTER INSERT ON logs BEGIN\n INSERT INTO logs_search(rowid, id, message, error_message)\n VALUES (new.rowid, new.id, new.message, new.error_message);\n END;\n `);\n\n this.sqlite.exec(`\n CREATE TRIGGER IF NOT EXISTS logs_ad AFTER DELETE ON logs BEGIN\n DELETE FROM logs_search WHERE rowid = old.rowid;\n END;\n `);\n\n this.sqlite.exec(`\n CREATE TRIGGER IF NOT EXISTS logs_au AFTER UPDATE ON logs BEGIN\n UPDATE logs_search SET message = new.message, error_message = new.error_message\n WHERE rowid = new.rowid;\n END;\n `);\n } catch (error) {\n throw new Error(`Failed to initialize database: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n /**\n * Get default database path in OS temp directory\n */\n private getDefaultDbPath(): string {\n return resolveDefaultDbPath();\n }\n\n /**\n * Check if the database has been initialized\n */\n isInitialized(): boolean {\n return this.sqlite !== null;\n }\n\n /**\n * Ensure database is initialized, auto-initializing with default path if needed\n */\n async ensureInitialized(): Promise<void> {\n if (!this.sqlite) {\n await this.initializeDatabase(this.getDefaultDbPath());\n }\n }\n\n /**\n * Convert a raw SQLite row into a log object.\n */\n private mapRow(row: LogRow): Log {\n return {\n id: row.id,\n timestamp: new Date(Number(row.timestamp) * 1000),\n level: row.level,\n message: row.message,\n traceId: row.trace_id,\n spanId: row.span_id,\n parentSpanId: row.parent_span_id,\n service: row.service,\n hostname: row.hostname,\n pid: row.pid,\n metadata: row.metadata ? JSON.parse(row.metadata) : null,\n errorType: row.error_type,\n errorMessage: row.error_message,\n errorStack: row.error_stack,\n createdAt: new Date(Number(row.created_at) * 1000),\n };\n }\n\n /**\n * Apply default values expected by the storage layer.\n */\n private prepareLogRecord(log: NewLog): Log {\n const now = new Date();\n\n return {\n id: log.id ?? ulid(),\n timestamp: log.timestamp ?? now,\n level: log.level,\n message: log.message,\n traceId: log.traceId ?? null,\n spanId: log.spanId ?? null,\n parentSpanId: log.parentSpanId ?? null,\n service: log.service,\n hostname: log.hostname ?? null,\n pid: log.pid ?? null,\n metadata: log.metadata ?? null,\n errorType: log.errorType ?? null,\n errorMessage: log.errorMessage ?? null,\n errorStack: log.errorStack ?? null,\n createdAt: log.createdAt ?? now,\n };\n }\n\n /**\n * Get the raw database client instance for other services.\n */\n getDatabaseClient(): DatabaseSync {\n if (!this.sqlite) {\n throw new Error('Database not initialized. Call initializeDatabase() first.');\n }\n return this.sqlite;\n }\n\n /**\n * Get the raw SQLite client instance for FTS queries.\n */\n getSqliteClient(): DatabaseSync {\n if (!this.sqlite) {\n throw new Error('Database not initialized. Call initializeDatabase() first.');\n }\n return this.sqlite;\n }\n\n /**\n * Get the current file-backed database path, if any.\n */\n getDatabasePath(): string | null {\n if (this.useInMemory || this.databasePath === ':memory:') {\n return null;\n }\n\n return this.databasePath;\n }\n\n /**\n * Get the current size of the SQLite database files in bytes.\n */\n async getDatabaseSizeBytes(): Promise<number | null> {\n const dbPath = this.getDatabasePath();\n if (!dbPath) {\n return null;\n }\n\n const filePaths = [dbPath, `${dbPath}-wal`, `${dbPath}-shm`];\n const sizes = await Promise.all(\n filePaths.map(async (filePath) => {\n try {\n return (await stat(filePath)).size;\n } catch {\n return 0;\n }\n }),\n );\n\n return sizes.reduce((sum, size) => sum + size, 0);\n }\n\n /**\n * Delete the oldest logs in the database.\n *\n * @param limit - Maximum number of log rows to delete\n * @returns Number of deleted rows\n */\n async deleteOldestLogs(limit: number): Promise<number> {\n try {\n if (!this.sqlite) {\n throw new Error('Database not initialized');\n }\n\n if (!Number.isInteger(limit) || limit <= 0) {\n return 0;\n }\n\n const result = this.sqlite\n .prepare(\n `\n DELETE FROM logs\n WHERE id IN (\n SELECT id\n FROM logs\n ORDER BY timestamp ASC, created_at ASC, id ASC\n LIMIT ?\n )\n `,\n )\n .run(limit);\n\n return Number(result.changes ?? 0);\n } catch (error) {\n throw new Error(`Failed to delete oldest logs: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n /**\n * Compact the database after cleanup so file size shrinks on disk.\n */\n async compactDatabase(): Promise<void> {\n try {\n if (!this.sqlite) {\n throw new Error('Database not initialized');\n }\n\n this.sqlite.exec('PRAGMA wal_checkpoint(TRUNCATE)');\n this.sqlite.exec('VACUUM');\n } catch (error) {\n throw new Error(`Failed to compact database: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n /**\n * Insert a single log entry\n *\n * @param log - Log entry to insert\n * @returns The inserted log with generated ID\n */\n async insertLog(log: NewLog): Promise<Log> {\n try {\n const sqlite = this.getSqliteClient();\n const inserted = this.prepareLogRecord(log);\n sqlite\n .prepare(\n `\n INSERT INTO logs (\n id, timestamp, level, message, trace_id, span_id, parent_span_id,\n service, hostname, pid, metadata, error_type, error_message,\n error_stack, created_at\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `,\n )\n .run(\n inserted.id,\n Math.floor(inserted.timestamp.getTime() / 1000),\n inserted.level,\n inserted.message,\n inserted.traceId,\n inserted.spanId,\n inserted.parentSpanId,\n inserted.service,\n inserted.hostname,\n inserted.pid,\n inserted.metadata ? JSON.stringify(inserted.metadata) : null,\n inserted.errorType,\n inserted.errorMessage,\n inserted.errorStack,\n Math.floor(inserted.createdAt.getTime() / 1000),\n );\n\n await this.tryIndexEmbeddings([inserted]);\n return inserted;\n } catch (error) {\n throw new Error(`Failed to insert log: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n /**\n * Insert multiple log entries in a batch\n *\n * @param logEntries - Array of log entries to insert\n * @returns Array of inserted logs with generated IDs\n */\n async insertBatch(logEntries: NewLog[]): Promise<Log[]> {\n try {\n if (logEntries.length === 0) {\n return [];\n }\n\n const sqlite = this.getSqliteClient();\n const inserted = logEntries.map((log) => this.prepareLogRecord(log));\n sqlite.exec('BEGIN');\n\n try {\n const insert = sqlite.prepare(`\n INSERT INTO logs (\n id, timestamp, level, message, trace_id, span_id, parent_span_id,\n service, hostname, pid, metadata, error_type, error_message,\n error_stack, created_at\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `);\n\n for (const row of inserted) {\n insert.run(\n row.id,\n Math.floor(row.timestamp.getTime() / 1000),\n row.level,\n row.message,\n row.traceId,\n row.spanId,\n row.parentSpanId,\n row.service,\n row.hostname,\n row.pid,\n row.metadata ? JSON.stringify(row.metadata) : null,\n row.errorType,\n row.errorMessage,\n row.errorStack,\n Math.floor(row.createdAt.getTime() / 1000),\n );\n }\n\n sqlite.exec('COMMIT');\n } catch (error) {\n sqlite.exec('ROLLBACK');\n throw error;\n }\n\n await this.tryIndexEmbeddings(inserted);\n return inserted;\n } catch (error) {\n throw new Error(`Failed to insert batch: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n private async tryIndexEmbeddings(logEntries: Log[]): Promise<void> {\n if (!this.embeddingWriter || logEntries.length === 0) {\n return;\n }\n\n void this.embeddingWriter(logEntries).catch((error) => {\n console.warn(`Failed to index semantic embeddings: ${error instanceof Error ? error.message : String(error)}`);\n });\n }\n\n /**\n * Query logs with filters\n *\n * @param filters - Query filters to apply\n * @returns Array of matching log entries\n */\n async queryLogs(filters: QueryFilters = {}): Promise<Log[]> {\n try {\n const sqlite = this.getSqliteClient();\n let whereSql = 'WHERE 1 = 1';\n const params: Array<string | number> = [];\n\n if (filters.level) {\n const levels = Array.isArray(filters.level) ? filters.level : [filters.level];\n if (levels.length === 1) {\n whereSql += ' AND level = ?';\n params.push(levels[0]);\n } else {\n whereSql += ` AND level IN (${levels.map(() => '?').join(',')})`;\n params.push(...levels);\n }\n }\n\n if (filters.service) {\n const services = Array.isArray(filters.service) ? filters.service : [filters.service];\n if (services.length === 1) {\n whereSql += ' AND service = ?';\n params.push(services[0]);\n } else {\n whereSql += ` AND service IN (${services.map(() => '?').join(',')})`;\n params.push(...services);\n }\n }\n\n if (filters.traceId) {\n whereSql += ' AND trace_id = ?';\n params.push(filters.traceId);\n }\n\n if (filters.spanId) {\n whereSql += ' AND span_id = ?';\n params.push(filters.spanId);\n }\n\n if (filters.errorType) {\n whereSql += ' AND error_type = ?';\n params.push(filters.errorType);\n }\n\n if (filters.startTime) {\n whereSql += ' AND timestamp >= ?';\n params.push(Math.floor(filters.startTime.getTime() / 1000));\n }\n\n if (filters.endTime) {\n whereSql += ' AND timestamp <= ?';\n params.push(Math.floor(filters.endTime.getTime() / 1000));\n }\n\n const limit = filters.limit ?? 100;\n const offset = filters.offset ?? 0;\n\n const rows = asRows<LogRow>(\n sqlite\n .prepare(\n `\n SELECT\n id,\n timestamp,\n level,\n message,\n trace_id,\n span_id,\n parent_span_id,\n service,\n hostname,\n pid,\n metadata,\n error_type,\n error_message,\n error_stack,\n created_at\n FROM logs\n ${whereSql}\n ORDER BY timestamp DESC\n LIMIT ?\n OFFSET ?\n `,\n )\n .all(...params, limit, offset),\n );\n\n return rows.map((row) => this.mapRow(row));\n } catch (error) {\n throw new Error(`Failed to query logs: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n /**\n * Delete all logs from the database\n *\n * @returns Number of deleted rows\n */\n async deleteAllLogs(): Promise<number> {\n try {\n const result = this.getSqliteClient().prepare('DELETE FROM logs').run();\n return Number(result.changes ?? 0);\n } catch (error) {\n throw new Error(`Failed to delete all logs: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n /**\n * Disconnect from the database\n */\n async disconnect(): Promise<void> {\n if (this.sqlite) {\n this.sqlite.close();\n this.sqlite = null;\n this.databasePath = null;\n this.useInMemory = false;\n }\n }\n}\n","/**\n * SemanticSearchService\n *\n * Hybrid semantic search on logs using local embeddings and sqlite-vec.\n */\n\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata';\nimport type { Log } from '../models/schema.js';\nimport { TYPES } from '../types/container.types.js';\nimport type { SearchOptions } from '../types/log.types.js';\nimport type { LogSearchService } from './LogSearchService.js';\nimport type { LogStorageService } from './LogStorageService.js';\nimport type { EmbeddingService } from './EmbeddingService.js';\n\nexport interface SemanticSearchResult extends Log {\n distance: number;\n score: number;\n}\n\ninterface SemanticSearchFilters extends Omit<SearchOptions, 'query'> {\n offset?: number;\n}\n\ninterface SemanticSearchRow {\n id: string;\n timestamp: number;\n level: string;\n message: string;\n traceId: string | null;\n spanId: string | null;\n parentSpanId: string | null;\n service: string;\n hostname: string | null;\n pid: number | null;\n metadata: string | null;\n errorType: string | null;\n errorMessage: string | null;\n errorStack: string | null;\n createdAt: number;\n distance: number;\n}\n\nfunction asRows<T>(rows: unknown): T[] {\n return rows as unknown as T[];\n}\n\n@injectable()\nexport class SemanticSearchService {\n constructor(\n @inject(TYPES.LogStorageService) private storage: LogStorageService,\n @inject(TYPES.LogSearchService) private searchService: LogSearchService,\n @inject(TYPES.EmbeddingService) private embeddings: EmbeddingService,\n ) {}\n\n async isAvailable(): Promise<boolean> {\n return this.embeddings.isAvailable();\n }\n\n private mapRow(row: SemanticSearchRow): SemanticSearchResult {\n const score = 1 / (1 + row.distance);\n\n return {\n id: row.id,\n timestamp: new Date(row.timestamp * 1000),\n level: row.level,\n message: row.message,\n traceId: row.traceId,\n spanId: row.spanId,\n parentSpanId: row.parentSpanId,\n service: row.service,\n hostname: row.hostname,\n pid: row.pid,\n metadata: row.metadata ? JSON.parse(row.metadata) : null,\n errorType: row.errorType,\n errorMessage: row.errorMessage,\n errorStack: row.errorStack,\n createdAt: new Date(row.createdAt * 1000),\n distance: row.distance,\n score,\n };\n }\n\n private mapFallbackRow(\n row: Awaited<ReturnType<LogSearchService['searchLogsPaginated']>>['results'][number],\n ): SemanticSearchResult {\n return {\n ...row,\n distance: Number.POSITIVE_INFINITY,\n score: 1 / (1 + Math.max(0, Math.abs(row.rank))),\n };\n }\n\n private buildFilterSql(filters: SemanticSearchFilters = {}) {\n let whereSql = 'WHERE 1 = 1';\n const params: Array<string | number> = [];\n\n if (filters.service) {\n const services = Array.isArray(filters.service) ? filters.service : [filters.service];\n whereSql += ` AND logs.service IN (${services.map(() => '?').join(',')})`;\n params.push(...services);\n }\n\n if (filters.level) {\n const levels = Array.isArray(filters.level) ? filters.level : [filters.level];\n whereSql += ` AND logs.level IN (${levels.map(() => '?').join(',')})`;\n params.push(...levels);\n }\n\n if (filters.startTime) {\n whereSql += ' AND logs.timestamp >= ?';\n params.push(Math.floor(filters.startTime.getTime() / 1000));\n }\n\n if (filters.endTime) {\n whereSql += ' AND logs.timestamp <= ?';\n params.push(Math.floor(filters.endTime.getTime() / 1000));\n }\n\n return { whereSql, params };\n }\n\n async indexMissingLogs(filters: Omit<SemanticSearchFilters, 'offset'> = {}, batchSize = 100): Promise<number> {\n const sqlite = this.storage.getSqliteClient();\n let indexed = 0;\n\n while (true) {\n const { whereSql, params } = this.buildFilterSql(filters);\n const rows = asRows<Omit<SemanticSearchRow, 'distance'>>(\n sqlite\n .prepare(\n `\n SELECT\n logs.id as id,\n logs.timestamp as timestamp,\n logs.level as level,\n logs.message as message,\n logs.trace_id as traceId,\n logs.span_id as spanId,\n logs.parent_span_id as parentSpanId,\n logs.service as service,\n logs.hostname as hostname,\n logs.pid as pid,\n logs.metadata as metadata,\n logs.error_type as errorType,\n logs.error_message as errorMessage,\n logs.error_stack as errorStack,\n logs.created_at as createdAt\n FROM logs\n LEFT JOIN log_embeddings ON log_embeddings.log_id = logs.id\n ${whereSql}\n AND log_embeddings.log_id IS NULL\n ORDER BY logs.timestamp ASC\n LIMIT ?\n `,\n )\n .all(...params, batchSize),\n );\n\n if (rows.length === 0) {\n return indexed;\n }\n\n await this.embeddings.indexLogs(\n rows.map((row) => ({\n id: row.id,\n timestamp: new Date(row.timestamp * 1000),\n level: row.level,\n message: row.message,\n traceId: row.traceId,\n spanId: row.spanId,\n parentSpanId: row.parentSpanId,\n service: row.service,\n hostname: row.hostname,\n pid: row.pid,\n metadata: row.metadata ? JSON.parse(row.metadata) : null,\n errorType: row.errorType,\n errorMessage: row.errorMessage,\n errorStack: row.errorStack,\n createdAt: new Date(row.createdAt * 1000),\n })),\n );\n\n indexed += rows.length;\n }\n }\n\n async searchLogs(\n query: string,\n filters: Omit<SemanticSearchFilters, 'query'> = {},\n limit = 100,\n ): Promise<SemanticSearchResult[]> {\n const { results } = await this.searchLogsPaginated(query, { ...filters, offset: 0 }, limit);\n return results;\n }\n\n async searchLogsPaginated(\n query: string,\n filters: SemanticSearchFilters = {},\n limit = 100,\n ): Promise<{ results: SemanticSearchResult[]; total: number }> {\n try {\n await this.storage.ensureInitialized();\n if (!(await this.embeddings.isAvailable())) {\n const fallback = await this.searchService.searchLogsPaginated(query, filters, limit);\n return {\n results: fallback.results.map((row) => this.mapFallbackRow(row)),\n total: fallback.total,\n };\n }\n\n await this.indexMissingLogs(filters);\n\n const sqlite = this.storage.getSqliteClient();\n const queryEmbedding = await this.embeddings.embedText(query);\n const queryVector = JSON.stringify(queryEmbedding);\n const { whereSql, params } = this.buildFilterSql(filters);\n const offset = filters.offset ?? 0;\n\n const countSql = `\n SELECT COUNT(*) as total\n FROM log_embeddings\n JOIN logs ON logs.id = log_embeddings.log_id\n ${whereSql}\n `;\n\n const dataSql = `\n SELECT\n logs.id as id,\n logs.timestamp as timestamp,\n logs.level as level,\n logs.message as message,\n logs.trace_id as traceId,\n logs.span_id as spanId,\n logs.parent_span_id as parentSpanId,\n logs.service as service,\n logs.hostname as hostname,\n logs.pid as pid,\n logs.metadata as metadata,\n logs.error_type as errorType,\n logs.error_message as errorMessage,\n logs.error_stack as errorStack,\n logs.created_at as createdAt,\n vec_distance_cosine(log_embeddings.embedding, vec_f32(?)) as distance\n FROM log_embeddings\n JOIN logs ON logs.id = log_embeddings.log_id\n ${whereSql}\n ORDER BY distance ASC\n LIMIT ?\n OFFSET ?\n `;\n\n const countResult = sqlite.prepare(countSql).get(...params) as unknown as { total: number };\n const rows = asRows<SemanticSearchRow>(sqlite.prepare(dataSql).all(queryVector, ...params, limit, offset));\n\n return {\n results: rows.map((row) => this.mapRow(row)),\n total: Number(countResult.total ?? 0),\n };\n } catch (error) {\n const fallback = await this.searchService.searchLogsPaginated(query, filters, limit);\n console.warn(\n `Semantic search unavailable, falling back to FTS: ${error instanceof Error ? error.message : String(error)}`,\n );\n return {\n results: fallback.results.map((row) => this.mapFallbackRow(row)),\n total: fallback.total,\n };\n }\n }\n}\n","/**\n * InversifyJS Container Configuration\n *\n * Central Dependency Injection container for log-sink-mcp.\n * Binds all services in singleton scope for shared state management.\n *\n * DESIGN PATTERNS:\n * - Dependency Injection with InversifyJS\n * - Container Module pattern\n * - Singleton scope for stateful services\n *\n * CODING STANDARDS:\n * - Use ContainerModule for modular binding organization\n * - Bind services in singleton scope to share database connection\n * - Export factory function for container creation\n *\n * AVOID:\n * - Hardcoding service instantiation\n * - Creating multiple container instances\n * - Using transient scope for stateful services\n */\n\nimport { Container, ContainerModule } from 'inversify';\nimport 'reflect-metadata';\nimport { HttpServerHealthCheck } from '../services/HttpServerHealthCheck.js';\nimport { HttpServerManager } from '../services/HttpServerManager.js';\nimport { EmbeddingService } from '../services/EmbeddingService.js';\nimport { LogQueryService } from '../services/LogQueryService.js';\nimport { LogRetentionService } from '../services/LogRetentionService.js';\nimport { LogSearchService } from '../services/LogSearchService.js';\nimport { LogStorageService } from '../services/LogStorageService.js';\nimport { SemanticSearchService } from '../services/SemanticSearchService.js';\nimport { TYPES } from '../types/container.types.js';\n\n/**\n * Services ContainerModule\n *\n * Binds all log-sink services in singleton scope.\n * All services share the same LogStorageService instance for database access.\n */\nexport const servicesModule = new ContainerModule((options) => {\n // Bind LogStorageService first - other services depend on it\n options.bind(TYPES.LogStorageService).to(LogStorageService).inSingletonScope();\n options.bind(TYPES.EmbeddingService).to(EmbeddingService).inSingletonScope();\n\n // Bind dependent services\n options.bind(TYPES.LogQueryService).to(LogQueryService).inSingletonScope();\n options.bind(TYPES.LogSearchService).to(LogSearchService).inSingletonScope();\n options.bind(TYPES.SemanticSearchService).to(SemanticSearchService).inSingletonScope();\n options.bind(TYPES.LogRetentionService).to(LogRetentionService).inSingletonScope();\n\n // Bind HTTP Server Management services\n options.bind(TYPES.HttpServerHealthCheck).to(HttpServerHealthCheck).inSingletonScope();\n options.bind(TYPES.HttpServerManager).to(HttpServerManager).inSingletonScope();\n});\n\n/**\n * Create and configure the InversifyJS container\n *\n * @returns Configured container with all service bindings\n */\nexport function createContainer(): Container {\n const container = new Container();\n container.load(servicesModule);\n const storage = container.get<LogStorageService>(TYPES.LogStorageService);\n const embeddingService = container.get<EmbeddingService>(TYPES.EmbeddingService);\n storage.setEmbeddingWriter((logs) => embeddingService.indexLogs(logs));\n return container;\n}\n","/**\n * AnalyzeErrorsTool\n *\n * DESIGN PATTERNS:\n * - Tool pattern with getDefinition() and execute() methods\n * - Service delegation for business logic\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Implement Tool interface from ../types\n * - Use TOOL_NAME constant with snake_case (e.g., 'file_read')\n * - Return CallToolResult with content array\n * - Handle errors with isError flag\n * - Delegate complex logic to services\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n * - Missing input validation\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport type { Container } from 'inversify';\nimport type { LogQueryService } from '../services/LogQueryService.js';\nimport { TYPES } from '../types/container.types.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\nimport type { LogLevel } from '../types/log.types.js';\n\ninterface AnalyzeErrorsToolInput {\n timeRange?: { start: string; end: string };\n traceId?: string;\n limit?: number;\n}\n\ninterface ErrorAnalysis {\n errorType: string | null;\n errorMessage: string | null;\n frequency: number;\n services: Set<string>;\n examples: Array<{ id: string; timestamp: string; service: string }>;\n}\n\nexport class AnalyzeErrorsTool implements Tool<AnalyzeErrorsToolInput> {\n static readonly TOOL_NAME = 'analyze_errors';\n\n private queryService: LogQueryService;\n\n constructor(container: Container) {\n this.queryService = container.get<LogQueryService>(TYPES.LogQueryService);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: AnalyzeErrorsTool.TOOL_NAME,\n description:\n 'Analyze error patterns and group similar errors by type and message. Calculates error frequencies, suggests root causes, and provides debugging insights.',\n inputSchema: {\n type: 'object',\n properties: {\n timeRange: {\n type: 'object',\n properties: {\n start: {\n type: 'string',\n format: 'date-time',\n description: 'Start time for error analysis (ISO 8601 format)',\n },\n end: {\n type: 'string',\n format: 'date-time',\n description: 'End time for error analysis (ISO 8601 format)',\n },\n },\n description: 'Optional time range to filter errors',\n },\n traceId: {\n type: 'string',\n description: 'Optional trace ID to analyze errors within a specific trace',\n },\n limit: {\n type: 'number',\n minimum: 1,\n maximum: 1000,\n default: 100,\n description: 'Maximum number of error entries to analyze',\n },\n },\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: AnalyzeErrorsToolInput): Promise<CallToolResult> {\n try {\n const limit = input.limit ?? 100;\n let errorLogs;\n\n // Get error logs by trace ID if provided\n if (input.traceId) {\n const allLogs = await this.queryService.filterByTrace(input.traceId);\n errorLogs = allLogs.filter((log) => log.level === 'error' || log.level === 'fatal').slice(0, limit);\n }\n // Get error logs by time range if provided\n else if (input.timeRange) {\n const start = new Date(input.timeRange.start);\n const end = new Date(input.timeRange.end);\n const allLogs = await this.queryService.filterByTimeRange(start, end);\n errorLogs = allLogs.filter((log) => log.level === 'error' || log.level === 'fatal').slice(0, limit);\n }\n // Get all error logs\n else {\n errorLogs = await this.queryService.filterByLevel(['error', 'fatal'] as LogLevel[], limit);\n }\n\n // Group errors by errorType and errorMessage\n const errorGroups = new Map<string, ErrorAnalysis>();\n\n for (const log of errorLogs) {\n const key = `${log.errorType}::${log.errorMessage}`;\n\n if (!errorGroups.has(key)) {\n errorGroups.set(key, {\n errorType: log.errorType,\n errorMessage: log.errorMessage,\n frequency: 0,\n services: new Set(),\n examples: [],\n });\n }\n\n const group = errorGroups.get(key)!;\n group.frequency += 1;\n group.services.add(log.service);\n\n if (group.examples.length < 3) {\n group.examples.push({\n id: log.id,\n timestamp: log.timestamp.toISOString(),\n service: log.service,\n });\n }\n }\n\n // Sort by frequency descending\n const analysis = Array.from(errorGroups.values())\n .sort((a, b) => b.frequency - a.frequency)\n .map((group) => ({\n errorType: group.errorType,\n errorMessage: group.errorMessage,\n frequency: group.frequency,\n affectedServices: Array.from(group.services),\n examples: group.examples,\n }));\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n totalErrorsAnalyzed: errorLogs.length,\n uniqueErrorPatterns: analysis.length,\n analysis,\n },\n null,\n 2,\n ),\n },\n ],\n };\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: `Error analyzing logs: ${error instanceof Error ? error.message : 'Unknown error'}`,\n },\n ],\n isError: true,\n };\n }\n }\n}\n","/**\n * ClearLogsTool\n *\n * DESIGN PATTERNS:\n * - Tool pattern with getDefinition() and execute() methods\n * - Service delegation for business logic\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Implement Tool interface from ../types\n * - Use TOOL_NAME constant with snake_case (e.g., 'file_read')\n * - Return CallToolResult with content array\n * - Handle errors with isError flag\n * - Delegate complex logic to services\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n * - Missing input validation\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport type { Container } from 'inversify';\nimport type { LogRetentionService } from '../services/LogRetentionService.js';\nimport { TYPES } from '../types/container.types.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\n\ntype ClearLogsToolInput = Record<string, never>;\n\nexport class ClearLogsTool implements Tool<ClearLogsToolInput> {\n static readonly TOOL_NAME = 'clear_logs';\n\n private retentionService: LogRetentionService;\n\n constructor(container: Container) {\n this.retentionService = container.get<LogRetentionService>(TYPES.LogRetentionService);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: ClearLogsTool.TOOL_NAME,\n description:\n 'Clear all logs from the database. Returns confirmation with count of deleted entries. Use with caution - this operation cannot be undone.',\n inputSchema: {\n type: 'object',\n properties: {},\n additionalProperties: false,\n },\n };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async execute(_input: ClearLogsToolInput): Promise<CallToolResult> {\n try {\n const deletedCount = await this.retentionService.clearAllLogs();\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n success: true,\n message: 'All logs have been cleared',\n deletedEntries: deletedCount,\n },\n null,\n 2,\n ),\n },\n ],\n };\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: `Error clearing logs: ${error instanceof Error ? error.message : 'Unknown error'}`,\n },\n ],\n isError: true,\n };\n }\n }\n}\n","/**\n * GetLogStatsTool\n *\n * DESIGN PATTERNS:\n * - Tool pattern with getDefinition() and execute() methods\n * - Service delegation for business logic\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Implement Tool interface from ../types\n * - Use TOOL_NAME constant with snake_case (e.g., 'file_read')\n * - Return CallToolResult with content array\n * - Handle errors with isError flag\n * - Delegate complex logic to services\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n * - Missing input validation\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport type { Container } from 'inversify';\nimport type { LogQueryService } from '../services/LogQueryService.js';\nimport { TYPES } from '../types/container.types.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\n\ninterface GetLogStatsToolInput {\n startTime?: string;\n endTime?: string;\n groupBy?: 'level' | 'service' | 'both';\n}\n\nexport class GetLogStatsTool implements Tool<GetLogStatsToolInput> {\n static readonly TOOL_NAME = 'get_log_stats';\n\n private queryService: LogQueryService;\n\n constructor(container: Container) {\n this.queryService = container.get<LogQueryService>(TYPES.LogQueryService);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: GetLogStatsTool.TOOL_NAME,\n description:\n 'Get aggregated log statistics grouped by level, service, or time period. Returns counts, distributions, and trends for log analysis.',\n inputSchema: {\n type: 'object',\n properties: {\n startTime: {\n type: 'string',\n format: 'date-time',\n description: 'Start time for statistics (ISO 8601 format)',\n },\n endTime: {\n type: 'string',\n format: 'date-time',\n description: 'End time for statistics (ISO 8601 format)',\n },\n groupBy: {\n type: 'string',\n enum: ['level', 'service', 'both'],\n default: 'both',\n description: \"Group statistics by 'level', 'service', or 'both'\",\n },\n },\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: GetLogStatsToolInput): Promise<CallToolResult> {\n try {\n let timeRange: { start: Date; end: Date } | undefined;\n\n if (input.startTime || input.endTime) {\n timeRange = {\n start: input.startTime ? new Date(input.startTime) : new Date(0),\n end: input.endTime ? new Date(input.endTime) : new Date(),\n };\n }\n\n const groupBy = input.groupBy ?? 'both';\n const statistics = await this.queryService.getStatistics(timeRange, groupBy);\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n timeRange,\n groupBy,\n totalEntries: statistics.reduce((sum, stat) => sum + stat.count, 0),\n statistics: statistics.map((stat) => ({\n service: stat.service,\n level: stat.level,\n count: stat.count,\n })),\n },\n null,\n 2,\n ),\n },\n ],\n };\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: `Error getting statistics: ${error instanceof Error ? error.message : 'Unknown error'}`,\n },\n ],\n isError: true,\n };\n }\n }\n}\n","/**\n * GetServicesTool\n *\n * DESIGN PATTERNS:\n * - Tool pattern with getDefinition() and execute() methods\n * - Service delegation for business logic\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Implement Tool interface from ../types\n * - Use TOOL_NAME constant with snake_case (e.g., 'file_read')\n * - Return CallToolResult with content array\n * - Handle errors with isError flag\n * - Delegate complex logic to services\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n * - Missing input validation\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport type { Container } from 'inversify';\nimport type { LogQueryService } from '../services/LogQueryService.js';\nimport { TYPES } from '../types/container.types.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\n\ntype GetServicesToolInput = Record<string, never>;\n\nexport class GetServicesTool implements Tool<GetServicesToolInput> {\n static readonly TOOL_NAME = 'get_services';\n\n private queryService: LogQueryService;\n\n constructor(container: Container) {\n this.queryService = container.get<LogQueryService>(TYPES.LogQueryService);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: GetServicesTool.TOOL_NAME,\n description:\n 'Get list of all unique services that have logged entries. Useful for discovering available services in the system.',\n inputSchema: {\n type: 'object',\n properties: {},\n additionalProperties: false,\n },\n };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async execute(_input: GetServicesToolInput): Promise<CallToolResult> {\n try {\n const services = await this.queryService.getActiveServices();\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n count: services.length,\n services: services.sort(),\n },\n null,\n 2,\n ),\n },\n ],\n };\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: `Error getting services: ${error instanceof Error ? error.message : 'Unknown error'}`,\n },\n ],\n isError: true,\n };\n }\n }\n}\n","/**\n * GetTraceTimelineTool\n *\n * DESIGN PATTERNS:\n * - Tool pattern with getDefinition() and execute() methods\n * - Service delegation for business logic\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Implement Tool interface from ../types\n * - Use TOOL_NAME constant with snake_case (e.g., 'file_read')\n * - Return CallToolResult with content array\n * - Handle errors with isError flag\n * - Delegate complex logic to services\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n * - Missing input validation\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport type { Container } from 'inversify';\nimport type { LogQueryService } from '../services/LogQueryService.js';\nimport { TYPES } from '../types/container.types.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\n\ninterface GetTraceTimelineToolInput {\n traceId: string;\n}\n\nexport class GetTraceTimelineTool implements Tool<GetTraceTimelineToolInput> {\n static readonly TOOL_NAME = 'get_trace_timeline';\n\n private queryService: LogQueryService;\n\n constructor(container: Container) {\n this.queryService = container.get<LogQueryService>(TYPES.LogQueryService);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: GetTraceTimelineTool.TOOL_NAME,\n description:\n 'Get complete trace timeline for a given trace ID, showing all log entries ordered by timestamp with span hierarchy visualization. Returns chronological sequence of events across services.',\n inputSchema: {\n type: 'object',\n properties: {\n traceId: {\n type: 'string',\n description: 'OTEL trace ID (32 hex characters)',\n },\n },\n required: ['traceId'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: GetTraceTimelineToolInput): Promise<CallToolResult> {\n try {\n const timeline = await this.queryService.getTraceTimeline(input.traceId);\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n traceId: input.traceId,\n count: timeline.length,\n timeline: timeline.map((entry) => ({\n timestamp: entry.timestamp.toISOString(),\n service: entry.service,\n spanId: entry.spanId,\n parentSpanId: entry.parentSpanId,\n level: entry.level,\n message: entry.message,\n duration: entry.duration,\n })),\n },\n null,\n 2,\n ),\n },\n ],\n };\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: `Error getting trace timeline: ${error instanceof Error ? error.message : 'Unknown error'}`,\n },\n ],\n isError: true,\n };\n }\n }\n}\n","/**\n * QueryLogsTool\n *\n * DESIGN PATTERNS:\n * - Tool pattern with getDefinition() and execute() methods\n * - Service delegation for business logic\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Implement Tool interface from ../types\n * - Use TOOL_NAME constant with snake_case (e.g., 'file_read')\n * - Return CallToolResult with content array\n * - Handle errors with isError flag\n * - Delegate complex logic to services\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n * - Missing input validation\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport type { Container } from 'inversify';\nimport type { LogQueryService } from '../services/LogQueryService.js';\nimport { TYPES } from '../types/container.types.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\nimport type { LogLevel } from '../types/log.types.js';\n\ninterface QueryLogsToolInput {\n level?: LogLevel | LogLevel[];\n traceId?: string;\n service?: string | string[];\n startTime?: string;\n endTime?: string;\n limit?: number;\n}\n\nexport class QueryLogsTool implements Tool<QueryLogsToolInput> {\n static readonly TOOL_NAME = 'query_logs';\n\n private queryService: LogQueryService;\n\n constructor(container: Container) {\n this.queryService = container.get<LogQueryService>(TYPES.LogQueryService);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: QueryLogsTool.TOOL_NAME,\n description:\n 'Query and filter log entries by level, trace ID, time range, service, or other criteria. Returns filtered log entries with full metadata.',\n inputSchema: {\n type: 'object',\n properties: {\n level: {\n oneOf: [\n { type: 'string', enum: ['trace', 'debug', 'info', 'warn', 'error', 'fatal'] },\n {\n type: 'array',\n items: { type: 'string', enum: ['trace', 'debug', 'info', 'warn', 'error', 'fatal'] },\n },\n ],\n description: 'Log level(s) to filter by',\n },\n traceId: {\n type: 'string',\n description: 'Trace ID to filter by (OTEL format: 32 hex characters)',\n },\n service: {\n oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],\n description: 'Service name(s) to filter by',\n },\n startTime: {\n type: 'string',\n format: 'date-time',\n description: 'Start time for log range (ISO 8601 format)',\n },\n endTime: {\n type: 'string',\n format: 'date-time',\n description: 'End time for log range (ISO 8601 format)',\n },\n limit: {\n type: 'number',\n minimum: 1,\n maximum: 1000,\n default: 100,\n description: 'Maximum number of log entries to return',\n },\n },\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: QueryLogsToolInput): Promise<CallToolResult> {\n try {\n const limit = input.limit ?? 100;\n let logs;\n\n // Query by trace ID if provided\n if (input.traceId) {\n logs = await this.queryService.filterByTrace(input.traceId);\n logs = logs.slice(0, limit); // Apply limit after fetching\n }\n // Query by level if provided\n else if (input.level) {\n logs = await this.queryService.filterByLevel(input.level as LogLevel | LogLevel[], limit);\n }\n // Query by service if provided\n else if (input.service) {\n logs = await this.queryService.filterByService(input.service);\n logs = logs.slice(0, limit); // Apply limit after fetching\n }\n // Query by time range if provided\n else if (input.startTime || input.endTime) {\n const start = input.startTime ? new Date(input.startTime) : new Date(0);\n const end = input.endTime ? new Date(input.endTime) : new Date();\n logs = await this.queryService.filterByTimeRange(start, end);\n logs = logs.slice(0, limit); // Apply limit after fetching\n }\n // No filters - return recent logs\n else {\n logs = await this.queryService.filterByLevel(['info', 'warn', 'error', 'fatal'] as LogLevel[], limit);\n }\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n count: logs.length,\n logs: logs.map((log) => ({\n id: log.id,\n timestamp: log.timestamp.toISOString(),\n level: log.level,\n message: log.message,\n service: log.service,\n traceId: log.traceId,\n spanId: log.spanId,\n parentSpanId: log.parentSpanId,\n hostname: log.hostname,\n pid: log.pid,\n metadata: log.metadata,\n errorType: log.errorType,\n errorMessage: log.errorMessage,\n errorStack: log.errorStack,\n })),\n },\n null,\n 2,\n ),\n },\n ],\n };\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: `Error querying logs: ${error instanceof Error ? error.message : 'Unknown error'}`,\n },\n ],\n isError: true,\n };\n }\n }\n}\n","/**\n * SearchLogsTool\n *\n * DESIGN PATTERNS:\n * - Tool pattern with getDefinition() and execute() methods\n * - Service delegation for business logic\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Implement Tool interface from ../types\n * - Use TOOL_NAME constant with snake_case (e.g., 'file_read')\n * - Return CallToolResult with content array\n * - Handle errors with isError flag\n * - Delegate complex logic to services\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n * - Missing input validation\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport type { Container } from 'inversify';\nimport type { LogSearchService } from '../services/LogSearchService.js';\nimport type { SemanticSearchService } from '../services/SemanticSearchService.js';\nimport { TYPES } from '../types/container.types.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\nimport type { LogLevel } from '../types/log.types.js';\n\ninterface SearchLogsToolInput {\n searchQuery: string;\n mode?: 'fts' | 'semantic' | 'hybrid';\n service?: string | string[];\n level?: LogLevel | LogLevel[];\n startTime?: string;\n endTime?: string;\n limit?: number;\n}\n\nexport class SearchLogsTool implements Tool<SearchLogsToolInput> {\n static readonly TOOL_NAME = 'search_logs';\n\n private searchService: LogSearchService;\n private semanticSearchService: SemanticSearchService;\n\n constructor(container: Container) {\n this.searchService = container.get<LogSearchService>(TYPES.LogSearchService);\n this.semanticSearchService = container.get<SemanticSearchService>(TYPES.SemanticSearchService);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: SearchLogsTool.TOOL_NAME,\n description:\n 'Search log entries using FTS5, local semantic embeddings, or both. Hybrid mode combines exact text and semantic similarity for better incident retrieval.',\n inputSchema: {\n type: 'object',\n properties: {\n searchQuery: {\n type: 'string',\n description:\n 'FTS5 search query (supports phrase queries with quotes, prefix matching with *, boolean operators AND/OR/NOT)',\n },\n mode: {\n type: 'string',\n enum: ['fts', 'semantic', 'hybrid'],\n default: 'hybrid',\n description: 'Search strategy to use',\n },\n service: {\n oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],\n description: 'Service name(s) to filter by',\n },\n level: {\n oneOf: [\n { type: 'string', enum: ['trace', 'debug', 'info', 'warn', 'error', 'fatal'] },\n {\n type: 'array',\n items: { type: 'string', enum: ['trace', 'debug', 'info', 'warn', 'error', 'fatal'] },\n },\n ],\n description: 'Log level(s) to filter by',\n },\n startTime: {\n type: 'string',\n format: 'date-time',\n description: 'Start time for log range (ISO 8601 format)',\n },\n endTime: {\n type: 'string',\n format: 'date-time',\n description: 'End time for log range (ISO 8601 format)',\n },\n limit: {\n type: 'number',\n minimum: 1,\n maximum: 1000,\n default: 100,\n description: 'Maximum number of search results to return',\n },\n },\n required: ['searchQuery'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: SearchLogsToolInput): Promise<CallToolResult> {\n try {\n const filters: any = {};\n const mode = input.mode ?? 'hybrid';\n\n if (input.service) {\n filters.service = input.service;\n }\n\n if (input.level) {\n filters.level = input.level;\n }\n\n if (input.startTime) {\n filters.startTime = new Date(input.startTime);\n }\n\n if (input.endTime) {\n filters.endTime = new Date(input.endTime);\n }\n\n const limit = input.limit ?? 100;\n const results =\n mode === 'semantic'\n ? await this.semanticSearchService.searchLogs(input.searchQuery, filters, limit)\n : mode === 'fts'\n ? await this.searchService.searchLogs(input.searchQuery, filters, limit)\n : await this.searchHybrid(input.searchQuery, filters, limit);\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n mode,\n count: results.length,\n query: input.searchQuery,\n results: results.map((result) => ({\n score: 'score' in result ? result.score : this.getFallbackScore(result.rank),\n rank: 'rank' in result ? result.rank : undefined,\n distance: 'distance' in result ? result.distance : undefined,\n sources: 'sources' in result ? result.sources : undefined,\n id: result.id,\n timestamp: result.timestamp.toISOString(),\n level: result.level,\n message: result.message,\n service: result.service,\n traceId: result.traceId,\n spanId: result.spanId,\n parentSpanId: result.parentSpanId,\n hostname: result.hostname,\n pid: result.pid,\n metadata: result.metadata,\n errorType: result.errorType,\n errorMessage: result.errorMessage,\n })),\n },\n null,\n 2,\n ),\n },\n ],\n };\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: `Error searching logs: ${error instanceof Error ? error.message : 'Unknown error'}`,\n },\n ],\n isError: true,\n };\n }\n }\n\n private getFallbackScore(rank: number): number {\n return 1 / (1 + Math.max(0, Math.abs(rank)));\n }\n\n private async searchHybrid(\n query: string,\n filters: Record<string, unknown>,\n limit: number,\n ): Promise<\n Array<\n | Awaited<ReturnType<LogSearchService['searchLogs']>>[number]\n | (Awaited<ReturnType<SemanticSearchService['searchLogs']>>[number] & { sources?: string[] })\n >\n > {\n const searchWindow = Math.max(limit * 2, 50);\n const ftsResultsPromise = this.searchService.searchLogs(query, filters as any, searchWindow);\n const semanticAvailable = await this.semanticSearchService.isAvailable();\n const ftsResults = await ftsResultsPromise;\n\n if (!semanticAvailable) {\n return ftsResults;\n }\n\n const semanticResults = await this.semanticSearchService.searchLogs(query, filters as any, searchWindow);\n\n type HybridRow = {\n result: (typeof ftsResults)[number] | (typeof semanticResults)[number];\n score: number;\n rank?: number;\n distance?: number;\n sources: Set<'fts' | 'semantic'>;\n };\n\n const merged = new Map<string, HybridRow>();\n\n ftsResults.forEach((result, index) => {\n merged.set(result.id, {\n result,\n score: 0.65 * (1 / (1 + index)),\n rank: result.rank,\n sources: new Set(['fts']),\n });\n });\n\n semanticResults.forEach((result, index) => {\n const current = merged.get(result.id);\n const semanticScore = 0.35 * (1 / (1 + index));\n\n if (current) {\n current.score += semanticScore;\n current.sources.add('semantic');\n current.distance = result.distance;\n if (current.rank === undefined && 'rank' in result) {\n current.rank = (result as { rank?: number }).rank;\n }\n current.result = { ...current.result, ...result } as typeof current.result;\n } else {\n merged.set(result.id, {\n result,\n score: semanticScore,\n distance: result.distance,\n sources: new Set(['semantic']),\n });\n }\n });\n\n return Array.from(merged.values())\n .sort((a, b) => b.score - a.score)\n .slice(0, limit)\n .map((entry) => ({\n ...(entry.result as any),\n score: entry.score,\n sources: Array.from(entry.sources),\n rank: entry.rank,\n distance: entry.distance,\n }));\n }\n}\n","/**\n * MCP Server Setup\n *\n * DESIGN PATTERNS:\n * - Factory pattern for server creation\n * - Dependency Injection with InversifyJS\n * - Tool registration pattern\n *\n * CODING STANDARDS:\n * - Register all tools, resources, and prompts here\n * - Keep server setup modular and extensible\n * - Import tools from ../tools/ and register them in the handlers\n * - Use InversifyJS container for service injection\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport type { Container } from 'inversify';\nimport { createContainer } from '../container/index.js';\nimport { AnalyzeErrorsTool } from '../tools/AnalyzeErrorsTool.js';\nimport { ClearLogsTool } from '../tools/ClearLogsTool.js';\nimport { GetLogStatsTool } from '../tools/GetLogStatsTool.js';\nimport { GetServicesTool } from '../tools/GetServicesTool.js';\nimport { GetTraceTimelineTool } from '../tools/GetTraceTimelineTool.js';\nimport { QueryLogsTool } from '../tools/QueryLogsTool.js';\nimport { SearchLogsTool } from '../tools/SearchLogsTool.js';\n\n/**\n * Create MCP server with optional pre-configured container\n *\n * @param existingContainer - Optional pre-configured InversifyJS container with initialized services\n * @returns Configured MCP server instance\n */\nexport function createServer(existingContainer?: Container): Server {\n const server = new Server(\n {\n name: 'log-sink-mcp',\n version: '0.1.0',\n },\n {\n capabilities: {\n tools: {},\n },\n },\n );\n\n // Use existing container if provided, otherwise create new one\n const container: Container = existingContainer ?? createContainer();\n\n // Initialize tools with container-injected services\n const queryLogsTool = new QueryLogsTool(container);\n const searchLogsTool = new SearchLogsTool(container);\n const getTraceTimelineTool = new GetTraceTimelineTool(container);\n const analyzeErrorsTool = new AnalyzeErrorsTool(container);\n const getLogStatsTool = new GetLogStatsTool(container);\n const getServicesTool = new GetServicesTool(container);\n const clearLogsTool = new ClearLogsTool(container);\n\n // List all available tools\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: [\n queryLogsTool.getDefinition(),\n searchLogsTool.getDefinition(),\n getTraceTimelineTool.getDefinition(),\n analyzeErrorsTool.getDefinition(),\n getLogStatsTool.getDefinition(),\n getServicesTool.getDefinition(),\n clearLogsTool.getDefinition(),\n ],\n }));\n\n // Handle tool execution requests with routing\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n try {\n switch (name) {\n case 'query_logs':\n return await queryLogsTool.execute(args as any);\n case 'search_logs':\n return await searchLogsTool.execute(args as any);\n case 'get_trace_timeline':\n return await getTraceTimelineTool.execute(args as any);\n case 'analyze_errors':\n return await analyzeErrorsTool.execute(args as any);\n case 'get_log_stats':\n return await getLogStatsTool.execute(args as any);\n case 'get_services':\n return await getServicesTool.execute(args as any);\n case 'clear_logs':\n return await clearLogsTool.execute(args as any);\n default:\n throw new Error(`Unknown tool: ${name}`);\n }\n } catch (error) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `Error executing tool ${name}: ${error instanceof Error ? error.message : String(error)}`,\n },\n ],\n isError: true,\n };\n }\n });\n\n return server;\n}\n","/**\n * STDIO Transport\n *\n * DESIGN PATTERNS:\n * - Transport handler pattern implementing TransportHandler interface\n * - Standard I/O based communication for CLI usage\n *\n * CODING STANDARDS:\n * - Initialize server and transport properly\n * - Handle cleanup on shutdown with stop() method\n * - Use async/await for all operations\n *\n * AVOID:\n * - Forgetting to close transport on shutdown\n * - Missing error handling for connection failures\n */\n\nimport type { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport type { TransportHandler } from '../types/index.js';\n\n/**\n * Stdio transport handler for MCP server\n * Used for command-line and direct integrations\n */\nexport class StdioTransportHandler implements TransportHandler {\n private server: Server;\n private transport: StdioServerTransport | null = null;\n\n constructor(server: Server) {\n this.server = server;\n }\n\n async start(): Promise<void> {\n this.transport = new StdioServerTransport();\n await this.server.connect(this.transport);\n }\n\n async stop(): Promise<void> {\n if (this.transport) {\n await this.transport.close();\n this.transport = null;\n }\n }\n}\n"],"mappings":"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"}
|