@crownpeak/dqm-react-component-dev-mcp 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -0
- package/data/.env.example +22 -0
- package/data/.gitattributes +47 -0
- package/data/.glfrc.json +7 -0
- package/data/.husky/pre-commit +5 -0
- package/data/.nvmrc +1 -0
- package/data/CHANGELOG.md +75 -0
- package/data/CODE_OF_CONDUCT.md +129 -0
- package/data/CONTRIBUTING.md +203 -0
- package/data/DOCS-STRUCTURE.md +307 -0
- package/data/I18N.md +292 -0
- package/data/LICENSE +22 -0
- package/data/README.md +315 -0
- package/data/SECURITY.md +125 -0
- package/data/WIKI-DEPLOYMENT.md +348 -0
- package/data/docs/AI-FEATURES.md +610 -0
- package/data/docs/API-REFERENCE.md +1022 -0
- package/data/docs/AUTHENTICATION.md +301 -0
- package/data/docs/BACKEND-API.md +468 -0
- package/data/docs/DEVELOPMENT.md +375 -0
- package/data/docs/EXAMPLES.md +622 -0
- package/data/docs/MCP-SERVER.md +307 -0
- package/data/docs/MIGRATION-GUIDE.md +367 -0
- package/data/docs/NPM-PUBLISH.md +193 -0
- package/data/docs/QUICKSTART.md +206 -0
- package/data/docs/REDIS-SETUP.md +162 -0
- package/data/docs/SERVER.md +228 -0
- package/data/docs/TROUBLESHOOTING.md +657 -0
- package/data/docs/WIDGET-GUIDE.md +638 -0
- package/data/docs/WIKI-HOME.md +58 -0
- package/data/docs/WIKI-SIDEBAR.md +39 -0
- package/data/package.json +171 -0
- package/data/playwright.config.ts +64 -0
- package/data/probe/.cargo/config.toml +10 -0
- package/data/probe/.claude/commands/performance-review.md +15 -0
- package/data/probe/.clinerules +288 -0
- package/data/probe/.dockerignore +57 -0
- package/data/probe/.githooks/post-commit +11 -0
- package/data/probe/.githooks/pre-commit +99 -0
- package/data/probe/.githooks/pre-commit-vow +9 -0
- package/data/probe/.prompts/engineer.md +41 -0
- package/data/probe/.roomodes +28 -0
- package/data/probe/.windsurfrules +0 -0
- package/data/probe/BASH_TOOL_SUMMARY.md +148 -0
- package/data/probe/BENCHMARKING.md +256 -0
- package/data/probe/CLAUDE.md +226 -0
- package/data/probe/CODE_OF_CONDUCT.md +128 -0
- package/data/probe/CONTRIBUTING.md +193 -0
- package/data/probe/Cargo.toml +120 -0
- package/data/probe/Cross.toml +10 -0
- package/data/probe/DOCKER-README.md +224 -0
- package/data/probe/Dockerfile +32 -0
- package/data/probe/ENHANCED_DEBUG_TELEMETRY.md +188 -0
- package/data/probe/LICENSE +201 -0
- package/data/probe/Makefile +210 -0
- package/data/probe/README.md +824 -0
- package/data/probe/SECURITY.md +67 -0
- package/data/probe/WINDOWS-GUIDE.md +294 -0
- package/data/probe/benches/parsing_benchmarks.rs +370 -0
- package/data/probe/benches/search_benchmarks.rs +599 -0
- package/data/probe/benches/simd_benchmarks.rs +372 -0
- package/data/probe/benches/timing_benchmarks.rs +287 -0
- package/data/probe/build-windows.bat +229 -0
- package/data/probe/codex-config/config.toml +6 -0
- package/data/probe/docs/PERFORMANCE_OPTIMIZATION.md +161 -0
- package/data/probe/examples/cache_demo.rs +46 -0
- package/data/probe/examples/chat/.dockerignore +37 -0
- package/data/probe/examples/chat/ChatSessionManager.js +295 -0
- package/data/probe/examples/chat/Dockerfile +98 -0
- package/data/probe/examples/chat/LICENSE +201 -0
- package/data/probe/examples/chat/LOCAL_IMAGE_SUPPORT.md +195 -0
- package/data/probe/examples/chat/MCP_INTEGRATION.md +400 -0
- package/data/probe/examples/chat/README.md +338 -0
- package/data/probe/examples/chat/TRACING.md +226 -0
- package/data/probe/examples/chat/appTracer.js +968 -0
- package/data/probe/examples/chat/auth.js +76 -0
- package/data/probe/examples/chat/bin/probe-chat.js +13 -0
- package/data/probe/examples/chat/build.js +104 -0
- package/data/probe/examples/chat/cancelRequest.js +84 -0
- package/data/probe/examples/chat/demo-agentic-image-flow.js +88 -0
- package/data/probe/examples/chat/demo-local-images.js +128 -0
- package/data/probe/examples/chat/fileSpanExporter.js +181 -0
- package/data/probe/examples/chat/implement/README.md +228 -0
- package/data/probe/examples/chat/implement/backends/AiderBackend.js +750 -0
- package/data/probe/examples/chat/implement/backends/BaseBackend.js +276 -0
- package/data/probe/examples/chat/implement/backends/ClaudeCodeBackend.js +767 -0
- package/data/probe/examples/chat/implement/backends/MockBackend.js +237 -0
- package/data/probe/examples/chat/implement/backends/registry.js +85 -0
- package/data/probe/examples/chat/implement/core/BackendManager.js +567 -0
- package/data/probe/examples/chat/implement/core/ImplementTool.js +354 -0
- package/data/probe/examples/chat/implement/core/config.js +428 -0
- package/data/probe/examples/chat/implement/core/timeouts.js +58 -0
- package/data/probe/examples/chat/implement/core/utils.js +496 -0
- package/data/probe/examples/chat/implement/types/BackendTypes.js +126 -0
- package/data/probe/examples/chat/index.js +669 -0
- package/data/probe/examples/chat/mcpServer.js +341 -0
- package/data/probe/examples/chat/npm/LICENSE +15 -0
- package/data/probe/examples/chat/npm/README.md +168 -0
- package/data/probe/examples/chat/npm/bin/probe-chat.js +156 -0
- package/data/probe/examples/chat/npm/index.js +259 -0
- package/data/probe/examples/chat/npm/package.json +54 -0
- package/data/probe/examples/chat/package.json +102 -0
- package/data/probe/examples/chat/probeChat.js +456 -0
- package/data/probe/examples/chat/probeTool.js +491 -0
- package/data/probe/examples/chat/storage/JsonChatStorage.js +476 -0
- package/data/probe/examples/chat/telemetry.js +281 -0
- package/data/probe/examples/chat/test/integration/chatFlows.test.js +320 -0
- package/data/probe/examples/chat/test/integration/toolCalling.test.js +471 -0
- package/data/probe/examples/chat/test/mocks/mockLLMProvider.js +269 -0
- package/data/probe/examples/chat/test/test-backends.js +90 -0
- package/data/probe/examples/chat/test/testUtils.js +530 -0
- package/data/probe/examples/chat/test/unit/backendTimeout.test.js +161 -0
- package/data/probe/examples/chat/test/unit/packageFiles.test.js +120 -0
- package/data/probe/examples/chat/test/verify-tests.js +118 -0
- package/data/probe/examples/chat/test-agentic-image-loading.js +294 -0
- package/data/probe/examples/chat/test-ai-sdk-telemetry.js +204 -0
- package/data/probe/examples/chat/test-chat-tracing.js +38 -0
- package/data/probe/examples/chat/test-direct-function.js +49 -0
- package/data/probe/examples/chat/test-file-size-validation.js +103 -0
- package/data/probe/examples/chat/test-full-mcp-integration.js +258 -0
- package/data/probe/examples/chat/test-github-context.txt +12 -0
- package/data/probe/examples/chat/test-hierarchy.js +203 -0
- package/data/probe/examples/chat/test-image-spans.js +37 -0
- package/data/probe/examples/chat/test-local-image-reading.js +176 -0
- package/data/probe/examples/chat/test-mcp-integration.js +136 -0
- package/data/probe/examples/chat/test-mcp-probe-server.js +161 -0
- package/data/probe/examples/chat/test-mcp-with-ai.js +279 -0
- package/data/probe/examples/chat/test-multiple-allowed-dirs.js +111 -0
- package/data/probe/examples/chat/test-probe-mcp-server.js +110 -0
- package/data/probe/examples/chat/test-security-validation.js +145 -0
- package/data/probe/examples/chat/test-simple-tracing.js +32 -0
- package/data/probe/examples/chat/test-trace-verification.js +235 -0
- package/data/probe/examples/chat/test-tracing.js +114 -0
- package/data/probe/examples/chat/tokenCounter.js +419 -0
- package/data/probe/examples/chat/tokenUsageDisplay.js +134 -0
- package/data/probe/examples/chat/webServer.js +1103 -0
- package/data/probe/examples/reranker/Cargo.toml +33 -0
- package/data/probe/examples/reranker/DEBUG_OUTPUT_ANALYSIS.md +71 -0
- package/data/probe/examples/reranker/MODELS.md +66 -0
- package/data/probe/examples/reranker/MODEL_COMPARISON.md +60 -0
- package/data/probe/examples/reranker/MULTI_MODEL_ANALYSIS.md +176 -0
- package/data/probe/examples/reranker/PERFORMANCE_SUMMARY.md +156 -0
- package/data/probe/examples/reranker/README.md +347 -0
- package/data/probe/examples/reranker/RUST_BERT_COMPARISON.md +82 -0
- package/data/probe/examples/reranker/TOKENIZATION_GUIDE.md +120 -0
- package/data/probe/examples/reranker/check_rust_tokenizer.py +108 -0
- package/data/probe/examples/reranker/convert_to_torchscript.py +109 -0
- package/data/probe/examples/reranker/debug_scoring.py +189 -0
- package/data/probe/examples/reranker/debug_tokenization.py +154 -0
- package/data/probe/examples/reranker/download_models.sh +73 -0
- package/data/probe/examples/reranker/requirements.txt +13 -0
- package/data/probe/examples/reranker/run_comprehensive_benchmark.sh +83 -0
- package/data/probe/examples/reranker/rust_bert_test/Cargo.toml +12 -0
- package/data/probe/examples/reranker/rust_bert_test/README.md +54 -0
- package/data/probe/examples/reranker/simple_test.py +50 -0
- package/data/probe/examples/reranker/test_all_models.sh +63 -0
- package/data/probe/examples/reranker/test_bert_results.sh +44 -0
- package/data/probe/examples/reranker/test_cross_encoder.py +334 -0
- package/data/probe/examples/reranker/test_cross_encoder.sh +80 -0
- package/data/probe/examples/reranker/test_exact_comparison.py +151 -0
- package/data/probe/examples/reranker/test_parallel_performance.sh +56 -0
- package/data/probe/examples/reranker/test_scores.py +132 -0
- package/data/probe/install.ps1 +508 -0
- package/data/probe/install.sh +460 -0
- package/data/probe/npm/CLONE_METHOD_EXAMPLES.md +596 -0
- package/data/probe/npm/CONTEXT_COMPACTION.md +303 -0
- package/data/probe/npm/DELEGATE_TOOL_README.md +166 -0
- package/data/probe/npm/MAID_INTEGRATION.md +313 -0
- package/data/probe/npm/MCP_INTEGRATION_SUMMARY.md +241 -0
- package/data/probe/npm/README.md +824 -0
- package/data/probe/npm/bin/.gitignore +7 -0
- package/data/probe/npm/bin/.gitkeep +0 -0
- package/data/probe/npm/bin/README.md +12 -0
- package/data/probe/npm/bin/probe +167 -0
- package/data/probe/npm/docs/CLAUDE_CODE_INTEGRATION.md +414 -0
- package/data/probe/npm/docs/CODEX_INTEGRATION.md +502 -0
- package/data/probe/npm/docs/EDIT_CREATE_TOOLS.md +233 -0
- package/data/probe/npm/docs/RETRY_AND_FALLBACK.md +674 -0
- package/data/probe/npm/example-usage.js +335 -0
- package/data/probe/npm/examples/multi-engine-demo.js +117 -0
- package/data/probe/npm/examples/probe-agent-cli.js +113 -0
- package/data/probe/npm/examples/test-agent-edit.js +114 -0
- package/data/probe/npm/examples/test-edit-create.js +120 -0
- package/data/probe/npm/examples/test-edit-direct.js +114 -0
- package/data/probe/npm/index.d.ts +744 -0
- package/data/probe/npm/jest.config.js +52 -0
- package/data/probe/npm/package.json +117 -0
- package/data/probe/npm/scripts/build-agent.cjs +75 -0
- package/data/probe/npm/scripts/build-cjs.js +124 -0
- package/data/probe/npm/scripts/build-mcp.cjs +36 -0
- package/data/probe/npm/scripts/postinstall.js +216 -0
- package/data/probe/npm/test-codex-e2e.js +78 -0
- package/data/probe/npm/test-download-lock.js +109 -0
- package/data/probe/npm/test-grep-security.js +94 -0
- package/data/probe/npm/test-grep-simplified.js +63 -0
- package/data/probe/npm/test-grep.js +51 -0
- package/data/probe/npm/tests/README.md +96 -0
- package/data/probe/npm/tests/agent-compact-history.test.js +174 -0
- package/data/probe/npm/tests/allow-tests-default.test.js +151 -0
- package/data/probe/npm/tests/contextCompactor.test.js +498 -0
- package/data/probe/npm/tests/delegate-config.test.js +353 -0
- package/data/probe/npm/tests/delegate-integration.test.js +348 -0
- package/data/probe/npm/tests/extractor-integration.test.js +162 -0
- package/data/probe/npm/tests/extractor.test.js +317 -0
- package/data/probe/npm/tests/fixtures/sampleDiagrams.js +267 -0
- package/data/probe/npm/tests/integration/claude-code-auto-fallback.spec.js +148 -0
- package/data/probe/npm/tests/integration/claude-code-multi-step.spec.js +127 -0
- package/data/probe/npm/tests/integration/claude-code-tool-events.spec.js +163 -0
- package/data/probe/npm/tests/integration/codex-auto-fallback.spec.js +191 -0
- package/data/probe/npm/tests/integration/codex-tool-events.spec.js +147 -0
- package/data/probe/npm/tests/integration/examplesChatMcp.test.js +402 -0
- package/data/probe/npm/tests/integration/mcpDotenvSupport.test.js +174 -0
- package/data/probe/npm/tests/integration/mcpErrorHandling.test.js +566 -0
- package/data/probe/npm/tests/integration/mcpRobustness.test.js +564 -0
- package/data/probe/npm/tests/integration/mcpStdoutPurity.test.js +355 -0
- package/data/probe/npm/tests/integration/probeAgentMcp.test.js +398 -0
- package/data/probe/npm/tests/integration/retryFallback.test.js +368 -0
- package/data/probe/npm/tests/integration/schema-in-initial-message.test.js +318 -0
- package/data/probe/npm/tests/integration/schema-validation-loop-prevention.test.js +244 -0
- package/data/probe/npm/tests/integration/schemaRetryLogic.test.js +94 -0
- package/data/probe/npm/tests/integration/validationFlow.test.js +329 -0
- package/data/probe/npm/tests/manual/test-codex-basic.js +110 -0
- package/data/probe/npm/tests/mcp/mcpClientManager.test.js +614 -0
- package/data/probe/npm/tests/mcp/mcpConfig.test.js +359 -0
- package/data/probe/npm/tests/mcp/mcpXmlBridge.test.js +436 -0
- package/data/probe/npm/tests/mcp/mockMcpServer.js +510 -0
- package/data/probe/npm/tests/mcp-strict-syntax.test.js +319 -0
- package/data/probe/npm/tests/mermaidQuoteEscaping.test.js +214 -0
- package/data/probe/npm/tests/nestedQuoteFix.test.js +40 -0
- package/data/probe/npm/tests/setup.js +46 -0
- package/data/probe/npm/tests/unit/allowed-tools.test.js +513 -0
- package/data/probe/npm/tests/unit/attempt-completion-closing-tag-in-content.test.js +188 -0
- package/data/probe/npm/tests/unit/attemptCompletionJsonFix.test.js +238 -0
- package/data/probe/npm/tests/unit/attemptCompletionJsonIssue.test.js +128 -0
- package/data/probe/npm/tests/unit/backtickAutoFix.test.js +35 -0
- package/data/probe/npm/tests/unit/bash-probe-agent-integration.test.js +389 -0
- package/data/probe/npm/tests/unit/bash-simple-commands.test.js +324 -0
- package/data/probe/npm/tests/unit/bash-tool-comprehensive.test.js +371 -0
- package/data/probe/npm/tests/unit/bash-tool-integration.test.js +310 -0
- package/data/probe/npm/tests/unit/bash-tool.test.js +341 -0
- package/data/probe/npm/tests/unit/completion-prompt.test.js +379 -0
- package/data/probe/npm/tests/unit/cwd-path-options.test.js +287 -0
- package/data/probe/npm/tests/unit/delegate-limits.test.js +422 -0
- package/data/probe/npm/tests/unit/direct-content-attempt-completion.test.js +235 -0
- package/data/probe/npm/tests/unit/edit-create-tools.test.js +609 -0
- package/data/probe/npm/tests/unit/enhancedMermaidValidation.test.js +577 -0
- package/data/probe/npm/tests/unit/extract-content.test.js +83 -0
- package/data/probe/npm/tests/unit/extract-multiple-targets.test.js +89 -0
- package/data/probe/npm/tests/unit/fallbackManager.test.js +442 -0
- package/data/probe/npm/tests/unit/githubCompatibilityValidation.test.js +258 -0
- package/data/probe/npm/tests/unit/imageConfig.test.js +149 -0
- package/data/probe/npm/tests/unit/imagePathResolution.test.js +345 -0
- package/data/probe/npm/tests/unit/json-fixing-agent.test.js +238 -0
- package/data/probe/npm/tests/unit/json-validation-enhanced-errors.test.js +199 -0
- package/data/probe/npm/tests/unit/jsonValidationInfiniteLoopFix.test.js +228 -0
- package/data/probe/npm/tests/unit/maidIntegration.test.js +139 -0
- package/data/probe/npm/tests/unit/maxIterationsWarning.test.js +195 -0
- package/data/probe/npm/tests/unit/mermaidEdgeLabelFix.test.js +161 -0
- package/data/probe/npm/tests/unit/mermaidHtmlEntities.test.js +76 -0
- package/data/probe/npm/tests/unit/mermaidInfiniteLoopFix.test.js +64 -0
- package/data/probe/npm/tests/unit/mermaidValidation.test.js +723 -0
- package/data/probe/npm/tests/unit/mermaidValidationVisorExample.test.js +309 -0
- package/data/probe/npm/tests/unit/probe-agent-clone-realistic.test.js +643 -0
- package/data/probe/npm/tests/unit/probe-agent-clone.test.js +476 -0
- package/data/probe/npm/tests/unit/probe-agent-delegate.test.js +400 -0
- package/data/probe/npm/tests/unit/probe-agent-model-option.test.js +118 -0
- package/data/probe/npm/tests/unit/probeTool-security.test.js +283 -0
- package/data/probe/npm/tests/unit/readImageTool.test.js +418 -0
- package/data/probe/npm/tests/unit/retryManager.test.js +317 -0
- package/data/probe/npm/tests/unit/schema-aware-reminders.test.js +288 -0
- package/data/probe/npm/tests/unit/schemaDefinitionDetection.test.js +115 -0
- package/data/probe/npm/tests/unit/schemaUtils.test.js +1268 -0
- package/data/probe/npm/tests/unit/simpleTelemetry.test.js +282 -0
- package/data/probe/npm/tests/unit/simplified-attempt-completion.test.js +274 -0
- package/data/probe/npm/tests/unit/single-quote-json-bug.test.js +231 -0
- package/data/probe/npm/tests/unit/subgraphAutoFix.test.js +110 -0
- package/data/probe/npm/tests/unit/system-prompt.test.js +32 -0
- package/data/probe/npm/tests/unit/types-probe-agent-options.test.js +42 -0
- package/data/probe/npm/tests/unit/xmlParsing.test.js +720 -0
- package/data/probe/npm/tsconfig.json +21 -0
- package/data/probe/result1.txt +19 -0
- package/data/probe/result2.txt +26 -0
- package/data/probe/scripts/benchmark.sh +270 -0
- package/data/probe/scripts/cache_memory_analysis.rs +844 -0
- package/data/probe/scripts/claude-hook-wrapper.sh +56 -0
- package/data/probe/site/.env.example +10 -0
- package/data/probe/site/DEPLOYMENT.md +86 -0
- package/data/probe/site/README.md +183 -0
- package/data/probe/site/adding-languages.md +135 -0
- package/data/probe/site/ai-chat.md +427 -0
- package/data/probe/site/ai-integration.md +1488 -0
- package/data/probe/site/blog/agentic-flow-custom-xml-protocol.md +407 -0
- package/data/probe/site/blog/index.md +118 -0
- package/data/probe/site/blog/v0.6.0-release.md +426 -0
- package/data/probe/site/blog.md +8 -0
- package/data/probe/site/changelog.md +200 -0
- package/data/probe/site/cli-mode.md +437 -0
- package/data/probe/site/code-extraction.md +436 -0
- package/data/probe/site/contributing/README.md +9 -0
- package/data/probe/site/contributing/documentation-cross-references.md +215 -0
- package/data/probe/site/contributing/documentation-maintenance.md +275 -0
- package/data/probe/site/contributing/documentation-structure.md +75 -0
- package/data/probe/site/documentation-cross-references.md +215 -0
- package/data/probe/site/documentation-guide.md +132 -0
- package/data/probe/site/documentation-maintenance.md +275 -0
- package/data/probe/site/features.md +147 -0
- package/data/probe/site/how-it-works.md +118 -0
- package/data/probe/site/index.md +175 -0
- package/data/probe/site/index.md.bak +133 -0
- package/data/probe/site/installation.md +235 -0
- package/data/probe/site/integrations/docker.md +248 -0
- package/data/probe/site/integrations/github-actions.md +413 -0
- package/data/probe/site/language-support-overview.md +168 -0
- package/data/probe/site/mcp-integration.md +587 -0
- package/data/probe/site/mcp-server.md +304 -0
- package/data/probe/site/navigation-structure.md +76 -0
- package/data/probe/site/nodejs-sdk.md +798 -0
- package/data/probe/site/output-formats.md +625 -0
- package/data/probe/site/package.json +21 -0
- package/data/probe/site/public/_headers +28 -0
- package/data/probe/site/public/_redirects +11 -0
- package/data/probe/site/quick-start.md +289 -0
- package/data/probe/site/search-functionality.md +291 -0
- package/data/probe/site/search-reference.md +291 -0
- package/data/probe/site/supported-languages.md +215 -0
- package/data/probe/site/use-cases/README.md +8 -0
- package/data/probe/site/use-cases/advanced-cli.md +253 -0
- package/data/probe/site/use-cases/ai-code-editors.md +239 -0
- package/data/probe/site/use-cases/building-ai-tools.md +529 -0
- package/data/probe/site/use-cases/cli-ai-workflows.md +285 -0
- package/data/probe/site/use-cases/deploying-probe-web-interface.md +255 -0
- package/data/probe/site/use-cases/integrating-probe-into-ai-code-editors.md +161 -0
- package/data/probe/site/use-cases/nodejs-sdk.md +596 -0
- package/data/probe/site/use-cases/team-chat.md +350 -0
- package/data/probe/site/web-interface.md +434 -0
- package/data/probe/site/wrangler.toml +9 -0
- package/data/probe/test-api-key.sh +1 -0
- package/data/probe/test-probe-implementation/hello.js +7 -0
- package/data/probe/test_cases/demonstrate_early_termination_issues.sh +176 -0
- package/data/probe/test_cases/early_termination_issues.rs +533 -0
- package/data/probe/test_data/test_nested_struct.go +26 -0
- package/data/probe/tests/README.md +286 -0
- package/data/probe/tests/README_search_determinism_tests.md +116 -0
- package/data/probe/tests/adjacent_comment_test.rs +152 -0
- package/data/probe/tests/apostrophe_handling_tests.rs +132 -0
- package/data/probe/tests/block_filtering_with_ast_tests.rs +669 -0
- package/data/probe/tests/block_merging_tests.rs +396 -0
- package/data/probe/tests/c_outline_format_tests.rs +2179 -0
- package/data/probe/tests/cache_invalidation_issues.rs.disabled +682 -0
- package/data/probe/tests/cache_order_tests.rs +147 -0
- package/data/probe/tests/cache_query_scoping_tests.rs +221 -0
- package/data/probe/tests/cli_tests.rs +680 -0
- package/data/probe/tests/comment_context_integration_test.rs +240 -0
- package/data/probe/tests/common.rs +33 -0
- package/data/probe/tests/complex_block_merging_tests.rs +599 -0
- package/data/probe/tests/complex_query_block_filtering_tests.rs +422 -0
- package/data/probe/tests/control_flow_closing_braces_test.rs +91 -0
- package/data/probe/tests/cpp_outline_format_tests.rs +1507 -0
- package/data/probe/tests/csharp_outline_format_tests.rs +941 -0
- package/data/probe/tests/elastic_query_integration_tests.rs +922 -0
- package/data/probe/tests/extract_command_tests.rs +1848 -0
- package/data/probe/tests/extract_deduplication_tests.rs +146 -0
- package/data/probe/tests/extract_input_file_tests.rs +84 -0
- package/data/probe/tests/extract_prompt_tests.rs +102 -0
- package/data/probe/tests/filename_search_tests.rs +96 -0
- package/data/probe/tests/fixtures/user/AssemblyInfo.cs +3 -0
- package/data/probe/tests/github_extract_tests.rs +234 -0
- package/data/probe/tests/go_comment_test.rs +253 -0
- package/data/probe/tests/go_outline_format_tests.rs +2587 -0
- package/data/probe/tests/go_path_resolver_tests.rs +96 -0
- package/data/probe/tests/html_outline_format_tests.rs +637 -0
- package/data/probe/tests/integration_tests.rs +837 -0
- package/data/probe/tests/ip_whitelist_test.rs +148 -0
- package/data/probe/tests/java_outline_format_tests.rs +1611 -0
- package/data/probe/tests/javascript_extract_tests.rs +315 -0
- package/data/probe/tests/javascript_outline_format_tests.rs +1464 -0
- package/data/probe/tests/json_format_tests.rs +436 -0
- package/data/probe/tests/json_schema_validation_tests.rs +450 -0
- package/data/probe/tests/lib_usage.rs +60 -0
- package/data/probe/tests/line_comment_context_extension_test.rs +459 -0
- package/data/probe/tests/line_map_cache_tests.rs +114 -0
- package/data/probe/tests/markdown_integration_tests.rs +190 -0
- package/data/probe/tests/mocks/test_ip_whitelist.go +11 -0
- package/data/probe/tests/mocks/test_object.js +27 -0
- package/data/probe/tests/mocks/test_struct.go +50 -0
- package/data/probe/tests/multi_keyword_pattern_tests.rs +464 -0
- package/data/probe/tests/multi_language_syntax_integration_tests.rs +218 -0
- package/data/probe/tests/multiple_capture_groups_tests.rs +169 -0
- package/data/probe/tests/negative_compound_word_tests.rs +246 -0
- package/data/probe/tests/nested_symbol_extraction_tests.rs +99 -0
- package/data/probe/tests/outline_cross_file_interference_test.rs +335 -0
- package/data/probe/tests/outline_keyword_preservation_test.rs +67 -0
- package/data/probe/tests/output_format_edge_cases_tests.rs +693 -0
- package/data/probe/tests/parallel_extraction_tests.rs +178 -0
- package/data/probe/tests/parallel_search_tests.rs +355 -0
- package/data/probe/tests/path_resolver_tests.rs +698 -0
- package/data/probe/tests/php_outline_format_extended_tests.rs +928 -0
- package/data/probe/tests/php_outline_format_tests.rs +768 -0
- package/data/probe/tests/property_tests.proptest-regressions +9 -0
- package/data/probe/tests/property_tests.rs +118 -0
- package/data/probe/tests/python_outline_format_tests.rs +1538 -0
- package/data/probe/tests/query_command_json_tests.rs +438 -0
- package/data/probe/tests/query_command_tests.rs +232 -0
- package/data/probe/tests/query_command_xml_tests.rs +569 -0
- package/data/probe/tests/quoted_term_with_negative_keyword_tests.rs +216 -0
- package/data/probe/tests/required_terms_filename_tests.rs +116 -0
- package/data/probe/tests/ruby_outline_format_tests.rs +1011 -0
- package/data/probe/tests/rust_line_comment_context_test.rs +151 -0
- package/data/probe/tests/rust_outline_format_enhanced_tests.rs +725 -0
- package/data/probe/tests/rust_outline_format_tests.rs +843 -0
- package/data/probe/tests/schemas/xml_output_schema.xsd +38 -0
- package/data/probe/tests/search_determinism_tests.rs +451 -0
- package/data/probe/tests/search_hints_tests.rs +253 -0
- package/data/probe/tests/special_character_escaping_tests.rs +417 -0
- package/data/probe/tests/stemming_compound_word_filtering_tests.rs +535 -0
- package/data/probe/tests/strict_elastic_syntax_tests.rs +404 -0
- package/data/probe/tests/swift_outline_format_tests.rs +3319 -0
- package/data/probe/tests/symbols_tests.rs +166 -0
- package/data/probe/tests/test_file.rs +45 -0
- package/data/probe/tests/test_tokenize.rs +28 -0
- package/data/probe/tests/timeout_tests.rs +82 -0
- package/data/probe/tests/tokenization_tests.rs +195 -0
- package/data/probe/tests/tokenized_block_filtering_tests.rs +174 -0
- package/data/probe/tests/typescript_extract_tests.rs +214 -0
- package/data/probe/tests/typescript_outline_format_tests.rs +2188 -0
- package/data/probe/tests/xml_format_tests.rs +568 -0
- package/data/probe/tests/xml_schema_validation_tests.rs +497 -0
- package/data/scripts/postinstall.mjs +9 -0
- package/data/scripts/set-version.js +0 -0
- package/data/scripts/wiki-build.sh +111 -0
- package/data/scripts/wiki-deploy.sh +73 -0
- package/data/serve.json +12 -0
- package/data/test/demo-dynamic.html +134 -0
- package/data/test/demo-esm.html +105 -0
- package/data/test/demo-iife.html +78 -0
- package/data/tsconfig.json +7 -0
- package/data/vite.server.ts +483 -0
- package/data/vitest.config.ts +40 -0
- package/data/wiki/Home.md +58 -0
- package/data/wiki/_Sidebar.md +39 -0
- package/docs-mcp.config.json +20 -0
- package/package.json +56 -0
- package/src/config.js +111 -0
- package/src/index.js +395 -0
|
@@ -0,0 +1,1103 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import { createServer } from 'http';
|
|
3
|
+
// import { streamText } from 'ai'; // streamText might not be suitable for the loop logic directly
|
|
4
|
+
import { readFileSync, existsSync } from 'fs';
|
|
5
|
+
import { resolve, dirname, join } from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { randomUUID } from 'crypto';
|
|
8
|
+
import { ChatSessionManager } from './ChatSessionManager.js';
|
|
9
|
+
import { TokenUsageDisplay } from './tokenUsageDisplay.js';
|
|
10
|
+
import { authMiddleware, withAuth } from './auth.js';
|
|
11
|
+
import {
|
|
12
|
+
// probeTool, // This is the compatibility layer, less critical now
|
|
13
|
+
searchToolInstance, // Keep direct instances for API endpoints
|
|
14
|
+
queryToolInstance,
|
|
15
|
+
extractToolInstance,
|
|
16
|
+
implementToolInstance,
|
|
17
|
+
toolCallEmitter,
|
|
18
|
+
cancelToolExecutions,
|
|
19
|
+
clearToolExecutionData,
|
|
20
|
+
isSessionCancelled
|
|
21
|
+
} from './probeTool.js';
|
|
22
|
+
import { registerRequest, cancelRequest, clearRequest, isRequestActive } from './cancelRequest.js';
|
|
23
|
+
import { JsonChatStorage } from './storage/JsonChatStorage.js';
|
|
24
|
+
|
|
25
|
+
// Get the directory name of the current module
|
|
26
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
|
|
28
|
+
// Global storage instance - will be initialized when web server starts
|
|
29
|
+
let globalStorage = null;
|
|
30
|
+
|
|
31
|
+
// Map to store chat instances by session ID
|
|
32
|
+
const chatSessions = new Map();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Retrieve or create a ChatSessionManager instance keyed by sessionId.
|
|
36
|
+
*/
|
|
37
|
+
function getOrCreateChat(sessionId, apiCredentials = null) {
|
|
38
|
+
if (!sessionId) {
|
|
39
|
+
// Safety fallback: generate a random ID if missing
|
|
40
|
+
sessionId = randomUUID(); // Use crypto.randomUUID() if available/preferred
|
|
41
|
+
console.warn(`[WARN] Missing sessionId, generated fallback: ${sessionId}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Check in-memory cache first
|
|
45
|
+
if (chatSessions.has(sessionId)) {
|
|
46
|
+
const existingChat = chatSessions.get(sessionId);
|
|
47
|
+
// Update activity timestamp in persistent storage
|
|
48
|
+
if (globalStorage) {
|
|
49
|
+
globalStorage.updateSessionActivity(sessionId).catch(err => {
|
|
50
|
+
console.error('Failed to update session activity:', err);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return existingChat;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Create options object with sessionId and API credentials if provided
|
|
57
|
+
const options = {
|
|
58
|
+
sessionId,
|
|
59
|
+
storage: globalStorage,
|
|
60
|
+
debug: process.env.DEBUG_CHAT === '1'
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (apiCredentials) {
|
|
64
|
+
options.apiProvider = apiCredentials.apiProvider;
|
|
65
|
+
options.apiKey = apiCredentials.apiKey;
|
|
66
|
+
options.apiUrl = apiCredentials.apiUrl;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const newChat = new ChatSessionManager(options);
|
|
70
|
+
|
|
71
|
+
// Store in memory cache
|
|
72
|
+
chatSessions.set(sessionId, newChat);
|
|
73
|
+
|
|
74
|
+
// Save session metadata to persistent storage
|
|
75
|
+
if (globalStorage) {
|
|
76
|
+
globalStorage.saveSession({
|
|
77
|
+
id: sessionId,
|
|
78
|
+
createdAt: newChat.createdAt,
|
|
79
|
+
lastActivity: newChat.lastActivity,
|
|
80
|
+
firstMessagePreview: null, // Will be updated when first message is sent
|
|
81
|
+
metadata: {
|
|
82
|
+
apiProvider: apiCredentials?.apiProvider || null
|
|
83
|
+
}
|
|
84
|
+
}).catch(err => {
|
|
85
|
+
console.error('Failed to save session to persistent storage:', err);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (process.env.DEBUG_CHAT === '1') {
|
|
90
|
+
console.log(`[DEBUG] Created and stored new ChatSessionManager instance for session: ${sessionId}. Total sessions: ${chatSessions.size}`);
|
|
91
|
+
if (apiCredentials && apiCredentials.apiKey) {
|
|
92
|
+
console.log(`[DEBUG] Chat instance created with client-provided API credentials (provider: ${apiCredentials.apiProvider})`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return newChat;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Start the web server
|
|
100
|
+
* @param {string} version - The version of the application
|
|
101
|
+
* @param {boolean} hasApiKeys - Whether any API keys are configured
|
|
102
|
+
* @param {Object} options - Additional options
|
|
103
|
+
* @param {boolean} options.allowEdit - Whether to allow editing files via the implement tool
|
|
104
|
+
*/
|
|
105
|
+
export async function startWebServer(version, hasApiKeys = true, options = {}) {
|
|
106
|
+
const allowEdit = options?.allowEdit || false;
|
|
107
|
+
|
|
108
|
+
if (allowEdit) {
|
|
109
|
+
console.log('Edit mode enabled: implement tool is available');
|
|
110
|
+
}
|
|
111
|
+
// Authentication configuration
|
|
112
|
+
const AUTH_ENABLED = process.env.AUTH_ENABLED === '1';
|
|
113
|
+
const AUTH_USERNAME = process.env.AUTH_USERNAME || 'admin';
|
|
114
|
+
const AUTH_PASSWORD = process.env.AUTH_PASSWORD || 'password';
|
|
115
|
+
|
|
116
|
+
if (AUTH_ENABLED) {
|
|
117
|
+
console.log(`Authentication enabled (username: ${AUTH_USERNAME})`);
|
|
118
|
+
} else {
|
|
119
|
+
console.log('Authentication disabled');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Initialize persistent storage for web mode
|
|
123
|
+
globalStorage = new JsonChatStorage({
|
|
124
|
+
webMode: true,
|
|
125
|
+
verbose: process.env.DEBUG_CHAT === '1'
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Initialize storage synchronously before server starts
|
|
129
|
+
try {
|
|
130
|
+
await globalStorage.initialize();
|
|
131
|
+
const stats = await globalStorage.getStats();
|
|
132
|
+
console.log(`Chat history storage: ${stats.storage_type} (${stats.session_count} sessions, ${stats.visible_message_count} messages)`);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.warn('Failed to initialize chat history storage:', error.message);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Map to store SSE clients by session ID
|
|
138
|
+
const sseClients = new Map();
|
|
139
|
+
|
|
140
|
+
// Initialize a default ProbeChat instance for /folders endpoint? Or make folders static?
|
|
141
|
+
// Let's make /folders rely on environment variables directly or a static config
|
|
142
|
+
// to avoid needing a default chat instance just for that.
|
|
143
|
+
const staticAllowedFolders = process.env.ALLOWED_FOLDERS
|
|
144
|
+
? process.env.ALLOWED_FOLDERS.split(',').map(folder => folder.trim()).filter(Boolean)
|
|
145
|
+
: [];
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
let noApiKeysMode = !hasApiKeys;
|
|
149
|
+
if (noApiKeysMode) {
|
|
150
|
+
console.log('Running in No API Keys mode - will show setup instructions to users');
|
|
151
|
+
} else {
|
|
152
|
+
console.log('API keys detected. Chat functionality enabled.');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
// Define the tools available for direct API calls (bypassing LLM loop)
|
|
157
|
+
// Note: probeTool is the backward compatibility wrapper
|
|
158
|
+
const directApiTools = {
|
|
159
|
+
search: searchToolInstance,
|
|
160
|
+
query: queryToolInstance,
|
|
161
|
+
extract: extractToolInstance
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Add implement tool if edit mode is enabled
|
|
165
|
+
if (allowEdit) {
|
|
166
|
+
directApiTools.implement = implementToolInstance;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
// Helper function to send SSE data
|
|
171
|
+
function sendSSEData(res, data, eventType = 'message') {
|
|
172
|
+
const DEBUG = process.env.DEBUG_CHAT === '1';
|
|
173
|
+
try {
|
|
174
|
+
// Check if the response stream is still writable
|
|
175
|
+
if (!res.writable || res.writableEnded) {
|
|
176
|
+
if (DEBUG) console.log(`[DEBUG] SSE stream closed for event type ${eventType}, cannot send.`);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (DEBUG) {
|
|
180
|
+
// console.log(`[DEBUG] Sending SSE data, event type: ${eventType}`); // Can be noisy
|
|
181
|
+
}
|
|
182
|
+
res.write(`event: ${eventType}\n`);
|
|
183
|
+
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
184
|
+
if (DEBUG) {
|
|
185
|
+
// console.log(`[DEBUG] SSE data sent successfully for event: ${eventType}`);
|
|
186
|
+
// const preview = JSON.stringify(data).substring(0, 100);
|
|
187
|
+
// console.log(`[DEBUG] SSE data content preview: ${preview}...`);
|
|
188
|
+
}
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.error(`[ERROR] Error sending SSE data:`, error);
|
|
191
|
+
// Attempt to close the connection gracefully on error?
|
|
192
|
+
try {
|
|
193
|
+
if (res.writable && !res.writableEnded) res.end();
|
|
194
|
+
} catch (closeError) {
|
|
195
|
+
console.error(`[ERROR] Error closing SSE stream after send error:`, closeError);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Map to store active chat instances by session ID for cancellation purposes
|
|
201
|
+
const activeChatInstances = new Map();
|
|
202
|
+
|
|
203
|
+
const server = createServer(async (req, res) => {
|
|
204
|
+
// Apply authentication middleware to all requests first
|
|
205
|
+
const processRequest = (routeHandler) => {
|
|
206
|
+
// First apply authentication middleware
|
|
207
|
+
authMiddleware(req, res, () => {
|
|
208
|
+
// Then process the route if authentication passes
|
|
209
|
+
routeHandler(req, res);
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// Define route handlers
|
|
214
|
+
const routes = {
|
|
215
|
+
// Handle OPTIONS requests for CORS preflight (Common)
|
|
216
|
+
'OPTIONS /api/token-usage': (req, res) => handleOptions(res),
|
|
217
|
+
'OPTIONS /api/sessions': (req, res) => handleOptions(res),
|
|
218
|
+
'OPTIONS /chat': (req, res) => handleOptions(res),
|
|
219
|
+
'OPTIONS /api/search': (req, res) => handleOptions(res),
|
|
220
|
+
'OPTIONS /api/query': (req, res) => handleOptions(res),
|
|
221
|
+
'OPTIONS /api/extract': (req, res) => handleOptions(res),
|
|
222
|
+
'OPTIONS /api/implement': (req, res) => handleOptions(res),
|
|
223
|
+
'OPTIONS /cancel-request': (req, res) => handleOptions(res),
|
|
224
|
+
'OPTIONS /folders': (req, res) => handleOptions(res), // Added for /folders
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
// Token usage API endpoint
|
|
228
|
+
'GET /api/token-usage': (req, res) => {
|
|
229
|
+
const sessionId = getSessionIdFromUrl(req);
|
|
230
|
+
if (!sessionId) return sendError(res, 400, 'Missing sessionId parameter');
|
|
231
|
+
|
|
232
|
+
const chatInstance = chatSessions.get(sessionId);
|
|
233
|
+
if (!chatInstance) return sendError(res, 404, 'Session not found');
|
|
234
|
+
|
|
235
|
+
const DEBUG = process.env.DEBUG_CHAT === '1';
|
|
236
|
+
|
|
237
|
+
// Update the tokenCounter's history with the chat history
|
|
238
|
+
if (chatInstance.tokenCounter && typeof chatInstance.tokenCounter.updateHistory === 'function' &&
|
|
239
|
+
chatInstance.history) {
|
|
240
|
+
chatInstance.tokenCounter.updateHistory(chatInstance.history);
|
|
241
|
+
if (DEBUG) {
|
|
242
|
+
console.log(`[DEBUG] Updated tokenCounter history with ${chatInstance.history.length} messages for token usage request`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Get raw token usage data from the chat instance
|
|
247
|
+
const tokenUsage = chatInstance.getTokenUsage();
|
|
248
|
+
|
|
249
|
+
if (DEBUG) {
|
|
250
|
+
console.log(`[DEBUG] Token usage request - Context window size: ${tokenUsage.contextWindow}`);
|
|
251
|
+
console.log(`[DEBUG] Token usage request - Cache metrics - Read: ${tokenUsage.current.cacheRead}, Write: ${tokenUsage.current.cacheWrite}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Send the raw token usage data to the client
|
|
255
|
+
// The client-side JavaScript will handle formatting
|
|
256
|
+
sendJson(res, 200, tokenUsage);
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
// Session history API endpoint for URL-based session restoration
|
|
260
|
+
'GET /api/session/:sessionId/history': async (req, res) => {
|
|
261
|
+
const sessionId = extractSessionIdFromHistoryPath(req.url);
|
|
262
|
+
if (!sessionId) return sendError(res, 400, 'Missing sessionId in URL path');
|
|
263
|
+
|
|
264
|
+
const DEBUG = process.env.DEBUG_CHAT === '1';
|
|
265
|
+
if (DEBUG) {
|
|
266
|
+
console.log(`[DEBUG] Fetching history for session: ${sessionId}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
// First check if session is in memory cache
|
|
271
|
+
const chatInstance = chatSessions.get(sessionId);
|
|
272
|
+
let history = [];
|
|
273
|
+
let tokenUsage = null;
|
|
274
|
+
let exists = false;
|
|
275
|
+
|
|
276
|
+
if (chatInstance) {
|
|
277
|
+
// Session is active - use in-memory display history for consistency
|
|
278
|
+
history = chatInstance.displayHistory || [];
|
|
279
|
+
tokenUsage = chatInstance.getTokenUsage();
|
|
280
|
+
exists = true;
|
|
281
|
+
} else if (globalStorage) {
|
|
282
|
+
// Session not in memory - try loading from persistent storage
|
|
283
|
+
const persistentHistory = await globalStorage.getSessionHistory(sessionId);
|
|
284
|
+
if (persistentHistory && persistentHistory.length > 0) {
|
|
285
|
+
// Convert stored messages to display format
|
|
286
|
+
history = persistentHistory.map(msg => ({
|
|
287
|
+
role: msg.role,
|
|
288
|
+
content: msg.content,
|
|
289
|
+
timestamp: new Date(msg.timestamp).toISOString(),
|
|
290
|
+
displayType: msg.display_type,
|
|
291
|
+
visible: msg.visible,
|
|
292
|
+
images: msg.images || []
|
|
293
|
+
}));
|
|
294
|
+
exists = true;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
sendJson(res, 200, {
|
|
299
|
+
history: history,
|
|
300
|
+
tokenUsage: tokenUsage,
|
|
301
|
+
sessionId: sessionId,
|
|
302
|
+
exists: exists,
|
|
303
|
+
timestamp: new Date().toISOString()
|
|
304
|
+
});
|
|
305
|
+
} catch (error) {
|
|
306
|
+
console.error('Error fetching session history:', error);
|
|
307
|
+
sendJson(res, 200, {
|
|
308
|
+
history: [],
|
|
309
|
+
tokenUsage: null,
|
|
310
|
+
sessionId: sessionId,
|
|
311
|
+
exists: false,
|
|
312
|
+
timestamp: new Date().toISOString()
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
|
|
317
|
+
// Sessions list endpoint for history dropdown
|
|
318
|
+
'GET /api/sessions': async (req, res) => {
|
|
319
|
+
const DEBUG = process.env.DEBUG_CHAT === '1';
|
|
320
|
+
if (DEBUG) {
|
|
321
|
+
console.log(`[DEBUG] Fetching sessions list`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
const sessions = [];
|
|
326
|
+
const now = Date.now();
|
|
327
|
+
const maxAge = 2 * 60 * 60 * 1000; // 2 hours in milliseconds
|
|
328
|
+
|
|
329
|
+
if (globalStorage) {
|
|
330
|
+
// Get sessions from persistent storage
|
|
331
|
+
const storedSessions = await globalStorage.listSessions(50);
|
|
332
|
+
|
|
333
|
+
for (const session of storedSessions) {
|
|
334
|
+
// Skip inactive sessions older than 2 hours
|
|
335
|
+
if (now - session.last_activity > maxAge) {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Use stored preview or generate from session metadata
|
|
340
|
+
let preview = session.first_message_preview;
|
|
341
|
+
if (!preview) {
|
|
342
|
+
// If no preview stored, try to get first message from history
|
|
343
|
+
const history = await globalStorage.getSessionHistory(session.id, 1);
|
|
344
|
+
if (history.length > 0 && history[0].role === 'user') {
|
|
345
|
+
const cleanContent = extractContentFromMessage(history[0].content);
|
|
346
|
+
preview = cleanContent.length > 100
|
|
347
|
+
? cleanContent.substring(0, 100) + '...'
|
|
348
|
+
: cleanContent;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (preview) {
|
|
353
|
+
sessions.push({
|
|
354
|
+
sessionId: session.id,
|
|
355
|
+
preview: preview,
|
|
356
|
+
messageCount: 0, // We could calculate this but it's not critical
|
|
357
|
+
createdAt: new Date(session.created_at).toISOString(),
|
|
358
|
+
lastActivity: new Date(session.last_activity).toISOString(),
|
|
359
|
+
relativeTime: getRelativeTime(session.last_activity)
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
} else {
|
|
364
|
+
// Fallback to in-memory sessions
|
|
365
|
+
for (const [sessionId, chatInstance] of chatSessions.entries()) {
|
|
366
|
+
// Skip sessions without any history
|
|
367
|
+
if (!chatInstance.history || chatInstance.history.length === 0) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Get session metadata
|
|
372
|
+
const createdAt = chatInstance.createdAt || now;
|
|
373
|
+
const lastActivity = chatInstance.lastActivity || createdAt;
|
|
374
|
+
|
|
375
|
+
// Skip inactive sessions older than 2 hours
|
|
376
|
+
if (now - lastActivity > maxAge) {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Find the first user message for preview
|
|
381
|
+
const firstUserMessage = chatInstance.history.find(msg => msg.role === 'user');
|
|
382
|
+
if (!firstUserMessage) {
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Extract clean content and create preview
|
|
387
|
+
const cleanContent = extractContentFromMessage(firstUserMessage.content);
|
|
388
|
+
const preview = cleanContent.length > 100
|
|
389
|
+
? cleanContent.substring(0, 100) + '...'
|
|
390
|
+
: cleanContent;
|
|
391
|
+
|
|
392
|
+
sessions.push({
|
|
393
|
+
sessionId: sessionId,
|
|
394
|
+
preview: preview,
|
|
395
|
+
messageCount: chatInstance.history.length,
|
|
396
|
+
createdAt: new Date(createdAt).toISOString(),
|
|
397
|
+
lastActivity: new Date(lastActivity).toISOString(),
|
|
398
|
+
relativeTime: getRelativeTime(lastActivity)
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (DEBUG) {
|
|
404
|
+
console.log(`[DEBUG] Returning ${sessions.length} sessions`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
sendJson(res, 200, {
|
|
408
|
+
sessions: sessions,
|
|
409
|
+
total: sessions.length,
|
|
410
|
+
timestamp: new Date().toISOString()
|
|
411
|
+
});
|
|
412
|
+
} catch (error) {
|
|
413
|
+
console.error('Error fetching sessions list:', error);
|
|
414
|
+
sendJson(res, 500, { error: 'Failed to fetch sessions' });
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
// Static file routes
|
|
418
|
+
'GET /logo.png': (req, res) => serveStatic(res, join(__dirname, 'logo.png'), 'image/png'),
|
|
419
|
+
// UI Routes
|
|
420
|
+
'GET /': (req, res) => {
|
|
421
|
+
const htmlPath = join(__dirname, 'index.html');
|
|
422
|
+
serveHtml(res, htmlPath, { 'data-no-api-keys': noApiKeysMode ? 'true' : 'false' });
|
|
423
|
+
},
|
|
424
|
+
|
|
425
|
+
// Chat session route - serves HTML with injected session ID
|
|
426
|
+
'GET /chat/:sessionId': (req, res) => {
|
|
427
|
+
const sessionId = extractSessionIdFromPath(req.url);
|
|
428
|
+
if (!sessionId) {
|
|
429
|
+
return sendError(res, 400, 'Invalid session ID in URL');
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Validate that session exists or at least has a valid UUID format
|
|
433
|
+
if (!isValidUUID(sessionId)) {
|
|
434
|
+
return sendError(res, 400, 'Invalid session ID format');
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const htmlPath = join(__dirname, 'index.html');
|
|
438
|
+
serveHtml(res, htmlPath, {
|
|
439
|
+
'data-no-api-keys': noApiKeysMode ? 'true' : 'false',
|
|
440
|
+
'data-session-id': sessionId
|
|
441
|
+
});
|
|
442
|
+
},
|
|
443
|
+
|
|
444
|
+
'GET /folders': (req, res) => {
|
|
445
|
+
const currentWorkingDir = process.cwd();
|
|
446
|
+
// Use static config or environment variables directly
|
|
447
|
+
const folders = staticAllowedFolders.length > 0 ? staticAllowedFolders : [currentWorkingDir];
|
|
448
|
+
// Use first allowed folder as currentDir, or fall back to process.cwd()
|
|
449
|
+
const currentDir = staticAllowedFolders.length > 0 ? staticAllowedFolders[0] : currentWorkingDir;
|
|
450
|
+
|
|
451
|
+
sendJson(res, 200, {
|
|
452
|
+
folders: folders,
|
|
453
|
+
currentDir: currentDir,
|
|
454
|
+
noApiKeysMode: noApiKeysMode
|
|
455
|
+
});
|
|
456
|
+
},
|
|
457
|
+
|
|
458
|
+
'GET /openapi.yaml': (req, res) => serveStatic(res, join(__dirname, 'openapi.yaml'), 'text/yaml'),
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
// SSE endpoint for tool calls - NO AUTH for easier client implementation
|
|
462
|
+
'GET /api/tool-events': (req, res) => {
|
|
463
|
+
const DEBUG = process.env.DEBUG_CHAT === '1';
|
|
464
|
+
const sessionId = getSessionIdFromUrl(req);
|
|
465
|
+
if (!sessionId) {
|
|
466
|
+
if (DEBUG) console.error(`[DEBUG] SSE: No sessionId found in URL: ${req.url}`);
|
|
467
|
+
return sendError(res, 400, 'Missing sessionId parameter');
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (DEBUG) console.log(`[DEBUG] SSE: Setting up connection for session: ${sessionId}`);
|
|
471
|
+
|
|
472
|
+
// Set headers for SSE
|
|
473
|
+
res.writeHead(200, {
|
|
474
|
+
'Content-Type': 'text/event-stream',
|
|
475
|
+
'Cache-Control': 'no-cache',
|
|
476
|
+
'Connection': 'keep-alive',
|
|
477
|
+
'Access-Control-Allow-Origin': '*' // Allow all origins for SSE
|
|
478
|
+
});
|
|
479
|
+
if (DEBUG) console.log(`[DEBUG] SSE: Headers set for session: ${sessionId}`);
|
|
480
|
+
|
|
481
|
+
// Send initial connection established event
|
|
482
|
+
const connectionData = { type: 'connection', message: 'SSE Connection Established', sessionId, timestamp: new Date().toISOString() };
|
|
483
|
+
sendSSEData(res, connectionData, 'connection');
|
|
484
|
+
if (DEBUG) console.log(`[DEBUG] SSE: Sent connection event for session: ${sessionId}`);
|
|
485
|
+
|
|
486
|
+
// Send a test event (optional, for debugging)
|
|
487
|
+
// setTimeout(() => sendSSEData(res, { type: 'test', message: 'Test event', sessionId }, 'test'), 1000);
|
|
488
|
+
|
|
489
|
+
// Function to handle tool call events for this session
|
|
490
|
+
const handleToolCall = (toolCall) => {
|
|
491
|
+
if (DEBUG) {
|
|
492
|
+
// console.log(`[DEBUG] SSE: Handling tool call event for session ${sessionId}: ${toolCall.name}`); // Noisy
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Store tool call in chat session's display history
|
|
496
|
+
const chatInstance = chatSessions.get(sessionId);
|
|
497
|
+
if (chatInstance && toolCall.status === 'completed') {
|
|
498
|
+
// Only store completed tool calls that users see
|
|
499
|
+
const displayToolCall = {
|
|
500
|
+
role: 'toolCall',
|
|
501
|
+
name: toolCall.name,
|
|
502
|
+
args: toolCall.args || {},
|
|
503
|
+
timestamp: toolCall.timestamp || new Date().toISOString(),
|
|
504
|
+
visible: true,
|
|
505
|
+
displayType: 'toolCall'
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
if (!chatInstance.displayHistory) {
|
|
509
|
+
chatInstance.displayHistory = [];
|
|
510
|
+
}
|
|
511
|
+
chatInstance.displayHistory.push(displayToolCall);
|
|
512
|
+
|
|
513
|
+
// Also save to persistent storage
|
|
514
|
+
if (globalStorage) {
|
|
515
|
+
globalStorage.saveMessage(sessionId, {
|
|
516
|
+
role: 'toolCall',
|
|
517
|
+
content: `Tool: ${toolCall.name}\nArgs: ${JSON.stringify(toolCall.args || {}, null, 2)}`,
|
|
518
|
+
timestamp: toolCall.timestamp ? new Date(toolCall.timestamp).getTime() : Date.now(),
|
|
519
|
+
displayType: 'toolCall',
|
|
520
|
+
visible: 1,
|
|
521
|
+
metadata: {
|
|
522
|
+
name: toolCall.name,
|
|
523
|
+
args: toolCall.args || {}
|
|
524
|
+
}
|
|
525
|
+
}).catch(err => {
|
|
526
|
+
console.error('Failed to save tool call to persistent storage:', err);
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (DEBUG) {
|
|
531
|
+
console.log(`[DEBUG] Stored tool call in display history: ${toolCall.name}`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Ensure data is serializable and add timestamp if missing
|
|
536
|
+
const serializableCall = {
|
|
537
|
+
...toolCall,
|
|
538
|
+
timestamp: toolCall.timestamp || new Date().toISOString(),
|
|
539
|
+
_sse_sent_at: new Date().toISOString()
|
|
540
|
+
};
|
|
541
|
+
sendSSEData(res, serializableCall, 'toolCall'); // Event type 'toolCall'
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// Register event listener for this specific session
|
|
545
|
+
const eventName = `toolCall:${sessionId}`;
|
|
546
|
+
// Remove previous listener for this exact session ID if any (safety measure)
|
|
547
|
+
const existingHandler = sseClients.get(sessionId)?.handler;
|
|
548
|
+
if (existingHandler) {
|
|
549
|
+
toolCallEmitter.removeListener(eventName, existingHandler);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
toolCallEmitter.on(eventName, handleToolCall);
|
|
553
|
+
if (DEBUG) console.log(`[DEBUG] SSE: Registered listener for ${eventName}`);
|
|
554
|
+
|
|
555
|
+
// Store client and handler for cleanup
|
|
556
|
+
sseClients.set(sessionId, { res, handler: handleToolCall });
|
|
557
|
+
if (DEBUG) console.log(`[DEBUG] SSE: Client added for session ${sessionId}. Total clients: ${sseClients.size}`);
|
|
558
|
+
|
|
559
|
+
// Handle client disconnect
|
|
560
|
+
req.on('close', () => {
|
|
561
|
+
if (DEBUG) console.log(`[DEBUG] SSE: Client disconnecting: ${sessionId}`);
|
|
562
|
+
toolCallEmitter.removeListener(eventName, handleToolCall);
|
|
563
|
+
sseClients.delete(sessionId);
|
|
564
|
+
if (DEBUG) console.log(`[DEBUG] SSE: Client removed for session ${sessionId}. Remaining clients: ${sseClients.size}`);
|
|
565
|
+
});
|
|
566
|
+
},
|
|
567
|
+
|
|
568
|
+
// Cancellation endpoint
|
|
569
|
+
'POST /cancel-request': async (req, res) => {
|
|
570
|
+
handlePostRequest(req, res, async (body) => {
|
|
571
|
+
const { sessionId } = body;
|
|
572
|
+
if (!sessionId) return sendError(res, 400, 'Missing required parameter: sessionId');
|
|
573
|
+
|
|
574
|
+
const DEBUG = process.env.DEBUG_CHAT === '1';
|
|
575
|
+
if (DEBUG) console.log(`\n[DEBUG] ===== Cancel Request for Session: ${sessionId} =====`);
|
|
576
|
+
|
|
577
|
+
// 1. Cancel Tool Executions (via probeTool.js)
|
|
578
|
+
const toolExecutionsCancelled = cancelToolExecutions(sessionId);
|
|
579
|
+
|
|
580
|
+
// 2. Cancel Active Chat Request (via probeChat instance)
|
|
581
|
+
const chatInstance = activeChatInstances.get(sessionId);
|
|
582
|
+
let chatInstanceAborted = false;
|
|
583
|
+
if (chatInstance && typeof chatInstance.abort === 'function') {
|
|
584
|
+
try {
|
|
585
|
+
chatInstance.abort(); // This sets chatInstance.cancelled = true and aborts controller
|
|
586
|
+
chatInstanceAborted = true;
|
|
587
|
+
if (DEBUG) console.log(`[DEBUG] Aborted chat instance processing for session: ${sessionId}`);
|
|
588
|
+
} catch (error) {
|
|
589
|
+
console.error(`Error aborting chat instance for session ${sessionId}:`, error);
|
|
590
|
+
}
|
|
591
|
+
} else {
|
|
592
|
+
if (DEBUG) console.log(`[DEBUG] No active chat instance found in map for session ${sessionId} to abort.`);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// 3. Cancel the request tracking entry (via cancelRequest.js - might be redundant if chatInstance.abort works)
|
|
596
|
+
const requestCancelled = cancelRequest(sessionId); // This calls the registered abort function
|
|
597
|
+
|
|
598
|
+
// Clean up map entry (might be done in finally block of chat endpoint too)
|
|
599
|
+
activeChatInstances.delete(sessionId);
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
console.log(`Cancellation processed for session ${sessionId}: Tools=${toolExecutionsCancelled}, Chat=${chatInstanceAborted}, RequestTracking=${requestCancelled}`);
|
|
603
|
+
|
|
604
|
+
sendJson(res, 200, {
|
|
605
|
+
success: true,
|
|
606
|
+
message: 'Cancellation request processed',
|
|
607
|
+
details: { toolExecutionsCancelled, chatInstanceAborted, requestCancelled },
|
|
608
|
+
timestamp: new Date().toISOString()
|
|
609
|
+
});
|
|
610
|
+
});
|
|
611
|
+
},
|
|
612
|
+
|
|
613
|
+
// --- Direct API Tool Endpoints (Bypass LLM Loop) ---
|
|
614
|
+
'POST /api/search': async (req, res) => {
|
|
615
|
+
handlePostRequest(req, res, async (body) => {
|
|
616
|
+
const { query, path, allow_tests, maxResults, maxTokens, sessionId: reqSessionId } = body; // Renamed params
|
|
617
|
+
if (!query) return sendError(res, 400, 'Missing required parameter: query');
|
|
618
|
+
|
|
619
|
+
const sessionId = reqSessionId || randomUUID(); // Use provided or generate new for direct call
|
|
620
|
+
const toolParams = { query, path, allow_tests, maxResults, maxTokens, sessionId };
|
|
621
|
+
|
|
622
|
+
await executeDirectTool(res, directApiTools.search, 'search', toolParams, sessionId);
|
|
623
|
+
});
|
|
624
|
+
},
|
|
625
|
+
'POST /api/query': async (req, res) => {
|
|
626
|
+
handlePostRequest(req, res, async (body) => {
|
|
627
|
+
const { pattern, path, language, allow_tests, sessionId: reqSessionId } = body;
|
|
628
|
+
if (!pattern) return sendError(res, 400, 'Missing required parameter: pattern');
|
|
629
|
+
|
|
630
|
+
const sessionId = reqSessionId || randomUUID();
|
|
631
|
+
const toolParams = { pattern, path, language, allow_tests, sessionId };
|
|
632
|
+
|
|
633
|
+
await executeDirectTool(res, directApiTools.query, 'query', toolParams, sessionId);
|
|
634
|
+
});
|
|
635
|
+
},
|
|
636
|
+
'POST /api/extract': async (req, res) => {
|
|
637
|
+
handlePostRequest(req, res, async (body) => {
|
|
638
|
+
const { file_path, line, end_line, allow_tests, context_lines, format, input_content, sessionId: reqSessionId } = body;
|
|
639
|
+
// file_path or input_content is required by the underlying tool implementation usually
|
|
640
|
+
if (!file_path && !input_content) return sendError(res, 400, 'Missing required parameter: file_path or input_content');
|
|
641
|
+
|
|
642
|
+
const sessionId = reqSessionId || randomUUID();
|
|
643
|
+
const toolParams = { file_path, line, end_line, allow_tests, context_lines, format, input_content, sessionId };
|
|
644
|
+
|
|
645
|
+
await executeDirectTool(res, directApiTools.extract, 'extract', toolParams, sessionId);
|
|
646
|
+
});
|
|
647
|
+
},
|
|
648
|
+
|
|
649
|
+
// Implement tool endpoint (only available if allowEdit is true)
|
|
650
|
+
'POST /api/implement': async (req, res) => {
|
|
651
|
+
// Check if edit mode is enabled
|
|
652
|
+
if (!directApiTools.implement) {
|
|
653
|
+
return sendError(res, 403, 'Implement tool is not enabled. Start server with --allow-edit to enable.');
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
handlePostRequest(req, res, async (body) => {
|
|
657
|
+
const { task, sessionId: reqSessionId } = body;
|
|
658
|
+
if (!task) return sendError(res, 400, 'Missing required parameter: task');
|
|
659
|
+
|
|
660
|
+
const sessionId = reqSessionId || randomUUID();
|
|
661
|
+
const toolParams = { task, sessionId };
|
|
662
|
+
|
|
663
|
+
await executeDirectTool(res, directApiTools.implement, 'implement', toolParams, sessionId);
|
|
664
|
+
});
|
|
665
|
+
},
|
|
666
|
+
|
|
667
|
+
// --- Main Chat Endpoint (Handles the Loop) ---
|
|
668
|
+
'POST /chat': (req, res) => { // This is the route used by the frontend UI
|
|
669
|
+
handlePostRequest(req, res, async (requestData) => {
|
|
670
|
+
const {
|
|
671
|
+
message,
|
|
672
|
+
images = [], // Array of base64 image URLs
|
|
673
|
+
sessionId: reqSessionId,
|
|
674
|
+
clearHistory,
|
|
675
|
+
apiProvider,
|
|
676
|
+
apiKey,
|
|
677
|
+
apiUrl
|
|
678
|
+
} = requestData;
|
|
679
|
+
const DEBUG = process.env.DEBUG_CHAT === '1';
|
|
680
|
+
|
|
681
|
+
if (DEBUG) {
|
|
682
|
+
console.log(`\n[DEBUG] ===== UI Chat Request =====`);
|
|
683
|
+
console.log(`[DEBUG] Request Data:`, { ...requestData, apiKey: requestData.apiKey ? '******' : undefined });
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// --- Session and Instance Management ---
|
|
687
|
+
const chatSessionId = reqSessionId || randomUUID(); // Ensure we always have a session ID
|
|
688
|
+
if (!reqSessionId && DEBUG) console.log(`[DEBUG] No session ID from UI, generated: ${chatSessionId}`);
|
|
689
|
+
else if (DEBUG) console.log(`[DEBUG] Using session ID from UI: ${chatSessionId}`);
|
|
690
|
+
|
|
691
|
+
// Get or create the chat instance *without* API key overrides here.
|
|
692
|
+
// API keys from request are ignored for existing sessions to preserve history consistency.
|
|
693
|
+
// If a *new* session is created AND keys are provided, the ProbeChat constructor *should* handle them.
|
|
694
|
+
// Extract API credentials from request if available
|
|
695
|
+
const apiCredentials = apiKey ? { apiProvider, apiKey, apiUrl } : null;
|
|
696
|
+
|
|
697
|
+
// Get or create chat instance with API credentials
|
|
698
|
+
const chatInstance = getOrCreateChat(chatSessionId, apiCredentials);
|
|
699
|
+
|
|
700
|
+
// Update last activity timestamp
|
|
701
|
+
chatInstance.lastActivity = Date.now();
|
|
702
|
+
|
|
703
|
+
// Check if API keys are needed but missing
|
|
704
|
+
if (chatInstance.noApiKeysMode) {
|
|
705
|
+
console.warn(`[WARN] Chat request for session ${chatSessionId} cannot proceed: No API keys configured.`);
|
|
706
|
+
return sendError(res, 503, 'Chat service unavailable: API key not configured on server.');
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Register this request as active for cancellation
|
|
710
|
+
registerRequest(chatSessionId, { abort: () => chatInstance.abort() });
|
|
711
|
+
if (DEBUG) console.log(`[DEBUG] Registered cancellable request for session: ${chatSessionId}`);
|
|
712
|
+
activeChatInstances.set(chatSessionId, chatInstance); // Store for direct access during cancellation
|
|
713
|
+
|
|
714
|
+
// --- Handle Clear History ---
|
|
715
|
+
if (message === '__clear_history__' || clearHistory) {
|
|
716
|
+
console.log(`Clearing chat history for session: ${chatSessionId}`);
|
|
717
|
+
const newSessionId = chatInstance.clearHistory(); // clearHistory now returns the *new* session ID
|
|
718
|
+
// Remove old session state
|
|
719
|
+
clearRequest(chatSessionId);
|
|
720
|
+
activeChatInstances.delete(chatSessionId);
|
|
721
|
+
clearToolExecutionData(chatSessionId);
|
|
722
|
+
chatSessions.delete(chatSessionId); // Remove old instance from map
|
|
723
|
+
// We don't create the *new* instance here, it will be created on the *next* message request
|
|
724
|
+
|
|
725
|
+
// Create a new empty token usage object for the cleared history
|
|
726
|
+
const emptyTokenUsage = {
|
|
727
|
+
contextWindow: 0,
|
|
728
|
+
current: {
|
|
729
|
+
request: 0,
|
|
730
|
+
response: 0,
|
|
731
|
+
total: 0,
|
|
732
|
+
cacheRead: 0,
|
|
733
|
+
cacheWrite: 0,
|
|
734
|
+
cacheTotal: 0
|
|
735
|
+
},
|
|
736
|
+
total: {
|
|
737
|
+
request: 0,
|
|
738
|
+
response: 0,
|
|
739
|
+
total: 0,
|
|
740
|
+
cacheRead: 0,
|
|
741
|
+
cacheWrite: 0,
|
|
742
|
+
cacheTotal: 0
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
sendJson(res, 200, {
|
|
747
|
+
response: 'Chat history cleared',
|
|
748
|
+
tokenUsage: emptyTokenUsage, // Include empty token usage data
|
|
749
|
+
newSessionId: newSessionId, // Inform UI about the new ID
|
|
750
|
+
timestamp: new Date().toISOString()
|
|
751
|
+
});
|
|
752
|
+
return; // Stop processing
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// --- Execute Chat Loop (Non-Streaming Response) ---
|
|
756
|
+
// The loop is inside chatInstance.chat now.
|
|
757
|
+
// We expect the *final* result string back.
|
|
758
|
+
try {
|
|
759
|
+
// ChatSessionManager handles session ID and API credentials internally
|
|
760
|
+
// Only pass the message and images
|
|
761
|
+
const result = await chatInstance.chat(message, images);
|
|
762
|
+
|
|
763
|
+
// Check if cancelled *during* the chat call (ProbeChat throws error)
|
|
764
|
+
// Error handled in catch block
|
|
765
|
+
|
|
766
|
+
// Handle the new structured response format
|
|
767
|
+
let responseText;
|
|
768
|
+
let tokenUsage;
|
|
769
|
+
|
|
770
|
+
if (result && typeof result === 'object' && 'response' in result) {
|
|
771
|
+
// New format: { response: string, tokenUsage: object }
|
|
772
|
+
responseText = result.response;
|
|
773
|
+
tokenUsage = result.tokenUsage;
|
|
774
|
+
|
|
775
|
+
if (process.env.DEBUG_CHAT === '1') {
|
|
776
|
+
console.log(`[DEBUG] Received structured response with token usage data`);
|
|
777
|
+
console.log(`[DEBUG] Context window size: ${tokenUsage.contextWindow}`);
|
|
778
|
+
console.log(`[DEBUG] Cache metrics - Read: ${tokenUsage.current.cacheRead}, Write: ${tokenUsage.current.cacheWrite}`);
|
|
779
|
+
}
|
|
780
|
+
} else {
|
|
781
|
+
// Legacy format: string response
|
|
782
|
+
responseText = result;
|
|
783
|
+
tokenUsage = chatInstance.getTokenUsage(); // Get token usage separately
|
|
784
|
+
|
|
785
|
+
if (process.env.DEBUG_CHAT === '1') {
|
|
786
|
+
console.log(`[DEBUG] Received legacy response format, fetched token usage separately`);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// Create the response object with the response text and token usage data
|
|
791
|
+
const responseObject = {
|
|
792
|
+
response: responseText,
|
|
793
|
+
tokenUsage: tokenUsage,
|
|
794
|
+
sessionId: chatSessionId,
|
|
795
|
+
timestamp: new Date().toISOString()
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
// Send the response with token usage in both the body and header
|
|
799
|
+
sendJson(res, 200, responseObject, { 'X-Token-Usage': JSON.stringify(tokenUsage) });
|
|
800
|
+
|
|
801
|
+
console.log(`Finished chat request for session: ${chatSessionId}`);
|
|
802
|
+
|
|
803
|
+
} catch (error) {
|
|
804
|
+
// Check if the error is actually a structured response with token usage
|
|
805
|
+
let errorResponse = error;
|
|
806
|
+
let tokenUsage;
|
|
807
|
+
|
|
808
|
+
if (error && typeof error === 'object' && error.response && error.tokenUsage) {
|
|
809
|
+
// This is a structured error response from probeChat
|
|
810
|
+
errorResponse = error.response;
|
|
811
|
+
tokenUsage = error.tokenUsage;
|
|
812
|
+
|
|
813
|
+
if (process.env.DEBUG_CHAT === '1') {
|
|
814
|
+
console.log(`[DEBUG] Received structured error response with token usage data`);
|
|
815
|
+
console.log(`[DEBUG] Context window size: ${tokenUsage.contextWindow}`);
|
|
816
|
+
console.log(`[DEBUG] Cache metrics - Read: ${tokenUsage.current.cacheRead}, Write: ${tokenUsage.current.cacheWrite}`);
|
|
817
|
+
}
|
|
818
|
+
} else {
|
|
819
|
+
// Get token usage separately for regular errors
|
|
820
|
+
|
|
821
|
+
// First update the tokenCounter's history with the chat history
|
|
822
|
+
if (chatInstance.tokenCounter && typeof chatInstance.tokenCounter.updateHistory === 'function' &&
|
|
823
|
+
chatInstance.history) {
|
|
824
|
+
chatInstance.tokenCounter.updateHistory(chatInstance.history);
|
|
825
|
+
if (DEBUG) {
|
|
826
|
+
console.log(`[DEBUG] Updated tokenCounter history with ${chatInstance.history.length} messages for error case`);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// Force recalculation of context window size
|
|
831
|
+
if (chatInstance.tokenCounter && typeof chatInstance.tokenCounter.calculateContextSize === 'function') {
|
|
832
|
+
chatInstance.tokenCounter.calculateContextSize(chatInstance.history);
|
|
833
|
+
if (DEBUG) {
|
|
834
|
+
console.log(`[DEBUG] Forced recalculation of context window size for error case`);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// Get updated token usage after history update and recalculation
|
|
839
|
+
tokenUsage = chatInstance.getTokenUsage();
|
|
840
|
+
|
|
841
|
+
if (DEBUG) {
|
|
842
|
+
console.log(`[DEBUG] Error case - Final context window size: ${tokenUsage.contextWindow}`);
|
|
843
|
+
console.log(`[DEBUG] Error case - Cache metrics - Read: ${tokenUsage.current.cacheRead}, Write: ${tokenUsage.current.cacheWrite}`);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// Handle errors, including cancellation
|
|
848
|
+
if (errorResponse.message && errorResponse.message.includes('cancelled') ||
|
|
849
|
+
(typeof errorResponse === 'string' && errorResponse.includes('cancelled'))) {
|
|
850
|
+
console.log(`Chat request processing was cancelled for session: ${chatSessionId}`);
|
|
851
|
+
// Send structured error response with token usage
|
|
852
|
+
sendJson(res, 499, {
|
|
853
|
+
error: 'Request cancelled by user',
|
|
854
|
+
tokenUsage: tokenUsage,
|
|
855
|
+
sessionId: chatSessionId,
|
|
856
|
+
timestamp: new Date().toISOString()
|
|
857
|
+
}); // 499 Client Closed Request
|
|
858
|
+
} else {
|
|
859
|
+
console.error(`Error processing chat for session ${chatSessionId}:`, error);
|
|
860
|
+
// Send structured error response with token usage
|
|
861
|
+
sendJson(res, 500, {
|
|
862
|
+
error: `Chat processing error: ${typeof errorResponse === 'string' ? errorResponse : errorResponse.message || 'Unknown error'}`,
|
|
863
|
+
tokenUsage: tokenUsage,
|
|
864
|
+
sessionId: chatSessionId,
|
|
865
|
+
timestamp: new Date().toISOString()
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
} finally {
|
|
869
|
+
// Cleanup regardless of success, error, or cancellation
|
|
870
|
+
clearRequest(chatSessionId);
|
|
871
|
+
activeChatInstances.delete(chatSessionId);
|
|
872
|
+
// Don't clear tool execution data here, it might be needed if user retries
|
|
873
|
+
// clearToolExecutionData(chatSessionId);
|
|
874
|
+
if (DEBUG) console.log(`[DEBUG] Cleaned up active request tracking for session: ${chatSessionId}`);
|
|
875
|
+
}
|
|
876
|
+
}); // End handlePostRequest for /chat
|
|
877
|
+
} // End /chat route
|
|
878
|
+
}; // End routes object
|
|
879
|
+
|
|
880
|
+
// --- Request Routing ---
|
|
881
|
+
const parsedUrl = new URL(req.url, `http://${req.headers.host}`);
|
|
882
|
+
const routeKey = `${req.method} ${parsedUrl.pathname}`;
|
|
883
|
+
let handler = routes[routeKey];
|
|
884
|
+
|
|
885
|
+
// Handle dynamic routes if no exact match found
|
|
886
|
+
if (!handler) {
|
|
887
|
+
// Check for /chat/:sessionId pattern
|
|
888
|
+
if (req.method === 'GET' && parsedUrl.pathname.match(/^\/chat\/[^/?]+$/)) {
|
|
889
|
+
handler = routes['GET /chat/:sessionId'];
|
|
890
|
+
}
|
|
891
|
+
// Check for /api/session/:sessionId/history pattern
|
|
892
|
+
else if (req.method === 'GET' && parsedUrl.pathname.match(/^\/api\/session\/[^/?]+\/history$/)) {
|
|
893
|
+
handler = routes['GET /api/session/:sessionId/history'];
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
if (handler) {
|
|
898
|
+
// Skip auth for specific public routes
|
|
899
|
+
const publicRoutes = ['GET /openapi.yaml', 'GET /api/tool-events', 'GET /logo.png', 'GET /', 'GET /folders', 'OPTIONS']; // Add OPTIONS
|
|
900
|
+
const isPublicRoute = publicRoutes.includes(routeKey) || req.method === 'OPTIONS' ||
|
|
901
|
+
parsedUrl.pathname.match(/^\/chat\/[^/?]+$/) || // Chat sessions are public
|
|
902
|
+
parsedUrl.pathname.match(/^\/api\/session\/[^/?]+\/history$/); // History API is public
|
|
903
|
+
|
|
904
|
+
if (isPublicRoute) {
|
|
905
|
+
handler(req, res);
|
|
906
|
+
} else {
|
|
907
|
+
processRequest(handler); // Apply auth middleware
|
|
908
|
+
}
|
|
909
|
+
} else {
|
|
910
|
+
// No route match, return 404
|
|
911
|
+
sendError(res, 404, 'Not Found');
|
|
912
|
+
}
|
|
913
|
+
}); // End createServer
|
|
914
|
+
|
|
915
|
+
// Start the server
|
|
916
|
+
const PORT = process.env.PORT || 8080;
|
|
917
|
+
server.listen(PORT, () => {
|
|
918
|
+
console.log(`Probe Web Interface v${version}`);
|
|
919
|
+
console.log(`Server running on http://localhost:${PORT}`);
|
|
920
|
+
console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
|
|
921
|
+
if (noApiKeysMode) {
|
|
922
|
+
console.log('*** Running in NO API KEYS mode. Chat functionality disabled. ***');
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
// --- Helper Functions ---
|
|
929
|
+
|
|
930
|
+
function handleOptions(res) {
|
|
931
|
+
res.writeHead(200, {
|
|
932
|
+
'Access-Control-Allow-Origin': '*', // Or specific origin
|
|
933
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
934
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Session-ID', // Add any custom headers needed
|
|
935
|
+
'Access-Control-Max-Age': '86400' // 24 hours
|
|
936
|
+
});
|
|
937
|
+
res.end();
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function sendJson(res, statusCode, data, headers = {}) {
|
|
941
|
+
if (res.headersSent) return;
|
|
942
|
+
res.writeHead(statusCode, {
|
|
943
|
+
'Content-Type': 'application/json',
|
|
944
|
+
'Access-Control-Allow-Origin': '*', // Adjust as needed
|
|
945
|
+
'Access-Control-Expose-Headers': 'X-Token-Usage', // Expose custom headers
|
|
946
|
+
...headers
|
|
947
|
+
});
|
|
948
|
+
res.end(JSON.stringify(data));
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function sendError(res, statusCode, message) {
|
|
952
|
+
if (res.headersSent) return;
|
|
953
|
+
console.error(`Sending error (${statusCode}): ${message}`);
|
|
954
|
+
res.writeHead(statusCode, {
|
|
955
|
+
'Content-Type': 'application/json',
|
|
956
|
+
'Access-Control-Allow-Origin': '*'
|
|
957
|
+
});
|
|
958
|
+
res.end(JSON.stringify({ error: message, status: statusCode }));
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function serveStatic(res, filePath, contentType) {
|
|
962
|
+
if (res.headersSent) return;
|
|
963
|
+
if (existsSync(filePath)) {
|
|
964
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
965
|
+
const fileData = readFileSync(filePath);
|
|
966
|
+
res.end(fileData);
|
|
967
|
+
} else {
|
|
968
|
+
sendError(res, 404, `${contentType} not found`);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function serveHtml(res, filePath, bodyAttributes = {}) {
|
|
973
|
+
if (res.headersSent) return;
|
|
974
|
+
if (existsSync(filePath)) {
|
|
975
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
976
|
+
let html = readFileSync(filePath, 'utf8');
|
|
977
|
+
// Inject attributes into body tag
|
|
978
|
+
const attributesString = Object.entries(bodyAttributes)
|
|
979
|
+
.map(([key, value]) => `${key}="${String(value).replace(/"/g, '"')}"`)
|
|
980
|
+
.join(' ');
|
|
981
|
+
if (attributesString) {
|
|
982
|
+
html = html.replace('<body', `<body ${attributesString}`);
|
|
983
|
+
}
|
|
984
|
+
res.end(html);
|
|
985
|
+
} else {
|
|
986
|
+
sendError(res, 404, 'HTML file not found');
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
function getSessionIdFromUrl(req) {
|
|
992
|
+
try {
|
|
993
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
994
|
+
return url.searchParams.get('sessionId');
|
|
995
|
+
} catch (error) {
|
|
996
|
+
console.error(`Error parsing URL for sessionId: ${error.message}`);
|
|
997
|
+
// Fallback: manual parsing (less reliable)
|
|
998
|
+
const match = req.url.match(/[?&]sessionId=([^&]+)/);
|
|
999
|
+
return match ? match[1] : null;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
async function handlePostRequest(req, res, callback) {
|
|
1004
|
+
let body = '';
|
|
1005
|
+
req.on('data', chunk => body += chunk);
|
|
1006
|
+
req.on('end', async () => {
|
|
1007
|
+
try {
|
|
1008
|
+
const parsedBody = JSON.parse(body);
|
|
1009
|
+
await callback(parsedBody);
|
|
1010
|
+
} catch (error) {
|
|
1011
|
+
if (error instanceof SyntaxError) {
|
|
1012
|
+
sendError(res, 400, 'Invalid JSON in request body');
|
|
1013
|
+
} else {
|
|
1014
|
+
console.error('Error handling POST request:', error);
|
|
1015
|
+
sendError(res, 500, `Internal Server Error: ${error.message}`);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
req.on('error', (err) => {
|
|
1020
|
+
console.error('Request error:', err);
|
|
1021
|
+
sendError(res, 500, 'Request error');
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
async function executeDirectTool(res, toolInstance, toolName, toolParams, sessionId) {
|
|
1026
|
+
const DEBUG = process.env.DEBUG_CHAT === '1';
|
|
1027
|
+
if (DEBUG) {
|
|
1028
|
+
console.log(`\n[DEBUG] ===== Direct API Tool Call: ${toolName} =====`);
|
|
1029
|
+
console.log(`[DEBUG] Session ID: ${sessionId}`);
|
|
1030
|
+
console.log(`[DEBUG] Params:`, toolParams);
|
|
1031
|
+
}
|
|
1032
|
+
try {
|
|
1033
|
+
// Execute the tool instance directly (it handles events/cancellation)
|
|
1034
|
+
const result = await toolInstance.execute(toolParams);
|
|
1035
|
+
sendJson(res, 200, { results: result, timestamp: new Date().toISOString() });
|
|
1036
|
+
} catch (error) {
|
|
1037
|
+
console.error(`Error executing direct tool ${toolName}:`, error);
|
|
1038
|
+
let statusCode = 500;
|
|
1039
|
+
let errorMessage = `Error executing ${toolName}`;
|
|
1040
|
+
if (error.message.includes('cancelled')) {
|
|
1041
|
+
statusCode = 499; // Client Closed Request
|
|
1042
|
+
errorMessage = 'Operation cancelled';
|
|
1043
|
+
} else if (error.code === 'ENOENT') {
|
|
1044
|
+
statusCode = 404; errorMessage = 'File or path not found';
|
|
1045
|
+
} else if (error.code === 'EACCES') {
|
|
1046
|
+
statusCode = 403; errorMessage = 'Permission denied';
|
|
1047
|
+
}
|
|
1048
|
+
// Add more specific error handling if needed
|
|
1049
|
+
sendError(res, statusCode, `${errorMessage}: ${error.message}`);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
function extractSessionIdFromPath(url) {
|
|
1054
|
+
// Extract session ID from URLs like /chat/session-id
|
|
1055
|
+
const match = url.match(/^\/chat\/([^/?]+)/);
|
|
1056
|
+
return match ? match[1] : null;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
function isValidUUID(str) {
|
|
1060
|
+
// Basic UUID validation (any version, with or without hyphens)
|
|
1061
|
+
const uuidRegex = /^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$/i;
|
|
1062
|
+
return uuidRegex.test(str);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
function extractSessionIdFromHistoryPath(url) {
|
|
1066
|
+
// Extract session ID from URLs like /api/session/session-id/history
|
|
1067
|
+
const match = url.match(/^\/api\/session\/([^/?]+)\/history/);
|
|
1068
|
+
return match ? match[1] : null;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
function extractContentFromMessage(content) {
|
|
1072
|
+
// Handle different XML patterns used by the assistant and clean user messages
|
|
1073
|
+
const patterns = [
|
|
1074
|
+
/<task>([\s\S]*?)<\/task>/,
|
|
1075
|
+
/<attempt_completion>\s*<result>([\s\S]*?)<\/result>\s*<\/attempt_completion>/,
|
|
1076
|
+
/<result>([\s\S]*?)<\/result>/
|
|
1077
|
+
];
|
|
1078
|
+
|
|
1079
|
+
for (const pattern of patterns) {
|
|
1080
|
+
const match = content.match(pattern);
|
|
1081
|
+
if (match) {
|
|
1082
|
+
return match[1].trim();
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// If no XML pattern matches, return the content as-is
|
|
1087
|
+
return content.trim();
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
function getRelativeTime(timestamp) {
|
|
1091
|
+
const now = Date.now();
|
|
1092
|
+
const diff = now - timestamp;
|
|
1093
|
+
|
|
1094
|
+
const seconds = Math.floor(diff / 1000);
|
|
1095
|
+
const minutes = Math.floor(seconds / 60);
|
|
1096
|
+
const hours = Math.floor(minutes / 60);
|
|
1097
|
+
const days = Math.floor(hours / 24);
|
|
1098
|
+
|
|
1099
|
+
if (days > 0) return `${days}d ago`;
|
|
1100
|
+
if (hours > 0) return `${hours}h ago`;
|
|
1101
|
+
if (minutes > 0) return `${minutes}m ago`;
|
|
1102
|
+
return 'just now';
|
|
1103
|
+
}
|