tracelattice 1.3.2 → 1.3.4
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/README.md +25 -25
- package/dist/ServerConfig.d.ts +16 -23
- package/dist/ServerConfig.d.ts.map +1 -1
- package/dist/ServerConfig.js +12 -1
- package/dist/ServerConfig.js.map +1 -1
- package/dist/__tests__/core/HistoryManager.ownership.test.d.ts +2 -0
- package/dist/__tests__/core/HistoryManager.ownership.test.d.ts.map +1 -0
- package/dist/__tests__/core/SessionLock.test.d.ts +6 -0
- package/dist/__tests__/core/SessionLock.test.d.ts.map +1 -0
- package/dist/__tests__/core/SessionManager.test.d.ts +8 -0
- package/dist/__tests__/core/SessionManager.test.d.ts.map +1 -0
- package/dist/__tests__/core/ThoughtProcessor.toolAllowlist.test.d.ts +2 -0
- package/dist/__tests__/core/ThoughtProcessor.toolAllowlist.test.d.ts.map +1 -0
- package/dist/__tests__/eval/fixtures/scenarios.d.ts.map +1 -1
- package/dist/__tests__/helpers/factories.d.ts +20 -1
- package/dist/__tests__/helpers/factories.d.ts.map +1 -1
- package/dist/__tests__/sanitize.enforceJsonShape.test.d.ts +2 -0
- package/dist/__tests__/sanitize.enforceJsonShape.test.d.ts.map +1 -0
- package/dist/__tests__/transport-owner-context.test.d.ts +8 -0
- package/dist/__tests__/transport-owner-context.test.d.ts.map +1 -0
- package/dist/cache/DiscoveryCache.d.ts +1 -1
- package/dist/cache/DiscoveryCache.d.ts.map +1 -1
- package/dist/cache/DiscoveryCache.js.map +1 -1
- package/dist/cli.js +3602 -8
- package/dist/config/ConfigLoader.d.ts +9 -2
- package/dist/config/ConfigLoader.d.ts.map +1 -1
- package/dist/config/ConfigLoader.js +12 -5
- package/dist/config/ConfigLoader.js.map +1 -1
- package/dist/context/RequestContext.d.ts +26 -0
- package/dist/context/RequestContext.d.ts.map +1 -1
- package/dist/context/RequestContext.js +7 -1
- package/dist/context/RequestContext.js.map +1 -1
- package/dist/contracts/PersistenceBackend.d.ts.map +1 -0
- package/dist/contracts/features.d.ts +39 -0
- package/dist/contracts/features.d.ts.map +1 -0
- package/dist/contracts/features.js +15 -0
- package/dist/contracts/features.js.map +1 -0
- package/dist/contracts/ids.d.ts +58 -0
- package/dist/contracts/ids.d.ts.map +1 -0
- package/dist/contracts/ids.js +31 -0
- package/dist/contracts/ids.js.map +1 -0
- package/dist/contracts/interfaces.d.ts +48 -3
- package/dist/contracts/interfaces.d.ts.map +1 -1
- package/dist/contracts/strategy.d.ts +2 -2
- package/dist/contracts/strategy.d.ts.map +1 -1
- package/dist/contracts/suspension.d.ts +3 -2
- package/dist/contracts/suspension.d.ts.map +1 -1
- package/dist/contracts/transport.d.ts +25 -0
- package/dist/contracts/transport.d.ts.map +1 -0
- package/dist/core/HistoryManager.d.ts +15 -4
- package/dist/core/HistoryManager.d.ts.map +1 -1
- package/dist/core/HistoryManager.js +25 -14
- package/dist/core/HistoryManager.js.map +1 -1
- package/dist/core/IHistoryManager.d.ts +10 -0
- package/dist/core/IHistoryManager.d.ts.map +1 -1
- package/dist/core/IThoughtFormatter.d.ts +51 -0
- package/dist/core/IThoughtFormatter.d.ts.map +1 -0
- package/dist/core/IThoughtFormatter.js +1 -0
- package/dist/core/InputNormalizer.d.ts.map +1 -1
- package/dist/core/InputNormalizer.js +9 -5
- package/dist/core/InputNormalizer.js.map +1 -1
- package/dist/core/PersistenceBuffer.d.ts +1 -1
- package/dist/core/PersistenceBuffer.d.ts.map +1 -1
- package/dist/core/PersistenceBuffer.js.map +1 -1
- package/dist/core/SessionLock.d.ts +56 -0
- package/dist/core/SessionLock.d.ts.map +1 -0
- package/dist/core/SessionLock.js +43 -0
- package/dist/core/SessionLock.js.map +1 -0
- package/dist/core/SessionManager.d.ts +18 -3
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +34 -1
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/ThoughtFormatter.d.ts +2 -1
- package/dist/core/ThoughtFormatter.d.ts.map +1 -1
- package/dist/core/ThoughtFormatter.js +3 -0
- package/dist/core/ThoughtFormatter.js.map +1 -1
- package/dist/core/ThoughtProcessor.d.ts +22 -3
- package/dist/core/ThoughtProcessor.d.ts.map +1 -1
- package/dist/core/ThoughtProcessor.js +41 -16
- package/dist/core/ThoughtProcessor.js.map +1 -1
- package/dist/core/compression/CompressionService.js +3 -3
- package/dist/core/compression/CompressionService.js.map +1 -1
- package/dist/core/compression/Summary.d.ts +4 -3
- package/dist/core/compression/Summary.d.ts.map +1 -1
- package/dist/core/graph/Edge.d.ts +11 -4
- package/dist/core/graph/Edge.d.ts.map +1 -1
- package/dist/core/graph/EdgeEmitter.js +5 -5
- package/dist/core/graph/EdgeEmitter.js.map +1 -1
- package/dist/core/reasoning/strategies/StrategyFactory.d.ts +1 -1
- package/dist/core/reasoning/strategies/StrategyFactory.d.ts.map +1 -1
- package/dist/core/reasoning/strategies/StrategyFactory.js.map +1 -1
- package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.d.ts.map +1 -1
- package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.js +5 -0
- package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.js.map +1 -1
- package/dist/core/reasoning.d.ts +8 -1
- package/dist/core/reasoning.d.ts.map +1 -1
- package/dist/core/step.d.ts +5 -0
- package/dist/core/step.d.ts.map +1 -1
- package/dist/core/thought.d.ts +4 -3
- package/dist/core/thought.d.ts.map +1 -1
- package/dist/core/tools/InMemorySuspensionStore.d.ts +3 -1
- package/dist/core/tools/InMemorySuspensionStore.d.ts.map +1 -1
- package/dist/core/tools/InMemorySuspensionStore.js +2 -2
- package/dist/core/tools/InMemorySuspensionStore.js.map +1 -1
- package/dist/di/Container.d.ts +6 -3
- package/dist/di/Container.d.ts.map +1 -1
- package/dist/di/Container.js.map +1 -1
- package/dist/di/ServiceRegistry.d.ts +6 -6
- package/dist/di/ServiceRegistry.d.ts.map +1 -1
- package/dist/errors.d.ts +84 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +85 -22
- package/dist/errors.js.map +1 -1
- package/dist/health/HealthChecker.d.ts +1 -1
- package/dist/health/HealthChecker.d.ts.map +1 -1
- package/dist/health/HealthChecker.js.map +1 -1
- package/dist/lib.d.ts +60 -2
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +9 -3
- package/dist/lib.js.map +1 -1
- package/dist/persistence/FilePersistence.d.ts +2 -2
- package/dist/persistence/FilePersistence.d.ts.map +1 -1
- package/dist/persistence/FilePersistence.js.map +1 -1
- package/dist/persistence/MemoryPersistence.d.ts +1 -1
- package/dist/persistence/MemoryPersistence.d.ts.map +1 -1
- package/dist/persistence/MemoryPersistence.js.map +1 -1
- package/dist/persistence/PersistenceFactory.d.ts +1 -1
- package/dist/persistence/PersistenceFactory.d.ts.map +1 -1
- package/dist/persistence/PersistenceFactory.js.map +1 -1
- package/dist/persistence/SqlitePersistence.d.ts +1 -1
- package/dist/persistence/SqlitePersistence.d.ts.map +1 -1
- package/dist/persistence/SqlitePersistence.js.map +1 -1
- package/dist/pool/ConnectionPool.d.ts +11 -13
- package/dist/pool/ConnectionPool.d.ts.map +1 -1
- package/dist/pool/ConnectionPool.js.map +1 -1
- package/dist/pool/IConnectionPool.d.ts +100 -0
- package/dist/pool/IConnectionPool.d.ts.map +1 -0
- package/dist/pool/IConnectionPool.js +1 -0
- package/dist/registry/BaseRegistry.d.ts +1 -1
- package/dist/registry/BaseRegistry.d.ts.map +1 -1
- package/dist/registry/BaseRegistry.js.map +1 -1
- package/dist/registry/ToolRegistry.d.ts +1 -0
- package/dist/registry/ToolRegistry.d.ts.map +1 -1
- package/dist/registry/ToolRegistry.js +3 -0
- package/dist/registry/ToolRegistry.js.map +1 -1
- package/dist/sanitize.d.ts +70 -0
- package/dist/sanitize.d.ts.map +1 -1
- package/dist/sanitize.js +77 -1
- package/dist/sanitize.js.map +1 -1
- package/dist/schema.d.ts +35 -35
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +15 -5
- package/dist/schema.js.map +1 -1
- package/dist/transport/BaseTransport.d.ts +3 -2
- package/dist/transport/BaseTransport.d.ts.map +1 -1
- package/dist/transport/BaseTransport.js +1 -1
- package/dist/transport/BaseTransport.js.map +1 -1
- package/dist/transport/HttpTransport.d.ts +4 -2
- package/dist/transport/HttpTransport.d.ts.map +1 -1
- package/dist/transport/HttpTransport.js +13 -4
- package/dist/transport/HttpTransport.js.map +1 -1
- package/dist/transport/SseTransport.d.ts +4 -2
- package/dist/transport/SseTransport.d.ts.map +1 -1
- package/dist/transport/SseTransport.js +13 -3
- package/dist/transport/SseTransport.js.map +1 -1
- package/dist/transport/StreamableHttpTransport.d.ts +4 -2
- package/dist/transport/StreamableHttpTransport.d.ts.map +1 -1
- package/dist/transport/StreamableHttpTransport.js +12 -4
- package/dist/transport/StreamableHttpTransport.js.map +1 -1
- package/dist/types/skill.d.ts +5 -0
- package/dist/types/skill.d.ts.map +1 -1
- package/dist/types/tool.d.ts +6 -1
- package/dist/types/tool.d.ts.map +1 -1
- package/package.json +12 -11
- package/dist/__tests__/helpers/index.d.ts +0 -3
- package/dist/__tests__/helpers/index.d.ts.map +0 -1
- package/dist/contracts/index.d.ts +0 -14
- package/dist/contracts/index.d.ts.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -1
- package/dist/persistence/PersistenceBackend.d.ts.map +0 -1
- /package/dist/{persistence → contracts}/PersistenceBackend.d.ts +0 -0
- /package/dist/{persistence → contracts}/PersistenceBackend.js +0 -0
- /package/dist/contracts/{index.js → transport.js} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pool/ConnectionPool.js","sources":["../../src/pool/ConnectionPool.ts"],"sourcesContent":["/**\n * Connection Pool for managing concurrent user sessions.\n *\n * This module provides session management for multi-user scenarios,\n * allowing multiple concurrent clients to each have isolated state.\n *\n * @example\n * ```typescript\n * const pool = new ConnectionPool({\n * maxSessions: 100,\n * sessionTimeout: 300000 // 5 minutes\n * });\n *\n * const sessionId = await pool.createSession();\n * await pool.process(sessionId, thought);\n * await pool.closeSession(sessionId);\n * ```\n */\n\nimport type { ThoughtData } from '../core/thought.js';\nimport {\n\tMaxSessionsReachedError,\n\tPoolTerminatedError,\n\tSessionNotActiveError,\n\tSessionNotFoundError,\n} from '../errors.js';\nimport type { Logger } from '../logger/StructuredLogger.js';\nimport type { IDisposable } from '../types/disposable.js';\n\nexport interface SessionOptions {\n\t/**\n\t * Maximum number of concurrent sessions\n\t * @default 100\n\t */\n\tmaxSessions?: number;\n\n\t/**\n\t * Logger instance\n\t */\n\tlogger?: Logger;\n\n\tserverFactory?: () => Promise<SessionServer>;\n\n\t/**\n\t * Session timeout in milliseconds\n\t * @default 300000 (5 minutes)\n\t */\n\tsessionTimeout?: number;\n\n\t/**\n\t * Whether to enable automatic session cleanup\n\t * @default true\n\t */\n\tautoCleanup?: boolean;\n\n\t/**\n\t * Cleanup interval in milliseconds\n\t * @default 60000 (1 minute)\n\t */\n\tcleanupInterval?: number;\n}\n\nexport interface SessionInfo {\n\tid: string;\n\tserver: SessionServer;\n\tcreatedAt: number;\n\tlastActivityAt: number;\n\tisActive: boolean;\n}\n\nexport interface SessionServer {\n\tprocessThought(input: ThoughtData): Promise<ProcessResult>;\n\tstop(): void | Promise<void>;\n}\n\nexport interface ProcessResult {\n\tcontent: Array<{\n\t\ttype: string;\n\t\ttext: string;\n\t}>;\n\tisError?: boolean;\n}\n\n/**\n * Represents a user session with its own server instance.\n */\nexport class Session {\n\tprivate _server: SessionServer;\n\tprivate _id: string;\n\tprivate _createdAt: number;\n\tprivate _lastActivityAt: number;\n\tprivate _isActiveValue: boolean;\n\tprivate _timeout: number;\n\tprivate _cleanupTimer: NodeJS.Timeout | null = null;\n\tprivate _logger: Logger;\n\n\tconstructor(id: string, server: SessionServer, timeout: number, logger: Logger) {\n\t\tthis._server = server;\n\t\tthis._id = id;\n\t\tthis._createdAt = Date.now();\n\t\tthis._lastActivityAt = this._createdAt;\n\t\tthis._isActiveValue = true;\n\t\tthis._timeout = timeout;\n\t\tthis._logger = logger;\n\n\t\t// Start session timeout timer\n\t\tthis._startTimeout();\n\t}\n\n\t/**\n\t * Check if the session is active.\n\t */\n\tget isActive(): boolean {\n\t\treturn this._isActiveValue;\n\t}\n\n\t/**\n\t * Process a thought through this session's server instance.\n\t */\n\tasync process(input: ThoughtData): Promise<ProcessResult> {\n\t\tif (!this.isActive) {\n\t\t\tthrow new SessionNotActiveError(this._id);\n\t\t}\n\n\t\t// Update last activity\n\t\tthis._lastActivityAt = Date.now();\n\n\t\t// Reset timeout timer\n\t\tthis._resetTimeout();\n\n\t\t// Process the thought\n\t\treturn this._server.processThought(input);\n\t}\n\n\t/**\n\t * Get session information.\n\t */\n\tgetInfo(): SessionInfo {\n\t\treturn {\n\t\t\tid: this._id,\n\t\t\tserver: this._server,\n\t\t\tcreatedAt: this._createdAt,\n\t\t\tlastActivityAt: this._lastActivityAt,\n\t\t\tisActive: this.isActive,\n\t\t};\n\t}\n\n\t/**\n\t * Check if the session has timed out.\n\t */\n\tisTimedOut(): boolean {\n\t\treturn Date.now() - this._lastActivityAt > this._timeout;\n\t}\n\n\t/**\n\t * Close the session and stop the server.\n\t */\n\tasync close(): Promise<void> {\n\t\tthis._isActiveValue = false;\n\n\t\t// Stop timeout timer\n\t\tif (this._cleanupTimer) {\n\t\t\tclearTimeout(this._cleanupTimer);\n\t\t\tthis._cleanupTimer = null;\n\t\t}\n\n\t\t// Stop the server\n\t\tthis._server.stop();\n\t}\n\n\t/**\n\t * Start the session timeout timer.\n\t */\n\tprivate _startTimeout(): void {\n\t\tif (this._cleanupTimer) {\n\t\t\tclearTimeout(this._cleanupTimer);\n\t\t}\n\n\t\tthis._cleanupTimer = setTimeout(() => {\n\t\t\tif (this.isTimedOut()) {\n\t\t\t\tthis._logger.warn(`Session ${this._id} timed out, closing`);\n\t\t\t\tthis.close().catch((err) => {\n\t\t\t\t\tthis._logger.error(`Error closing timed out session ${this._id}:`, err);\n\t\t\t\t});\n\t\t\t}\n\t\t}, this._timeout);\n\t}\n\n\t/**\n\t * Reset the timeout timer after activity.\n\t */\n\tprivate _resetTimeout(): void {\n\t\tthis._startTimeout();\n\t}\n}\n\n/**\n * ConnectionPool manages multiple concurrent user sessions.\n *\n * Each session has its own server instance with isolated state,\n * allowing multiple users to interact with the system simultaneously.\n */\nexport class ConnectionPool implements IDisposable {\n\tprivate _sessions: Map<string, Session> = new Map();\n\tprivate _createSessionLock: Promise<void> | null = null;\n\tprivate _maxSessions: number;\n\tprivate _sessionTimeout: number;\n\tprivate _autoCleanup: boolean;\n\tprivate _cleanupInterval: number;\n\tprivate _cleanupTimerId: number | null = null;\n\tprivate _terminated: boolean = false;\n\tprivate _logger: Logger;\n\tprivate _serverFactory: (() => Promise<SessionServer>) | null;\n\n\tconstructor(options: SessionOptions = {}) {\n\t\tthis._maxSessions = options.maxSessions ?? 100;\n\t\tthis._sessionTimeout = options.sessionTimeout ?? 300000; // 5 minutes\n\t\tthis._autoCleanup = options.autoCleanup ?? true;\n\t\tthis._cleanupInterval = options.cleanupInterval ?? 60000; // 1 minute\n\t\tthis._serverFactory = options.serverFactory ?? null;\n\t\tthis._logger = options.logger ?? this._createNoopLogger();\n\n\t\tif (this._autoCleanup) {\n\t\t\tthis._startCleanup();\n\t\t}\n\t}\n\n\t/**\n\t * Create a no-op logger when none is provided.\n\t */\n\tprivate _createNoopLogger(): Logger {\n\t\treturn {\n\t\t\tinfo: (): void => {},\n\t\t\twarn: (): void => {},\n\t\t\terror: (): void => {},\n\t\t\tdebug: (): void => {},\n\t\t\tsetLevel: (): void => {},\n\t\t\tgetLevel: (): 'info' => 'info',\n\t\t};\n\t}\n\n\t/**\n\t * Create a new session.\n\t *\n\t * @returns The session ID\n\t * @throws Error if max sessions reached\n\t */\n\tasync createSession(): Promise<string> {\n\t\twhile (this._createSessionLock) {\n\t\t\tawait this._createSessionLock;\n\t\t}\n\n\t\tif (this._terminated) {\n\t\t\tthrow new PoolTerminatedError();\n\t\t}\n\n\t\tif (this._sessions.size >= this._maxSessions) {\n\t\t\tthrow new MaxSessionsReachedError(this._maxSessions);\n\t\t}\n\n\t\tif (!this._serverFactory) {\n\t\t\tthrow new Error('ConnectionPool requires a serverFactory option to create sessions');\n\t\t}\n\n\t\tlet resolveLock!: () => void;\n\t\tthis._createSessionLock = new Promise<void>((resolve) => {\n\t\t\tresolveLock = resolve;\n\t\t});\n\n\t\ttry {\n\t\t\t// Generate unique session ID\n\t\t\tconst sessionId = `session_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;\n\n\t\t\t// Create a new server instance for this session\n\t\t\tconst server = await this._serverFactory();\n\n\t\t\t// Create session\n\t\t\tconst session = new Session(sessionId, server, this._sessionTimeout, this._logger);\n\t\t\tthis._sessions.set(sessionId, session);\n\n\t\t\tthis._logger.info(\n\t\t\t\t`Created session ${sessionId} (${this._sessions.size}/${this._maxSessions} active sessions)`\n\t\t\t);\n\t\t\treturn sessionId;\n\t\t} finally {\n\t\t\tresolveLock();\n\t\t\tthis._createSessionLock = null;\n\t\t}\n\t}\n\n\t/**\n\t * Process a thought in the specified session.\n\t *\n\t * @param sessionId - The session ID\n\t * @param input - The thought data to process\n\t * @returns Promise with the processing result\n\t * @throws Error if session not found\n\t */\n\tasync process(sessionId: string, input: ThoughtData): Promise<ProcessResult> {\n\t\tconst session = this._sessions.get(sessionId);\n\n\t\tif (!session) {\n\t\t\tthrow new SessionNotFoundError(sessionId);\n\t\t}\n\n\t\treturn session.process(input);\n\t}\n\n\t/**\n\t * Close a session and release resources.\n\t *\n\t * @param sessionId - The session ID to close\n\t * @throws Error if session not found\n\t */\n\tasync closeSession(sessionId: string): Promise<void> {\n\t\tconst session = this._sessions.get(sessionId);\n\n\t\tif (!session) {\n\t\t\tthrow new SessionNotFoundError(sessionId);\n\t\t}\n\n\t\tawait session.close();\n\t\tthis._sessions.delete(sessionId);\n\n\t\tthis._logger.info(\n\t\t\t`Closed session ${sessionId} (${this._sessions.size}/${this._maxSessions} active sessions)`\n\t\t);\n\t}\n\n\t/**\n\t * Get information about a session.\n\t *\n\t * @param sessionId - The session ID\n\t * @returns Session info or undefined if not found\n\t */\n\tgetSessionInfo(sessionId: string): SessionInfo | undefined {\n\t\treturn this._sessions.get(sessionId)?.getInfo();\n\t}\n\n\t/**\n\t * Get all active sessions.\n\t *\n\t * @returns Array of session information\n\t */\n\tgetActiveSessions(): SessionInfo[] {\n\t\treturn Array.from(this._sessions.values())\n\t\t\t.filter((s) => s.isActive)\n\t\t\t.map((s) => s.getInfo());\n\t}\n\n\t/**\n\t * Get connection pool statistics.\n\t */\n\tgetStats(): {\n\t\ttotalSessions: number;\n\t\tactiveSessions: number;\n\t\tmaxSessions: number;\n\t\tcleanupEnabled: boolean;\n\t\tsessionTimeout: number;\n\t} {\n\t\tconst activeSessions = this.getActiveSessions();\n\n\t\treturn {\n\t\t\ttotalSessions: this._sessions.size,\n\t\t\tactiveSessions: activeSessions.length,\n\t\t\tmaxSessions: this._maxSessions,\n\t\t\tcleanupEnabled: this._autoCleanup,\n\t\t\tsessionTimeout: this._sessionTimeout,\n\t\t};\n\t}\n\n\t/**\n\t * Start the automatic cleanup timer.\n\t */\n\tprivate _startCleanup(): void {\n\t\tif (this._cleanupTimerId !== null) {\n\t\t\tclearInterval(this._cleanupTimerId);\n\t\t}\n\n\t\tthis._cleanupTimerId = setInterval(() => {\n\t\t\tthis._cleanupTimedOutSessions();\n\t\t}, this._cleanupInterval) as unknown as number;\n\t}\n\n\t/**\n\t * Remove timed-out sessions.\n\t */\n\tprivate _cleanupTimedOutSessions(): void {\n\t\tlet cleaned = 0;\n\n\t\tfor (const [sessionId, session] of this._sessions.entries()) {\n\t\t\tif (session.isTimedOut()) {\n\t\t\t\tsession.close().catch((err) => {\n\t\t\t\t\tthis._logger.error(`Error closing timed out session ${sessionId}:`, err);\n\t\t\t\t});\n\t\t\t\tthis._sessions.delete(sessionId);\n\t\t\t\tcleaned++;\n\t\t\t}\n\t\t}\n\n\t\tif (cleaned > 0) {\n\t\t\tthis._logger.info(\n\t\t\t\t`Cleaned ${cleaned} timed-out sessions (${this._sessions.size}/${this._maxSessions} active sessions)`\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Close all sessions and stop the cleanup timer.\n\t */\n\tasync terminate(): Promise<void> {\n\t\tif (this._terminated) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._terminated = true;\n\n\t\t// Stop cleanup timer\n\t\tif (this._cleanupTimerId !== null) {\n\t\t\tclearInterval(this._cleanupTimerId);\n\t\t\tthis._cleanupTimerId = null;\n\t\t}\n\n\t\t// Close all sessions\n\t\tconst closePromises = Array.from(this._sessions.values()).map((session) =>\n\t\t\tsession.close().catch((err) => {\n\t\t\t\tthis._logger.error(`Error closing session ${session.getInfo().id}:`, err);\n\t\t\t})\n\t\t);\n\n\t\tawait Promise.all(closePromises);\n\t\tthis._sessions.clear();\n\n\t\tthis._logger.info('ConnectionPool terminated');\n\t}\n\n\t/**\n\t * Dispose of the connection pool, releasing all resources.\n\t * Implements the IDisposable interface.\n\t * Delegates to terminate() for backward compatibility.\n\t */\n\tasync dispose(): Promise<void> {\n\t\tawait this.terminate();\n\t}\n\n\t/**\n\t * Check if the connection pool is active.\n\t */\n\tisRunning(): boolean {\n\t\treturn !this._terminated;\n\t}\n}\n\n/**\n * Create a connection pool with the given options.\n *\n * @param options - Connection pool configuration\n * @returns A configured connection pool\n *\n * @example\n * ```typescript\n * const pool = createConnectionPool({\n * maxSessions: 50,\n * sessionTimeout: 300000\n * });\n * ```\n */\nexport function createConnectionPool(options?: SessionOptions): ConnectionPool {\n\treturn new ConnectionPool(options);\n}\n"],"names":["Session","id","server","timeout","logger","Date","input","SessionNotActiveError","clearTimeout","setTimeout","err","ConnectionPool","Map","options","PoolTerminatedError","MaxSessionsReachedError","Error","resolveLock","Promise","resolve","sessionId","Math","session","SessionNotFoundError","Array","s","activeSessions","clearInterval","setInterval","cleaned","closePromises","createConnectionPool"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pool/ConnectionPool.js","sources":["../../src/pool/ConnectionPool.ts"],"sourcesContent":["/**\n * Connection Pool for managing concurrent user sessions.\n *\n * This module provides session management for multi-user scenarios,\n * allowing multiple concurrent clients to each have isolated state.\n *\n * @example\n * ```typescript\n * const pool = new ConnectionPool({\n * maxSessions: 100,\n * sessionTimeout: 300000 // 5 minutes\n * });\n *\n * const sessionId = await pool.createSession();\n * await pool.process(sessionId, thought);\n * await pool.closeSession(sessionId);\n * ```\n */\n\nimport type { ThoughtData } from '../core/thought.js';\nimport {\n\tMaxSessionsReachedError,\n\tPoolTerminatedError,\n\tSessionNotActiveError,\n\tSessionNotFoundError,\n} from '../errors.js';\nimport type { Logger } from '../logger/StructuredLogger.js';\nimport type { ConnectionPoolStats, IConnectionPool } from './IConnectionPool.js';\n\nexport interface SessionOptions {\n\t/**\n\t * Maximum number of concurrent sessions\n\t * @default 100\n\t */\n\tmaxSessions?: number;\n\n\t/**\n\t * Logger instance\n\t */\n\tlogger?: Logger;\n\n\tserverFactory?: () => Promise<SessionServer>;\n\n\t/**\n\t * Session timeout in milliseconds\n\t * @default 300000 (5 minutes)\n\t */\n\tsessionTimeout?: number;\n\n\t/**\n\t * Whether to enable automatic session cleanup\n\t * @default true\n\t */\n\tautoCleanup?: boolean;\n\n\t/**\n\t * Cleanup interval in milliseconds\n\t * @default 60000 (1 minute)\n\t */\n\tcleanupInterval?: number;\n}\n\nexport interface SessionInfo {\n\tid: string;\n\tserver: SessionServer;\n\tcreatedAt: number;\n\tlastActivityAt: number;\n\tisActive: boolean;\n}\n\nexport interface SessionServer {\n\tprocessThought(input: ThoughtData): Promise<ProcessResult>;\n\tstop(): void | Promise<void>;\n}\n\n/**\n * Represents a content block in a process result.\n */\nexport type ContentBlock = { type: 'text'; text: string };\n\nexport interface ProcessResult {\n\tcontent: ContentBlock[];\n\tisError?: boolean;\n}\n\n/**\n * Represents a user session with its own server instance.\n */\nexport class Session {\n\tprivate _server: SessionServer;\n\tprivate _id: string;\n\tprivate _createdAt: number;\n\tprivate _lastActivityAt: number;\n\tprivate _isActiveValue: boolean;\n\tprivate _timeout: number;\n\tprivate _cleanupTimer: NodeJS.Timeout | null = null;\n\tprivate _logger: Logger;\n\n\tconstructor(id: string, server: SessionServer, timeout: number, logger: Logger) {\n\t\tthis._server = server;\n\t\tthis._id = id;\n\t\tthis._createdAt = Date.now();\n\t\tthis._lastActivityAt = this._createdAt;\n\t\tthis._isActiveValue = true;\n\t\tthis._timeout = timeout;\n\t\tthis._logger = logger;\n\n\t\t// Start session timeout timer\n\t\tthis._startTimeout();\n\t}\n\n\t/**\n\t * Check if the session is active.\n\t */\n\tget isActive(): boolean {\n\t\treturn this._isActiveValue;\n\t}\n\n\t/**\n\t * Process a thought through this session's server instance.\n\t */\n\tasync process(input: ThoughtData): Promise<ProcessResult> {\n\t\tif (!this.isActive) {\n\t\t\tthrow new SessionNotActiveError(this._id);\n\t\t}\n\n\t\t// Update last activity\n\t\tthis._lastActivityAt = Date.now();\n\n\t\t// Reset timeout timer\n\t\tthis._resetTimeout();\n\n\t\t// Process the thought\n\t\treturn this._server.processThought(input);\n\t}\n\n\t/**\n\t * Get session information.\n\t */\n\tgetInfo(): SessionInfo {\n\t\treturn {\n\t\t\tid: this._id,\n\t\t\tserver: this._server,\n\t\t\tcreatedAt: this._createdAt,\n\t\t\tlastActivityAt: this._lastActivityAt,\n\t\t\tisActive: this.isActive,\n\t\t};\n\t}\n\n\t/**\n\t * Check if the session has timed out.\n\t */\n\tisTimedOut(): boolean {\n\t\treturn Date.now() - this._lastActivityAt > this._timeout;\n\t}\n\n\t/**\n\t * Close the session and stop the server.\n\t */\n\tasync close(): Promise<void> {\n\t\tthis._isActiveValue = false;\n\n\t\t// Stop timeout timer\n\t\tif (this._cleanupTimer) {\n\t\t\tclearTimeout(this._cleanupTimer);\n\t\t\tthis._cleanupTimer = null;\n\t\t}\n\n\t\t// Stop the server\n\t\tthis._server.stop();\n\t}\n\n\t/**\n\t * Start the session timeout timer.\n\t */\n\tprivate _startTimeout(): void {\n\t\tif (this._cleanupTimer) {\n\t\t\tclearTimeout(this._cleanupTimer);\n\t\t}\n\n\t\tthis._cleanupTimer = setTimeout(() => {\n\t\t\tif (this.isTimedOut()) {\n\t\t\t\tthis._logger.warn(`Session ${this._id} timed out, closing`);\n\t\t\t\tthis.close().catch((err) => {\n\t\t\t\t\tthis._logger.error(`Error closing timed out session ${this._id}:`, err);\n\t\t\t\t});\n\t\t\t}\n\t\t}, this._timeout);\n\t}\n\n\t/**\n\t * Reset the timeout timer after activity.\n\t */\n\tprivate _resetTimeout(): void {\n\t\tthis._startTimeout();\n\t}\n}\n\n/**\n * ConnectionPool manages multiple concurrent user sessions.\n *\n * Each session has its own server instance with isolated state,\n * allowing multiple users to interact with the system simultaneously.\n */\nexport class ConnectionPool implements IConnectionPool {\n\tprivate _sessions: Map<string, Session> = new Map();\n\tprivate _createSessionLock: Promise<void> | null = null;\n\tprivate _maxSessions: number;\n\tprivate _sessionTimeout: number;\n\tprivate _autoCleanup: boolean;\n\tprivate _cleanupInterval: number;\n\tprivate _cleanupTimerId: number | null = null;\n\tprivate _terminated: boolean = false;\n\tprivate _logger: Logger;\n\tprivate _serverFactory: (() => Promise<SessionServer>) | null;\n\n\tconstructor(options: SessionOptions = {}) {\n\t\tthis._maxSessions = options.maxSessions ?? 100;\n\t\tthis._sessionTimeout = options.sessionTimeout ?? 300000; // 5 minutes\n\t\tthis._autoCleanup = options.autoCleanup ?? true;\n\t\tthis._cleanupInterval = options.cleanupInterval ?? 60000; // 1 minute\n\t\tthis._serverFactory = options.serverFactory ?? null;\n\t\tthis._logger = options.logger ?? this._createNoopLogger();\n\n\t\tif (this._autoCleanup) {\n\t\t\tthis._startCleanup();\n\t\t}\n\t}\n\n\t/**\n\t * Create a no-op logger when none is provided.\n\t */\n\tprivate _createNoopLogger(): Logger {\n\t\treturn {\n\t\t\tinfo: (): void => {},\n\t\t\twarn: (): void => {},\n\t\t\terror: (): void => {},\n\t\t\tdebug: (): void => {},\n\t\t\tsetLevel: (): void => {},\n\t\t\tgetLevel: (): 'info' => 'info',\n\t\t};\n\t}\n\n\t/**\n\t * Create a new session.\n\t *\n\t * @returns The session ID\n\t * @throws Error if max sessions reached\n\t */\n\tasync createSession(): Promise<string> {\n\t\twhile (this._createSessionLock) {\n\t\t\tawait this._createSessionLock;\n\t\t}\n\n\t\tif (this._terminated) {\n\t\t\tthrow new PoolTerminatedError();\n\t\t}\n\n\t\tif (this._sessions.size >= this._maxSessions) {\n\t\t\tthrow new MaxSessionsReachedError(this._maxSessions);\n\t\t}\n\n\t\tif (!this._serverFactory) {\n\t\t\tthrow new Error('ConnectionPool requires a serverFactory option to create sessions');\n\t\t}\n\n\t\tlet resolveLock!: () => void;\n\t\tthis._createSessionLock = new Promise<void>((resolve) => {\n\t\t\tresolveLock = resolve;\n\t\t});\n\n\t\ttry {\n\t\t\t// Generate unique session ID\n\t\t\tconst sessionId = `session_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;\n\n\t\t\t// Create a new server instance for this session\n\t\t\tconst server = await this._serverFactory();\n\n\t\t\t// Create session\n\t\t\tconst session = new Session(sessionId, server, this._sessionTimeout, this._logger);\n\t\t\tthis._sessions.set(sessionId, session);\n\n\t\t\tthis._logger.info(\n\t\t\t\t`Created session ${sessionId} (${this._sessions.size}/${this._maxSessions} active sessions)`\n\t\t\t);\n\t\t\treturn sessionId;\n\t\t} finally {\n\t\t\tresolveLock();\n\t\t\tthis._createSessionLock = null;\n\t\t}\n\t}\n\n\t/**\n\t * Process a thought in the specified session.\n\t *\n\t * @param sessionId - The session ID\n\t * @param input - The thought data to process\n\t * @returns Promise with the processing result\n\t * @throws Error if session not found\n\t */\n\tasync process(sessionId: string, input: ThoughtData): Promise<ProcessResult> {\n\t\tconst session = this._sessions.get(sessionId);\n\n\t\tif (!session) {\n\t\t\tthrow new SessionNotFoundError(sessionId);\n\t\t}\n\n\t\treturn session.process(input);\n\t}\n\n\t/**\n\t * Close a session and release resources.\n\t *\n\t * @param sessionId - The session ID to close\n\t * @throws Error if session not found\n\t */\n\tasync closeSession(sessionId: string): Promise<void> {\n\t\tconst session = this._sessions.get(sessionId);\n\n\t\tif (!session) {\n\t\t\tthrow new SessionNotFoundError(sessionId);\n\t\t}\n\n\t\tawait session.close();\n\t\tthis._sessions.delete(sessionId);\n\n\t\tthis._logger.info(\n\t\t\t`Closed session ${sessionId} (${this._sessions.size}/${this._maxSessions} active sessions)`\n\t\t);\n\t}\n\n\t/**\n\t * Get information about a session.\n\t *\n\t * @param sessionId - The session ID\n\t * @returns Session info or undefined if not found\n\t */\n\tgetSessionInfo(sessionId: string): SessionInfo | undefined {\n\t\treturn this._sessions.get(sessionId)?.getInfo();\n\t}\n\n\t/**\n\t * Get all active sessions.\n\t *\n\t * @returns Array of session information\n\t */\n\tgetActiveSessions(): SessionInfo[] {\n\t\treturn Array.from(this._sessions.values())\n\t\t\t.filter((s) => s.isActive)\n\t\t\t.map((s) => s.getInfo());\n\t}\n\n\t/**\n\t * Get connection pool statistics.\n\t */\n\tgetStats(): ConnectionPoolStats {\n\t\tconst activeSessions = this.getActiveSessions();\n\n\t\treturn {\n\t\t\ttotalSessions: this._sessions.size,\n\t\t\tactiveSessions: activeSessions.length,\n\t\t\tmaxSessions: this._maxSessions,\n\t\t\tcleanupEnabled: this._autoCleanup,\n\t\t\tsessionTimeout: this._sessionTimeout,\n\t\t};\n\t}\n\n\t/**\n\t * Start the automatic cleanup timer.\n\t */\n\tprivate _startCleanup(): void {\n\t\tif (this._cleanupTimerId !== null) {\n\t\t\tclearInterval(this._cleanupTimerId);\n\t\t}\n\n\t\tthis._cleanupTimerId = setInterval(() => {\n\t\t\tthis._cleanupTimedOutSessions();\n\t\t}, this._cleanupInterval) as unknown as number;\n\t}\n\n\t/**\n\t * Remove timed-out sessions.\n\t */\n\tprivate _cleanupTimedOutSessions(): void {\n\t\tlet cleaned = 0;\n\n\t\tfor (const [sessionId, session] of this._sessions.entries()) {\n\t\t\tif (session.isTimedOut()) {\n\t\t\t\tsession.close().catch((err) => {\n\t\t\t\t\tthis._logger.error(`Error closing timed out session ${sessionId}:`, err);\n\t\t\t\t});\n\t\t\t\tthis._sessions.delete(sessionId);\n\t\t\t\tcleaned++;\n\t\t\t}\n\t\t}\n\n\t\tif (cleaned > 0) {\n\t\t\tthis._logger.info(\n\t\t\t\t`Cleaned ${cleaned} timed-out sessions (${this._sessions.size}/${this._maxSessions} active sessions)`\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Close all sessions and stop the cleanup timer.\n\t */\n\tasync terminate(): Promise<void> {\n\t\tif (this._terminated) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._terminated = true;\n\n\t\t// Stop cleanup timer\n\t\tif (this._cleanupTimerId !== null) {\n\t\t\tclearInterval(this._cleanupTimerId);\n\t\t\tthis._cleanupTimerId = null;\n\t\t}\n\n\t\t// Close all sessions\n\t\tconst closePromises = Array.from(this._sessions.values()).map((session) =>\n\t\t\tsession.close().catch((err) => {\n\t\t\t\tthis._logger.error(`Error closing session ${session.getInfo().id}:`, err);\n\t\t\t})\n\t\t);\n\n\t\tawait Promise.all(closePromises);\n\t\tthis._sessions.clear();\n\n\t\tthis._logger.info('ConnectionPool terminated');\n\t}\n\n\t/**\n\t * Dispose of the connection pool, releasing all resources.\n\t * Implements the IDisposable interface.\n\t * Delegates to terminate() for backward compatibility.\n\t */\n\tasync dispose(): Promise<void> {\n\t\tawait this.terminate();\n\t}\n\n\t/**\n\t * Check if the connection pool is active.\n\t */\n\tisRunning(): boolean {\n\t\treturn !this._terminated;\n\t}\n}\n\n/**\n * Create a connection pool with the given options.\n *\n * @param options - Connection pool configuration\n * @returns A configured connection pool\n *\n * @example\n * ```typescript\n * const pool = createConnectionPool({\n * maxSessions: 50,\n * sessionTimeout: 300000\n * });\n * ```\n */\nexport function createConnectionPool(options?: SessionOptions): ConnectionPool {\n\treturn new ConnectionPool(options);\n}\n"],"names":["Session","id","server","timeout","logger","Date","input","SessionNotActiveError","clearTimeout","setTimeout","err","ConnectionPool","Map","options","PoolTerminatedError","MaxSessionsReachedError","Error","resolveLock","Promise","resolve","sessionId","Math","session","SessionNotFoundError","Array","s","activeSessions","clearInterval","setInterval","cleaned","closePromises","createConnectionPool"],"mappings":";AAwFO,MAAMA;IACJ,QAAuB;IACvB,IAAY;IACZ,WAAmB;IACnB,gBAAwB;IACxB,eAAwB;IACxB,SAAiB;IACjB,gBAAuC,KAAK;IAC5C,QAAgB;IAExB,YAAYC,EAAU,EAAEC,MAAqB,EAAEC,OAAe,EAAEC,MAAc,CAAE;QAC/E,IAAI,CAAC,OAAO,GAAGF;QACf,IAAI,CAAC,GAAG,GAAGD;QACX,IAAI,CAAC,UAAU,GAAGI,KAAK,GAAG;QAC1B,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,UAAU;QACtC,IAAI,CAAC,cAAc,GAAG;QACtB,IAAI,CAAC,QAAQ,GAAGF;QAChB,IAAI,CAAC,OAAO,GAAGC;QAGf,IAAI,CAAC,aAAa;IACnB;IAKA,IAAI,WAAoB;QACvB,OAAO,IAAI,CAAC,cAAc;IAC3B;IAKA,MAAM,QAAQE,KAAkB,EAA0B;QACzD,IAAI,CAAC,IAAI,CAAC,QAAQ,EACjB,MAAM,IAAIC,sBAAsB,IAAI,CAAC,GAAG;QAIzC,IAAI,CAAC,eAAe,GAAGF,KAAK,GAAG;QAG/B,IAAI,CAAC,aAAa;QAGlB,OAAO,IAAI,CAAC,OAAO,CAAC,cAAc,CAACC;IACpC;IAKA,UAAuB;QACtB,OAAO;YACN,IAAI,IAAI,CAAC,GAAG;YACZ,QAAQ,IAAI,CAAC,OAAO;YACpB,WAAW,IAAI,CAAC,UAAU;YAC1B,gBAAgB,IAAI,CAAC,eAAe;YACpC,UAAU,IAAI,CAAC,QAAQ;QACxB;IACD;IAKA,aAAsB;QACrB,OAAOD,KAAK,GAAG,KAAK,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,QAAQ;IACzD;IAKA,MAAM,QAAuB;QAC5B,IAAI,CAAC,cAAc,GAAG;QAGtB,IAAI,IAAI,CAAC,aAAa,EAAE;YACvBG,aAAa,IAAI,CAAC,aAAa;YAC/B,IAAI,CAAC,aAAa,GAAG;QACtB;QAGA,IAAI,CAAC,OAAO,CAAC,IAAI;IAClB;IAKQ,gBAAsB;QAC7B,IAAI,IAAI,CAAC,aAAa,EACrBA,aAAa,IAAI,CAAC,aAAa;QAGhC,IAAI,CAAC,aAAa,GAAGC,WAAW;YAC/B,IAAI,IAAI,CAAC,UAAU,IAAI;gBACtB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,mBAAmB,CAAC;gBAC1D,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,CAACC;oBACnB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,gCAAgC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAEA;gBACpE;YACD;QACD,GAAG,IAAI,CAAC,QAAQ;IACjB;IAKQ,gBAAsB;QAC7B,IAAI,CAAC,aAAa;IACnB;AACD;AAQO,MAAMC;IACJ,YAAkC,IAAIC,MAAM;IAC5C,qBAA2C,KAAK;IAChD,aAAqB;IACrB,gBAAwB;IACxB,aAAsB;IACtB,iBAAyB;IACzB,kBAAiC,KAAK;IACtC,cAAuB,MAAM;IAC7B,QAAgB;IAChB,eAAsD;IAE9D,YAAYC,UAA0B,CAAC,CAAC,CAAE;QACzC,IAAI,CAAC,YAAY,GAAGA,QAAQ,WAAW,IAAI;QAC3C,IAAI,CAAC,eAAe,GAAGA,QAAQ,cAAc,IAAI;QACjD,IAAI,CAAC,YAAY,GAAGA,QAAQ,WAAW,IAAI;QAC3C,IAAI,CAAC,gBAAgB,GAAGA,QAAQ,eAAe,IAAI;QACnD,IAAI,CAAC,cAAc,GAAGA,QAAQ,aAAa,IAAI;QAC/C,IAAI,CAAC,OAAO,GAAGA,QAAQ,MAAM,IAAI,IAAI,CAAC,iBAAiB;QAEvD,IAAI,IAAI,CAAC,YAAY,EACpB,IAAI,CAAC,aAAa;IAEpB;IAKQ,oBAA4B;QACnC,OAAO;YACN,MAAM,KAAa;YACnB,MAAM,KAAa;YACnB,OAAO,KAAa;YACpB,OAAO,KAAa;YACpB,UAAU,KAAa;YACvB,UAAU,IAAc;QACzB;IACD;IAQA,MAAM,gBAAiC;QACtC,MAAO,IAAI,CAAC,kBAAkB,CAC7B,MAAM,IAAI,CAAC,kBAAkB;QAG9B,IAAI,IAAI,CAAC,WAAW,EACnB,MAAM,IAAIC;QAGX,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI,IAAI,CAAC,YAAY,EAC3C,MAAM,IAAIC,wBAAwB,IAAI,CAAC,YAAY;QAGpD,IAAI,CAAC,IAAI,CAAC,cAAc,EACvB,MAAM,IAAIC,MAAM;QAGjB,IAAIC;QACJ,IAAI,CAAC,kBAAkB,GAAG,IAAIC,QAAc,CAACC;YAC5CF,cAAcE;QACf;QAEA,IAAI;YAEH,MAAMC,YAAY,CAAC,QAAQ,EAAEf,KAAK,GAAG,GAAG,CAAC,EAAEgB,KAAK,MAAM,GAAG,QAAQ,CAAC,IAAI,SAAS,CAAC,GAAG,KAAK;YAGxF,MAAMnB,SAAS,MAAM,IAAI,CAAC,cAAc;YAGxC,MAAMoB,UAAU,IAAItB,QAAQoB,WAAWlB,QAAQ,IAAI,CAAC,eAAe,EAAE,IAAI,CAAC,OAAO;YACjF,IAAI,CAAC,SAAS,CAAC,GAAG,CAACkB,WAAWE;YAE9B,IAAI,CAAC,OAAO,CAAC,IAAI,CAChB,CAAC,gBAAgB,EAAEF,UAAU,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,YAAY,CAAC,iBAAiB,CAAC;YAE7F,OAAOA;QACR,SAAU;YACTH;YACA,IAAI,CAAC,kBAAkB,GAAG;QAC3B;IACD;IAUA,MAAM,QAAQG,SAAiB,EAAEd,KAAkB,EAA0B;QAC5E,MAAMgB,UAAU,IAAI,CAAC,SAAS,CAAC,GAAG,CAACF;QAEnC,IAAI,CAACE,SACJ,MAAM,IAAIC,qBAAqBH;QAGhC,OAAOE,QAAQ,OAAO,CAAChB;IACxB;IAQA,MAAM,aAAac,SAAiB,EAAiB;QACpD,MAAME,UAAU,IAAI,CAAC,SAAS,CAAC,GAAG,CAACF;QAEnC,IAAI,CAACE,SACJ,MAAM,IAAIC,qBAAqBH;QAGhC,MAAME,QAAQ,KAAK;QACnB,IAAI,CAAC,SAAS,CAAC,MAAM,CAACF;QAEtB,IAAI,CAAC,OAAO,CAAC,IAAI,CAChB,CAAC,eAAe,EAAEA,UAAU,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,YAAY,CAAC,iBAAiB,CAAC;IAE7F;IAQA,eAAeA,SAAiB,EAA2B;QAC1D,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAACA,YAAY;IACvC;IAOA,oBAAmC;QAClC,OAAOI,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,IACrC,MAAM,CAAC,CAACC,IAAMA,EAAE,QAAQ,EACxB,GAAG,CAAC,CAACA,IAAMA,EAAE,OAAO;IACvB;IAKA,WAAgC;QAC/B,MAAMC,iBAAiB,IAAI,CAAC,iBAAiB;QAE7C,OAAO;YACN,eAAe,IAAI,CAAC,SAAS,CAAC,IAAI;YAClC,gBAAgBA,eAAe,MAAM;YACrC,aAAa,IAAI,CAAC,YAAY;YAC9B,gBAAgB,IAAI,CAAC,YAAY;YACjC,gBAAgB,IAAI,CAAC,eAAe;QACrC;IACD;IAKQ,gBAAsB;QAC7B,IAAI,AAAyB,SAAzB,IAAI,CAAC,eAAe,EACvBC,cAAc,IAAI,CAAC,eAAe;QAGnC,IAAI,CAAC,eAAe,GAAGC,YAAY;YAClC,IAAI,CAAC,wBAAwB;QAC9B,GAAG,IAAI,CAAC,gBAAgB;IACzB;IAKQ,2BAAiC;QACxC,IAAIC,UAAU;QAEd,KAAK,MAAM,CAACT,WAAWE,QAAQ,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,GACxD,IAAIA,QAAQ,UAAU,IAAI;YACzBA,QAAQ,KAAK,GAAG,KAAK,CAAC,CAACZ;gBACtB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,gCAAgC,EAAEU,UAAU,CAAC,CAAC,EAAEV;YACrE;YACA,IAAI,CAAC,SAAS,CAAC,MAAM,CAACU;YACtBS;QACD;QAGD,IAAIA,UAAU,GACb,IAAI,CAAC,OAAO,CAAC,IAAI,CAChB,CAAC,QAAQ,EAAEA,QAAQ,qBAAqB,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,YAAY,CAAC,iBAAiB,CAAC;IAGxG;IAKA,MAAM,YAA2B;QAChC,IAAI,IAAI,CAAC,WAAW,EACnB;QAGD,IAAI,CAAC,WAAW,GAAG;QAGnB,IAAI,AAAyB,SAAzB,IAAI,CAAC,eAAe,EAAW;YAClCF,cAAc,IAAI,CAAC,eAAe;YAClC,IAAI,CAAC,eAAe,GAAG;QACxB;QAGA,MAAMG,gBAAgBN,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,GAAG,CAAC,CAACF,UAC9DA,QAAQ,KAAK,GAAG,KAAK,CAAC,CAACZ;gBACtB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,sBAAsB,EAAEY,QAAQ,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,EAAEZ;YACtE;QAGD,MAAMQ,QAAQ,GAAG,CAACY;QAClB,IAAI,CAAC,SAAS,CAAC,KAAK;QAEpB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;IACnB;IAOA,MAAM,UAAyB;QAC9B,MAAM,IAAI,CAAC,SAAS;IACrB;IAKA,YAAqB;QACpB,OAAO,CAAC,IAAI,CAAC,WAAW;IACzB;AACD;AAgBO,SAASC,qBAAqBlB,OAAwB;IAC5D,OAAO,IAAIF,eAAeE;AAC3B"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface for the connection pool managing concurrent user sessions.
|
|
3
|
+
*
|
|
4
|
+
* This module provides the `IConnectionPool` interface which defines the
|
|
5
|
+
* contract for connection pool implementations. This allows for decoupling
|
|
6
|
+
* and testability for multi-user transports.
|
|
7
|
+
*
|
|
8
|
+
* @module IConnectionPool
|
|
9
|
+
*/
|
|
10
|
+
import type { ThoughtData } from '../core/thought.js';
|
|
11
|
+
import type { IDisposable } from '../types/disposable.js';
|
|
12
|
+
import type { ProcessResult, SessionInfo } from './ConnectionPool.js';
|
|
13
|
+
/**
|
|
14
|
+
* Statistics describing the current state of the connection pool.
|
|
15
|
+
*/
|
|
16
|
+
export interface ConnectionPoolStats {
|
|
17
|
+
totalSessions: number;
|
|
18
|
+
activeSessions: number;
|
|
19
|
+
maxSessions: number;
|
|
20
|
+
cleanupEnabled: boolean;
|
|
21
|
+
sessionTimeout: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Interface for the connection pool.
|
|
25
|
+
*
|
|
26
|
+
* This interface defines the contract for managing multiple concurrent
|
|
27
|
+
* user sessions, each with its own isolated server instance. Supports
|
|
28
|
+
* dependency injection and mocking for testing purposes.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* class MockPool implements IConnectionPool {
|
|
33
|
+
* async createSession(): Promise<string> { return 'mock'; }
|
|
34
|
+
* // ...
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export interface IConnectionPool extends IDisposable {
|
|
39
|
+
/**
|
|
40
|
+
* Create a new session.
|
|
41
|
+
*
|
|
42
|
+
* @returns The new session ID
|
|
43
|
+
* @throws PoolTerminatedError if the pool has been terminated
|
|
44
|
+
* @throws MaxSessionsReachedError if the maximum number of sessions is reached
|
|
45
|
+
*/
|
|
46
|
+
createSession(): Promise<string>;
|
|
47
|
+
/**
|
|
48
|
+
* Process a thought in the specified session.
|
|
49
|
+
*
|
|
50
|
+
* @param sessionId - The session ID
|
|
51
|
+
* @param input - The thought data to process
|
|
52
|
+
* @returns The processing result
|
|
53
|
+
* @throws SessionNotFoundError if the session does not exist
|
|
54
|
+
*/
|
|
55
|
+
process(sessionId: string, input: ThoughtData): Promise<ProcessResult>;
|
|
56
|
+
/**
|
|
57
|
+
* Close a session and release its resources.
|
|
58
|
+
*
|
|
59
|
+
* @param sessionId - The session ID to close
|
|
60
|
+
* @throws SessionNotFoundError if the session does not exist
|
|
61
|
+
*/
|
|
62
|
+
closeSession(sessionId: string): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Get information about a session.
|
|
65
|
+
*
|
|
66
|
+
* @param sessionId - The session ID
|
|
67
|
+
* @returns Session info, or undefined if not found
|
|
68
|
+
*/
|
|
69
|
+
getSessionInfo(sessionId: string): SessionInfo | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* Get all active sessions.
|
|
72
|
+
*
|
|
73
|
+
* @returns Array of session information for currently active sessions
|
|
74
|
+
*/
|
|
75
|
+
getActiveSessions(): SessionInfo[];
|
|
76
|
+
/**
|
|
77
|
+
* Get connection pool statistics.
|
|
78
|
+
*
|
|
79
|
+
* @returns A snapshot of the pool's statistics
|
|
80
|
+
*/
|
|
81
|
+
getStats(): ConnectionPoolStats;
|
|
82
|
+
/**
|
|
83
|
+
* Close all sessions and stop the cleanup timer.
|
|
84
|
+
*/
|
|
85
|
+
terminate(): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Dispose of the connection pool, releasing all resources.
|
|
88
|
+
*
|
|
89
|
+
* Implements the {@link IDisposable} interface. Typically delegates
|
|
90
|
+
* to {@link IConnectionPool.terminate}.
|
|
91
|
+
*/
|
|
92
|
+
dispose(): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* Check if the connection pool is active (not terminated).
|
|
95
|
+
*
|
|
96
|
+
* @returns true if the pool is still running
|
|
97
|
+
*/
|
|
98
|
+
isRunning(): boolean;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=IConnectionPool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IConnectionPool.d.ts","sourceRoot":"","sources":["../../src/pool/IConnectionPool.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEtE;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,eAAgB,SAAQ,WAAW;IACnD;;;;;;OAMG;IACH,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAEjC;;;;;;;OAOG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAEvE;;;;;OAKG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/C;;;;;OAKG;IACH,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAAC;IAE3D;;;;OAIG;IACH,iBAAiB,IAAI,WAAW,EAAE,CAAC;IAEnC;;;;OAIG;IACH,QAAQ,IAAI,mBAAmB,CAAC;IAEhC;;OAEG;IACH,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3B;;;;;OAKG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzB;;;;OAIG;IACH,SAAS,IAAI,OAAO,CAAC;CACrB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -97,7 +97,7 @@ export declare abstract class BaseRegistry<T extends {
|
|
|
97
97
|
* The entity name used in log messages (e.g., 'tool', 'skill').
|
|
98
98
|
*/
|
|
99
99
|
protected abstract readonly _entityName: string;
|
|
100
|
-
constructor(options: BaseRegistryOptions
|
|
100
|
+
constructor(options: BaseRegistryOptions);
|
|
101
101
|
/**
|
|
102
102
|
* Internal logging method.
|
|
103
103
|
* @param message - The message to log
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseRegistry.d.ts","sourceRoot":"","sources":["../../src/registry/BaseRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAG5D;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,kCAAkC;IAClC,KAAK,CAAC,EAAE,cAAc,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEzC;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAEtB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,8BAAsB,YAAY,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE;IAC5D,kDAAkD;IAClD,SAAS,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAEjC,8BAA8B;IAC9B,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC;IAE1B,kCAAkC;IAClC,SAAS,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IAEpC,2CAA2C;IAC3C,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC;IAEhC,4CAA4C;IAC5C,SAAS,CAAC,WAAW,EAAE,OAAO,CAAS;IAEvC,mEAAmE;IACnE,SAAS,CAAC,iBAAiB,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CAAQ;IAE3D,iDAAiD;IACjD,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,EAAE,CAAC;IAEtD;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK;IAE7D;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK;IAE7D;;;;OAIG;IACH,SAAS,CAAC,QAAQ,CAAC,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK;IAE5E;;;;OAIG;IACH,SAAS,CAAC,QAAQ,CAAC,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;IAEvF;;;;OAIG;IACH,SAAS,CAAC,QAAQ,CAAC,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAE7D;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI;IAE3D;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAEpC,OAAO,EAAE,mBAAmB
|
|
1
|
+
{"version":3,"file":"BaseRegistry.d.ts","sourceRoot":"","sources":["../../src/registry/BaseRegistry.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry/BaseRegistry.js","sources":["../../src/registry/BaseRegistry.ts"],"sourcesContent":["/**\n * Base registry providing shared CRUD, caching, and discovery logic.\n *\n * This abstract generic class extracts the common patterns from `ToolRegistry`\n * and `SkillRegistry` into a single reusable base. Subclasses only need to\n * implement item-specific parsing, file filtering, and error construction.\n *\n * @template T - The registry item type (must have a `name` property)\n * @module registry\n */\n\nimport { existsSync } from 'node:fs';\nimport { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport { DiscoveryCache } from '../cache/DiscoveryCache.js';\nimport { NullLogger } from '../logger/NullLogger.js';\nimport type { Logger } from '../logger/StructuredLogger.js';\nimport { getErrorMessage } from '../errors.js';\n\n/**\n * Configuration options for creating a `BaseRegistry` instance.\n */\nexport interface BaseRegistryOptions {\n\t/** Optional logger for diagnostics. */\n\tlogger?: Logger;\n\n\t/** Optional cache for lookups. */\n\tcache?: DiscoveryCache<{ name: string }>;\n\n\t/**\n\t * Directory paths to search for items.\n\t */\n\tsearchDirs?: string[];\n\n\t/**\n\t * Enable lazy discovery (discover on first access instead of startup).\n\t * @default false\n\t */\n\tlazyDiscovery?: boolean;\n}\n\n/**\n * Abstract base registry for managing named items with discovery and caching.\n *\n * Provides shared CRUD operations, filesystem discovery with deduplication,\n * and optional LRU caching. Subclasses implement item-specific parsing logic\n * and error construction.\n *\n * @template T - The registry item type (must have a `name` property)\n */\nexport abstract class BaseRegistry<T extends { name: string }> {\n\t/** Internal storage for items indexed by name. */\n\tprotected _items: Map<string, T>;\n\n\t/** Logger for diagnostics. */\n\tprotected _logger: Logger;\n\n\t/** Optional cache for lookups. */\n\tprotected _cache: DiscoveryCache<T>;\n\n\t/** Directory paths to search for items. */\n\tprotected _searchDirs: string[];\n\n\t/** Whether discovery has been performed. */\n\tprotected _discovered: boolean = false;\n\n\t/** Promise for in-progress discovery (null if not in progress). */\n\tprotected _discoveryPromise: Promise<number> | null = null;\n\n\t/** File extensions to match during discovery. */\n\tprotected abstract readonly _fileExtensions: string[];\n\n\t/**\n\t * Creates an error for invalid item data.\n\t * @param reason - The reason for the validation failure\n\t */\n\tprotected abstract _createInvalidError(reason: string): Error;\n\n\t/**\n\t * Creates an error for duplicate items.\n\t * @param name - The name of the duplicate item\n\t */\n\tprotected abstract _createDuplicateError(name: string): Error;\n\n\t/**\n\t * Creates an error for items not found.\n\t * @param name - The name of the missing item\n\t * @param action - The action that was attempted\n\t */\n\tprotected abstract _createNotFoundError(name: string, action: string): Error;\n\n\t/**\n\t * Parses frontmatter content into a partial item.\n\t * @param content - The file content to parse\n\t * @returns A partial item, with an `_error` property if parsing failed\n\t */\n\tprotected abstract _parseFrontmatter(content: string): Partial<T> & { _error?: string };\n\n\t/**\n\t * Determines whether a file should be skipped during discovery.\n\t * @param fileName - The name of the file to check\n\t * @returns true if the file should be skipped\n\t */\n\tprotected abstract _shouldSkipFile(fileName: string): boolean;\n\n\t/**\n\t * Constructs a complete item from parsed frontmatter data.\n\t * Returns null if the parsed data is insufficient.\n\t * @param parsed - The parsed frontmatter data\n\t * @returns A complete item, or null if data is insufficient\n\t */\n\tprotected abstract _buildItem(parsed: Partial<T>): T | null;\n\n\t/**\n\t * The entity name used in log messages (e.g., 'tool', 'skill').\n\t */\n\tprotected abstract readonly _entityName: string;\n\n\tconstructor(options: BaseRegistryOptions & Record<string, unknown>) {\n\t\tthis._items = new Map();\n\t\tthis._logger = (options.logger ?? new NullLogger()) as Logger;\n\t\tthis._cache = (options.cache ||\n\t\t\tnew DiscoveryCache<T>({ maxSize: 50, ttl: 300000 })) as DiscoveryCache<T>;\n\t\tthis._searchDirs = (options.searchDirs ?? []) as string[];\n\t}\n\n\t/**\n\t * Internal logging method.\n\t * @param message - The message to log\n\t * @param meta - Optional metadata\n\t */\n\tprotected log(message: string, meta?: Record<string, unknown>): void {\n\t\tthis._logger.info(message, meta);\n\t}\n\n\t/**\n\t * Adds an item to the registry.\n\t *\n\t * @param item - The item to add\n\t * @throws If item already exists or name is invalid\n\t */\n\tpublic add(item: T): void {\n\t\tif (!item.name) {\n\t\t\tthrow this._createInvalidError(`${this._entityName} must have a valid name`);\n\t\t}\n\t\tif (this._items.has(item.name)) {\n\t\t\tthrow this._createDuplicateError(item.name);\n\t\t}\n\t\tthis._items.set(item.name, item);\n\t\tthis.log(`Added ${this._entityName}: ${item.name}`, { [`${this._entityName}Name`]: item.name });\n\t\t// Invalidate cache when adding a new item\n\t\tthis._cache?.invalidate('all');\n\t}\n\n\t/**\n\t * Removes an item from the registry.\n\t *\n\t * @param name - The name of the item to remove\n\t * @throws If item not found\n\t */\n\tpublic remove(name: string): void {\n\t\tif (!this._items.has(name)) {\n\t\t\tthrow this._createNotFoundError(name, 'remove');\n\t\t}\n\t\tthis._items.delete(name);\n\t\tthis.log(`Removed ${this._entityName}: ${name}`, { [`${this._entityName}Name`]: name });\n\t\t// Invalidate cache when removing an item\n\t\tthis._cache?.invalidate('all');\n\t\tthis._cache?.invalidate(name);\n\t}\n\n\t/**\n\t * Updates an existing item with partial data.\n\t *\n\t * @param name - The name of the item to update\n\t * @param updates - Partial item data with fields to update\n\t * @throws If item not found\n\t */\n\tpublic update(name: string, updates: Partial<T>): void {\n\t\tif (!this._items.has(name)) {\n\t\t\tthrow this._createNotFoundError(name, 'update');\n\t\t}\n\t\tconst existing = this._items.get(name)!;\n\t\tconst updated = { ...existing, ...updates };\n\t\tthis._items.set(name, updated);\n\t\tthis.log(`Updated ${this._entityName}: ${name}`, { [`${this._entityName}Name`]: name });\n\t\t// Invalidate cache when updating an item\n\t\tthis._cache?.invalidate('all');\n\t\tthis._cache?.invalidate(name);\n\t}\n\n\t/**\n\t * Gets an item by name.\n\t *\n\t * @param name - The name of the item to get\n\t * @returns The item if found, undefined otherwise\n\t */\n\tpublic get(name: string): T | undefined {\n\t\treturn this._items.get(name);\n\t}\n\n\t/**\n\t * Gets all items as an array.\n\t *\n\t * Uses cache if available for performance.\n\t *\n\t * @returns An array of all registered items\n\t */\n\tpublic getAll(): T[] {\n\t\t// Check cache first\n\t\tif (this._cache) {\n\t\t\tconst cached = this._cache.get('all');\n\t\t\tif (cached) {\n\t\t\t\treturn cached;\n\t\t\t}\n\t\t}\n\t\t// Get from storage\n\t\tconst items = Array.from(this._items.values());\n\t\t// Cache the result\n\t\tthis._cache?.set('all', items);\n\t\treturn items;\n\t}\n\n\t/**\n\t * Checks if an item exists in the registry.\n\t *\n\t * @param name - The name of the item to check\n\t * @returns true if the item exists, false otherwise\n\t */\n\tpublic has(name: string): boolean {\n\t\treturn this._items.has(name);\n\t}\n\n\t/**\n\t * Gets all item names as an array.\n\t *\n\t * @returns An array of item names\n\t */\n\tpublic getNames(): string[] {\n\t\treturn Array.from(this._items.keys());\n\t}\n\n\t/**\n\t * Clears all items from the registry.\n\t */\n\tpublic clear(): void {\n\t\tthis._items.clear();\n\t\tthis.log(`Cleared all ${this._entityName}s`);\n\t\t// Invalidate cache when clearing all items\n\t\tthis._cache?.clear();\n\t}\n\n\t/**\n\t * Gets the number of items in the registry.\n\t *\n\t * @returns The count of registered items\n\t */\n\tpublic size(): number {\n\t\treturn this._items.size;\n\t}\n\n\t/**\n\t * Asynchronously discovers items from the configured directories.\n\t *\n\t * Multiple concurrent calls share the same discovery promise.\n\t * Subsequent calls return cached results if discovery has already completed.\n\t *\n\t * @returns A Promise resolving to the number of items discovered\n\t */\n\tpublic async discoverAsync(): Promise<number> {\n\t\t// Return existing promise if discovery is in progress\n\t\tif (this._discoveryPromise) {\n\t\t\treturn this._discoveryPromise;\n\t\t}\n\n\t\t// Use cached results if already discovered\n\t\tif (this._discovered) {\n\t\t\tconst cached = this._cache.get('all');\n\t\t\treturn cached?.length ?? 0;\n\t\t}\n\n\t\t// Create discovery promise\n\t\tthis._discoveryPromise = this._performDiscovery();\n\n\t\ttry {\n\t\t\tconst count = await this._discoveryPromise;\n\t\t\treturn count;\n\t\t} finally {\n\t\t\tthis._discoveryPromise = null;\n\t\t}\n\t}\n\n\t/**\n\t * Performs the actual discovery operation.\n\t *\n\t * Scans configured directories for item files, parses their frontmatter,\n\t * and adds valid items to the registry.\n\t *\n\t * @returns A Promise resolving to the number of items discovered\n\t */\n\tprotected async _performDiscovery(): Promise<number> {\n\t\tlet discoveredCount = 0;\n\n\t\tfor (const dir of this._searchDirs) {\n\t\t\ttry {\n\t\t\t\tif (!existsSync(dir)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst entries = await readdir(dir, { withFileTypes: true });\n\t\t\t\tfor (const entry of entries) {\n\t\t\t\t\tif (this._shouldSkipFile(entry.name)) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (entry.isFile() && this._fileExtensions.some((ext) => entry.name.endsWith(ext))) {\n\t\t\t\t\t\tconst filePath = join(dir, entry.name);\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst content = await readFile(filePath, 'utf-8');\n\t\t\t\t\t\t\tconst parsed = this._parseFrontmatter(content);\n\t\t\t\t\t\t\tif (parsed._error) {\n\t\t\t\t\t\t\t\tthis.log(`Skipped ${entry.name}: ${parsed._error}`);\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (parsed.name) {\n\t\t\t\t\t\t\t\t// Check if already exists before adding\n\t\t\t\t\t\t\t\tif (!this._items.has(parsed.name)) {\n\t\t\t\t\t\t\t\t\tconst item = this._buildItem(parsed);\n\t\t\t\t\t\t\t\t\tif (item) {\n\t\t\t\t\t\t\t\t\t\tthis._items.set(item.name, item);\n\t\t\t\t\t\t\t\t\t\tdiscoveredCount++;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (readError) {\n\t\t\t\t\t\t\tthis.log(`Failed to read ${this._entityName} file ${entry.name}`, {\n\t\t\t\t\t\t\t\terror: getErrorMessage(readError),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tthis.log(`Failed to scan ${this._entityName} directory: ${dir}`, {\n\t\t\t\t\terror: getErrorMessage(error),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tthis._discovered = true;\n\t\tthis._cache?.set('all', Array.from(this._items.values()));\n\t\tthis.log(`Discovery complete: found ${discoveredCount} ${this._entityName}s`, {\n\t\t\tdiscoveredCount,\n\t\t});\n\t\treturn discoveredCount;\n\t}\n\n\t/**\n\t * Parses YAML frontmatter from file content.\n\t *\n\t * This is a shared utility for subclasses that parse YAML frontmatter.\n\t *\n\t * @param content - The file content to parse\n\t * @returns The parsed YAML object, or null if no frontmatter found\n\t */\n\tprotected _extractFrontmatter(content: string): Record<string, unknown> | null {\n\t\tconst match = content.match(/^---\\n([\\s\\S]+?)\\n---/);\n\t\tif (!match) {\n\t\t\treturn null;\n\t\t}\n\t\treturn parseYaml(match[1]!) as Record<string, unknown>;\n\t}\n\n\t/**\n\t * Sets items from an external source.\n\t *\n\t * Clears existing items and adds new ones from the provided array.\n\t *\n\t * @param items - Array of items from an external source\n\t */\n\tpublic setAll(items: T[]): void {\n\t\tthis.clear();\n\t\tfor (const item of items) {\n\t\t\ttry {\n\t\t\t\tthis.add(item);\n\t\t\t} catch (error) {\n\t\t\t\tthis.log(`Error adding ${this._entityName} '${item.name}':`, {\n\t\t\t\t\t[`${this._entityName}Name`]: item.name,\n\t\t\t\t\terror: getErrorMessage(error),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tthis.log(`Set ${items.length} ${this._entityName}s from external source`, {\n\t\t\t[`${this._entityName}Count`]: items.length,\n\t\t});\n\t}\n}\n"],"names":["BaseRegistry","options","Map","NullLogger","DiscoveryCache","message","meta","item","name","updates","existing","updated","cached","items","Array","count","discoveredCount","dir","existsSync","entries","readdir","entry","ext","filePath","join","content","readFile","parsed","readError","getErrorMessage","error","match","parseYaml"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"registry/BaseRegistry.js","sources":["../../src/registry/BaseRegistry.ts"],"sourcesContent":["/**\n * Base registry providing shared CRUD, caching, and discovery logic.\n *\n * This abstract generic class extracts the common patterns from `ToolRegistry`\n * and `SkillRegistry` into a single reusable base. Subclasses only need to\n * implement item-specific parsing, file filtering, and error construction.\n *\n * @template T - The registry item type (must have a `name` property)\n * @module registry\n */\n\nimport { existsSync } from 'node:fs';\nimport { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport { DiscoveryCache } from '../cache/DiscoveryCache.js';\nimport { NullLogger } from '../logger/NullLogger.js';\nimport type { Logger } from '../logger/StructuredLogger.js';\nimport { getErrorMessage } from '../errors.js';\n\n/**\n * Configuration options for creating a `BaseRegistry` instance.\n */\nexport interface BaseRegistryOptions {\n\t/** Optional logger for diagnostics. */\n\tlogger?: Logger;\n\n\t/** Optional cache for lookups. */\n\tcache?: DiscoveryCache<{ name: string }>;\n\n\t/**\n\t * Directory paths to search for items.\n\t */\n\tsearchDirs?: string[];\n\n\t/**\n\t * Enable lazy discovery (discover on first access instead of startup).\n\t * @default false\n\t */\n\tlazyDiscovery?: boolean;\n}\n\n/**\n * Abstract base registry for managing named items with discovery and caching.\n *\n * Provides shared CRUD operations, filesystem discovery with deduplication,\n * and optional LRU caching. Subclasses implement item-specific parsing logic\n * and error construction.\n *\n * @template T - The registry item type (must have a `name` property)\n */\nexport abstract class BaseRegistry<T extends { name: string }> {\n\t/** Internal storage for items indexed by name. */\n\tprotected _items: Map<string, T>;\n\n\t/** Logger for diagnostics. */\n\tprotected _logger: Logger;\n\n\t/** Optional cache for lookups. */\n\tprotected _cache: DiscoveryCache<T>;\n\n\t/** Directory paths to search for items. */\n\tprotected _searchDirs: string[];\n\n\t/** Whether discovery has been performed. */\n\tprotected _discovered: boolean = false;\n\n\t/** Promise for in-progress discovery (null if not in progress). */\n\tprotected _discoveryPromise: Promise<number> | null = null;\n\n\t/** File extensions to match during discovery. */\n\tprotected abstract readonly _fileExtensions: string[];\n\n\t/**\n\t * Creates an error for invalid item data.\n\t * @param reason - The reason for the validation failure\n\t */\n\tprotected abstract _createInvalidError(reason: string): Error;\n\n\t/**\n\t * Creates an error for duplicate items.\n\t * @param name - The name of the duplicate item\n\t */\n\tprotected abstract _createDuplicateError(name: string): Error;\n\n\t/**\n\t * Creates an error for items not found.\n\t * @param name - The name of the missing item\n\t * @param action - The action that was attempted\n\t */\n\tprotected abstract _createNotFoundError(name: string, action: string): Error;\n\n\t/**\n\t * Parses frontmatter content into a partial item.\n\t * @param content - The file content to parse\n\t * @returns A partial item, with an `_error` property if parsing failed\n\t */\n\tprotected abstract _parseFrontmatter(content: string): Partial<T> & { _error?: string };\n\n\t/**\n\t * Determines whether a file should be skipped during discovery.\n\t * @param fileName - The name of the file to check\n\t * @returns true if the file should be skipped\n\t */\n\tprotected abstract _shouldSkipFile(fileName: string): boolean;\n\n\t/**\n\t * Constructs a complete item from parsed frontmatter data.\n\t * Returns null if the parsed data is insufficient.\n\t * @param parsed - The parsed frontmatter data\n\t * @returns A complete item, or null if data is insufficient\n\t */\n\tprotected abstract _buildItem(parsed: Partial<T>): T | null;\n\n\t/**\n\t * The entity name used in log messages (e.g., 'tool', 'skill').\n\t */\n\tprotected abstract readonly _entityName: string;\n\n\tconstructor(options: BaseRegistryOptions) {\n\t\tthis._items = new Map();\n\t\tthis._logger = (options.logger ?? new NullLogger()) as Logger;\n\t\tthis._cache = (options.cache ||\n\t\t\tnew DiscoveryCache<T>({ maxSize: 50, ttl: 300000 })) as DiscoveryCache<T>;\n\t\tthis._searchDirs = (options.searchDirs ?? []) as string[];\n\t}\n\n\t/**\n\t * Internal logging method.\n\t * @param message - The message to log\n\t * @param meta - Optional metadata\n\t */\n\tprotected log(message: string, meta?: Record<string, unknown>): void {\n\t\tthis._logger.info(message, meta);\n\t}\n\n\t/**\n\t * Adds an item to the registry.\n\t *\n\t * @param item - The item to add\n\t * @throws If item already exists or name is invalid\n\t */\n\tpublic add(item: T): void {\n\t\tif (!item.name) {\n\t\t\tthrow this._createInvalidError(`${this._entityName} must have a valid name`);\n\t\t}\n\t\tif (this._items.has(item.name)) {\n\t\t\tthrow this._createDuplicateError(item.name);\n\t\t}\n\t\tthis._items.set(item.name, item);\n\t\tthis.log(`Added ${this._entityName}: ${item.name}`, { [`${this._entityName}Name`]: item.name });\n\t\t// Invalidate cache when adding a new item\n\t\tthis._cache?.invalidate('all');\n\t}\n\n\t/**\n\t * Removes an item from the registry.\n\t *\n\t * @param name - The name of the item to remove\n\t * @throws If item not found\n\t */\n\tpublic remove(name: string): void {\n\t\tif (!this._items.has(name)) {\n\t\t\tthrow this._createNotFoundError(name, 'remove');\n\t\t}\n\t\tthis._items.delete(name);\n\t\tthis.log(`Removed ${this._entityName}: ${name}`, { [`${this._entityName}Name`]: name });\n\t\t// Invalidate cache when removing an item\n\t\tthis._cache?.invalidate('all');\n\t\tthis._cache?.invalidate(name);\n\t}\n\n\t/**\n\t * Updates an existing item with partial data.\n\t *\n\t * @param name - The name of the item to update\n\t * @param updates - Partial item data with fields to update\n\t * @throws If item not found\n\t */\n\tpublic update(name: string, updates: Partial<T>): void {\n\t\tif (!this._items.has(name)) {\n\t\t\tthrow this._createNotFoundError(name, 'update');\n\t\t}\n\t\tconst existing = this._items.get(name)!;\n\t\tconst updated = { ...existing, ...updates };\n\t\tthis._items.set(name, updated);\n\t\tthis.log(`Updated ${this._entityName}: ${name}`, { [`${this._entityName}Name`]: name });\n\t\t// Invalidate cache when updating an item\n\t\tthis._cache?.invalidate('all');\n\t\tthis._cache?.invalidate(name);\n\t}\n\n\t/**\n\t * Gets an item by name.\n\t *\n\t * @param name - The name of the item to get\n\t * @returns The item if found, undefined otherwise\n\t */\n\tpublic get(name: string): T | undefined {\n\t\treturn this._items.get(name);\n\t}\n\n\t/**\n\t * Gets all items as an array.\n\t *\n\t * Uses cache if available for performance.\n\t *\n\t * @returns An array of all registered items\n\t */\n\tpublic getAll(): T[] {\n\t\t// Check cache first\n\t\tif (this._cache) {\n\t\t\tconst cached = this._cache.get('all');\n\t\t\tif (cached) {\n\t\t\t\treturn cached;\n\t\t\t}\n\t\t}\n\t\t// Get from storage\n\t\tconst items = Array.from(this._items.values());\n\t\t// Cache the result\n\t\tthis._cache?.set('all', items);\n\t\treturn items;\n\t}\n\n\t/**\n\t * Checks if an item exists in the registry.\n\t *\n\t * @param name - The name of the item to check\n\t * @returns true if the item exists, false otherwise\n\t */\n\tpublic has(name: string): boolean {\n\t\treturn this._items.has(name);\n\t}\n\n\t/**\n\t * Gets all item names as an array.\n\t *\n\t * @returns An array of item names\n\t */\n\tpublic getNames(): string[] {\n\t\treturn Array.from(this._items.keys());\n\t}\n\n\t/**\n\t * Clears all items from the registry.\n\t */\n\tpublic clear(): void {\n\t\tthis._items.clear();\n\t\tthis.log(`Cleared all ${this._entityName}s`);\n\t\t// Invalidate cache when clearing all items\n\t\tthis._cache?.clear();\n\t}\n\n\t/**\n\t * Gets the number of items in the registry.\n\t *\n\t * @returns The count of registered items\n\t */\n\tpublic size(): number {\n\t\treturn this._items.size;\n\t}\n\n\t/**\n\t * Asynchronously discovers items from the configured directories.\n\t *\n\t * Multiple concurrent calls share the same discovery promise.\n\t * Subsequent calls return cached results if discovery has already completed.\n\t *\n\t * @returns A Promise resolving to the number of items discovered\n\t */\n\tpublic async discoverAsync(): Promise<number> {\n\t\t// Return existing promise if discovery is in progress\n\t\tif (this._discoveryPromise) {\n\t\t\treturn this._discoveryPromise;\n\t\t}\n\n\t\t// Use cached results if already discovered\n\t\tif (this._discovered) {\n\t\t\tconst cached = this._cache.get('all');\n\t\t\treturn cached?.length ?? 0;\n\t\t}\n\n\t\t// Create discovery promise\n\t\tthis._discoveryPromise = this._performDiscovery();\n\n\t\ttry {\n\t\t\tconst count = await this._discoveryPromise;\n\t\t\treturn count;\n\t\t} finally {\n\t\t\tthis._discoveryPromise = null;\n\t\t}\n\t}\n\n\t/**\n\t * Performs the actual discovery operation.\n\t *\n\t * Scans configured directories for item files, parses their frontmatter,\n\t * and adds valid items to the registry.\n\t *\n\t * @returns A Promise resolving to the number of items discovered\n\t */\n\tprotected async _performDiscovery(): Promise<number> {\n\t\tlet discoveredCount = 0;\n\n\t\tfor (const dir of this._searchDirs) {\n\t\t\ttry {\n\t\t\t\tif (!existsSync(dir)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst entries = await readdir(dir, { withFileTypes: true });\n\t\t\t\tfor (const entry of entries) {\n\t\t\t\t\tif (this._shouldSkipFile(entry.name)) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (entry.isFile() && this._fileExtensions.some((ext) => entry.name.endsWith(ext))) {\n\t\t\t\t\t\tconst filePath = join(dir, entry.name);\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst content = await readFile(filePath, 'utf-8');\n\t\t\t\t\t\t\tconst parsed = this._parseFrontmatter(content);\n\t\t\t\t\t\t\tif (parsed._error) {\n\t\t\t\t\t\t\t\tthis.log(`Skipped ${entry.name}: ${parsed._error}`);\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (parsed.name) {\n\t\t\t\t\t\t\t\t// Check if already exists before adding\n\t\t\t\t\t\t\t\tif (!this._items.has(parsed.name)) {\n\t\t\t\t\t\t\t\t\tconst item = this._buildItem(parsed);\n\t\t\t\t\t\t\t\t\tif (item) {\n\t\t\t\t\t\t\t\t\t\tthis._items.set(item.name, item);\n\t\t\t\t\t\t\t\t\t\tdiscoveredCount++;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (readError) {\n\t\t\t\t\t\t\tthis.log(`Failed to read ${this._entityName} file ${entry.name}`, {\n\t\t\t\t\t\t\t\terror: getErrorMessage(readError),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tthis.log(`Failed to scan ${this._entityName} directory: ${dir}`, {\n\t\t\t\t\terror: getErrorMessage(error),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tthis._discovered = true;\n\t\tthis._cache?.set('all', Array.from(this._items.values()));\n\t\tthis.log(`Discovery complete: found ${discoveredCount} ${this._entityName}s`, {\n\t\t\tdiscoveredCount,\n\t\t});\n\t\treturn discoveredCount;\n\t}\n\n\t/**\n\t * Parses YAML frontmatter from file content.\n\t *\n\t * This is a shared utility for subclasses that parse YAML frontmatter.\n\t *\n\t * @param content - The file content to parse\n\t * @returns The parsed YAML object, or null if no frontmatter found\n\t */\n\tprotected _extractFrontmatter(content: string): Record<string, unknown> | null {\n\t\tconst match = content.match(/^---\\n([\\s\\S]+?)\\n---/);\n\t\tif (!match) {\n\t\t\treturn null;\n\t\t}\n\t\treturn parseYaml(match[1]!) as Record<string, unknown>;\n\t}\n\n\t/**\n\t * Sets items from an external source.\n\t *\n\t * Clears existing items and adds new ones from the provided array.\n\t *\n\t * @param items - Array of items from an external source\n\t */\n\tpublic setAll(items: T[]): void {\n\t\tthis.clear();\n\t\tfor (const item of items) {\n\t\t\ttry {\n\t\t\t\tthis.add(item);\n\t\t\t} catch (error) {\n\t\t\t\tthis.log(`Error adding ${this._entityName} '${item.name}':`, {\n\t\t\t\t\t[`${this._entityName}Name`]: item.name,\n\t\t\t\t\terror: getErrorMessage(error),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tthis.log(`Set ${items.length} ${this._entityName}s from external source`, {\n\t\t\t[`${this._entityName}Count`]: items.length,\n\t\t});\n\t}\n}\n"],"names":["BaseRegistry","options","Map","NullLogger","DiscoveryCache","message","meta","item","name","updates","existing","updated","cached","items","Array","count","discoveredCount","dir","existsSync","entries","readdir","entry","ext","filePath","join","content","readFile","parsed","readError","getErrorMessage","error","match","parseYaml"],"mappings":"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"}
|
|
@@ -64,6 +64,7 @@ export declare class ToolRegistry extends BaseRegistry<Tool> {
|
|
|
64
64
|
updateTool(name: string, updates: Partial<Tool>): void;
|
|
65
65
|
hasTool(name: string): boolean;
|
|
66
66
|
getTool(name: string): Tool | undefined;
|
|
67
|
+
list(): string[];
|
|
67
68
|
setTools(tools: Tool[]): void;
|
|
68
69
|
}
|
|
69
70
|
//# sourceMappingURL=ToolRegistry.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToolRegistry.d.ts","sourceRoot":"","sources":["../../src/registry/ToolRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,mBAAmB;IACnC,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,uCAAuC;IACvC,KAAK,CAAC,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC;IAE7B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAEpB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,qBAAa,YAAa,SAAQ,YAAY,CAAC,IAAI,CAAC;IACnD,mBAA4B,eAAe,WAAgB;IAC3D,mBAA4B,WAAW,UAAU;gBAErC,OAAO,GAAE,mBAAwB;cAW1B,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK;cAI1C,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK;cAI1C,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK;cAMzD,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;cAI3C,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;cA0BvE,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI;IAajD,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;IAY5C,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAIzB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI9B,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAItD,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI9B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;IAIvC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI;CAGpC"}
|
|
1
|
+
{"version":3,"file":"ToolRegistry.d.ts","sourceRoot":"","sources":["../../src/registry/ToolRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,mBAAmB;IACnC,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,uCAAuC;IACvC,KAAK,CAAC,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC;IAE7B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAEpB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,qBAAa,YAAa,SAAQ,YAAY,CAAC,IAAI,CAAC;IACnD,mBAA4B,eAAe,WAAgB;IAC3D,mBAA4B,WAAW,UAAU;gBAErC,OAAO,GAAE,mBAAwB;cAW1B,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK;cAI1C,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK;cAI1C,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK;cAMzD,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;cAI3C,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;cA0BvE,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI;IAajD,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;IAY5C,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAIzB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI9B,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAItD,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI9B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;IAIvC,IAAI,IAAI,MAAM,EAAE;IAIhB,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI;CAGpC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry/ToolRegistry.js","sources":["../../src/registry/ToolRegistry.ts"],"sourcesContent":["/**\n * Tool registry for managing MCP tool CRUD operations.\n *\n * This module provides the `ToolRegistry` class which manages the registration,\n * retrieval, update, and removal of MCP tools. It supports optional caching\n * for improved performance, filesystem discovery, and integrates with the logging system.\n *\n * @module registry\n */\n\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { DiscoveryCache } from '../cache/DiscoveryCache.js';\nimport { DuplicateToolError, InvalidToolError, ToolNotFoundError } from '../errors.js';\nimport type { Logger } from '../logger/StructuredLogger.js';\nimport type { Tool } from '../types/tool.js';\nimport { BaseRegistry } from './BaseRegistry.js';\n\n/**\n * Configuration options for creating a `ToolRegistry` instance.\n *\n * @example\n * ```typescript\n * const options: ToolRegistryOptions = {\n * logger: new StructuredLogger({ context: 'ToolRegistry' }),\n * cache: new DiscoveryCache({ ttl: 300000, maxSize: 100 }),\n * toolDirs: ['./custom-tools', '~/.claude/tools'],\n * lazyDiscovery: true\n * };\n * ```\n */\nexport interface ToolRegistryOptions {\n\t/** Optional logger for diagnostics. */\n\tlogger?: Logger;\n\n\t/** Optional cache for tool lookups. */\n\tcache?: DiscoveryCache<Tool>;\n\n\t/**\n\t * Directory paths to search for tools.\n\t * @default ['.claude/tools', '~/.claude/tools']\n\t */\n\ttoolDirs?: string[];\n\n\t/**\n\t * Enable lazy discovery (discover on first access instead of startup).\n\t * @default false\n\t */\n\tlazyDiscovery?: boolean;\n}\n\n/**\n * Registry for managing MCP tool operations.\n *\n * Extends `BaseRegistry<Tool>` with tool-specific frontmatter parsing\n * and backward-compatible aliases (`addTool`, `removeTool`, etc.).\n */\nexport class ToolRegistry extends BaseRegistry<Tool> {\n\tprotected override readonly _fileExtensions = ['.tool.md'];\n\tprotected override readonly _entityName = 'tool';\n\n\tconstructor(options: ToolRegistryOptions = {}) {\n\t\tsuper({\n\t\t\tlogger: options.logger,\n\t\t\tcache: options.cache,\n\t\t\tsearchDirs: options.toolDirs || ['.claude/tools', join(homedir(), '.claude/tools')],\n\t\t\tlazyDiscovery: options.lazyDiscovery,\n\t\t});\n\t}\n\n\t// --- Error factories ---\n\n\tprotected override _createInvalidError(reason: string): Error {\n\t\treturn new InvalidToolError(reason);\n\t}\n\n\tprotected override _createDuplicateError(name: string): Error {\n\t\treturn new DuplicateToolError(name);\n\t}\n\n\tprotected override _createNotFoundError(name: string, action: string): Error {\n\t\treturn new ToolNotFoundError(name, action);\n\t}\n\n\t// --- Discovery ---\n\n\tprotected override _shouldSkipFile(_fileName: string): boolean {\n\t\treturn false;\n\t}\n\n\tprotected override _parseFrontmatter(content: string): Partial<Tool> & { _error?: string } {\n\t\tconst frontmatter = this._extractFrontmatter(content);\n\t\tif (!frontmatter) {\n\t\t\treturn { _error: 'No YAML frontmatter found' };\n\t\t}\n\n\t\ttry {\n\t\t\tconst result: Partial<Tool> = {\n\t\t\t\tname: typeof frontmatter.name === 'string' ? frontmatter.name : undefined,\n\t\t\t\tdescription: typeof frontmatter.description === 'string' ? frontmatter.description : '',\n\t\t\t\tinputSchema: frontmatter.inputSchema as Tool['inputSchema'],\n\t\t\t};\n\n\t\t\tif (!result.name) {\n\t\t\t\treturn { _error: 'Missing required field: name' };\n\t\t\t}\n\t\t\tif (!result.inputSchema) {\n\t\t\t\treturn { _error: 'Missing required field: inputSchema' };\n\t\t\t}\n\n\t\t\treturn result;\n\t\t} catch {\n\t\t\treturn { _error: 'YAML parse error' };\n\t\t}\n\t}\n\n\tprotected override _buildItem(parsed: Partial<Tool>): Tool | null {\n\t\tif (!parsed.name || !parsed.inputSchema) {\n\t\t\treturn null;\n\t\t}\n\t\treturn {\n\t\t\tname: parsed.name,\n\t\t\tdescription: parsed.description || '',\n\t\t\tinputSchema: parsed.inputSchema,\n\t\t};\n\t}\n\n\t// --- Tool-specific get with cache lookup ---\n\n\tpublic override get(name: string): Tool | undefined {\n\t\tif (this._cache) {\n\t\t\tconst cached = this._cache.get(`tool:${name}`);\n\t\t\tif (cached && cached.length > 0) {\n\t\t\t\treturn cached[0];\n\t\t\t}\n\t\t}\n\t\treturn this._items.get(name);\n\t}\n\n\t// --- Backward-compatible aliases ---\n\n\tpublic addTool(tool: Tool): void {\n\t\tthis.add(tool);\n\t}\n\n\tpublic removeTool(name: string): void {\n\t\tthis.remove(name);\n\t}\n\n\tpublic updateTool(name: string, updates: Partial<Tool>): void {\n\t\tthis.update(name, updates);\n\t}\n\n\tpublic hasTool(name: string): boolean {\n\t\treturn this.has(name);\n\t}\n\n\tpublic getTool(name: string): Tool | undefined {\n\t\treturn this.get(name);\n\t}\n\n\tpublic setTools(tools: Tool[]): void {\n\t\tthis.setAll(tools);\n\t}\n}\n"],"names":["ToolRegistry","BaseRegistry","options","join","homedir","reason","InvalidToolError","name","DuplicateToolError","action","ToolNotFoundError","_fileName","content","frontmatter","result","undefined","parsed","cached","tool","updates","tools"],"mappings":";;;;AAyDO,MAAMA,qBAAqBC;IACL,kBAAkB;QAAC;KAAW,CAAC;IAC/B,cAAc,OAAO;IAEjD,YAAYC,UAA+B,CAAC,CAAC,CAAE;QAC9C,KAAK,CAAC;YACL,QAAQA,QAAQ,MAAM;YACtB,OAAOA,QAAQ,KAAK;YACpB,YAAYA,QAAQ,QAAQ,IAAI;gBAAC;gBAAiBC,KAAKC,WAAW;aAAiB;YACnF,eAAeF,QAAQ,aAAa;QACrC;IACD;IAImB,oBAAoBG,MAAc,EAAS;QAC7D,OAAO,IAAIC,iBAAiBD;IAC7B;IAEmB,sBAAsBE,IAAY,EAAS;QAC7D,OAAO,IAAIC,mBAAmBD;IAC/B;IAEmB,qBAAqBA,IAAY,EAAEE,MAAc,EAAS;QAC5E,OAAO,IAAIC,kBAAkBH,MAAME;IACpC;IAImB,gBAAgBE,SAAiB,EAAW;QAC9D,OAAO;IACR;IAEmB,kBAAkBC,OAAe,EAAuC;QAC1F,MAAMC,cAAc,IAAI,CAAC,mBAAmB,CAACD;QAC7C,IAAI,CAACC,aACJ,OAAO;YAAE,QAAQ;QAA4B;QAG9C,IAAI;YACH,MAAMC,SAAwB;gBAC7B,MAAM,AAA4B,YAA5B,OAAOD,YAAY,IAAI,GAAgBA,YAAY,IAAI,GAAGE;gBAChE,aAAa,AAAmC,YAAnC,OAAOF,YAAY,WAAW,GAAgBA,YAAY,WAAW,GAAG;gBACrF,aAAaA,YAAY,WAAW;YACrC;YAEA,IAAI,CAACC,OAAO,IAAI,EACf,OAAO;gBAAE,QAAQ;YAA+B;YAEjD,IAAI,CAACA,OAAO,WAAW,EACtB,OAAO;gBAAE,QAAQ;YAAsC;YAGxD,OAAOA;QACR,EAAE,OAAM;YACP,OAAO;gBAAE,QAAQ;YAAmB;QACrC;IACD;IAEmB,WAAWE,MAAqB,EAAe;QACjE,IAAI,CAACA,OAAO,IAAI,IAAI,CAACA,OAAO,WAAW,EACtC,OAAO;QAER,OAAO;YACN,MAAMA,OAAO,IAAI;YACjB,aAAaA,OAAO,WAAW,IAAI;YACnC,aAAaA,OAAO,WAAW;QAChC;IACD;IAIgB,IAAIT,IAAY,EAAoB;QACnD,IAAI,IAAI,CAAC,MAAM,EAAE;YAChB,MAAMU,SAAS,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAEV,MAAM;YAC7C,IAAIU,UAAUA,OAAO,MAAM,GAAG,GAC7B,OAAOA,MAAM,CAAC,EAAE;QAElB;QACA,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAACV;IACxB;IAIO,QAAQW,IAAU,EAAQ;QAChC,IAAI,CAAC,GAAG,CAACA;IACV;IAEO,WAAWX,IAAY,EAAQ;QACrC,IAAI,CAAC,MAAM,CAACA;IACb;IAEO,WAAWA,IAAY,EAAEY,OAAsB,EAAQ;QAC7D,IAAI,CAAC,MAAM,CAACZ,MAAMY;IACnB;IAEO,QAAQZ,IAAY,EAAW;QACrC,OAAO,IAAI,CAAC,GAAG,CAACA;IACjB;IAEO,QAAQA,IAAY,EAAoB;QAC9C,OAAO,IAAI,CAAC,GAAG,CAACA;IACjB;IAEO,SAASa,KAAa,EAAQ;QACpC,IAAI,CAAC,MAAM,CAACA;IACb;AACD"}
|
|
1
|
+
{"version":3,"file":"registry/ToolRegistry.js","sources":["../../src/registry/ToolRegistry.ts"],"sourcesContent":["/**\n * Tool registry for managing MCP tool CRUD operations.\n *\n * This module provides the `ToolRegistry` class which manages the registration,\n * retrieval, update, and removal of MCP tools. It supports optional caching\n * for improved performance, filesystem discovery, and integrates with the logging system.\n *\n * @module registry\n */\n\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { DiscoveryCache } from '../cache/DiscoveryCache.js';\nimport { DuplicateToolError, InvalidToolError, ToolNotFoundError } from '../errors.js';\nimport type { Logger } from '../logger/StructuredLogger.js';\nimport type { Tool } from '../types/tool.js';\nimport { BaseRegistry } from './BaseRegistry.js';\n\n/**\n * Configuration options for creating a `ToolRegistry` instance.\n *\n * @example\n * ```typescript\n * const options: ToolRegistryOptions = {\n * logger: new StructuredLogger({ context: 'ToolRegistry' }),\n * cache: new DiscoveryCache({ ttl: 300000, maxSize: 100 }),\n * toolDirs: ['./custom-tools', '~/.claude/tools'],\n * lazyDiscovery: true\n * };\n * ```\n */\nexport interface ToolRegistryOptions {\n\t/** Optional logger for diagnostics. */\n\tlogger?: Logger;\n\n\t/** Optional cache for tool lookups. */\n\tcache?: DiscoveryCache<Tool>;\n\n\t/**\n\t * Directory paths to search for tools.\n\t * @default ['.claude/tools', '~/.claude/tools']\n\t */\n\ttoolDirs?: string[];\n\n\t/**\n\t * Enable lazy discovery (discover on first access instead of startup).\n\t * @default false\n\t */\n\tlazyDiscovery?: boolean;\n}\n\n/**\n * Registry for managing MCP tool operations.\n *\n * Extends `BaseRegistry<Tool>` with tool-specific frontmatter parsing\n * and backward-compatible aliases (`addTool`, `removeTool`, etc.).\n */\nexport class ToolRegistry extends BaseRegistry<Tool> {\n\tprotected override readonly _fileExtensions = ['.tool.md'];\n\tprotected override readonly _entityName = 'tool';\n\n\tconstructor(options: ToolRegistryOptions = {}) {\n\t\tsuper({\n\t\t\tlogger: options.logger,\n\t\t\tcache: options.cache,\n\t\t\tsearchDirs: options.toolDirs || ['.claude/tools', join(homedir(), '.claude/tools')],\n\t\t\tlazyDiscovery: options.lazyDiscovery,\n\t\t});\n\t}\n\n\t// --- Error factories ---\n\n\tprotected override _createInvalidError(reason: string): Error {\n\t\treturn new InvalidToolError(reason);\n\t}\n\n\tprotected override _createDuplicateError(name: string): Error {\n\t\treturn new DuplicateToolError(name);\n\t}\n\n\tprotected override _createNotFoundError(name: string, action: string): Error {\n\t\treturn new ToolNotFoundError(name, action);\n\t}\n\n\t// --- Discovery ---\n\n\tprotected override _shouldSkipFile(_fileName: string): boolean {\n\t\treturn false;\n\t}\n\n\tprotected override _parseFrontmatter(content: string): Partial<Tool> & { _error?: string } {\n\t\tconst frontmatter = this._extractFrontmatter(content);\n\t\tif (!frontmatter) {\n\t\t\treturn { _error: 'No YAML frontmatter found' };\n\t\t}\n\n\t\ttry {\n\t\t\tconst result: Partial<Tool> = {\n\t\t\t\tname: typeof frontmatter.name === 'string' ? frontmatter.name : undefined,\n\t\t\t\tdescription: typeof frontmatter.description === 'string' ? frontmatter.description : '',\n\t\t\t\tinputSchema: frontmatter.inputSchema as Tool['inputSchema'],\n\t\t\t};\n\n\t\t\tif (!result.name) {\n\t\t\t\treturn { _error: 'Missing required field: name' };\n\t\t\t}\n\t\t\tif (!result.inputSchema) {\n\t\t\t\treturn { _error: 'Missing required field: inputSchema' };\n\t\t\t}\n\n\t\t\treturn result;\n\t\t} catch {\n\t\t\treturn { _error: 'YAML parse error' };\n\t\t}\n\t}\n\n\tprotected override _buildItem(parsed: Partial<Tool>): Tool | null {\n\t\tif (!parsed.name || !parsed.inputSchema) {\n\t\t\treturn null;\n\t\t}\n\t\treturn {\n\t\t\tname: parsed.name,\n\t\t\tdescription: parsed.description || '',\n\t\t\tinputSchema: parsed.inputSchema,\n\t\t};\n\t}\n\n\t// --- Tool-specific get with cache lookup ---\n\n\tpublic override get(name: string): Tool | undefined {\n\t\tif (this._cache) {\n\t\t\tconst cached = this._cache.get(`tool:${name}`);\n\t\t\tif (cached && cached.length > 0) {\n\t\t\t\treturn cached[0];\n\t\t\t}\n\t\t}\n\t\treturn this._items.get(name);\n\t}\n\n\t// --- Backward-compatible aliases ---\n\n\tpublic addTool(tool: Tool): void {\n\t\tthis.add(tool);\n\t}\n\n\tpublic removeTool(name: string): void {\n\t\tthis.remove(name);\n\t}\n\n\tpublic updateTool(name: string, updates: Partial<Tool>): void {\n\t\tthis.update(name, updates);\n\t}\n\n\tpublic hasTool(name: string): boolean {\n\t\treturn this.has(name);\n\t}\n\n\tpublic getTool(name: string): Tool | undefined {\n\t\treturn this.get(name);\n\t}\n\n\tpublic list(): string[] {\n\t\treturn this.getNames();\n\t}\n\n\tpublic setTools(tools: Tool[]): void {\n\t\tthis.setAll(tools);\n\t}\n}\n"],"names":["ToolRegistry","BaseRegistry","options","join","homedir","reason","InvalidToolError","name","DuplicateToolError","action","ToolNotFoundError","_fileName","content","frontmatter","result","undefined","parsed","cached","tool","updates","tools"],"mappings":";;;;AAyDO,MAAMA,qBAAqBC;IACL,kBAAkB;QAAC;KAAW,CAAC;IAC/B,cAAc,OAAO;IAEjD,YAAYC,UAA+B,CAAC,CAAC,CAAE;QAC9C,KAAK,CAAC;YACL,QAAQA,QAAQ,MAAM;YACtB,OAAOA,QAAQ,KAAK;YACpB,YAAYA,QAAQ,QAAQ,IAAI;gBAAC;gBAAiBC,KAAKC,WAAW;aAAiB;YACnF,eAAeF,QAAQ,aAAa;QACrC;IACD;IAImB,oBAAoBG,MAAc,EAAS;QAC7D,OAAO,IAAIC,iBAAiBD;IAC7B;IAEmB,sBAAsBE,IAAY,EAAS;QAC7D,OAAO,IAAIC,mBAAmBD;IAC/B;IAEmB,qBAAqBA,IAAY,EAAEE,MAAc,EAAS;QAC5E,OAAO,IAAIC,kBAAkBH,MAAME;IACpC;IAImB,gBAAgBE,SAAiB,EAAW;QAC9D,OAAO;IACR;IAEmB,kBAAkBC,OAAe,EAAuC;QAC1F,MAAMC,cAAc,IAAI,CAAC,mBAAmB,CAACD;QAC7C,IAAI,CAACC,aACJ,OAAO;YAAE,QAAQ;QAA4B;QAG9C,IAAI;YACH,MAAMC,SAAwB;gBAC7B,MAAM,AAA4B,YAA5B,OAAOD,YAAY,IAAI,GAAgBA,YAAY,IAAI,GAAGE;gBAChE,aAAa,AAAmC,YAAnC,OAAOF,YAAY,WAAW,GAAgBA,YAAY,WAAW,GAAG;gBACrF,aAAaA,YAAY,WAAW;YACrC;YAEA,IAAI,CAACC,OAAO,IAAI,EACf,OAAO;gBAAE,QAAQ;YAA+B;YAEjD,IAAI,CAACA,OAAO,WAAW,EACtB,OAAO;gBAAE,QAAQ;YAAsC;YAGxD,OAAOA;QACR,EAAE,OAAM;YACP,OAAO;gBAAE,QAAQ;YAAmB;QACrC;IACD;IAEmB,WAAWE,MAAqB,EAAe;QACjE,IAAI,CAACA,OAAO,IAAI,IAAI,CAACA,OAAO,WAAW,EACtC,OAAO;QAER,OAAO;YACN,MAAMA,OAAO,IAAI;YACjB,aAAaA,OAAO,WAAW,IAAI;YACnC,aAAaA,OAAO,WAAW;QAChC;IACD;IAIgB,IAAIT,IAAY,EAAoB;QACnD,IAAI,IAAI,CAAC,MAAM,EAAE;YAChB,MAAMU,SAAS,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAEV,MAAM;YAC7C,IAAIU,UAAUA,OAAO,MAAM,GAAG,GAC7B,OAAOA,MAAM,CAAC,EAAE;QAElB;QACA,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAACV;IACxB;IAIO,QAAQW,IAAU,EAAQ;QAChC,IAAI,CAAC,GAAG,CAACA;IACV;IAEO,WAAWX,IAAY,EAAQ;QACrC,IAAI,CAAC,MAAM,CAACA;IACb;IAEO,WAAWA,IAAY,EAAEY,OAAsB,EAAQ;QAC7D,IAAI,CAAC,MAAM,CAACZ,MAAMY;IACnB;IAEO,QAAQZ,IAAY,EAAW;QACrC,OAAO,IAAI,CAAC,GAAG,CAACA;IACjB;IAEO,QAAQA,IAAY,EAAoB;QAC9C,OAAO,IAAI,CAAC,GAAG,CAACA;IACjB;IAEO,OAAiB;QACvB,OAAO,IAAI,CAAC,QAAQ;IACrB;IAEO,SAASa,KAAa,EAAQ;QACpC,IAAI,CAAC,MAAM,CAACA;IACb;AACD"}
|
package/dist/sanitize.d.ts
CHANGED
|
@@ -60,4 +60,74 @@ export declare function stripControlChars(input: string): string;
|
|
|
60
60
|
* ```
|
|
61
61
|
*/
|
|
62
62
|
export declare function sanitizeString(input: string): string;
|
|
63
|
+
export interface EnforceJsonShapeOptions {
|
|
64
|
+
maxDepth?: number;
|
|
65
|
+
maxBytes?: number;
|
|
66
|
+
}
|
|
67
|
+
export declare class JsonShapeError extends Error {
|
|
68
|
+
readonly reason: string;
|
|
69
|
+
constructor(reason: string);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Enforce safety constraints on a JSON-shaped value.
|
|
73
|
+
*
|
|
74
|
+
* Rejects:
|
|
75
|
+
* - Prototype-pollution keys (`__proto__`, `constructor`, `prototype`) at any depth
|
|
76
|
+
* - Nesting deeper than `maxDepth` (default 8)
|
|
77
|
+
* - Serialized JSON byte length exceeding `maxBytes` (default 16384)
|
|
78
|
+
* - Functions, symbols, and other non-JSON-safe values
|
|
79
|
+
*
|
|
80
|
+
* Used as a defense-in-depth gate for untrusted structured input such as
|
|
81
|
+
* `tool_arguments` from an LLM.
|
|
82
|
+
*
|
|
83
|
+
* @param value - The value to validate
|
|
84
|
+
* @param opts - Optional limits
|
|
85
|
+
* @throws {JsonShapeError} when any constraint is violated
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* enforceJsonShape({ q: 'hello' }); // ok
|
|
90
|
+
* enforceJsonShape({ __proto__: { polluted: true } }); // throws
|
|
91
|
+
* enforceJsonShape({ a: { b: { c: { d: { e: { f: { g: { h: { i: 1 } } } } } } } } }); // throws (depth)
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare function enforceJsonShape(value: unknown, opts?: EnforceJsonShapeOptions): void;
|
|
95
|
+
/**
|
|
96
|
+
* Sanitize a rationale string by stripping urgency phrases, capping length,
|
|
97
|
+
* and applying standard string sanitization.
|
|
98
|
+
*
|
|
99
|
+
* Applied to `recommended_tools[].rationale` and `recommended_skills[].rationale`
|
|
100
|
+
* to prevent prompt-injection via urgency language.
|
|
101
|
+
*
|
|
102
|
+
* @param input - The rationale string to sanitize
|
|
103
|
+
* @param truncated - Optional object to receive truncation signal (sets `.value = true` if truncated)
|
|
104
|
+
* @returns The sanitized rationale
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```ts
|
|
108
|
+
* sanitizeRationale('URGENT: run this now'); // '[redacted-urgency] run this now'
|
|
109
|
+
* sanitizeRationale('Best for web search'); // 'Best for web search' (unchanged)
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
export declare function sanitizeRationale(input: string, truncated?: {
|
|
113
|
+
value: boolean;
|
|
114
|
+
}): string;
|
|
115
|
+
/**
|
|
116
|
+
* Sanitize suggested_inputs: cap string value lengths, strip control chars and dangerous tags.
|
|
117
|
+
*
|
|
118
|
+
* Only processes flat primitive values (string | number | boolean | null).
|
|
119
|
+
* Non-primitive values (nested objects, arrays) are silently skipped — schema validation
|
|
120
|
+
* is expected to reject them upstream.
|
|
121
|
+
*
|
|
122
|
+
* @param inputs - The suggested_inputs record to sanitize
|
|
123
|
+
* @returns The sanitized record with cleaned string values
|
|
124
|
+
* @throws {Error} if string value exceeds max length, or if more than 32 keys are present
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* sanitizeSuggestedInputs({ url: 'x', limit: 5 }); // { url: 'x', limit: 5 }
|
|
129
|
+
* sanitizeSuggestedInputs({ url: '<script>alert(1)</script>' }); // { url: 'alert(1)' }
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
export declare function sanitizeSuggestedInputs(inputs: Record<string, unknown>): Record<string, string | number | boolean | null>;
|
|
63
133
|
//# sourceMappingURL=sanitize.d.ts.map
|
package/dist/sanitize.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAiBH;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD"}
|
|
1
|
+
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAiBH;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAgBD,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,cAAe,SAAQ,KAAK;IACxC,SAAgB,MAAM,EAAE,MAAM,CAAC;gBACnB,MAAM,EAAE,MAAM;CAK1B;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,GAAE,uBAA4B,GAAG,IAAI,CAoDzF;AAcD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,CAQvF;AAYD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,uBAAuB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,CAuBlD"}
|
package/dist/sanitize.js
CHANGED
|
@@ -9,6 +9,82 @@ function stripControlChars(input) {
|
|
|
9
9
|
function sanitizeString(input) {
|
|
10
10
|
return stripDangerousTags(stripControlChars(input));
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
const FORBIDDEN_KEYS = new Set([
|
|
13
|
+
'__proto__',
|
|
14
|
+
'constructor',
|
|
15
|
+
'prototype'
|
|
16
|
+
]);
|
|
17
|
+
const DEFAULT_MAX_DEPTH = 8;
|
|
18
|
+
const DEFAULT_MAX_BYTES = 16384;
|
|
19
|
+
class JsonShapeError extends Error {
|
|
20
|
+
reason;
|
|
21
|
+
constructor(reason){
|
|
22
|
+
super(reason);
|
|
23
|
+
this.name = 'JsonShapeError';
|
|
24
|
+
this.reason = reason;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function enforceJsonShape(value, opts = {}) {
|
|
28
|
+
const maxDepth = opts.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
29
|
+
const maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
30
|
+
const seen = new WeakSet();
|
|
31
|
+
const walk = (node, depth)=>{
|
|
32
|
+
if (depth > maxDepth) throw new JsonShapeError(`exceeds max depth of ${maxDepth}`);
|
|
33
|
+
if (null === node) return;
|
|
34
|
+
const type = typeof node;
|
|
35
|
+
if ('string' === type || 'number' === type || 'boolean' === type) return;
|
|
36
|
+
if ('undefined' === type) return;
|
|
37
|
+
if ('function' === type || 'symbol' === type || 'bigint' === type) throw new JsonShapeError(`unsupported value type '${type}'`);
|
|
38
|
+
if ('object' !== type) throw new JsonShapeError(`unsupported value type '${type}'`);
|
|
39
|
+
const obj = node;
|
|
40
|
+
if (seen.has(obj)) throw new JsonShapeError('circular reference detected');
|
|
41
|
+
seen.add(obj);
|
|
42
|
+
if (Array.isArray(node)) {
|
|
43
|
+
for (const item of node)walk(item, depth + 1);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
for (const key of Object.keys(obj)){
|
|
47
|
+
if (FORBIDDEN_KEYS.has(key)) throw new JsonShapeError(`forbidden key '${key}'`);
|
|
48
|
+
walk(obj[key], depth + 1);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
walk(value, 0);
|
|
52
|
+
let serialized;
|
|
53
|
+
try {
|
|
54
|
+
serialized = JSON.stringify(value);
|
|
55
|
+
} catch {
|
|
56
|
+
throw new JsonShapeError('value is not JSON-serializable');
|
|
57
|
+
}
|
|
58
|
+
if (void 0 === serialized) return;
|
|
59
|
+
const bytes = Buffer.byteLength(serialized, 'utf8');
|
|
60
|
+
if (bytes > maxBytes) throw new JsonShapeError(`exceeds max serialized size of ${maxBytes} bytes (got ${bytes})`);
|
|
61
|
+
}
|
|
62
|
+
const URGENCY_PHRASES = /\b(URGENT(?:LY)?|IMMEDIATELY|MUST\s+RUN|CRITICAL:|ACTION\s+REQUIRED|DO\s+NOT\s+IGNORE|EXECUTE\s+NOW|RUN\s+THIS\s+NOW)/gi;
|
|
63
|
+
const MAX_RATIONALE_LENGTH = 2000;
|
|
64
|
+
function sanitizeRationale(input, truncated) {
|
|
65
|
+
let result = sanitizeString(input);
|
|
66
|
+
result = result.replace(URGENCY_PHRASES, '[redacted-urgency]');
|
|
67
|
+
if (result.length > MAX_RATIONALE_LENGTH) {
|
|
68
|
+
result = result.slice(0, MAX_RATIONALE_LENGTH);
|
|
69
|
+
if (truncated) truncated.value = true;
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
const MAX_SUGGESTED_INPUTS_KEYS = 32;
|
|
74
|
+
const MAX_SUGGESTED_INPUT_VALUE_LENGTH = 512;
|
|
75
|
+
function sanitizeSuggestedInputs(inputs) {
|
|
76
|
+
const keys = Object.keys(inputs);
|
|
77
|
+
if (keys.length > MAX_SUGGESTED_INPUTS_KEYS) throw new Error(`suggested_inputs exceeds max keys of ${MAX_SUGGESTED_INPUTS_KEYS} (got ${keys.length})`);
|
|
78
|
+
const result = {};
|
|
79
|
+
for (const key of keys){
|
|
80
|
+
const value = inputs[key];
|
|
81
|
+
if ('string' == typeof value) {
|
|
82
|
+
if (value.length > MAX_SUGGESTED_INPUT_VALUE_LENGTH) throw new Error(`suggested_inputs value for key '${key}' exceeds max length of ${MAX_SUGGESTED_INPUT_VALUE_LENGTH}`);
|
|
83
|
+
result[key] = sanitizeString(value);
|
|
84
|
+
} else if ('number' == typeof value || 'boolean' == typeof value || null === value) result[key] = value;
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
export { JsonShapeError, enforceJsonShape, sanitizeRationale, sanitizeString, sanitizeSuggestedInputs, stripControlChars, stripDangerousTags };
|
|
13
89
|
|
|
14
90
|
//# sourceMappingURL=sanitize.js.map
|
package/dist/sanitize.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitize.js","sources":["../src/sanitize.ts"],"sourcesContent":["/**\n * Input sanitization for the sequential thinking MCP tool.\n *\n * Provides pure functions for stripping dangerous content from free-text fields.\n * Uses a targeted blocklist approach: only removes HTML tags that can execute code,\n * while preserving generic angle-bracket content like TypeScript generics (`Array<string>`),\n * mathematical comparisons (`x < 5`), and markdown formatting.\n *\n * @module sanitize\n */\n\n/**\n * Regex matching dangerous HTML tags that can execute JavaScript or load external resources.\n * Targets: script, iframe, img, style, svg, embed, object, link, meta, base, form.\n * Preserves: `Array<string>`, `x < 5 && y > 3`, `<details>`, `<code>`, `<pre>`, etc.\n */\nconst DANGEROUS_TAG_REGEX =\n\t/<\\/?(script|iframe|img|style|svg|embed|object|link|meta|base|form)(\\s[^>]*)?\\s*\\/?>/gi;\n\n/**\n * Null bytes and C0 control characters (except tab \\t, newline \\n, carriage return \\r).\n * These can cause truncation in C bindings, file I/O, and some databases.\n */\n// eslint-disable-next-line no-control-regex -- intentional: matches C0 control chars to strip them\nconst CONTROL_CHAR_REGEX = /[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F]/g;\n\n/**\n * Strip dangerous HTML tags that can execute JavaScript or load external resources.\n *\n * Uses a targeted blocklist to remove only tags known to be dangerous (script, iframe,\n * img, style, svg, embed, object, link, meta, base, form) while preserving safe\n * angle-bracket content like TypeScript generics and mathematical comparisons.\n *\n * @param input - The string to sanitize\n * @returns The input with dangerous HTML tags removed\n *\n * @example\n * ```ts\n * stripDangerousTags('<script>alert(1)</script>hello'); // 'hello'\n * stripDangerousTags('Array<string>'); // 'Array<string>' (preserved)\n * stripDangerousTags('x < 5 && y > 3'); // 'x < 5 && y > 3' (preserved)\n * ```\n */\nexport function stripDangerousTags(input: string): string {\n\treturn input.replace(DANGEROUS_TAG_REGEX, '');\n}\n\n/**\n * Strip null bytes and C0 control characters from a string.\n *\n * Removes characters in the range U+0000–U+0008, U+000B, U+000C, U+000E–U+001F.\n * Preserves tab (`\\t`, U+0009), newline (`\\n`, U+000A), and carriage return (`\\r`, U+000D)\n * as these are commonly used in thought content.\n *\n * @param input - The string to sanitize\n * @returns The input with control characters removed\n *\n * @example\n * ```ts\n * stripControlChars('a\\x00b'); // 'ab'\n * stripControlChars('a\\tb\\nc'); // 'a\\tb\\nc' (tab and newline preserved)\n * ```\n */\nexport function stripControlChars(input: string): string {\n\treturn input.replace(CONTROL_CHAR_REGEX, '');\n}\n\n/**\n * Sanitize a string by stripping both control characters and dangerous HTML tags.\n *\n * Composes {@link stripControlChars} and {@link stripDangerousTags} in sequence.\n * Does not trim whitespace — thought content may depend on leading/trailing spaces.\n * Always returns a string, even if the input is empty.\n *\n * @param input - The string to sanitize\n * @returns The fully sanitized string\n *\n * @example\n * ```ts\n * sanitizeString('<script>alert(1)</script>hello\\x00world'); // 'helloworld'\n * sanitizeString('Array<string>\\x00'); // 'Array<string>'\n * ```\n */\nexport function sanitizeString(input: string): string {\n\treturn stripDangerousTags(stripControlChars(input));\n}\n"],"names":["DANGEROUS_TAG_REGEX","CONTROL_CHAR_REGEX","stripDangerousTags","input","stripControlChars","sanitizeString"],"mappings":"AAgBA,MAAMA,sBACL;AAOD,MAAMC,qBAAqB;AAmBpB,SAASC,mBAAmBC,KAAa;IAC/C,OAAOA,MAAM,OAAO,CAACH,qBAAqB;AAC3C;AAkBO,SAASI,kBAAkBD,KAAa;IAC9C,OAAOA,MAAM,OAAO,CAACF,oBAAoB;AAC1C;AAkBO,SAASI,eAAeF,KAAa;IAC3C,OAAOD,mBAAmBE,kBAAkBD;AAC7C"}
|
|
1
|
+
{"version":3,"file":"sanitize.js","sources":["../src/sanitize.ts"],"sourcesContent":["/**\n * Input sanitization for the sequential thinking MCP tool.\n *\n * Provides pure functions for stripping dangerous content from free-text fields.\n * Uses a targeted blocklist approach: only removes HTML tags that can execute code,\n * while preserving generic angle-bracket content like TypeScript generics (`Array<string>`),\n * mathematical comparisons (`x < 5`), and markdown formatting.\n *\n * @module sanitize\n */\n\n/**\n * Regex matching dangerous HTML tags that can execute JavaScript or load external resources.\n * Targets: script, iframe, img, style, svg, embed, object, link, meta, base, form.\n * Preserves: `Array<string>`, `x < 5 && y > 3`, `<details>`, `<code>`, `<pre>`, etc.\n */\nconst DANGEROUS_TAG_REGEX =\n\t/<\\/?(script|iframe|img|style|svg|embed|object|link|meta|base|form)(\\s[^>]*)?\\s*\\/?>/gi;\n\n/**\n * Null bytes and C0 control characters (except tab \\t, newline \\n, carriage return \\r).\n * These can cause truncation in C bindings, file I/O, and some databases.\n */\n// eslint-disable-next-line no-control-regex -- intentional: matches C0 control chars to strip them\nconst CONTROL_CHAR_REGEX = /[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F]/g;\n\n/**\n * Strip dangerous HTML tags that can execute JavaScript or load external resources.\n *\n * Uses a targeted blocklist to remove only tags known to be dangerous (script, iframe,\n * img, style, svg, embed, object, link, meta, base, form) while preserving safe\n * angle-bracket content like TypeScript generics and mathematical comparisons.\n *\n * @param input - The string to sanitize\n * @returns The input with dangerous HTML tags removed\n *\n * @example\n * ```ts\n * stripDangerousTags('<script>alert(1)</script>hello'); // 'hello'\n * stripDangerousTags('Array<string>'); // 'Array<string>' (preserved)\n * stripDangerousTags('x < 5 && y > 3'); // 'x < 5 && y > 3' (preserved)\n * ```\n */\nexport function stripDangerousTags(input: string): string {\n\treturn input.replace(DANGEROUS_TAG_REGEX, '');\n}\n\n/**\n * Strip null bytes and C0 control characters from a string.\n *\n * Removes characters in the range U+0000–U+0008, U+000B, U+000C, U+000E–U+001F.\n * Preserves tab (`\\t`, U+0009), newline (`\\n`, U+000A), and carriage return (`\\r`, U+000D)\n * as these are commonly used in thought content.\n *\n * @param input - The string to sanitize\n * @returns The input with control characters removed\n *\n * @example\n * ```ts\n * stripControlChars('a\\x00b'); // 'ab'\n * stripControlChars('a\\tb\\nc'); // 'a\\tb\\nc' (tab and newline preserved)\n * ```\n */\nexport function stripControlChars(input: string): string {\n\treturn input.replace(CONTROL_CHAR_REGEX, '');\n}\n\n/**\n * Sanitize a string by stripping both control characters and dangerous HTML tags.\n *\n * Composes {@link stripControlChars} and {@link stripDangerousTags} in sequence.\n * Does not trim whitespace — thought content may depend on leading/trailing spaces.\n * Always returns a string, even if the input is empty.\n *\n * @param input - The string to sanitize\n * @returns The fully sanitized string\n *\n * @example\n * ```ts\n * sanitizeString('<script>alert(1)</script>hello\\x00world'); // 'helloworld'\n * sanitizeString('Array<string>\\x00'); // 'Array<string>'\n * ```\n */\nexport function sanitizeString(input: string): string {\n\treturn stripDangerousTags(stripControlChars(input));\n}\n\n\n/**\n * Forbidden object keys that enable prototype pollution.\n * These keys allow attackers to inject properties onto Object.prototype,\n * affecting all subsequent objects in the runtime.\n */\nconst FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\n/**\n * Default options for {@link enforceJsonShape}.\n */\nconst DEFAULT_MAX_DEPTH = 8;\nconst DEFAULT_MAX_BYTES = 16384;\n\nexport interface EnforceJsonShapeOptions {\n\tmaxDepth?: number;\n\tmaxBytes?: number;\n}\n\nexport class JsonShapeError extends Error {\n\tpublic readonly reason: string;\n\tconstructor(reason: string) {\n\t\tsuper(reason);\n\t\tthis.name = 'JsonShapeError';\n\t\tthis.reason = reason;\n\t}\n}\n\n/**\n * Enforce safety constraints on a JSON-shaped value.\n *\n * Rejects:\n * - Prototype-pollution keys (`__proto__`, `constructor`, `prototype`) at any depth\n * - Nesting deeper than `maxDepth` (default 8)\n * - Serialized JSON byte length exceeding `maxBytes` (default 16384)\n * - Functions, symbols, and other non-JSON-safe values\n *\n * Used as a defense-in-depth gate for untrusted structured input such as\n * `tool_arguments` from an LLM.\n *\n * @param value - The value to validate\n * @param opts - Optional limits\n * @throws {JsonShapeError} when any constraint is violated\n *\n * @example\n * ```ts\n * enforceJsonShape({ q: 'hello' }); // ok\n * enforceJsonShape({ __proto__: { polluted: true } }); // throws\n * enforceJsonShape({ a: { b: { c: { d: { e: { f: { g: { h: { i: 1 } } } } } } } } }); // throws (depth)\n * ```\n */\nexport function enforceJsonShape(value: unknown, opts: EnforceJsonShapeOptions = {}): void {\n\tconst maxDepth = opts.maxDepth ?? DEFAULT_MAX_DEPTH;\n\tconst maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES;\n\n\tconst seen = new WeakSet<object>();\n\n\tconst walk = (node: unknown, depth: number): void => {\n\t\tif (depth > maxDepth) {\n\t\t\tthrow new JsonShapeError(`exceeds max depth of ${maxDepth}`);\n\t\t}\n\t\tif (node === null) return;\n\t\tconst type = typeof node;\n\t\tif (type === 'string' || type === 'number' || type === 'boolean') return;\n\t\tif (type === 'undefined') return;\n\t\tif (type === 'function' || type === 'symbol' || type === 'bigint') {\n\t\t\tthrow new JsonShapeError(`unsupported value type '${type}'`);\n\t\t}\n\t\tif (type !== 'object') {\n\t\t\tthrow new JsonShapeError(`unsupported value type '${type}'`);\n\t\t}\n\t\tconst obj = node as object;\n\t\tif (seen.has(obj)) {\n\t\t\tthrow new JsonShapeError('circular reference detected');\n\t\t}\n\t\tseen.add(obj);\n\n\t\tif (Array.isArray(node)) {\n\t\t\tfor (const item of node) walk(item, depth + 1);\n\t\t\treturn;\n\t\t}\n\n\t\tfor (const key of Object.keys(obj)) {\n\t\t\tif (FORBIDDEN_KEYS.has(key)) {\n\t\t\t\tthrow new JsonShapeError(`forbidden key '${key}'`);\n\t\t\t}\n\t\t\twalk((obj as Record<string, unknown>)[key], depth + 1);\n\t\t}\n\t};\n\n\twalk(value, 0);\n\n\tlet serialized: string;\n\ttry {\n\t\tserialized = JSON.stringify(value);\n\t} catch {\n\t\tthrow new JsonShapeError('value is not JSON-serializable');\n\t}\n\tif (serialized === undefined) return;\n\tconst bytes = Buffer.byteLength(serialized, 'utf8');\n\tif (bytes > maxBytes) {\n\t\tthrow new JsonShapeError(`exceeds max serialized size of ${maxBytes} bytes (got ${bytes})`);\n\t}\n}\n\n/**\n * Urgency/imperative phrases that could be used for prompt injection.\n * Matched case-insensitively and replaced with [redacted-urgency].\n */\nconst URGENCY_PHRASES =\n\t/\\b(URGENT(?:LY)?|IMMEDIATELY|MUST\\s+RUN|CRITICAL:|ACTION\\s+REQUIRED|DO\\s+NOT\\s+IGNORE|EXECUTE\\s+NOW|RUN\\s+THIS\\s+NOW)/gi;\n\n/**\n * Maximum allowed length for rationale strings.\n */\nconst MAX_RATIONALE_LENGTH = 2000;\n\n/**\n * Sanitize a rationale string by stripping urgency phrases, capping length,\n * and applying standard string sanitization.\n *\n * Applied to `recommended_tools[].rationale` and `recommended_skills[].rationale`\n * to prevent prompt-injection via urgency language.\n *\n * @param input - The rationale string to sanitize\n * @param truncated - Optional object to receive truncation signal (sets `.value = true` if truncated)\n * @returns The sanitized rationale\n *\n * @example\n * ```ts\n * sanitizeRationale('URGENT: run this now'); // '[redacted-urgency] run this now'\n * sanitizeRationale('Best for web search'); // 'Best for web search' (unchanged)\n * ```\n */\nexport function sanitizeRationale(input: string, truncated?: { value: boolean }): string {\n\tlet result = sanitizeString(input);\n\tresult = result.replace(URGENCY_PHRASES, '[redacted-urgency]');\n\tif (result.length > MAX_RATIONALE_LENGTH) {\n\t\tresult = result.slice(0, MAX_RATIONALE_LENGTH);\n\t\tif (truncated) truncated.value = true;\n\t}\n\treturn result;\n}\n\n/**\n * Maximum number of keys allowed in suggested_inputs.\n */\nconst MAX_SUGGESTED_INPUTS_KEYS = 32;\n\n/**\n * Maximum string value length in suggested_inputs.\n */\nconst MAX_SUGGESTED_INPUT_VALUE_LENGTH = 512;\n\n/**\n * Sanitize suggested_inputs: cap string value lengths, strip control chars and dangerous tags.\n *\n * Only processes flat primitive values (string | number | boolean | null).\n * Non-primitive values (nested objects, arrays) are silently skipped — schema validation\n * is expected to reject them upstream.\n *\n * @param inputs - The suggested_inputs record to sanitize\n * @returns The sanitized record with cleaned string values\n * @throws {Error} if string value exceeds max length, or if more than 32 keys are present\n *\n * @example\n * ```ts\n * sanitizeSuggestedInputs({ url: 'x', limit: 5 }); // { url: 'x', limit: 5 }\n * sanitizeSuggestedInputs({ url: '<script>alert(1)</script>' }); // { url: 'alert(1)' }\n * ```\n */\nexport function sanitizeSuggestedInputs(\n\tinputs: Record<string, unknown>,\n): Record<string, string | number | boolean | null> {\n\tconst keys = Object.keys(inputs);\n\tif (keys.length > MAX_SUGGESTED_INPUTS_KEYS) {\n\t\tthrow new Error(\n\t\t\t`suggested_inputs exceeds max keys of ${MAX_SUGGESTED_INPUTS_KEYS} (got ${keys.length})`,\n\t\t);\n\t}\n\tconst result: Record<string, string | number | boolean | null> = {};\n\tfor (const key of keys) {\n\t\tconst value = inputs[key];\n\t\tif (typeof value === 'string') {\n\t\t\tif (value.length > MAX_SUGGESTED_INPUT_VALUE_LENGTH) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`suggested_inputs value for key '${key}' exceeds max length of ${MAX_SUGGESTED_INPUT_VALUE_LENGTH}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tresult[key] = sanitizeString(value);\n\t\t} else if (typeof value === 'number' || typeof value === 'boolean' || value === null) {\n\t\t\tresult[key] = value;\n\t\t}\n\t\t// Skip other types (shouldn't reach here if schema validates first)\n\t}\n\treturn result;\n}"],"names":["DANGEROUS_TAG_REGEX","CONTROL_CHAR_REGEX","stripDangerousTags","input","stripControlChars","sanitizeString","FORBIDDEN_KEYS","Set","DEFAULT_MAX_DEPTH","DEFAULT_MAX_BYTES","JsonShapeError","Error","reason","enforceJsonShape","value","opts","maxDepth","maxBytes","seen","WeakSet","walk","node","depth","type","obj","Array","item","key","Object","serialized","JSON","undefined","bytes","Buffer","URGENCY_PHRASES","MAX_RATIONALE_LENGTH","sanitizeRationale","truncated","result","MAX_SUGGESTED_INPUTS_KEYS","MAX_SUGGESTED_INPUT_VALUE_LENGTH","sanitizeSuggestedInputs","inputs","keys"],"mappings":"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"}
|