@framers/agentos 0.1.119 → 0.1.121
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 +21 -0
- package/dist/api/agency.d.ts.map +1 -1
- package/dist/api/agency.js +227 -84
- package/dist/api/agency.js.map +1 -1
- package/dist/api/analyzeVideo.d.ts +127 -0
- package/dist/api/analyzeVideo.d.ts.map +1 -0
- package/dist/api/analyzeVideo.js +136 -0
- package/dist/api/analyzeVideo.js.map +1 -0
- package/dist/api/detectScenes.d.ts +82 -0
- package/dist/api/detectScenes.d.ts.map +1 -0
- package/dist/api/detectScenes.js +67 -0
- package/dist/api/detectScenes.js.map +1 -0
- package/dist/api/generateImage.d.ts +7 -0
- package/dist/api/generateImage.d.ts.map +1 -1
- package/dist/api/generateImage.js +133 -9
- package/dist/api/generateImage.js.map +1 -1
- package/dist/api/generateMusic.d.ts +98 -0
- package/dist/api/generateMusic.d.ts.map +1 -0
- package/dist/api/generateMusic.js +319 -0
- package/dist/api/generateMusic.js.map +1 -0
- package/dist/api/generateSFX.d.ts +96 -0
- package/dist/api/generateSFX.d.ts.map +1 -0
- package/dist/api/generateSFX.js +317 -0
- package/dist/api/generateSFX.js.map +1 -0
- package/dist/api/generateVideo.d.ts +113 -0
- package/dist/api/generateVideo.d.ts.map +1 -0
- package/dist/api/generateVideo.js +342 -0
- package/dist/api/generateVideo.js.map +1 -0
- package/dist/api/model.d.ts.map +1 -1
- package/dist/api/model.js +8 -4
- package/dist/api/model.js.map +1 -1
- package/dist/api/performOCR.d.ts +169 -0
- package/dist/api/performOCR.d.ts.map +1 -0
- package/dist/api/performOCR.js +198 -0
- package/dist/api/performOCR.js.map +1 -0
- package/dist/api/provider-defaults.d.ts +7 -5
- package/dist/api/provider-defaults.d.ts.map +1 -1
- package/dist/api/provider-defaults.js +32 -10
- package/dist/api/provider-defaults.js.map +1 -1
- package/dist/api/strategies/debate.d.ts.map +1 -1
- package/dist/api/strategies/debate.js +1 -0
- package/dist/api/strategies/debate.js.map +1 -1
- package/dist/api/strategies/graph.d.ts.map +1 -1
- package/dist/api/strategies/graph.js +69 -13
- package/dist/api/strategies/graph.js.map +1 -1
- package/dist/api/strategies/hierarchical.d.ts.map +1 -1
- package/dist/api/strategies/hierarchical.js +1 -0
- package/dist/api/strategies/hierarchical.js.map +1 -1
- package/dist/api/strategies/parallel.d.ts.map +1 -1
- package/dist/api/strategies/parallel.js +1 -0
- package/dist/api/strategies/parallel.js.map +1 -1
- package/dist/api/strategies/review-loop.d.ts.map +1 -1
- package/dist/api/strategies/review-loop.js +1 -0
- package/dist/api/strategies/review-loop.js.map +1 -1
- package/dist/api/strategies/sequential.d.ts.map +1 -1
- package/dist/api/strategies/sequential.js +54 -48
- package/dist/api/strategies/sequential.js.map +1 -1
- package/dist/api/streamBuffer.d.ts +20 -0
- package/dist/api/streamBuffer.d.ts.map +1 -0
- package/dist/api/streamBuffer.js +81 -0
- package/dist/api/streamBuffer.js.map +1 -0
- package/dist/api/types.d.ts +145 -5
- package/dist/api/types.d.ts.map +1 -1
- package/dist/api/types.js.map +1 -1
- package/dist/channels/adapters/RedditChannelAdapter.js.map +1 -1
- package/dist/core/audio/AudioProcessor.d.ts.map +1 -1
- package/dist/core/audio/AudioProcessor.js +1 -0
- package/dist/core/audio/AudioProcessor.js.map +1 -1
- package/dist/core/audio/EnvironmentalCalibrator.d.ts.map +1 -1
- package/dist/core/audio/EnvironmentalCalibrator.js +1 -0
- package/dist/core/audio/EnvironmentalCalibrator.js.map +1 -1
- package/dist/core/audio/FallbackAudioProxy.d.ts +169 -0
- package/dist/core/audio/FallbackAudioProxy.d.ts.map +1 -0
- package/dist/core/audio/FallbackAudioProxy.js +236 -0
- package/dist/core/audio/FallbackAudioProxy.js.map +1 -0
- package/dist/core/audio/IAudioGenerator.d.ts +103 -0
- package/dist/core/audio/IAudioGenerator.d.ts.map +1 -0
- package/dist/core/audio/IAudioGenerator.js +24 -0
- package/dist/core/audio/IAudioGenerator.js.map +1 -0
- package/dist/core/audio/index.d.ts +54 -0
- package/dist/core/audio/index.d.ts.map +1 -1
- package/dist/core/audio/index.js +93 -0
- package/dist/core/audio/index.js.map +1 -1
- package/dist/core/audio/providers/AudioGenLocalProvider.d.ts +136 -0
- package/dist/core/audio/providers/AudioGenLocalProvider.d.ts.map +1 -0
- package/dist/core/audio/providers/AudioGenLocalProvider.js +235 -0
- package/dist/core/audio/providers/AudioGenLocalProvider.js.map +1 -0
- package/dist/core/audio/providers/ElevenLabsSFXProvider.d.ts +107 -0
- package/dist/core/audio/providers/ElevenLabsSFXProvider.d.ts.map +1 -0
- package/dist/core/audio/providers/ElevenLabsSFXProvider.js +154 -0
- package/dist/core/audio/providers/ElevenLabsSFXProvider.js.map +1 -0
- package/dist/core/audio/providers/FalAudioProvider.d.ts +207 -0
- package/dist/core/audio/providers/FalAudioProvider.d.ts.map +1 -0
- package/dist/core/audio/providers/FalAudioProvider.js +315 -0
- package/dist/core/audio/providers/FalAudioProvider.js.map +1 -0
- package/dist/core/audio/providers/MusicGenLocalProvider.d.ts +136 -0
- package/dist/core/audio/providers/MusicGenLocalProvider.d.ts.map +1 -0
- package/dist/core/audio/providers/MusicGenLocalProvider.js +235 -0
- package/dist/core/audio/providers/MusicGenLocalProvider.js.map +1 -0
- package/dist/core/audio/providers/ReplicateAudioProvider.d.ts +200 -0
- package/dist/core/audio/providers/ReplicateAudioProvider.d.ts.map +1 -0
- package/dist/core/audio/providers/ReplicateAudioProvider.js +346 -0
- package/dist/core/audio/providers/ReplicateAudioProvider.js.map +1 -0
- package/dist/core/audio/providers/StableAudioProvider.d.ts +138 -0
- package/dist/core/audio/providers/StableAudioProvider.d.ts.map +1 -0
- package/dist/core/audio/providers/StableAudioProvider.js +192 -0
- package/dist/core/audio/providers/StableAudioProvider.js.map +1 -0
- package/dist/core/audio/providers/SunoProvider.d.ts +182 -0
- package/dist/core/audio/providers/SunoProvider.d.ts.map +1 -0
- package/dist/core/audio/providers/SunoProvider.js +312 -0
- package/dist/core/audio/providers/SunoProvider.js.map +1 -0
- package/dist/core/audio/providers/UdioProvider.d.ts +177 -0
- package/dist/core/audio/providers/UdioProvider.d.ts.map +1 -0
- package/dist/core/audio/providers/UdioProvider.js +305 -0
- package/dist/core/audio/providers/UdioProvider.js.map +1 -0
- package/dist/core/audio/types.d.ts +257 -0
- package/dist/core/audio/types.d.ts.map +1 -0
- package/dist/core/audio/types.js +21 -0
- package/dist/core/audio/types.js.map +1 -0
- package/dist/core/images/FallbackImageProxy.d.ts +183 -0
- package/dist/core/images/FallbackImageProxy.d.ts.map +1 -0
- package/dist/core/images/FallbackImageProxy.js +283 -0
- package/dist/core/images/FallbackImageProxy.js.map +1 -0
- package/dist/core/images/IImageProvider.d.ts +1 -1
- package/dist/core/images/IImageProvider.d.ts.map +1 -1
- package/dist/core/images/index.d.ts +1 -0
- package/dist/core/images/index.d.ts.map +1 -1
- package/dist/core/images/index.js +1 -0
- package/dist/core/images/index.js.map +1 -1
- package/dist/core/llm/providers/AIModelProviderManager.d.ts +3 -1
- package/dist/core/llm/providers/AIModelProviderManager.d.ts.map +1 -1
- package/dist/core/llm/providers/AIModelProviderManager.js +8 -0
- package/dist/core/llm/providers/AIModelProviderManager.js.map +1 -1
- package/dist/core/llm/providers/errors/ClaudeCodeProviderError.d.ts +52 -0
- package/dist/core/llm/providers/errors/ClaudeCodeProviderError.d.ts.map +1 -0
- package/dist/core/llm/providers/errors/ClaudeCodeProviderError.js +36 -0
- package/dist/core/llm/providers/errors/ClaudeCodeProviderError.js.map +1 -0
- package/dist/core/llm/providers/errors/GeminiCLIProviderError.d.ts +32 -0
- package/dist/core/llm/providers/errors/GeminiCLIProviderError.d.ts.map +1 -0
- package/dist/core/llm/providers/errors/GeminiCLIProviderError.js +27 -0
- package/dist/core/llm/providers/errors/GeminiCLIProviderError.js.map +1 -0
- package/dist/core/llm/providers/implementations/ClaudeCodeCLIBridge.d.ts +38 -0
- package/dist/core/llm/providers/implementations/ClaudeCodeCLIBridge.d.ts.map +1 -0
- package/dist/core/llm/providers/implementations/ClaudeCodeCLIBridge.js +128 -0
- package/dist/core/llm/providers/implementations/ClaudeCodeCLIBridge.js.map +1 -0
- package/dist/core/llm/providers/implementations/ClaudeCodeProvider.d.ts +107 -0
- package/dist/core/llm/providers/implementations/ClaudeCodeProvider.d.ts.map +1 -0
- package/dist/core/llm/providers/implementations/ClaudeCodeProvider.js +504 -0
- package/dist/core/llm/providers/implementations/ClaudeCodeProvider.js.map +1 -0
- package/dist/core/llm/providers/implementations/GeminiCLIBridge.d.ts +60 -0
- package/dist/core/llm/providers/implementations/GeminiCLIBridge.d.ts.map +1 -0
- package/dist/core/llm/providers/implementations/GeminiCLIBridge.js +177 -0
- package/dist/core/llm/providers/implementations/GeminiCLIBridge.js.map +1 -0
- package/dist/core/llm/providers/implementations/GeminiCLIProvider.d.ts +55 -0
- package/dist/core/llm/providers/implementations/GeminiCLIProvider.d.ts.map +1 -0
- package/dist/core/llm/providers/implementations/GeminiCLIProvider.js +447 -0
- package/dist/core/llm/providers/implementations/GeminiCLIProvider.js.map +1 -0
- package/dist/core/media/ProviderPreferences.d.ts +158 -0
- package/dist/core/media/ProviderPreferences.d.ts.map +1 -0
- package/dist/core/media/ProviderPreferences.js +183 -0
- package/dist/core/media/ProviderPreferences.js.map +1 -0
- package/dist/core/subprocess/CLIRegistry.d.ts +71 -0
- package/dist/core/subprocess/CLIRegistry.d.ts.map +1 -0
- package/dist/core/subprocess/CLIRegistry.js +210 -0
- package/dist/core/subprocess/CLIRegistry.js.map +1 -0
- package/dist/core/subprocess/CLISubprocessBridge.d.ts +117 -0
- package/dist/core/subprocess/CLISubprocessBridge.d.ts.map +1 -0
- package/dist/core/subprocess/CLISubprocessBridge.js +199 -0
- package/dist/core/subprocess/CLISubprocessBridge.js.map +1 -0
- package/dist/core/subprocess/errors.d.ts +76 -0
- package/dist/core/subprocess/errors.d.ts.map +1 -0
- package/dist/core/subprocess/errors.js +75 -0
- package/dist/core/subprocess/errors.js.map +1 -0
- package/dist/core/subprocess/index.d.ts +11 -0
- package/dist/core/subprocess/index.d.ts.map +1 -0
- package/dist/core/subprocess/index.js +10 -0
- package/dist/core/subprocess/index.js.map +1 -0
- package/dist/core/subprocess/types.d.ts +100 -0
- package/dist/core/subprocess/types.d.ts.map +1 -0
- package/dist/core/subprocess/types.js +9 -0
- package/dist/core/subprocess/types.js.map +1 -0
- package/dist/core/video/FallbackVideoProxy.d.ts +166 -0
- package/dist/core/video/FallbackVideoProxy.d.ts.map +1 -0
- package/dist/core/video/FallbackVideoProxy.js +228 -0
- package/dist/core/video/FallbackVideoProxy.js.map +1 -0
- package/dist/core/video/IVideoAnalyzer.d.ts +29 -0
- package/dist/core/video/IVideoAnalyzer.d.ts.map +1 -0
- package/dist/core/video/IVideoAnalyzer.js +12 -0
- package/dist/core/video/IVideoAnalyzer.js.map +1 -0
- package/dist/core/video/IVideoGenerator.d.ts +76 -0
- package/dist/core/video/IVideoGenerator.d.ts.map +1 -0
- package/dist/core/video/IVideoGenerator.js +13 -0
- package/dist/core/video/IVideoGenerator.js.map +1 -0
- package/dist/core/video/VideoAnalyzer.d.ts +278 -0
- package/dist/core/video/VideoAnalyzer.d.ts.map +1 -0
- package/dist/core/video/VideoAnalyzer.js +648 -0
- package/dist/core/video/VideoAnalyzer.js.map +1 -0
- package/dist/core/video/index.d.ts +55 -0
- package/dist/core/video/index.d.ts.map +1 -0
- package/dist/core/video/index.js +78 -0
- package/dist/core/video/index.js.map +1 -0
- package/dist/core/video/providers/FalVideoProvider.d.ts +195 -0
- package/dist/core/video/providers/FalVideoProvider.d.ts.map +1 -0
- package/dist/core/video/providers/FalVideoProvider.js +322 -0
- package/dist/core/video/providers/FalVideoProvider.js.map +1 -0
- package/dist/core/video/providers/ReplicateVideoProvider.d.ts +194 -0
- package/dist/core/video/providers/ReplicateVideoProvider.d.ts.map +1 -0
- package/dist/core/video/providers/ReplicateVideoProvider.js +356 -0
- package/dist/core/video/providers/ReplicateVideoProvider.js.map +1 -0
- package/dist/core/video/providers/RunwayVideoProvider.d.ts +175 -0
- package/dist/core/video/providers/RunwayVideoProvider.d.ts.map +1 -0
- package/dist/core/video/providers/RunwayVideoProvider.js +293 -0
- package/dist/core/video/providers/RunwayVideoProvider.js.map +1 -0
- package/dist/core/video/types.d.ts +441 -0
- package/dist/core/video/types.d.ts.map +1 -0
- package/dist/core/video/types.js +10 -0
- package/dist/core/video/types.js.map +1 -0
- package/dist/core/vision/SceneDetector.d.ts +180 -0
- package/dist/core/vision/SceneDetector.d.ts.map +1 -0
- package/dist/core/vision/SceneDetector.js +366 -0
- package/dist/core/vision/SceneDetector.js.map +1 -0
- package/dist/core/vision/index.d.ts +2 -1
- package/dist/core/vision/index.d.ts.map +1 -1
- package/dist/core/vision/index.js +1 -0
- package/dist/core/vision/index.js.map +1 -1
- package/dist/core/vision/types.d.ts +125 -0
- package/dist/core/vision/types.d.ts.map +1 -1
- package/dist/discovery/CapabilityDiscoveryEngine.d.ts +32 -0
- package/dist/discovery/CapabilityDiscoveryEngine.d.ts.map +1 -1
- package/dist/discovery/CapabilityDiscoveryEngine.js +46 -0
- package/dist/discovery/CapabilityDiscoveryEngine.js.map +1 -1
- package/dist/extensions/MultiRegistryLoader.js.map +1 -1
- package/dist/index.d.ts +17 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/memory/CognitiveMemoryManager.d.ts +40 -0
- package/dist/memory/CognitiveMemoryManager.d.ts.map +1 -1
- package/dist/memory/CognitiveMemoryManager.js +54 -1
- package/dist/memory/CognitiveMemoryManager.js.map +1 -1
- package/dist/memory/facade/Memory.d.ts +4 -0
- package/dist/memory/facade/Memory.d.ts.map +1 -1
- package/dist/memory/facade/Memory.js +140 -4
- package/dist/memory/facade/Memory.js.map +1 -1
- package/dist/memory/facade/types.d.ts +30 -2
- package/dist/memory/facade/types.d.ts.map +1 -1
- package/dist/memory/index.d.ts +1 -0
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +1 -0
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/store/HnswSidecar.d.ts +115 -0
- package/dist/memory/store/HnswSidecar.d.ts.map +1 -0
- package/dist/memory/store/HnswSidecar.js +256 -0
- package/dist/memory/store/HnswSidecar.js.map +1 -0
- package/dist/memory/types.d.ts +15 -0
- package/dist/memory/types.d.ts.map +1 -1
- package/dist/query-router/QueryClassifier.d.ts +192 -21
- package/dist/query-router/QueryClassifier.d.ts.map +1 -1
- package/dist/query-router/QueryClassifier.js +604 -23
- package/dist/query-router/QueryClassifier.js.map +1 -1
- package/dist/query-router/QueryDispatcher.d.ts +106 -8
- package/dist/query-router/QueryDispatcher.d.ts.map +1 -1
- package/dist/query-router/QueryDispatcher.js +387 -8
- package/dist/query-router/QueryDispatcher.js.map +1 -1
- package/dist/query-router/QueryRouter.d.ts +198 -14
- package/dist/query-router/QueryRouter.d.ts.map +1 -1
- package/dist/query-router/QueryRouter.js +738 -50
- package/dist/query-router/QueryRouter.js.map +1 -1
- package/dist/query-router/index.d.ts +1 -1
- package/dist/query-router/index.d.ts.map +1 -1
- package/dist/query-router/index.js +1 -1
- package/dist/query-router/index.js.map +1 -1
- package/dist/query-router/types.d.ts +396 -3
- package/dist/query-router/types.d.ts.map +1 -1
- package/dist/query-router/types.js +35 -0
- package/dist/query-router/types.js.map +1 -1
- package/dist/rag/HydeRetriever.d.ts +108 -0
- package/dist/rag/HydeRetriever.d.ts.map +1 -1
- package/dist/rag/HydeRetriever.js +184 -0
- package/dist/rag/HydeRetriever.js.map +1 -1
- package/dist/rag/IRetrievalAugmentor.d.ts +15 -0
- package/dist/rag/IRetrievalAugmentor.d.ts.map +1 -1
- package/dist/rag/RetrievalAugmentor.d.ts +58 -0
- package/dist/rag/RetrievalAugmentor.d.ts.map +1 -1
- package/dist/rag/RetrievalAugmentor.js +200 -32
- package/dist/rag/RetrievalAugmentor.js.map +1 -1
- package/dist/rag/VectorStoreManager.js +1 -1
- package/dist/rag/audit/RAGAuditCollector.d.ts +7 -0
- package/dist/rag/audit/RAGAuditCollector.d.ts.map +1 -1
- package/dist/rag/audit/RAGAuditCollector.js +10 -0
- package/dist/rag/audit/RAGAuditCollector.js.map +1 -1
- package/dist/rag/audit/RAGAuditTypes.d.ts +10 -1
- package/dist/rag/audit/RAGAuditTypes.d.ts.map +1 -1
- package/dist/rag/chunking/SemanticChunker.d.ts +210 -0
- package/dist/rag/chunking/SemanticChunker.d.ts.map +1 -0
- package/dist/rag/chunking/SemanticChunker.js +460 -0
- package/dist/rag/chunking/SemanticChunker.js.map +1 -0
- package/dist/rag/chunking/index.d.ts +10 -0
- package/dist/rag/chunking/index.d.ts.map +1 -0
- package/dist/rag/chunking/index.js +10 -0
- package/dist/rag/chunking/index.js.map +1 -0
- package/dist/rag/implementations/vector_stores/PineconeVectorStore.d.ts +103 -0
- package/dist/rag/implementations/vector_stores/PineconeVectorStore.d.ts.map +1 -0
- package/dist/rag/implementations/vector_stores/PineconeVectorStore.js +315 -0
- package/dist/rag/implementations/vector_stores/PineconeVectorStore.js.map +1 -0
- package/dist/rag/implementations/vector_stores/PostgresVectorStore.d.ts +107 -0
- package/dist/rag/implementations/vector_stores/PostgresVectorStore.d.ts.map +1 -0
- package/dist/rag/implementations/vector_stores/PostgresVectorStore.js +438 -0
- package/dist/rag/implementations/vector_stores/PostgresVectorStore.js.map +1 -0
- package/dist/rag/index.d.ts +15 -1
- package/dist/rag/index.d.ts.map +1 -1
- package/dist/rag/index.js +32 -0
- package/dist/rag/index.js.map +1 -1
- package/dist/rag/migration/MigrationEngine.d.ts +47 -0
- package/dist/rag/migration/MigrationEngine.d.ts.map +1 -0
- package/dist/rag/migration/MigrationEngine.js +168 -0
- package/dist/rag/migration/MigrationEngine.js.map +1 -0
- package/dist/rag/migration/adapters/PineconeSourceAdapter.d.ts +23 -0
- package/dist/rag/migration/adapters/PineconeSourceAdapter.d.ts.map +1 -0
- package/dist/rag/migration/adapters/PineconeSourceAdapter.js +63 -0
- package/dist/rag/migration/adapters/PineconeSourceAdapter.js.map +1 -0
- package/dist/rag/migration/adapters/PostgresSourceAdapter.d.ts +30 -0
- package/dist/rag/migration/adapters/PostgresSourceAdapter.d.ts.map +1 -0
- package/dist/rag/migration/adapters/PostgresSourceAdapter.js +71 -0
- package/dist/rag/migration/adapters/PostgresSourceAdapter.js.map +1 -0
- package/dist/rag/migration/adapters/PostgresTargetAdapter.d.ts +38 -0
- package/dist/rag/migration/adapters/PostgresTargetAdapter.d.ts.map +1 -0
- package/dist/rag/migration/adapters/PostgresTargetAdapter.js +114 -0
- package/dist/rag/migration/adapters/PostgresTargetAdapter.js.map +1 -0
- package/dist/rag/migration/adapters/QdrantSourceAdapter.d.ts +36 -0
- package/dist/rag/migration/adapters/QdrantSourceAdapter.d.ts.map +1 -0
- package/dist/rag/migration/adapters/QdrantSourceAdapter.js +109 -0
- package/dist/rag/migration/adapters/QdrantSourceAdapter.js.map +1 -0
- package/dist/rag/migration/adapters/QdrantTargetAdapter.d.ts +35 -0
- package/dist/rag/migration/adapters/QdrantTargetAdapter.d.ts.map +1 -0
- package/dist/rag/migration/adapters/QdrantTargetAdapter.js +110 -0
- package/dist/rag/migration/adapters/QdrantTargetAdapter.js.map +1 -0
- package/dist/rag/migration/adapters/SqliteSourceAdapter.d.ts +37 -0
- package/dist/rag/migration/adapters/SqliteSourceAdapter.d.ts.map +1 -0
- package/dist/rag/migration/adapters/SqliteSourceAdapter.js +72 -0
- package/dist/rag/migration/adapters/SqliteSourceAdapter.js.map +1 -0
- package/dist/rag/migration/adapters/SqliteTargetAdapter.d.ts +47 -0
- package/dist/rag/migration/adapters/SqliteTargetAdapter.d.ts.map +1 -0
- package/dist/rag/migration/adapters/SqliteTargetAdapter.js +93 -0
- package/dist/rag/migration/adapters/SqliteTargetAdapter.js.map +1 -0
- package/dist/rag/migration/types.d.ts +108 -0
- package/dist/rag/migration/types.d.ts.map +1 -0
- package/dist/rag/migration/types.js +11 -0
- package/dist/rag/migration/types.js.map +1 -0
- package/dist/rag/multimodal/MultimodalIndexer.d.ts +35 -0
- package/dist/rag/multimodal/MultimodalIndexer.d.ts.map +1 -1
- package/dist/rag/multimodal/MultimodalIndexer.js +66 -1
- package/dist/rag/multimodal/MultimodalIndexer.js.map +1 -1
- package/dist/rag/multimodal/types.d.ts +24 -0
- package/dist/rag/multimodal/types.d.ts.map +1 -1
- package/dist/rag/raptor/RaptorTree.d.ts +268 -0
- package/dist/rag/raptor/RaptorTree.d.ts.map +1 -0
- package/dist/rag/raptor/RaptorTree.js +443 -0
- package/dist/rag/raptor/RaptorTree.js.map +1 -0
- package/dist/rag/raptor/index.d.ts +11 -0
- package/dist/rag/raptor/index.d.ts.map +1 -0
- package/dist/rag/raptor/index.js +11 -0
- package/dist/rag/raptor/index.js.map +1 -0
- package/dist/rag/reranking/providers/CohereReranker.js.map +1 -1
- package/dist/rag/search/BM25Index.d.ts +282 -0
- package/dist/rag/search/BM25Index.d.ts.map +1 -0
- package/dist/rag/search/BM25Index.js +344 -0
- package/dist/rag/search/BM25Index.js.map +1 -0
- package/dist/rag/search/HybridSearcher.d.ts +198 -0
- package/dist/rag/search/HybridSearcher.d.ts.map +1 -0
- package/dist/rag/search/HybridSearcher.js +316 -0
- package/dist/rag/search/HybridSearcher.js.map +1 -0
- package/dist/rag/search/index.d.ts +12 -0
- package/dist/rag/search/index.d.ts.map +1 -0
- package/dist/rag/search/index.js +12 -0
- package/dist/rag/search/index.js.map +1 -0
- package/dist/rag/setup/DockerDetector.d.ts +67 -0
- package/dist/rag/setup/DockerDetector.d.ts.map +1 -0
- package/dist/rag/setup/DockerDetector.js +125 -0
- package/dist/rag/setup/DockerDetector.js.map +1 -0
- package/dist/rag/setup/PostgresSetup.d.ts +20 -0
- package/dist/rag/setup/PostgresSetup.d.ts.map +1 -0
- package/dist/rag/setup/PostgresSetup.js +133 -0
- package/dist/rag/setup/PostgresSetup.js.map +1 -0
- package/dist/rag/setup/QdrantSetup.d.ts +26 -0
- package/dist/rag/setup/QdrantSetup.d.ts.map +1 -0
- package/dist/rag/setup/QdrantSetup.js +96 -0
- package/dist/rag/setup/QdrantSetup.js.map +1 -0
- package/dist/rag/setup/types.d.ts +55 -0
- package/dist/rag/setup/types.d.ts.map +1 -0
- package/dist/rag/setup/types.js +6 -0
- package/dist/rag/setup/types.js.map +1 -0
- package/dist/rag/unified/UnifiedRetriever.d.ts +472 -0
- package/dist/rag/unified/UnifiedRetriever.d.ts.map +1 -0
- package/dist/rag/unified/UnifiedRetriever.js +887 -0
- package/dist/rag/unified/UnifiedRetriever.js.map +1 -0
- package/dist/rag/unified/index.d.ts +24 -0
- package/dist/rag/unified/index.d.ts.map +1 -0
- package/dist/rag/unified/index.js +23 -0
- package/dist/rag/unified/index.js.map +1 -0
- package/dist/rag/unified/types.d.ts +546 -0
- package/dist/rag/unified/types.d.ts.map +1 -0
- package/dist/rag/unified/types.js +177 -0
- package/dist/rag/unified/types.js.map +1 -0
- package/dist/speech/providers/AssemblyAISTTProvider.js.map +1 -1
- package/dist/speech/providers/AzureSpeechSTTProvider.js.map +1 -1
- package/dist/speech/providers/BuiltInAdaptiveVadProvider.d.ts +1 -1
- package/dist/speech/providers/DeepgramBatchSTTProvider.js.map +1 -1
- package/package.json +5 -1
|
@@ -29,13 +29,14 @@
|
|
|
29
29
|
* router falls back gracefully to {@link KeywordFallback} for all retrieval.
|
|
30
30
|
*/
|
|
31
31
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
32
|
-
import {
|
|
32
|
+
import { dirname, extname, join, resolve } from 'node:path';
|
|
33
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
33
34
|
import { QueryClassifier } from './QueryClassifier.js';
|
|
34
35
|
import { QueryDispatcher } from './QueryDispatcher.js';
|
|
35
36
|
import { QueryGenerator } from './QueryGenerator.js';
|
|
36
37
|
import { TopicExtractor } from './TopicExtractor.js';
|
|
37
38
|
import { KeywordFallback } from './KeywordFallback.js';
|
|
38
|
-
import { DEFAULT_QUERY_ROUTER_CONFIG } from './types.js';
|
|
39
|
+
import { DEFAULT_QUERY_ROUTER_CONFIG, DEFAULT_STRATEGY_CONFIG, } from './types.js';
|
|
39
40
|
/** Regex for splitting markdown by h1-h3 headings. */
|
|
40
41
|
const HEADING_REGEX = /^#{1,3}\s+(.+)/;
|
|
41
42
|
/** Maximum character length for a single corpus chunk. */
|
|
@@ -44,6 +45,11 @@ const MAX_CHUNK_CHARS = 6000;
|
|
|
44
45
|
const MIN_CHUNK_CHARS = 20;
|
|
45
46
|
/** Supported markdown file extensions for corpus loading. */
|
|
46
47
|
const MARKDOWN_EXTENSIONS = new Set(['.md', '.mdx']);
|
|
48
|
+
const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
49
|
+
const GITHUB_EXTENSION_LOCAL_ENTRY_CANDIDATES = [
|
|
50
|
+
resolve(MODULE_DIR, '../../../../packages/agentos-extensions/registry/curated/integrations/github/dist/index.js'),
|
|
51
|
+
resolve(MODULE_DIR, '../../../../apps/wunderland-sol/packages/agentos-extensions/registry/curated/integrations/github/dist/index.js'),
|
|
52
|
+
];
|
|
47
53
|
// ============================================================================
|
|
48
54
|
// QueryRouter
|
|
49
55
|
// ============================================================================
|
|
@@ -102,6 +108,19 @@ export class QueryRouter {
|
|
|
102
108
|
this.providerManager = null;
|
|
103
109
|
/** Embedding dimension for the configured model. Zero if embeddings unavailable. */
|
|
104
110
|
this.embeddingDimension = 0;
|
|
111
|
+
/** Current embedding availability state for corpus retrieval. */
|
|
112
|
+
this.embeddingStatus = 'disabled-no-key';
|
|
113
|
+
/**
|
|
114
|
+
* Optional UnifiedRetriever for plan-based retrieval.
|
|
115
|
+
*
|
|
116
|
+
* When set via {@link setUnifiedRetriever}, the `route()` method uses
|
|
117
|
+
* the UnifiedRetriever instead of the legacy QueryDispatcher for the
|
|
118
|
+
* retrieval phase. The UnifiedRetriever executes a structured
|
|
119
|
+
* {@link RetrievalPlan} across all available sources in parallel.
|
|
120
|
+
*
|
|
121
|
+
* @see setUnifiedRetriever
|
|
122
|
+
*/
|
|
123
|
+
this.unifiedRetriever = null;
|
|
105
124
|
/**
|
|
106
125
|
* The data source ID used for corpus embeddings in the vector store.
|
|
107
126
|
* Matches the collection name configured during init().
|
|
@@ -112,9 +131,77 @@ export class QueryRouter {
|
|
|
112
131
|
...config,
|
|
113
132
|
deepResearchEnabled: config.deepResearchEnabled ?? Boolean(process.env.SERPER_API_KEY),
|
|
114
133
|
availableTools: config.availableTools ?? [...DEFAULT_QUERY_ROUTER_CONFIG.availableTools],
|
|
134
|
+
strategyConfig: {
|
|
135
|
+
...DEFAULT_STRATEGY_CONFIG,
|
|
136
|
+
...config.strategyConfig,
|
|
137
|
+
},
|
|
115
138
|
};
|
|
116
139
|
}
|
|
117
140
|
// ==========================================================================
|
|
141
|
+
// UNIFIED RETRIEVER INTEGRATION
|
|
142
|
+
// ==========================================================================
|
|
143
|
+
/**
|
|
144
|
+
* Attach a {@link UnifiedRetriever} for plan-based retrieval.
|
|
145
|
+
*
|
|
146
|
+
* When set, the `route()` method uses the UnifiedRetriever instead of
|
|
147
|
+
* the legacy QueryDispatcher for the retrieval phase. The classifier
|
|
148
|
+
* automatically produces a {@link RetrievalPlan} via `classifyWithPlan()`
|
|
149
|
+
* and the retriever executes it across all available sources in parallel.
|
|
150
|
+
*
|
|
151
|
+
* Pass `null` to revert to the legacy QueryDispatcher pipeline.
|
|
152
|
+
*
|
|
153
|
+
* @param retriever - A configured UnifiedRetriever instance, or `null` to disable.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```typescript
|
|
157
|
+
* const retriever = new UnifiedRetriever({
|
|
158
|
+
* hybridSearcher, raptorTree, graphEngine, memoryManager,
|
|
159
|
+
* });
|
|
160
|
+
* router.setUnifiedRetriever(retriever);
|
|
161
|
+
* // Now route() uses plan-based retrieval automatically
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
setUnifiedRetriever(retriever) {
|
|
165
|
+
this.unifiedRetriever = retriever;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Get the attached UnifiedRetriever, or `null` if not configured.
|
|
169
|
+
*
|
|
170
|
+
* @returns The UnifiedRetriever instance, or `null`.
|
|
171
|
+
*/
|
|
172
|
+
getUnifiedRetriever() {
|
|
173
|
+
return this.unifiedRetriever;
|
|
174
|
+
}
|
|
175
|
+
// ==========================================================================
|
|
176
|
+
// CAPABILITY DISCOVERY INTEGRATION
|
|
177
|
+
// ==========================================================================
|
|
178
|
+
/**
|
|
179
|
+
* Attach a {@link CapabilityDiscoveryEngine} for capability-aware classification.
|
|
180
|
+
*
|
|
181
|
+
* When set, the classifier injects Tier 0 capability summaries (~150 tokens)
|
|
182
|
+
* into its LLM prompt, enabling it to recommend which skills, tools, and
|
|
183
|
+
* extensions should be activated for each query. The recommendations are
|
|
184
|
+
* included in the {@link ExecutionPlan} returned by `classifyWithPlan()`.
|
|
185
|
+
*
|
|
186
|
+
* Pass `null` to detach and revert to keyword-based heuristic capability
|
|
187
|
+
* selection.
|
|
188
|
+
*
|
|
189
|
+
* @param engine - A configured and initialized CapabilityDiscoveryEngine, or `null` to detach.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```typescript
|
|
193
|
+
* const engine = new CapabilityDiscoveryEngine(embeddingManager, vectorStore);
|
|
194
|
+
* await engine.initialize({ tools, skills, extensions, channels });
|
|
195
|
+
* router.setCapabilityDiscoveryEngine(engine);
|
|
196
|
+
* // Now route() includes skill/tool/extension recommendations in the execution plan
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
setCapabilityDiscoveryEngine(engine) {
|
|
200
|
+
if (this.classifier) {
|
|
201
|
+
this.classifier.setCapabilityDiscoveryEngine(engine);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// ==========================================================================
|
|
118
205
|
// PUBLIC API
|
|
119
206
|
// ==========================================================================
|
|
120
207
|
/**
|
|
@@ -132,6 +219,9 @@ export class QueryRouter {
|
|
|
132
219
|
async init() {
|
|
133
220
|
// 1. Load corpus chunks from the configured knowledge directories
|
|
134
221
|
this.corpus = this.loadCorpus(this.config.knowledgeCorpus);
|
|
222
|
+
if (this.corpus.length === 0) {
|
|
223
|
+
throw new Error(this.buildEmptyCorpusError(this.config.knowledgeCorpus));
|
|
224
|
+
}
|
|
135
225
|
// 2. Extract topics for the classifier's system prompt
|
|
136
226
|
const topicExtractor = new TopicExtractor();
|
|
137
227
|
this.topics = topicExtractor.extract(this.corpus);
|
|
@@ -143,36 +233,125 @@ export class QueryRouter {
|
|
|
143
233
|
// fallback will still work for all retrieval operations.
|
|
144
234
|
await this.embedCorpus();
|
|
145
235
|
// 5. Instantiate the classifier
|
|
146
|
-
this.classifier =
|
|
147
|
-
model: this.config.classifierModel,
|
|
148
|
-
provider: this.config.classifierProvider,
|
|
149
|
-
confidenceThreshold: this.config.confidenceThreshold,
|
|
150
|
-
maxTier: this.config.maxTier,
|
|
151
|
-
topicList,
|
|
152
|
-
toolList: this.formatToolList(this.config.availableTools),
|
|
153
|
-
apiKey: this.config.apiKey,
|
|
154
|
-
baseUrl: this.config.baseUrl,
|
|
155
|
-
});
|
|
236
|
+
this.classifier = this.createClassifier(topicList);
|
|
156
237
|
// 6. Instantiate the generator
|
|
157
238
|
this.generator = new QueryGenerator({
|
|
158
239
|
model: this.config.generationModel,
|
|
159
240
|
modelDeep: this.config.generationModelDeep,
|
|
160
241
|
provider: this.config.generationProvider,
|
|
161
|
-
apiKey: this.
|
|
162
|
-
baseUrl: this.
|
|
242
|
+
apiKey: this.getLlmApiKey(),
|
|
243
|
+
baseUrl: this.getLlmBaseUrl(),
|
|
163
244
|
maxContextTokens: this.config.maxContextTokens,
|
|
164
245
|
});
|
|
165
246
|
// 7. Instantiate the dispatcher with callback dependencies
|
|
166
247
|
this.dispatcher = new QueryDispatcher({
|
|
167
248
|
vectorSearch: (query, topK) => this.vectorSearch(query, topK),
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
249
|
+
hydeSearch: (query, topK) => this.hydeSearch(query, topK),
|
|
250
|
+
decompose: (query, maxSubQueries) => this.decomposeQuery(query, maxSubQueries),
|
|
251
|
+
graphExpand: (seeds) => this.config.graphExpand ? this.config.graphExpand(seeds) : this.graphExpand(seeds),
|
|
252
|
+
rerank: (query, chunks, topN) => this.config.rerank
|
|
253
|
+
? this.config.rerank(query, chunks, topN)
|
|
254
|
+
: this.rerank(query, chunks, topN),
|
|
255
|
+
deepResearch: (query, sources) => this.config.deepResearch
|
|
256
|
+
? this.config.deepResearch(query, sources)
|
|
257
|
+
: this.deepResearch(query, sources),
|
|
171
258
|
emit: (event) => this.emit(event),
|
|
172
259
|
graphEnabled: this.config.graphEnabled,
|
|
173
260
|
deepResearchEnabled: this.config.deepResearchEnabled,
|
|
261
|
+
maxSubQueries: this.config.strategyConfig.maxSubQueries,
|
|
174
262
|
});
|
|
175
263
|
this.initialized = true;
|
|
264
|
+
// ----------------------------------------------------------------
|
|
265
|
+
// Background GitHub repo indexing (non-blocking)
|
|
266
|
+
// ----------------------------------------------------------------
|
|
267
|
+
// Runs after the router is fully initialized so that query routing
|
|
268
|
+
// is available immediately. Indexed chunks are merged into the
|
|
269
|
+
// corpus, then the live retrieval/classification state is refreshed
|
|
270
|
+
// once indexing completes.
|
|
271
|
+
const repoConfig = this.config.githubRepos ?? {};
|
|
272
|
+
const includeEcosystem = repoConfig.includeEcosystem ?? true;
|
|
273
|
+
if (includeEcosystem || (repoConfig.repos && repoConfig.repos.length > 0)) {
|
|
274
|
+
Promise.resolve().then(async () => {
|
|
275
|
+
try {
|
|
276
|
+
const { GitHubRepoIndexer, GitHubService } = await this.loadGitHubExtensionModule();
|
|
277
|
+
const indexedChunks = [];
|
|
278
|
+
const token = repoConfig.token || process.env.GITHUB_TOKEN || process.env.GH_TOKEN || '';
|
|
279
|
+
const service = new GitHubService(token);
|
|
280
|
+
// initialize() creates the Octokit and validates auth.
|
|
281
|
+
// If the token is empty/invalid, Octokit is still created before
|
|
282
|
+
// the auth check throws, so public-only API access still works.
|
|
283
|
+
try {
|
|
284
|
+
await service.initialize();
|
|
285
|
+
}
|
|
286
|
+
catch { /* public-only mode */ }
|
|
287
|
+
const indexer = new GitHubRepoIndexer(service);
|
|
288
|
+
if (includeEcosystem) {
|
|
289
|
+
const results = await indexer.indexEcosystem();
|
|
290
|
+
const totalChunks = results.reduce((s, r) => s + r.chunks.length, 0);
|
|
291
|
+
for (const result of results) {
|
|
292
|
+
indexedChunks.push(...result.chunks);
|
|
293
|
+
this.emit({
|
|
294
|
+
type: 'github:index:complete',
|
|
295
|
+
repo: result.repo,
|
|
296
|
+
chunksTotal: result.chunks.length,
|
|
297
|
+
durationMs: result.durationMs,
|
|
298
|
+
timestamp: Date.now(),
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
console.log(`[QueryRouter] GitHub ecosystem indexed: ${results.length} repos, ${totalChunks} chunks`);
|
|
302
|
+
}
|
|
303
|
+
if (repoConfig.repos?.length) {
|
|
304
|
+
for (const { owner, repo } of repoConfig.repos) {
|
|
305
|
+
try {
|
|
306
|
+
const result = await indexer.indexRepo(owner, repo);
|
|
307
|
+
indexedChunks.push(...result.chunks);
|
|
308
|
+
this.emit({
|
|
309
|
+
type: 'github:index:complete',
|
|
310
|
+
repo: result.repo,
|
|
311
|
+
chunksTotal: result.chunks.length,
|
|
312
|
+
durationMs: result.durationMs,
|
|
313
|
+
timestamp: Date.now(),
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
this.emit({
|
|
318
|
+
type: 'github:index:error',
|
|
319
|
+
repo: `${owner}/${repo}`,
|
|
320
|
+
error: err instanceof Error ? err.message : String(err),
|
|
321
|
+
timestamp: Date.now(),
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
await this.syncIndexedCorpusChunks(indexedChunks);
|
|
327
|
+
}
|
|
328
|
+
catch (err) {
|
|
329
|
+
console.warn(`[QueryRouter] GitHub indexing failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async loadGitHubExtensionModule() {
|
|
335
|
+
const specifiers = [
|
|
336
|
+
'@framers/agentos-ext-github',
|
|
337
|
+
...GITHUB_EXTENSION_LOCAL_ENTRY_CANDIDATES
|
|
338
|
+
.filter((candidate) => existsSync(candidate))
|
|
339
|
+
.map((candidate) => pathToFileURL(candidate).href),
|
|
340
|
+
];
|
|
341
|
+
const failures = [];
|
|
342
|
+
for (const specifier of specifiers) {
|
|
343
|
+
try {
|
|
344
|
+
const module = (await import(/* @vite-ignore */ specifier));
|
|
345
|
+
if (module.GitHubRepoIndexer && module.GitHubService) {
|
|
346
|
+
return module;
|
|
347
|
+
}
|
|
348
|
+
failures.push(`${specifier} loaded without GitHubRepoIndexer/GitHubService exports`);
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
failures.push(`${specifier}: ${error instanceof Error ? error.message : String(error)}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
throw new Error(`Unable to load GitHub extension runtime for QueryRouter indexing. ${failures.join(' | ')}`);
|
|
176
355
|
}
|
|
177
356
|
/**
|
|
178
357
|
* Classify a query into a complexity tier without dispatching or generating.
|
|
@@ -248,9 +427,48 @@ export class QueryRouter {
|
|
|
248
427
|
if (this.config.onClassification) {
|
|
249
428
|
this.config.onClassification(classification);
|
|
250
429
|
}
|
|
251
|
-
// --- Phase 2: Retrieval ---
|
|
430
|
+
// --- Phase 2: Retrieval (with capability recommendations) ---
|
|
431
|
+
// When a UnifiedRetriever is attached, use plan-based retrieval with
|
|
432
|
+
// full ExecutionPlan (including skill/tool/extension recommendations).
|
|
433
|
+
// Otherwise fall back to the legacy QueryDispatcher pipeline.
|
|
252
434
|
const retrievalEventStart = this.events.length;
|
|
253
|
-
|
|
435
|
+
let retrieval;
|
|
436
|
+
if (this.unifiedRetriever && this.classifier) {
|
|
437
|
+
// Plan-based retrieval via UnifiedRetriever
|
|
438
|
+
const { buildDefaultExecutionPlan } = await import('../rag/unified/types.js');
|
|
439
|
+
let plan;
|
|
440
|
+
try {
|
|
441
|
+
const [, classifiedPlan] = await this.classifier.classifyWithPlan(query, conversationHistory);
|
|
442
|
+
plan = classifiedPlan;
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
plan = buildDefaultExecutionPlan(classification.strategy);
|
|
446
|
+
}
|
|
447
|
+
// Emit capabilities:activate event when the plan recommends capabilities.
|
|
448
|
+
// The agent runtime is responsible for deciding which to honor — the
|
|
449
|
+
// router only recommends, it does not activate.
|
|
450
|
+
if (plan.skills.length > 0 || plan.tools.length > 0 || plan.extensions.length > 0) {
|
|
451
|
+
this.emit({
|
|
452
|
+
type: 'capabilities:activate',
|
|
453
|
+
skills: plan.skills,
|
|
454
|
+
tools: plan.tools,
|
|
455
|
+
extensions: plan.extensions,
|
|
456
|
+
timestamp: Date.now(),
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
const unifiedResult = await this.unifiedRetriever.retrieve(query, plan);
|
|
460
|
+
retrieval = {
|
|
461
|
+
chunks: unifiedResult.chunks,
|
|
462
|
+
researchSynthesis: unifiedResult.researchSynthesis,
|
|
463
|
+
durationMs: unifiedResult.durationMs,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
// Legacy dispatcher pipeline (HyDE-aware strategy or tier-based)
|
|
468
|
+
retrieval = classification.strategy
|
|
469
|
+
? await this.dispatcher.dispatchByStrategy(query, classification.strategy, classification.suggestedSources)
|
|
470
|
+
: await this.dispatcher.dispatch(query, classification.tier, classification.suggestedSources);
|
|
471
|
+
}
|
|
254
472
|
const retrievalEvents = this.events.slice(retrievalEventStart);
|
|
255
473
|
const fallbacksUsed = this.collectFallbacks(classification, retrievalEvents);
|
|
256
474
|
const tiersUsed = this.collectTiersUsed(classification, fallbacksUsed);
|
|
@@ -285,6 +503,7 @@ export class QueryRouter {
|
|
|
285
503
|
answer: generateResult.answer,
|
|
286
504
|
classification,
|
|
287
505
|
sources,
|
|
506
|
+
researchSynthesis: retrieval.researchSynthesis,
|
|
288
507
|
durationMs: totalDuration,
|
|
289
508
|
tiersUsed,
|
|
290
509
|
fallbacksUsed,
|
|
@@ -329,6 +548,7 @@ export class QueryRouter {
|
|
|
329
548
|
this.vectorStoreManager = null;
|
|
330
549
|
this.providerManager = null;
|
|
331
550
|
this.embeddingDimension = 0;
|
|
551
|
+
this.embeddingStatus = 'disabled-no-key';
|
|
332
552
|
this.classifier = null;
|
|
333
553
|
this.dispatcher = null;
|
|
334
554
|
this.generator = null;
|
|
@@ -338,6 +558,45 @@ export class QueryRouter {
|
|
|
338
558
|
this.events = [];
|
|
339
559
|
this.initialized = false;
|
|
340
560
|
}
|
|
561
|
+
/**
|
|
562
|
+
* Return lightweight corpus/index stats for observability and host startup
|
|
563
|
+
* logs.
|
|
564
|
+
*
|
|
565
|
+
* Useful after {@link init} so callers can confirm the router loaded a real
|
|
566
|
+
* corpus instead of only knowing that initialisation completed.
|
|
567
|
+
*/
|
|
568
|
+
getCorpusStats() {
|
|
569
|
+
const vectorActive = Boolean(this.embeddingManager && this.vectorStoreManager);
|
|
570
|
+
const graphRuntimeMode = this.config.graphEnabled
|
|
571
|
+
? (this.hasLiveGraphRuntime() ? 'active' : this.hasHeuristicGraphRuntime() ? 'heuristic' : 'placeholder')
|
|
572
|
+
: 'disabled';
|
|
573
|
+
const deepResearchRuntimeMode = this.config.deepResearchEnabled
|
|
574
|
+
? (this.hasLiveDeepResearchRuntime()
|
|
575
|
+
? 'active'
|
|
576
|
+
: this.hasHeuristicDeepResearchRuntime()
|
|
577
|
+
? 'heuristic'
|
|
578
|
+
: 'placeholder')
|
|
579
|
+
: 'disabled';
|
|
580
|
+
return {
|
|
581
|
+
initialized: this.initialized,
|
|
582
|
+
configuredPathCount: this.config.knowledgeCorpus.length,
|
|
583
|
+
chunkCount: this.corpus.length,
|
|
584
|
+
topicCount: this.topics.length,
|
|
585
|
+
sourceCount: new Set(this.corpus.map((chunk) => chunk.sourcePath)).size,
|
|
586
|
+
retrievalMode: vectorActive ? 'vector+keyword-fallback' : 'keyword-only',
|
|
587
|
+
embeddingStatus: vectorActive ? 'active' : this.embeddingStatus,
|
|
588
|
+
embeddingDimension: vectorActive ? this.embeddingDimension : 0,
|
|
589
|
+
graphEnabled: this.config.graphEnabled,
|
|
590
|
+
deepResearchEnabled: this.config.deepResearchEnabled,
|
|
591
|
+
graphRuntimeMode,
|
|
592
|
+
rerankRuntimeMode: this.hasLiveRerankerRuntime()
|
|
593
|
+
? 'active'
|
|
594
|
+
: this.hasHeuristicRerankerRuntime()
|
|
595
|
+
? 'heuristic'
|
|
596
|
+
: 'placeholder',
|
|
597
|
+
deepResearchRuntimeMode,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
341
600
|
// ==========================================================================
|
|
342
601
|
// PRIVATE — Corpus loading
|
|
343
602
|
// ==========================================================================
|
|
@@ -386,6 +645,22 @@ export class QueryRouter {
|
|
|
386
645
|
}
|
|
387
646
|
return chunks;
|
|
388
647
|
}
|
|
648
|
+
/**
|
|
649
|
+
* Build a clear init-time error for empty or unreadable corpora.
|
|
650
|
+
*
|
|
651
|
+
* The router can technically operate with keyword fallback only, but it
|
|
652
|
+
* should not silently mark itself ready when no corpus content was loaded
|
|
653
|
+
* at all. Callers usually interpret a successful `init()` as "docs loaded".
|
|
654
|
+
*
|
|
655
|
+
* @param paths - Configured knowledge corpus directory paths.
|
|
656
|
+
* @returns Human-readable error message for throwing from {@link init}.
|
|
657
|
+
*/
|
|
658
|
+
buildEmptyCorpusError(paths) {
|
|
659
|
+
return ('QueryRouter init failed: no readable markdown corpus chunks were loaded. ' +
|
|
660
|
+
`Checked paths: ${paths.join(', ')}. ` +
|
|
661
|
+
'Make sure at least one configured directory exists and contains readable ' +
|
|
662
|
+
'.md or .mdx files with non-trivial section content.');
|
|
663
|
+
}
|
|
389
664
|
/**
|
|
390
665
|
* Recursively walk a directory tree, invoking a callback for each file.
|
|
391
666
|
*
|
|
@@ -480,8 +755,9 @@ export class QueryRouter {
|
|
|
480
755
|
// Quick check: bail out early if there's obviously no API key configured.
|
|
481
756
|
// This avoids the overhead of dynamic imports and provider initialization
|
|
482
757
|
// in test environments and when no embedding provider is available.
|
|
483
|
-
const
|
|
484
|
-
if (!
|
|
758
|
+
const embeddingApiKey = this.getEmbeddingApiKey();
|
|
759
|
+
if (!embeddingApiKey) {
|
|
760
|
+
this.disableVectorRetrieval('disabled-no-key');
|
|
485
761
|
console.debug('[QueryRouter] No embedding API key configured; skipping vector store embedding (keyword fallback active).');
|
|
486
762
|
return;
|
|
487
763
|
}
|
|
@@ -500,8 +776,8 @@ export class QueryRouter {
|
|
|
500
776
|
providerId: this.config.embeddingProvider,
|
|
501
777
|
enabled: true,
|
|
502
778
|
config: {
|
|
503
|
-
apiKey:
|
|
504
|
-
...(this.
|
|
779
|
+
apiKey: embeddingApiKey,
|
|
780
|
+
...(this.getEmbeddingBaseUrl() ? { baseUrl: this.getEmbeddingBaseUrl() } : {}),
|
|
505
781
|
},
|
|
506
782
|
isDefault: true,
|
|
507
783
|
},
|
|
@@ -539,6 +815,7 @@ export class QueryRouter {
|
|
|
539
815
|
}
|
|
540
816
|
}
|
|
541
817
|
this.embeddingDimension = dimension;
|
|
818
|
+
this.embeddingStatus = 'active';
|
|
542
819
|
// --- 3. Initialise the vector store manager (in-memory) ---
|
|
543
820
|
const vsm = new VectorStoreManagerClass();
|
|
544
821
|
const collectionName = this.corpusDataSourceId;
|
|
@@ -596,12 +873,161 @@ export class QueryRouter {
|
|
|
596
873
|
// Non-fatal: warn and continue — keyword fallback still works
|
|
597
874
|
const message = error instanceof Error ? error.message : String(error);
|
|
598
875
|
console.warn(`[QueryRouter] Embedding initialisation failed, falling back to keyword search: ${message}`);
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
876
|
+
this.disableVectorRetrieval('failed-init');
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
async syncIndexedCorpusChunks(chunks) {
|
|
880
|
+
if (chunks.length === 0) {
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
const appendedChunks = this.appendCorpusChunks(chunks);
|
|
884
|
+
await this.indexAdditionalCorpusChunks(appendedChunks);
|
|
885
|
+
this.rebuildCorpusSearchState();
|
|
886
|
+
}
|
|
887
|
+
appendCorpusChunks(chunks) {
|
|
888
|
+
const existingKeys = new Set(this.corpus.map((chunk) => `${chunk.sourcePath}\u0000${chunk.heading}\u0000${chunk.content}`));
|
|
889
|
+
const filteredChunks = chunks
|
|
890
|
+
.map((chunk) => ({
|
|
891
|
+
heading: chunk.heading,
|
|
892
|
+
sourcePath: chunk.sourcePath,
|
|
893
|
+
content: chunk.content.slice(0, MAX_CHUNK_CHARS).trim(),
|
|
894
|
+
}))
|
|
895
|
+
.filter((chunk) => {
|
|
896
|
+
if (chunk.content.length < MIN_CHUNK_CHARS) {
|
|
897
|
+
return false;
|
|
898
|
+
}
|
|
899
|
+
const dedupeKey = `${chunk.sourcePath}\u0000${chunk.heading}\u0000${chunk.content}`;
|
|
900
|
+
if (existingKeys.has(dedupeKey)) {
|
|
901
|
+
return false;
|
|
902
|
+
}
|
|
903
|
+
existingKeys.add(dedupeKey);
|
|
904
|
+
return true;
|
|
905
|
+
});
|
|
906
|
+
const startIndex = this.corpus.length;
|
|
907
|
+
const appendedChunks = filteredChunks.map((chunk, index) => ({
|
|
908
|
+
id: `gh_${startIndex + index}`,
|
|
909
|
+
heading: chunk.heading,
|
|
910
|
+
content: chunk.content,
|
|
911
|
+
sourcePath: chunk.sourcePath,
|
|
912
|
+
}));
|
|
913
|
+
this.corpus.push(...appendedChunks);
|
|
914
|
+
return appendedChunks;
|
|
915
|
+
}
|
|
916
|
+
rebuildCorpusSearchState() {
|
|
917
|
+
this.keywordFallback = new KeywordFallback(this.corpus);
|
|
918
|
+
const topicExtractor = new TopicExtractor();
|
|
919
|
+
this.topics = topicExtractor.extract(this.corpus);
|
|
920
|
+
if (this.classifier) {
|
|
921
|
+
this.classifier = this.createClassifier(topicExtractor.formatForPrompt(this.topics));
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
createClassifier(topicList) {
|
|
925
|
+
return new QueryClassifier({
|
|
926
|
+
model: this.config.classifierModel,
|
|
927
|
+
provider: this.config.classifierProvider,
|
|
928
|
+
confidenceThreshold: this.config.confidenceThreshold,
|
|
929
|
+
maxTier: this.config.maxTier,
|
|
930
|
+
topicList,
|
|
931
|
+
toolList: this.formatToolList(this.config.availableTools),
|
|
932
|
+
apiKey: this.getLlmApiKey(),
|
|
933
|
+
baseUrl: this.getLlmBaseUrl(),
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
async indexAdditionalCorpusChunks(chunks) {
|
|
937
|
+
if (chunks.length === 0 ||
|
|
938
|
+
!this.embeddingManager ||
|
|
939
|
+
!this.vectorStoreManager ||
|
|
940
|
+
this.embeddingStatus !== 'active') {
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
try {
|
|
944
|
+
const { store, collectionName } = await this.vectorStoreManager.getStoreForDataSource(this.corpusDataSourceId);
|
|
945
|
+
const BATCH_SIZE = 50;
|
|
946
|
+
const allDocuments = [];
|
|
947
|
+
for (let i = 0; i < chunks.length; i += BATCH_SIZE) {
|
|
948
|
+
const batch = chunks.slice(i, i + BATCH_SIZE);
|
|
949
|
+
const result = await this.embeddingManager.generateEmbeddings({
|
|
950
|
+
texts: batch.map((chunk) => chunk.content),
|
|
951
|
+
});
|
|
952
|
+
for (let j = 0; j < batch.length; j++) {
|
|
953
|
+
const embedding = result.embeddings[j];
|
|
954
|
+
if (!embedding || embedding.length === 0) {
|
|
955
|
+
continue;
|
|
956
|
+
}
|
|
957
|
+
batch[j].embedding = embedding;
|
|
958
|
+
allDocuments.push({
|
|
959
|
+
id: batch[j].id,
|
|
960
|
+
embedding,
|
|
961
|
+
textContent: batch[j].content,
|
|
962
|
+
metadata: {
|
|
963
|
+
heading: batch[j].heading,
|
|
964
|
+
sourcePath: batch[j].sourcePath,
|
|
965
|
+
},
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
if (allDocuments.length > 0) {
|
|
970
|
+
await store.upsert(collectionName, allDocuments);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
catch (error) {
|
|
974
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
975
|
+
console.warn(`[QueryRouter] Incremental GitHub corpus embedding failed; falling back to keyword search: ${message}`);
|
|
976
|
+
this.disableVectorRetrieval('failed-init');
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
disableVectorRetrieval(status) {
|
|
980
|
+
this.embeddingManager = null;
|
|
981
|
+
this.vectorStoreManager = null;
|
|
982
|
+
this.providerManager = null;
|
|
983
|
+
this.embeddingDimension = 0;
|
|
984
|
+
this.embeddingStatus = status;
|
|
985
|
+
}
|
|
986
|
+
getEmbeddingApiKey() {
|
|
987
|
+
return (this.config.embeddingApiKey ??
|
|
988
|
+
this.config.apiKey ??
|
|
989
|
+
process.env.OPENAI_API_KEY ??
|
|
990
|
+
process.env.OPENROUTER_API_KEY ??
|
|
991
|
+
'');
|
|
992
|
+
}
|
|
993
|
+
getEmbeddingBaseUrl() {
|
|
994
|
+
if (this.config.embeddingBaseUrl !== undefined) {
|
|
995
|
+
return this.config.embeddingBaseUrl;
|
|
996
|
+
}
|
|
997
|
+
if (this.config.embeddingApiKey !== undefined) {
|
|
998
|
+
return undefined;
|
|
999
|
+
}
|
|
1000
|
+
if (this.config.baseUrl !== undefined) {
|
|
1001
|
+
return this.config.baseUrl;
|
|
604
1002
|
}
|
|
1003
|
+
if (this.config.apiKey !== undefined) {
|
|
1004
|
+
return undefined;
|
|
1005
|
+
}
|
|
1006
|
+
if (process.env.OPENAI_API_KEY) {
|
|
1007
|
+
return undefined;
|
|
1008
|
+
}
|
|
1009
|
+
if (process.env.OPENROUTER_API_KEY) {
|
|
1010
|
+
return 'https://openrouter.ai/api/v1';
|
|
1011
|
+
}
|
|
1012
|
+
return undefined;
|
|
1013
|
+
}
|
|
1014
|
+
getLlmApiKey() {
|
|
1015
|
+
return this.config.apiKey ?? process.env.OPENAI_API_KEY ?? process.env.OPENROUTER_API_KEY ?? '';
|
|
1016
|
+
}
|
|
1017
|
+
getLlmBaseUrl() {
|
|
1018
|
+
if (this.config.baseUrl !== undefined) {
|
|
1019
|
+
return this.config.baseUrl;
|
|
1020
|
+
}
|
|
1021
|
+
if (this.config.apiKey !== undefined) {
|
|
1022
|
+
return undefined;
|
|
1023
|
+
}
|
|
1024
|
+
if (process.env.OPENAI_API_KEY) {
|
|
1025
|
+
return undefined;
|
|
1026
|
+
}
|
|
1027
|
+
if (process.env.OPENROUTER_API_KEY) {
|
|
1028
|
+
return 'https://openrouter.ai/api/v1';
|
|
1029
|
+
}
|
|
1030
|
+
return undefined;
|
|
605
1031
|
}
|
|
606
1032
|
/**
|
|
607
1033
|
* Return a known embedding dimension for common models.
|
|
@@ -620,9 +1046,93 @@ export class QueryRouter {
|
|
|
620
1046
|
};
|
|
621
1047
|
return KNOWN_DIMENSIONS[modelId] ?? 0;
|
|
622
1048
|
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Whether graph expansion is backed by a live implementation.
|
|
1051
|
+
*
|
|
1052
|
+
* Hosts should not treat `graphEnabled` as meaning GraphRAG is actually live.
|
|
1053
|
+
* `active` is reserved for a host-injected or future provider-backed graph
|
|
1054
|
+
* runtime rather than the built-in heuristic.
|
|
1055
|
+
*/
|
|
1056
|
+
hasLiveGraphRuntime() {
|
|
1057
|
+
return typeof this.config.graphExpand === 'function';
|
|
1058
|
+
}
|
|
1059
|
+
/**
|
|
1060
|
+
* Whether graph expansion is backed by the built-in heuristic expansion.
|
|
1061
|
+
*/
|
|
1062
|
+
hasHeuristicGraphRuntime() {
|
|
1063
|
+
return true;
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Whether reranking is backed by a live implementation.
|
|
1067
|
+
*
|
|
1068
|
+
* `active` is reserved for a host-injected or future provider-backed
|
|
1069
|
+
* reranker rather than the built-in lexical heuristic.
|
|
1070
|
+
*/
|
|
1071
|
+
hasLiveRerankerRuntime() {
|
|
1072
|
+
return typeof this.config.rerank === 'function';
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Whether reranking is backed by the built-in lexical reranker.
|
|
1076
|
+
*/
|
|
1077
|
+
hasHeuristicRerankerRuntime() {
|
|
1078
|
+
return true;
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Whether deep research is backed by a live implementation.
|
|
1082
|
+
*
|
|
1083
|
+
* `deepResearchEnabled` only means the branch may be attempted by config.
|
|
1084
|
+
* `active` is reserved for a host-injected or future provider-backed
|
|
1085
|
+
* research runtime rather than the built-in local-corpus heuristic.
|
|
1086
|
+
*/
|
|
1087
|
+
hasLiveDeepResearchRuntime() {
|
|
1088
|
+
return typeof this.config.deepResearch === 'function';
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Whether deep research is backed by the built-in corpus-only heuristic.
|
|
1092
|
+
*/
|
|
1093
|
+
hasHeuristicDeepResearchRuntime() {
|
|
1094
|
+
return true;
|
|
1095
|
+
}
|
|
623
1096
|
// ==========================================================================
|
|
624
1097
|
// PRIVATE — Retrieval callbacks (injected into QueryDispatcher)
|
|
625
1098
|
// ==========================================================================
|
|
1099
|
+
/**
|
|
1100
|
+
* HyDE (Hypothetical Document Embeddings) search callback for the dispatcher.
|
|
1101
|
+
*
|
|
1102
|
+
* Generates a hypothetical answer to the query using the LLM, then searches
|
|
1103
|
+
* for documents similar to that hypothetical answer. Falls back to standard
|
|
1104
|
+
* vector search if no generation provider is available.
|
|
1105
|
+
*
|
|
1106
|
+
* @param query - The user's query string.
|
|
1107
|
+
* @param topK - Maximum number of chunks to return.
|
|
1108
|
+
* @returns Promise resolving to an array of matched chunks.
|
|
1109
|
+
*/
|
|
1110
|
+
async hydeSearch(query, topK) {
|
|
1111
|
+
// HyDE falls back to standard vector search in the built-in implementation.
|
|
1112
|
+
// A host-injected HyDE retriever would generate a hypothetical document first.
|
|
1113
|
+
return this.vectorSearch(query, topK);
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Query decomposition callback for the dispatcher.
|
|
1117
|
+
*
|
|
1118
|
+
* Splits a complex multi-part query into independent sub-queries.
|
|
1119
|
+
* The built-in implementation uses simple sentence splitting as a heuristic.
|
|
1120
|
+
* A host-injected decomposer would use an LLM for semantic decomposition.
|
|
1121
|
+
*
|
|
1122
|
+
* @param query - The original multi-part user query.
|
|
1123
|
+
* @param maxSubQueries - Maximum number of sub-queries to generate.
|
|
1124
|
+
* @returns Array of decomposed sub-query strings.
|
|
1125
|
+
*/
|
|
1126
|
+
async decomposeQuery(query, maxSubQueries) {
|
|
1127
|
+
// Built-in heuristic: split on sentence boundaries or question marks.
|
|
1128
|
+
const parts = query
|
|
1129
|
+
.split(/[?.!]\s+/)
|
|
1130
|
+
.map(s => s.trim())
|
|
1131
|
+
.filter(s => s.length > 0);
|
|
1132
|
+
if (parts.length <= 1)
|
|
1133
|
+
return [query];
|
|
1134
|
+
return parts.slice(0, maxSubQueries);
|
|
1135
|
+
}
|
|
626
1136
|
/**
|
|
627
1137
|
* Vector search callback for the dispatcher.
|
|
628
1138
|
*
|
|
@@ -696,44 +1206,156 @@ export class QueryRouter {
|
|
|
696
1206
|
/**
|
|
697
1207
|
* Graph expansion callback for the dispatcher.
|
|
698
1208
|
*
|
|
699
|
-
*
|
|
700
|
-
* // Follow-up: wire GraphRAGEngine
|
|
1209
|
+
* Built-in heuristic graph expansion over the loaded corpus.
|
|
701
1210
|
*
|
|
702
|
-
*
|
|
703
|
-
*
|
|
1211
|
+
* This is not yet a true GraphRAG engine. It expands from seed chunks by
|
|
1212
|
+
* preferring:
|
|
1213
|
+
* - chunks from the same source document
|
|
1214
|
+
* - heading overlap with seed headings/content
|
|
1215
|
+
* - content overlap with seed headings/content
|
|
1216
|
+
*
|
|
1217
|
+
* @param seeds - Seed chunks to expand from.
|
|
1218
|
+
* @returns Promise resolving to related chunks marked as `graph`.
|
|
704
1219
|
*/
|
|
705
|
-
async graphExpand(
|
|
706
|
-
|
|
707
|
-
|
|
1220
|
+
async graphExpand(seeds) {
|
|
1221
|
+
if (seeds.length === 0 || this.corpus.length === 0) {
|
|
1222
|
+
return [];
|
|
1223
|
+
}
|
|
1224
|
+
const seedIds = new Set(seeds.map((seed) => seed.id));
|
|
1225
|
+
const seedSourcePaths = new Set(seeds.map((seed) => seed.sourcePath));
|
|
1226
|
+
const seedTerms = new Set();
|
|
1227
|
+
for (const seed of seeds) {
|
|
1228
|
+
for (const term of this.tokenizeForRerank(`${seed.heading} ${seed.content}`)) {
|
|
1229
|
+
seedTerms.add(term);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
const scored = this.corpus
|
|
1233
|
+
.filter((chunk) => !seedIds.has(chunk.id))
|
|
1234
|
+
.map((chunk, index) => {
|
|
1235
|
+
const sameSourceBoost = seedSourcePaths.has(chunk.sourcePath) ? 0.48 : 0;
|
|
1236
|
+
const headingOverlap = this.computeTermOverlap(seedTerms, this.tokenizeForRerank(chunk.heading));
|
|
1237
|
+
const contentOverlap = this.computeTermOverlap(seedTerms, this.tokenizeForRerank(chunk.content));
|
|
1238
|
+
const score = sameSourceBoost + headingOverlap * 0.32 + contentOverlap * 0.2;
|
|
1239
|
+
return { chunk, index, score };
|
|
1240
|
+
})
|
|
1241
|
+
.filter((entry) => entry.score >= 0.18);
|
|
1242
|
+
scored.sort((left, right) => {
|
|
1243
|
+
if (right.score !== left.score) {
|
|
1244
|
+
return right.score - left.score;
|
|
1245
|
+
}
|
|
1246
|
+
return left.index - right.index;
|
|
1247
|
+
});
|
|
1248
|
+
return scored.slice(0, 8).map(({ chunk, score }) => ({
|
|
1249
|
+
id: chunk.id,
|
|
1250
|
+
heading: chunk.heading,
|
|
1251
|
+
content: chunk.content,
|
|
1252
|
+
sourcePath: chunk.sourcePath,
|
|
1253
|
+
relevanceScore: Math.min(0.99, score),
|
|
1254
|
+
matchType: 'graph',
|
|
1255
|
+
}));
|
|
708
1256
|
}
|
|
709
1257
|
/**
|
|
710
1258
|
* Reranking callback for the dispatcher.
|
|
711
1259
|
*
|
|
712
|
-
*
|
|
713
|
-
*
|
|
1260
|
+
* Built-in heuristic reranker.
|
|
1261
|
+
*
|
|
1262
|
+
* This is not yet a cross-encoder. It reorders candidate chunks by combining:
|
|
1263
|
+
* - original retrieval score
|
|
1264
|
+
* - heading term overlap
|
|
1265
|
+
* - content term overlap
|
|
1266
|
+
* - exact phrase containment
|
|
1267
|
+
*
|
|
1268
|
+
* This gives tier-2 routing a real second-pass ranking step today without
|
|
1269
|
+
* pretending the deeper reranker service is already wired.
|
|
714
1270
|
*
|
|
715
|
-
* @param
|
|
1271
|
+
* @param query - The user's query.
|
|
716
1272
|
* @param chunks - Candidate chunks to rerank.
|
|
717
1273
|
* @param topN - Maximum number of chunks to keep.
|
|
718
|
-
* @returns Promise resolving to the
|
|
1274
|
+
* @returns Promise resolving to the best-ranked chunks.
|
|
719
1275
|
*/
|
|
720
|
-
async rerank(
|
|
721
|
-
|
|
722
|
-
|
|
1276
|
+
async rerank(query, chunks, topN) {
|
|
1277
|
+
if (chunks.length <= 1) {
|
|
1278
|
+
return chunks.slice(0, topN);
|
|
1279
|
+
}
|
|
1280
|
+
const queryTerms = this.tokenizeForRerank(query);
|
|
1281
|
+
const normalizedQuery = this.normalizeForRerank(query);
|
|
1282
|
+
const scored = chunks.map((chunk, index) => {
|
|
1283
|
+
const headingTerms = this.tokenizeForRerank(chunk.heading);
|
|
1284
|
+
const contentTerms = this.tokenizeForRerank(chunk.content);
|
|
1285
|
+
const headingOverlap = this.computeTermOverlap(queryTerms, headingTerms);
|
|
1286
|
+
const contentOverlap = this.computeTermOverlap(queryTerms, contentTerms);
|
|
1287
|
+
const normalizedText = this.normalizeForRerank(`${chunk.heading} ${chunk.content}`);
|
|
1288
|
+
const exactPhraseBoost = normalizedQuery.length >= 4 && normalizedText.includes(normalizedQuery) ? 0.2 : 0;
|
|
1289
|
+
const score = chunk.relevanceScore * 0.55 +
|
|
1290
|
+
headingOverlap * 0.25 +
|
|
1291
|
+
contentOverlap * 0.2 +
|
|
1292
|
+
exactPhraseBoost;
|
|
1293
|
+
return { chunk, index, score };
|
|
1294
|
+
});
|
|
1295
|
+
scored.sort((left, right) => {
|
|
1296
|
+
if (right.score !== left.score) {
|
|
1297
|
+
return right.score - left.score;
|
|
1298
|
+
}
|
|
1299
|
+
if (right.chunk.relevanceScore !== left.chunk.relevanceScore) {
|
|
1300
|
+
return right.chunk.relevanceScore - left.chunk.relevanceScore;
|
|
1301
|
+
}
|
|
1302
|
+
return left.index - right.index;
|
|
1303
|
+
});
|
|
1304
|
+
return scored.slice(0, topN).map((entry) => entry.chunk);
|
|
723
1305
|
}
|
|
724
1306
|
/**
|
|
725
1307
|
* Deep research callback for the dispatcher.
|
|
726
1308
|
*
|
|
727
|
-
*
|
|
728
|
-
*
|
|
1309
|
+
* Built-in corpus-only research heuristic.
|
|
1310
|
+
*
|
|
1311
|
+
* This is not web-backed research. It runs a few local keyword-based passes
|
|
1312
|
+
* over the loaded corpus using slightly different query formulations, merges
|
|
1313
|
+
* the results, and returns a compact synthesis built from the top findings.
|
|
729
1314
|
*
|
|
730
|
-
* @param
|
|
731
|
-
* @param
|
|
732
|
-
* @returns Promise resolving to
|
|
1315
|
+
* @param query - The user's query.
|
|
1316
|
+
* @param sources - Optional source hints used to broaden local matching.
|
|
1317
|
+
* @returns Promise resolving to synthesized local-corpus findings.
|
|
733
1318
|
*/
|
|
734
|
-
async deepResearch(
|
|
735
|
-
|
|
736
|
-
|
|
1319
|
+
async deepResearch(query, sources) {
|
|
1320
|
+
if (!this.keywordFallback || this.corpus.length === 0) {
|
|
1321
|
+
return { synthesis: '', sources: [] };
|
|
1322
|
+
}
|
|
1323
|
+
const normalizedSourceHints = sources
|
|
1324
|
+
.map((source) => this.normalizeForRerank(source))
|
|
1325
|
+
.filter(Boolean);
|
|
1326
|
+
const queryTerms = [...this.tokenizeForRerank(query)];
|
|
1327
|
+
const narrowedTerms = queryTerms.slice(0, 4).join(' ');
|
|
1328
|
+
const researchQueries = [
|
|
1329
|
+
query,
|
|
1330
|
+
[query, normalizedSourceHints.join(' ')].filter(Boolean).join(' ').trim(),
|
|
1331
|
+
[query, 'architecture tradeoffs details'].join(' ').trim(),
|
|
1332
|
+
narrowedTerms,
|
|
1333
|
+
].filter(Boolean);
|
|
1334
|
+
const merged = new Map();
|
|
1335
|
+
for (const researchQuery of researchQueries) {
|
|
1336
|
+
const hits = this.keywordFallback.search(researchQuery, 4);
|
|
1337
|
+
for (const hit of hits) {
|
|
1338
|
+
const existing = merged.get(hit.id);
|
|
1339
|
+
const researchChunk = {
|
|
1340
|
+
...hit,
|
|
1341
|
+
relevanceScore: Math.min(0.99, hit.relevanceScore),
|
|
1342
|
+
matchType: 'research',
|
|
1343
|
+
};
|
|
1344
|
+
if (!existing || researchChunk.relevanceScore > existing.relevanceScore) {
|
|
1345
|
+
merged.set(researchChunk.id, researchChunk);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
const researchChunks = [...merged.values()]
|
|
1350
|
+
.sort((left, right) => right.relevanceScore - left.relevanceScore)
|
|
1351
|
+
.slice(0, 5);
|
|
1352
|
+
if (researchChunks.length === 0) {
|
|
1353
|
+
return { synthesis: '', sources: [] };
|
|
1354
|
+
}
|
|
1355
|
+
const synthesis = researchChunks
|
|
1356
|
+
.map((chunk, index) => `${index + 1}. ${chunk.heading}: ${this.firstSentence(chunk.content)}`)
|
|
1357
|
+
.join('\n');
|
|
1358
|
+
return { synthesis, sources: researchChunks };
|
|
737
1359
|
}
|
|
738
1360
|
/**
|
|
739
1361
|
* Format available tools for the classifier prompt.
|
|
@@ -741,6 +1363,72 @@ export class QueryRouter {
|
|
|
741
1363
|
formatToolList(availableTools) {
|
|
742
1364
|
return availableTools.length > 0 ? availableTools.join(', ') : '(none available)';
|
|
743
1365
|
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Normalize text for simple lexical reranking.
|
|
1368
|
+
*/
|
|
1369
|
+
normalizeForRerank(text) {
|
|
1370
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* Tokenize text for lexical reranking.
|
|
1374
|
+
*/
|
|
1375
|
+
tokenizeForRerank(text) {
|
|
1376
|
+
const STOP_WORDS = new Set([
|
|
1377
|
+
'a',
|
|
1378
|
+
'an',
|
|
1379
|
+
'and',
|
|
1380
|
+
'are',
|
|
1381
|
+
'as',
|
|
1382
|
+
'at',
|
|
1383
|
+
'be',
|
|
1384
|
+
'by',
|
|
1385
|
+
'for',
|
|
1386
|
+
'from',
|
|
1387
|
+
'how',
|
|
1388
|
+
'in',
|
|
1389
|
+
'is',
|
|
1390
|
+
'it',
|
|
1391
|
+
'of',
|
|
1392
|
+
'on',
|
|
1393
|
+
'or',
|
|
1394
|
+
'that',
|
|
1395
|
+
'the',
|
|
1396
|
+
'this',
|
|
1397
|
+
'to',
|
|
1398
|
+
'what',
|
|
1399
|
+
'when',
|
|
1400
|
+
'where',
|
|
1401
|
+
'which',
|
|
1402
|
+
'why',
|
|
1403
|
+
'with',
|
|
1404
|
+
]);
|
|
1405
|
+
return new Set(this.normalizeForRerank(text)
|
|
1406
|
+
.split(/\s+/)
|
|
1407
|
+
.filter((term) => term.length >= 2 && !STOP_WORDS.has(term)));
|
|
1408
|
+
}
|
|
1409
|
+
/**
|
|
1410
|
+
* Compute overlap ratio between query terms and candidate terms.
|
|
1411
|
+
*/
|
|
1412
|
+
computeTermOverlap(queryTerms, candidateTerms) {
|
|
1413
|
+
if (queryTerms.size === 0 || candidateTerms.size === 0) {
|
|
1414
|
+
return 0;
|
|
1415
|
+
}
|
|
1416
|
+
let matches = 0;
|
|
1417
|
+
for (const term of queryTerms) {
|
|
1418
|
+
if (candidateTerms.has(term)) {
|
|
1419
|
+
matches += 1;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
return matches / queryTerms.size;
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* Extract a short first-sentence style summary from a chunk for synthesis.
|
|
1426
|
+
*/
|
|
1427
|
+
firstSentence(text) {
|
|
1428
|
+
const normalized = text.replace(/\s+/g, ' ').trim();
|
|
1429
|
+
const sentence = normalized.match(/^(.{1,220}?[.!?])(?:\s|$)/);
|
|
1430
|
+
return sentence?.[1] ?? normalized.slice(0, 220);
|
|
1431
|
+
}
|
|
744
1432
|
/**
|
|
745
1433
|
* Derive fallback strategy names from classification + retrieval events.
|
|
746
1434
|
*/
|