PraisonAI 3.0.0__py3-none-any.whl
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.
- praisonai/__init__.py +54 -0
- praisonai/__main__.py +15 -0
- praisonai/acp/__init__.py +54 -0
- praisonai/acp/config.py +159 -0
- praisonai/acp/server.py +587 -0
- praisonai/acp/session.py +219 -0
- praisonai/adapters/__init__.py +50 -0
- praisonai/adapters/readers.py +395 -0
- praisonai/adapters/rerankers.py +315 -0
- praisonai/adapters/retrievers.py +394 -0
- praisonai/adapters/vector_stores.py +409 -0
- praisonai/agent_scheduler.py +337 -0
- praisonai/agents_generator.py +903 -0
- praisonai/api/call.py +292 -0
- praisonai/auto.py +1197 -0
- praisonai/capabilities/__init__.py +275 -0
- praisonai/capabilities/a2a.py +140 -0
- praisonai/capabilities/assistants.py +283 -0
- praisonai/capabilities/audio.py +320 -0
- praisonai/capabilities/batches.py +469 -0
- praisonai/capabilities/completions.py +336 -0
- praisonai/capabilities/container_files.py +155 -0
- praisonai/capabilities/containers.py +93 -0
- praisonai/capabilities/embeddings.py +158 -0
- praisonai/capabilities/files.py +467 -0
- praisonai/capabilities/fine_tuning.py +293 -0
- praisonai/capabilities/guardrails.py +182 -0
- praisonai/capabilities/images.py +330 -0
- praisonai/capabilities/mcp.py +190 -0
- praisonai/capabilities/messages.py +270 -0
- praisonai/capabilities/moderations.py +154 -0
- praisonai/capabilities/ocr.py +217 -0
- praisonai/capabilities/passthrough.py +204 -0
- praisonai/capabilities/rag.py +207 -0
- praisonai/capabilities/realtime.py +160 -0
- praisonai/capabilities/rerank.py +165 -0
- praisonai/capabilities/responses.py +266 -0
- praisonai/capabilities/search.py +109 -0
- praisonai/capabilities/skills.py +133 -0
- praisonai/capabilities/vector_store_files.py +334 -0
- praisonai/capabilities/vector_stores.py +304 -0
- praisonai/capabilities/videos.py +141 -0
- praisonai/chainlit_ui.py +304 -0
- praisonai/chat/__init__.py +106 -0
- praisonai/chat/app.py +125 -0
- praisonai/cli/__init__.py +26 -0
- praisonai/cli/app.py +213 -0
- praisonai/cli/commands/__init__.py +75 -0
- praisonai/cli/commands/acp.py +70 -0
- praisonai/cli/commands/completion.py +333 -0
- praisonai/cli/commands/config.py +166 -0
- praisonai/cli/commands/debug.py +142 -0
- praisonai/cli/commands/diag.py +55 -0
- praisonai/cli/commands/doctor.py +166 -0
- praisonai/cli/commands/environment.py +179 -0
- praisonai/cli/commands/lsp.py +112 -0
- praisonai/cli/commands/mcp.py +210 -0
- praisonai/cli/commands/profile.py +457 -0
- praisonai/cli/commands/run.py +228 -0
- praisonai/cli/commands/schedule.py +150 -0
- praisonai/cli/commands/serve.py +97 -0
- praisonai/cli/commands/session.py +212 -0
- praisonai/cli/commands/traces.py +145 -0
- praisonai/cli/commands/version.py +101 -0
- praisonai/cli/configuration/__init__.py +18 -0
- praisonai/cli/configuration/loader.py +353 -0
- praisonai/cli/configuration/paths.py +114 -0
- praisonai/cli/configuration/schema.py +164 -0
- praisonai/cli/features/__init__.py +268 -0
- praisonai/cli/features/acp.py +236 -0
- praisonai/cli/features/action_orchestrator.py +546 -0
- praisonai/cli/features/agent_scheduler.py +773 -0
- praisonai/cli/features/agent_tools.py +474 -0
- praisonai/cli/features/agents.py +375 -0
- praisonai/cli/features/at_mentions.py +471 -0
- praisonai/cli/features/auto_memory.py +182 -0
- praisonai/cli/features/autonomy_mode.py +490 -0
- praisonai/cli/features/background.py +356 -0
- praisonai/cli/features/base.py +168 -0
- praisonai/cli/features/capabilities.py +1326 -0
- praisonai/cli/features/checkpoints.py +338 -0
- praisonai/cli/features/code_intelligence.py +652 -0
- praisonai/cli/features/compaction.py +294 -0
- praisonai/cli/features/compare.py +534 -0
- praisonai/cli/features/cost_tracker.py +514 -0
- praisonai/cli/features/debug.py +810 -0
- praisonai/cli/features/deploy.py +517 -0
- praisonai/cli/features/diag.py +289 -0
- praisonai/cli/features/doctor/__init__.py +63 -0
- praisonai/cli/features/doctor/checks/__init__.py +24 -0
- praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
- praisonai/cli/features/doctor/checks/config_checks.py +366 -0
- praisonai/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai/cli/features/doctor/checks/env_checks.py +543 -0
- praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
- praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
- praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
- praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai/cli/features/doctor/engine.py +266 -0
- praisonai/cli/features/doctor/formatters.py +310 -0
- praisonai/cli/features/doctor/handler.py +397 -0
- praisonai/cli/features/doctor/models.py +264 -0
- praisonai/cli/features/doctor/registry.py +239 -0
- praisonai/cli/features/endpoints.py +1019 -0
- praisonai/cli/features/eval.py +560 -0
- praisonai/cli/features/external_agents.py +231 -0
- praisonai/cli/features/fast_context.py +410 -0
- praisonai/cli/features/flow_display.py +566 -0
- praisonai/cli/features/git_integration.py +651 -0
- praisonai/cli/features/guardrail.py +171 -0
- praisonai/cli/features/handoff.py +185 -0
- praisonai/cli/features/hooks.py +583 -0
- praisonai/cli/features/image.py +384 -0
- praisonai/cli/features/interactive_runtime.py +585 -0
- praisonai/cli/features/interactive_tools.py +380 -0
- praisonai/cli/features/interactive_tui.py +603 -0
- praisonai/cli/features/jobs.py +632 -0
- praisonai/cli/features/knowledge.py +531 -0
- praisonai/cli/features/lite.py +244 -0
- praisonai/cli/features/lsp_cli.py +225 -0
- praisonai/cli/features/mcp.py +169 -0
- praisonai/cli/features/message_queue.py +587 -0
- praisonai/cli/features/metrics.py +211 -0
- praisonai/cli/features/n8n.py +673 -0
- praisonai/cli/features/observability.py +293 -0
- praisonai/cli/features/ollama.py +361 -0
- praisonai/cli/features/output_style.py +273 -0
- praisonai/cli/features/package.py +631 -0
- praisonai/cli/features/performance.py +308 -0
- praisonai/cli/features/persistence.py +636 -0
- praisonai/cli/features/profile.py +226 -0
- praisonai/cli/features/profiler/__init__.py +81 -0
- praisonai/cli/features/profiler/core.py +558 -0
- praisonai/cli/features/profiler/optimizations.py +652 -0
- praisonai/cli/features/profiler/suite.py +386 -0
- praisonai/cli/features/profiling.py +350 -0
- praisonai/cli/features/queue/__init__.py +73 -0
- praisonai/cli/features/queue/manager.py +395 -0
- praisonai/cli/features/queue/models.py +286 -0
- praisonai/cli/features/queue/persistence.py +564 -0
- praisonai/cli/features/queue/scheduler.py +484 -0
- praisonai/cli/features/queue/worker.py +372 -0
- praisonai/cli/features/recipe.py +1723 -0
- praisonai/cli/features/recipes.py +449 -0
- praisonai/cli/features/registry.py +229 -0
- praisonai/cli/features/repo_map.py +860 -0
- praisonai/cli/features/router.py +466 -0
- praisonai/cli/features/sandbox_executor.py +515 -0
- praisonai/cli/features/serve.py +829 -0
- praisonai/cli/features/session.py +222 -0
- praisonai/cli/features/skills.py +856 -0
- praisonai/cli/features/slash_commands.py +650 -0
- praisonai/cli/features/telemetry.py +179 -0
- praisonai/cli/features/templates.py +1384 -0
- praisonai/cli/features/thinking.py +305 -0
- praisonai/cli/features/todo.py +334 -0
- praisonai/cli/features/tools.py +680 -0
- praisonai/cli/features/tui/__init__.py +83 -0
- praisonai/cli/features/tui/app.py +580 -0
- praisonai/cli/features/tui/cli.py +566 -0
- praisonai/cli/features/tui/debug.py +511 -0
- praisonai/cli/features/tui/events.py +99 -0
- praisonai/cli/features/tui/mock_provider.py +328 -0
- praisonai/cli/features/tui/orchestrator.py +652 -0
- praisonai/cli/features/tui/screens/__init__.py +50 -0
- praisonai/cli/features/tui/screens/main.py +245 -0
- praisonai/cli/features/tui/screens/queue.py +174 -0
- praisonai/cli/features/tui/screens/session.py +124 -0
- praisonai/cli/features/tui/screens/settings.py +148 -0
- praisonai/cli/features/tui/widgets/__init__.py +56 -0
- praisonai/cli/features/tui/widgets/chat.py +261 -0
- praisonai/cli/features/tui/widgets/composer.py +224 -0
- praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
- praisonai/cli/features/tui/widgets/status.py +167 -0
- praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
- praisonai/cli/features/workflow.py +720 -0
- praisonai/cli/legacy.py +236 -0
- praisonai/cli/main.py +5559 -0
- praisonai/cli/schedule_cli.py +54 -0
- praisonai/cli/state/__init__.py +31 -0
- praisonai/cli/state/identifiers.py +161 -0
- praisonai/cli/state/sessions.py +313 -0
- praisonai/code/__init__.py +93 -0
- praisonai/code/agent_tools.py +344 -0
- praisonai/code/diff/__init__.py +21 -0
- praisonai/code/diff/diff_strategy.py +432 -0
- praisonai/code/tools/__init__.py +27 -0
- praisonai/code/tools/apply_diff.py +221 -0
- praisonai/code/tools/execute_command.py +275 -0
- praisonai/code/tools/list_files.py +274 -0
- praisonai/code/tools/read_file.py +206 -0
- praisonai/code/tools/search_replace.py +248 -0
- praisonai/code/tools/write_file.py +217 -0
- praisonai/code/utils/__init__.py +46 -0
- praisonai/code/utils/file_utils.py +307 -0
- praisonai/code/utils/ignore_utils.py +308 -0
- praisonai/code/utils/text_utils.py +276 -0
- praisonai/db/__init__.py +64 -0
- praisonai/db/adapter.py +531 -0
- praisonai/deploy/__init__.py +62 -0
- praisonai/deploy/api.py +231 -0
- praisonai/deploy/docker.py +454 -0
- praisonai/deploy/doctor.py +367 -0
- praisonai/deploy/main.py +327 -0
- praisonai/deploy/models.py +179 -0
- praisonai/deploy/providers/__init__.py +33 -0
- praisonai/deploy/providers/aws.py +331 -0
- praisonai/deploy/providers/azure.py +358 -0
- praisonai/deploy/providers/base.py +101 -0
- praisonai/deploy/providers/gcp.py +314 -0
- praisonai/deploy/schema.py +208 -0
- praisonai/deploy.py +185 -0
- praisonai/endpoints/__init__.py +53 -0
- praisonai/endpoints/a2u_server.py +410 -0
- praisonai/endpoints/discovery.py +165 -0
- praisonai/endpoints/providers/__init__.py +28 -0
- praisonai/endpoints/providers/a2a.py +253 -0
- praisonai/endpoints/providers/a2u.py +208 -0
- praisonai/endpoints/providers/agents_api.py +171 -0
- praisonai/endpoints/providers/base.py +231 -0
- praisonai/endpoints/providers/mcp.py +263 -0
- praisonai/endpoints/providers/recipe.py +206 -0
- praisonai/endpoints/providers/tools_mcp.py +150 -0
- praisonai/endpoints/registry.py +131 -0
- praisonai/endpoints/server.py +161 -0
- praisonai/inbuilt_tools/__init__.py +24 -0
- praisonai/inbuilt_tools/autogen_tools.py +117 -0
- praisonai/inc/__init__.py +2 -0
- praisonai/inc/config.py +96 -0
- praisonai/inc/models.py +155 -0
- praisonai/integrations/__init__.py +56 -0
- praisonai/integrations/base.py +303 -0
- praisonai/integrations/claude_code.py +270 -0
- praisonai/integrations/codex_cli.py +255 -0
- praisonai/integrations/cursor_cli.py +195 -0
- praisonai/integrations/gemini_cli.py +222 -0
- praisonai/jobs/__init__.py +67 -0
- praisonai/jobs/executor.py +425 -0
- praisonai/jobs/models.py +230 -0
- praisonai/jobs/router.py +314 -0
- praisonai/jobs/server.py +186 -0
- praisonai/jobs/store.py +203 -0
- praisonai/llm/__init__.py +66 -0
- praisonai/llm/registry.py +382 -0
- praisonai/mcp_server/__init__.py +152 -0
- praisonai/mcp_server/adapters/__init__.py +74 -0
- praisonai/mcp_server/adapters/agents.py +128 -0
- praisonai/mcp_server/adapters/capabilities.py +168 -0
- praisonai/mcp_server/adapters/cli_tools.py +568 -0
- praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
- praisonai/mcp_server/adapters/knowledge.py +93 -0
- praisonai/mcp_server/adapters/memory.py +104 -0
- praisonai/mcp_server/adapters/prompts.py +306 -0
- praisonai/mcp_server/adapters/resources.py +124 -0
- praisonai/mcp_server/adapters/tools_bridge.py +280 -0
- praisonai/mcp_server/auth/__init__.py +48 -0
- praisonai/mcp_server/auth/api_key.py +291 -0
- praisonai/mcp_server/auth/oauth.py +460 -0
- praisonai/mcp_server/auth/oidc.py +289 -0
- praisonai/mcp_server/auth/scopes.py +260 -0
- praisonai/mcp_server/cli.py +852 -0
- praisonai/mcp_server/elicitation.py +445 -0
- praisonai/mcp_server/icons.py +302 -0
- praisonai/mcp_server/recipe_adapter.py +573 -0
- praisonai/mcp_server/recipe_cli.py +824 -0
- praisonai/mcp_server/registry.py +703 -0
- praisonai/mcp_server/sampling.py +422 -0
- praisonai/mcp_server/server.py +490 -0
- praisonai/mcp_server/tasks.py +443 -0
- praisonai/mcp_server/transports/__init__.py +18 -0
- praisonai/mcp_server/transports/http_stream.py +376 -0
- praisonai/mcp_server/transports/stdio.py +132 -0
- praisonai/persistence/__init__.py +84 -0
- praisonai/persistence/config.py +238 -0
- praisonai/persistence/conversation/__init__.py +25 -0
- praisonai/persistence/conversation/async_mysql.py +427 -0
- praisonai/persistence/conversation/async_postgres.py +410 -0
- praisonai/persistence/conversation/async_sqlite.py +371 -0
- praisonai/persistence/conversation/base.py +151 -0
- praisonai/persistence/conversation/json_store.py +250 -0
- praisonai/persistence/conversation/mysql.py +387 -0
- praisonai/persistence/conversation/postgres.py +401 -0
- praisonai/persistence/conversation/singlestore.py +240 -0
- praisonai/persistence/conversation/sqlite.py +341 -0
- praisonai/persistence/conversation/supabase.py +203 -0
- praisonai/persistence/conversation/surrealdb.py +287 -0
- praisonai/persistence/factory.py +301 -0
- praisonai/persistence/hooks/__init__.py +18 -0
- praisonai/persistence/hooks/agent_hooks.py +297 -0
- praisonai/persistence/knowledge/__init__.py +26 -0
- praisonai/persistence/knowledge/base.py +144 -0
- praisonai/persistence/knowledge/cassandra.py +232 -0
- praisonai/persistence/knowledge/chroma.py +295 -0
- praisonai/persistence/knowledge/clickhouse.py +242 -0
- praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
- praisonai/persistence/knowledge/couchbase.py +286 -0
- praisonai/persistence/knowledge/lancedb.py +216 -0
- praisonai/persistence/knowledge/langchain_adapter.py +291 -0
- praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
- praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
- praisonai/persistence/knowledge/milvus.py +277 -0
- praisonai/persistence/knowledge/mongodb_vector.py +306 -0
- praisonai/persistence/knowledge/pgvector.py +335 -0
- praisonai/persistence/knowledge/pinecone.py +253 -0
- praisonai/persistence/knowledge/qdrant.py +301 -0
- praisonai/persistence/knowledge/redis_vector.py +291 -0
- praisonai/persistence/knowledge/singlestore_vector.py +299 -0
- praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
- praisonai/persistence/knowledge/upstash_vector.py +266 -0
- praisonai/persistence/knowledge/weaviate.py +223 -0
- praisonai/persistence/migrations/__init__.py +10 -0
- praisonai/persistence/migrations/manager.py +251 -0
- praisonai/persistence/orchestrator.py +406 -0
- praisonai/persistence/state/__init__.py +21 -0
- praisonai/persistence/state/async_mongodb.py +200 -0
- praisonai/persistence/state/base.py +107 -0
- praisonai/persistence/state/dynamodb.py +226 -0
- praisonai/persistence/state/firestore.py +175 -0
- praisonai/persistence/state/gcs.py +155 -0
- praisonai/persistence/state/memory.py +245 -0
- praisonai/persistence/state/mongodb.py +158 -0
- praisonai/persistence/state/redis.py +190 -0
- praisonai/persistence/state/upstash.py +144 -0
- praisonai/persistence/tests/__init__.py +3 -0
- praisonai/persistence/tests/test_all_backends.py +633 -0
- praisonai/profiler.py +1214 -0
- praisonai/recipe/__init__.py +134 -0
- praisonai/recipe/bridge.py +278 -0
- praisonai/recipe/core.py +893 -0
- praisonai/recipe/exceptions.py +54 -0
- praisonai/recipe/history.py +402 -0
- praisonai/recipe/models.py +266 -0
- praisonai/recipe/operations.py +440 -0
- praisonai/recipe/policy.py +422 -0
- praisonai/recipe/registry.py +849 -0
- praisonai/recipe/runtime.py +214 -0
- praisonai/recipe/security.py +711 -0
- praisonai/recipe/serve.py +859 -0
- praisonai/recipe/server.py +613 -0
- praisonai/scheduler/__init__.py +45 -0
- praisonai/scheduler/agent_scheduler.py +552 -0
- praisonai/scheduler/base.py +124 -0
- praisonai/scheduler/daemon_manager.py +225 -0
- praisonai/scheduler/state_manager.py +155 -0
- praisonai/scheduler/yaml_loader.py +193 -0
- praisonai/scheduler.py +194 -0
- praisonai/setup/__init__.py +1 -0
- praisonai/setup/build.py +21 -0
- praisonai/setup/post_install.py +23 -0
- praisonai/setup/setup_conda_env.py +25 -0
- praisonai/setup.py +16 -0
- praisonai/templates/__init__.py +116 -0
- praisonai/templates/cache.py +364 -0
- praisonai/templates/dependency_checker.py +358 -0
- praisonai/templates/discovery.py +391 -0
- praisonai/templates/loader.py +564 -0
- praisonai/templates/registry.py +511 -0
- praisonai/templates/resolver.py +206 -0
- praisonai/templates/security.py +327 -0
- praisonai/templates/tool_override.py +498 -0
- praisonai/templates/tools_doctor.py +256 -0
- praisonai/test.py +105 -0
- praisonai/train.py +562 -0
- praisonai/train_vision.py +306 -0
- praisonai/ui/agents.py +824 -0
- praisonai/ui/callbacks.py +57 -0
- praisonai/ui/chainlit_compat.py +246 -0
- praisonai/ui/chat.py +532 -0
- praisonai/ui/code.py +717 -0
- praisonai/ui/colab.py +474 -0
- praisonai/ui/colab_chainlit.py +81 -0
- praisonai/ui/components/aicoder.py +284 -0
- praisonai/ui/context.py +283 -0
- praisonai/ui/database_config.py +56 -0
- praisonai/ui/db.py +294 -0
- praisonai/ui/realtime.py +488 -0
- praisonai/ui/realtimeclient/__init__.py +756 -0
- praisonai/ui/realtimeclient/tools.py +242 -0
- praisonai/ui/sql_alchemy.py +710 -0
- praisonai/upload_vision.py +140 -0
- praisonai/version.py +1 -0
- praisonai-3.0.0.dist-info/METADATA +3493 -0
- praisonai-3.0.0.dist-info/RECORD +393 -0
- praisonai-3.0.0.dist-info/WHEEL +5 -0
- praisonai-3.0.0.dist-info/entry_points.txt +4 -0
- praisonai-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Code Intelligence Router for PraisonAI.
|
|
3
|
+
|
|
4
|
+
Routes code-related queries to LSP when available, with fallback to grep/file search.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import re
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from .interactive_runtime import InteractiveRuntime
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CodeIntent(Enum):
|
|
21
|
+
"""Classification of code-related intents."""
|
|
22
|
+
LIST_SYMBOLS = "list_symbols"
|
|
23
|
+
GO_TO_DEFINITION = "go_to_definition"
|
|
24
|
+
FIND_REFERENCES = "find_references"
|
|
25
|
+
GET_DIAGNOSTICS = "get_diagnostics"
|
|
26
|
+
EXPLAIN_CODE = "explain_code"
|
|
27
|
+
SEARCH_CODE = "search_code"
|
|
28
|
+
UNKNOWN = "unknown"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class CodeQueryResult:
|
|
33
|
+
"""Result of a code intelligence query."""
|
|
34
|
+
intent: CodeIntent
|
|
35
|
+
success: bool
|
|
36
|
+
lsp_used: bool
|
|
37
|
+
data: Any = None
|
|
38
|
+
citations: List[Dict[str, Any]] = None
|
|
39
|
+
error: Optional[str] = None
|
|
40
|
+
fallback_used: bool = False
|
|
41
|
+
|
|
42
|
+
def __post_init__(self):
|
|
43
|
+
if self.citations is None:
|
|
44
|
+
self.citations = []
|
|
45
|
+
|
|
46
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
47
|
+
return {
|
|
48
|
+
"intent": self.intent.value,
|
|
49
|
+
"success": self.success,
|
|
50
|
+
"lsp_used": self.lsp_used,
|
|
51
|
+
"data": self.data,
|
|
52
|
+
"citations": self.citations,
|
|
53
|
+
"error": self.error,
|
|
54
|
+
"fallback_used": self.fallback_used
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class CodeIntelligenceRouter:
|
|
59
|
+
"""
|
|
60
|
+
Routes code intelligence queries to LSP or fallback mechanisms.
|
|
61
|
+
|
|
62
|
+
LSP-first approach:
|
|
63
|
+
- If LSP is available, use it for semantic queries
|
|
64
|
+
- If LSP fails or unavailable, fall back to grep/file search
|
|
65
|
+
- Always provide citations with file:line references
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
# Patterns for intent classification
|
|
69
|
+
INTENT_PATTERNS = {
|
|
70
|
+
CodeIntent.LIST_SYMBOLS: [
|
|
71
|
+
r"list\s+(all\s+)?(functions?|classes?|methods?|symbols?)",
|
|
72
|
+
r"what\s+(functions?|classes?|methods?)\s+(are|exist)",
|
|
73
|
+
r"show\s+(me\s+)?(all\s+)?(functions?|classes?|symbols?)",
|
|
74
|
+
r"find\s+(all\s+)?(functions?|classes?|methods?)",
|
|
75
|
+
],
|
|
76
|
+
CodeIntent.GO_TO_DEFINITION: [
|
|
77
|
+
r"(go\s+to|find|show|where\s+is)\s+(the\s+)?definition",
|
|
78
|
+
r"where\s+is\s+(\w+)\s+defined",
|
|
79
|
+
r"definition\s+of\s+(\w+)",
|
|
80
|
+
r"(\w+)\s+is\s+defined\s+where",
|
|
81
|
+
],
|
|
82
|
+
CodeIntent.FIND_REFERENCES: [
|
|
83
|
+
r"(find|show|list)\s+(all\s+)?references",
|
|
84
|
+
r"where\s+is\s+(\w+)\s+used",
|
|
85
|
+
r"usages?\s+of\s+(\w+)",
|
|
86
|
+
r"who\s+(calls?|uses?)\s+(\w+)",
|
|
87
|
+
],
|
|
88
|
+
CodeIntent.GET_DIAGNOSTICS: [
|
|
89
|
+
r"(show|list|get)\s+(all\s+)?(errors?|warnings?|diagnostics?|problems?)",
|
|
90
|
+
r"what('s|\s+is)\s+wrong",
|
|
91
|
+
r"any\s+(errors?|issues?|problems?)",
|
|
92
|
+
r"check\s+(for\s+)?(errors?|issues?)",
|
|
93
|
+
],
|
|
94
|
+
CodeIntent.EXPLAIN_CODE: [
|
|
95
|
+
r"explain\s+(this|the)\s+code",
|
|
96
|
+
r"what\s+does\s+(this|the)\s+code\s+do",
|
|
97
|
+
r"how\s+does\s+(\w+)\s+work",
|
|
98
|
+
],
|
|
99
|
+
CodeIntent.SEARCH_CODE: [
|
|
100
|
+
r"search\s+(for\s+)?",
|
|
101
|
+
r"find\s+(the\s+)?string",
|
|
102
|
+
r"grep\s+",
|
|
103
|
+
],
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
def __init__(self, runtime: "InteractiveRuntime"):
|
|
107
|
+
"""Initialize with runtime reference."""
|
|
108
|
+
self.runtime = runtime
|
|
109
|
+
|
|
110
|
+
def classify_intent(self, query: str) -> CodeIntent:
|
|
111
|
+
"""Classify the intent of a code query."""
|
|
112
|
+
query_lower = query.lower()
|
|
113
|
+
|
|
114
|
+
for intent, patterns in self.INTENT_PATTERNS.items():
|
|
115
|
+
for pattern in patterns:
|
|
116
|
+
if re.search(pattern, query_lower):
|
|
117
|
+
return intent
|
|
118
|
+
|
|
119
|
+
return CodeIntent.UNKNOWN
|
|
120
|
+
|
|
121
|
+
async def handle_query(self, query: str, file_path: str = None) -> CodeQueryResult:
|
|
122
|
+
"""
|
|
123
|
+
Handle a code intelligence query.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
query: The user's query
|
|
127
|
+
file_path: Optional file path context
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
CodeQueryResult with data and citations
|
|
131
|
+
"""
|
|
132
|
+
intent = self.classify_intent(query)
|
|
133
|
+
|
|
134
|
+
if intent == CodeIntent.LIST_SYMBOLS:
|
|
135
|
+
return await self._handle_list_symbols(query, file_path)
|
|
136
|
+
elif intent == CodeIntent.GO_TO_DEFINITION:
|
|
137
|
+
return await self._handle_go_to_definition(query, file_path)
|
|
138
|
+
elif intent == CodeIntent.FIND_REFERENCES:
|
|
139
|
+
return await self._handle_find_references(query, file_path)
|
|
140
|
+
elif intent == CodeIntent.GET_DIAGNOSTICS:
|
|
141
|
+
return await self._handle_get_diagnostics(query, file_path)
|
|
142
|
+
elif intent == CodeIntent.SEARCH_CODE:
|
|
143
|
+
return await self._handle_search_code(query, file_path)
|
|
144
|
+
else:
|
|
145
|
+
return CodeQueryResult(
|
|
146
|
+
intent=intent,
|
|
147
|
+
success=False,
|
|
148
|
+
lsp_used=False,
|
|
149
|
+
error="Could not determine code query intent"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
async def _handle_list_symbols(self, query: str, file_path: str = None) -> CodeQueryResult:
|
|
153
|
+
"""Handle list symbols query."""
|
|
154
|
+
if not file_path:
|
|
155
|
+
# Try to find a file from the query
|
|
156
|
+
file_path = self._extract_file_from_query(query)
|
|
157
|
+
|
|
158
|
+
if not file_path:
|
|
159
|
+
return CodeQueryResult(
|
|
160
|
+
intent=CodeIntent.LIST_SYMBOLS,
|
|
161
|
+
success=False,
|
|
162
|
+
lsp_used=False,
|
|
163
|
+
error="No file specified. Please specify a file path."
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Try LSP first
|
|
167
|
+
if self.runtime.lsp_ready:
|
|
168
|
+
try:
|
|
169
|
+
symbols = await self.runtime.lsp_get_symbols(file_path)
|
|
170
|
+
if symbols:
|
|
171
|
+
citations = [
|
|
172
|
+
{"file": file_path, "type": "symbols", "count": len(symbols)}
|
|
173
|
+
]
|
|
174
|
+
return CodeQueryResult(
|
|
175
|
+
intent=CodeIntent.LIST_SYMBOLS,
|
|
176
|
+
success=True,
|
|
177
|
+
lsp_used=True,
|
|
178
|
+
data=symbols,
|
|
179
|
+
citations=citations
|
|
180
|
+
)
|
|
181
|
+
except Exception as e:
|
|
182
|
+
logger.warning(f"LSP symbols failed: {e}")
|
|
183
|
+
|
|
184
|
+
# Fallback to regex-based symbol extraction
|
|
185
|
+
return await self._fallback_list_symbols(file_path)
|
|
186
|
+
|
|
187
|
+
async def _fallback_list_symbols(self, file_path: str) -> CodeQueryResult:
|
|
188
|
+
"""Fallback symbol listing using regex."""
|
|
189
|
+
try:
|
|
190
|
+
path = Path(file_path)
|
|
191
|
+
if not path.exists():
|
|
192
|
+
path = Path(self.runtime.config.workspace) / file_path
|
|
193
|
+
|
|
194
|
+
if not path.exists():
|
|
195
|
+
return CodeQueryResult(
|
|
196
|
+
intent=CodeIntent.LIST_SYMBOLS,
|
|
197
|
+
success=False,
|
|
198
|
+
lsp_used=False,
|
|
199
|
+
fallback_used=True,
|
|
200
|
+
error=f"File not found: {file_path}"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
content = path.read_text()
|
|
204
|
+
symbols = []
|
|
205
|
+
|
|
206
|
+
# Python patterns
|
|
207
|
+
if path.suffix == ".py":
|
|
208
|
+
# Functions
|
|
209
|
+
for match in re.finditer(r'^(async\s+)?def\s+(\w+)\s*\(', content, re.MULTILINE):
|
|
210
|
+
line_num = content[:match.start()].count('\n') + 1
|
|
211
|
+
symbols.append({
|
|
212
|
+
"name": match.group(2),
|
|
213
|
+
"kind": "function",
|
|
214
|
+
"line": line_num
|
|
215
|
+
})
|
|
216
|
+
# Classes
|
|
217
|
+
for match in re.finditer(r'^class\s+(\w+)\s*[:\(]', content, re.MULTILINE):
|
|
218
|
+
line_num = content[:match.start()].count('\n') + 1
|
|
219
|
+
symbols.append({
|
|
220
|
+
"name": match.group(1),
|
|
221
|
+
"kind": "class",
|
|
222
|
+
"line": line_num
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
# JavaScript/TypeScript patterns
|
|
226
|
+
elif path.suffix in [".js", ".ts", ".jsx", ".tsx"]:
|
|
227
|
+
# Functions
|
|
228
|
+
for match in re.finditer(r'(?:async\s+)?function\s+(\w+)\s*\(', content):
|
|
229
|
+
line_num = content[:match.start()].count('\n') + 1
|
|
230
|
+
symbols.append({
|
|
231
|
+
"name": match.group(1),
|
|
232
|
+
"kind": "function",
|
|
233
|
+
"line": line_num
|
|
234
|
+
})
|
|
235
|
+
# Arrow functions assigned to const/let/var
|
|
236
|
+
for match in re.finditer(r'(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(', content):
|
|
237
|
+
line_num = content[:match.start()].count('\n') + 1
|
|
238
|
+
symbols.append({
|
|
239
|
+
"name": match.group(1),
|
|
240
|
+
"kind": "function",
|
|
241
|
+
"line": line_num
|
|
242
|
+
})
|
|
243
|
+
# Classes
|
|
244
|
+
for match in re.finditer(r'class\s+(\w+)\s*(?:extends|implements|{)', content):
|
|
245
|
+
line_num = content[:match.start()].count('\n') + 1
|
|
246
|
+
symbols.append({
|
|
247
|
+
"name": match.group(1),
|
|
248
|
+
"kind": "class",
|
|
249
|
+
"line": line_num
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
citations = [
|
|
253
|
+
{"file": str(path), "type": "symbols", "count": len(symbols)}
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
return CodeQueryResult(
|
|
257
|
+
intent=CodeIntent.LIST_SYMBOLS,
|
|
258
|
+
success=True,
|
|
259
|
+
lsp_used=False,
|
|
260
|
+
fallback_used=True,
|
|
261
|
+
data=symbols,
|
|
262
|
+
citations=citations
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
except Exception as e:
|
|
266
|
+
return CodeQueryResult(
|
|
267
|
+
intent=CodeIntent.LIST_SYMBOLS,
|
|
268
|
+
success=False,
|
|
269
|
+
lsp_used=False,
|
|
270
|
+
fallback_used=True,
|
|
271
|
+
error=str(e)
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
async def _handle_go_to_definition(self, query: str, file_path: str = None) -> CodeQueryResult:
|
|
275
|
+
"""Handle go to definition query."""
|
|
276
|
+
# Extract symbol name from query
|
|
277
|
+
symbol = self._extract_symbol_from_query(query)
|
|
278
|
+
|
|
279
|
+
if not symbol:
|
|
280
|
+
return CodeQueryResult(
|
|
281
|
+
intent=CodeIntent.GO_TO_DEFINITION,
|
|
282
|
+
success=False,
|
|
283
|
+
lsp_used=False,
|
|
284
|
+
error="Could not identify symbol to find definition for"
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
if not file_path:
|
|
288
|
+
file_path = self._extract_file_from_query(query)
|
|
289
|
+
|
|
290
|
+
# Try LSP first
|
|
291
|
+
if self.runtime.lsp_ready and file_path:
|
|
292
|
+
try:
|
|
293
|
+
# Need to find the symbol position first
|
|
294
|
+
line, col = await self._find_symbol_position(file_path, symbol)
|
|
295
|
+
if line is not None:
|
|
296
|
+
definitions = await self.runtime.lsp_get_definition(file_path, line, col)
|
|
297
|
+
if definitions:
|
|
298
|
+
citations = [
|
|
299
|
+
{"file": d.get("uri", "").replace("file://", ""),
|
|
300
|
+
"line": d.get("range", {}).get("start", {}).get("line", 0) + 1,
|
|
301
|
+
"type": "definition"}
|
|
302
|
+
for d in definitions
|
|
303
|
+
]
|
|
304
|
+
return CodeQueryResult(
|
|
305
|
+
intent=CodeIntent.GO_TO_DEFINITION,
|
|
306
|
+
success=True,
|
|
307
|
+
lsp_used=True,
|
|
308
|
+
data={"symbol": symbol, "definitions": definitions},
|
|
309
|
+
citations=citations
|
|
310
|
+
)
|
|
311
|
+
except Exception as e:
|
|
312
|
+
logger.warning(f"LSP definition failed: {e}")
|
|
313
|
+
|
|
314
|
+
# Fallback to grep
|
|
315
|
+
return await self._fallback_find_definition(symbol)
|
|
316
|
+
|
|
317
|
+
async def _fallback_find_definition(self, symbol: str) -> CodeQueryResult:
|
|
318
|
+
"""Fallback definition finding using grep."""
|
|
319
|
+
try:
|
|
320
|
+
import subprocess
|
|
321
|
+
workspace = self.runtime.config.workspace
|
|
322
|
+
|
|
323
|
+
# Search for definition patterns
|
|
324
|
+
patterns = [
|
|
325
|
+
f"def {symbol}\\s*\\(", # Python function
|
|
326
|
+
f"class {symbol}\\s*[:\\(]", # Python class
|
|
327
|
+
f"function {symbol}\\s*\\(", # JS function
|
|
328
|
+
f"const {symbol}\\s*=", # JS const
|
|
329
|
+
f"let {symbol}\\s*=", # JS let
|
|
330
|
+
]
|
|
331
|
+
|
|
332
|
+
results = []
|
|
333
|
+
for pattern in patterns:
|
|
334
|
+
try:
|
|
335
|
+
result = subprocess.run(
|
|
336
|
+
["grep", "-rn", "-E", pattern, workspace],
|
|
337
|
+
capture_output=True,
|
|
338
|
+
text=True,
|
|
339
|
+
timeout=5
|
|
340
|
+
)
|
|
341
|
+
if result.stdout:
|
|
342
|
+
for line in result.stdout.strip().split('\n'):
|
|
343
|
+
if line:
|
|
344
|
+
parts = line.split(':', 2)
|
|
345
|
+
if len(parts) >= 2:
|
|
346
|
+
results.append({
|
|
347
|
+
"file": parts[0],
|
|
348
|
+
"line": int(parts[1]),
|
|
349
|
+
"content": parts[2] if len(parts) > 2 else ""
|
|
350
|
+
})
|
|
351
|
+
except subprocess.TimeoutExpired:
|
|
352
|
+
pass
|
|
353
|
+
|
|
354
|
+
if results:
|
|
355
|
+
citations = [
|
|
356
|
+
{"file": r["file"], "line": r["line"], "type": "definition"}
|
|
357
|
+
for r in results
|
|
358
|
+
]
|
|
359
|
+
return CodeQueryResult(
|
|
360
|
+
intent=CodeIntent.GO_TO_DEFINITION,
|
|
361
|
+
success=True,
|
|
362
|
+
lsp_used=False,
|
|
363
|
+
fallback_used=True,
|
|
364
|
+
data={"symbol": symbol, "definitions": results},
|
|
365
|
+
citations=citations
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
return CodeQueryResult(
|
|
369
|
+
intent=CodeIntent.GO_TO_DEFINITION,
|
|
370
|
+
success=False,
|
|
371
|
+
lsp_used=False,
|
|
372
|
+
fallback_used=True,
|
|
373
|
+
error=f"Definition for '{symbol}' not found"
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
except Exception as e:
|
|
377
|
+
return CodeQueryResult(
|
|
378
|
+
intent=CodeIntent.GO_TO_DEFINITION,
|
|
379
|
+
success=False,
|
|
380
|
+
lsp_used=False,
|
|
381
|
+
fallback_used=True,
|
|
382
|
+
error=str(e)
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
async def _handle_find_references(self, query: str, file_path: str = None) -> CodeQueryResult:
|
|
386
|
+
"""Handle find references query."""
|
|
387
|
+
symbol = self._extract_symbol_from_query(query)
|
|
388
|
+
|
|
389
|
+
if not symbol:
|
|
390
|
+
return CodeQueryResult(
|
|
391
|
+
intent=CodeIntent.FIND_REFERENCES,
|
|
392
|
+
success=False,
|
|
393
|
+
lsp_used=False,
|
|
394
|
+
error="Could not identify symbol to find references for"
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
if not file_path:
|
|
398
|
+
file_path = self._extract_file_from_query(query)
|
|
399
|
+
|
|
400
|
+
# Try LSP first
|
|
401
|
+
if self.runtime.lsp_ready and file_path:
|
|
402
|
+
try:
|
|
403
|
+
line, col = await self._find_symbol_position(file_path, symbol)
|
|
404
|
+
if line is not None:
|
|
405
|
+
references = await self.runtime.lsp_get_references(file_path, line, col)
|
|
406
|
+
if references:
|
|
407
|
+
citations = [
|
|
408
|
+
{"file": r.get("uri", "").replace("file://", ""),
|
|
409
|
+
"line": r.get("range", {}).get("start", {}).get("line", 0) + 1,
|
|
410
|
+
"type": "reference"}
|
|
411
|
+
for r in references
|
|
412
|
+
]
|
|
413
|
+
return CodeQueryResult(
|
|
414
|
+
intent=CodeIntent.FIND_REFERENCES,
|
|
415
|
+
success=True,
|
|
416
|
+
lsp_used=True,
|
|
417
|
+
data={"symbol": symbol, "references": references},
|
|
418
|
+
citations=citations
|
|
419
|
+
)
|
|
420
|
+
except Exception as e:
|
|
421
|
+
logger.warning(f"LSP references failed: {e}")
|
|
422
|
+
|
|
423
|
+
# Fallback to grep
|
|
424
|
+
return await self._fallback_find_references(symbol)
|
|
425
|
+
|
|
426
|
+
async def _fallback_find_references(self, symbol: str) -> CodeQueryResult:
|
|
427
|
+
"""Fallback reference finding using grep."""
|
|
428
|
+
try:
|
|
429
|
+
import subprocess
|
|
430
|
+
workspace = self.runtime.config.workspace
|
|
431
|
+
|
|
432
|
+
result = subprocess.run(
|
|
433
|
+
["grep", "-rn", "-w", symbol, workspace],
|
|
434
|
+
capture_output=True,
|
|
435
|
+
text=True,
|
|
436
|
+
timeout=10
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
results = []
|
|
440
|
+
if result.stdout:
|
|
441
|
+
for line in result.stdout.strip().split('\n'):
|
|
442
|
+
if line:
|
|
443
|
+
parts = line.split(':', 2)
|
|
444
|
+
if len(parts) >= 2:
|
|
445
|
+
results.append({
|
|
446
|
+
"file": parts[0],
|
|
447
|
+
"line": int(parts[1]),
|
|
448
|
+
"content": parts[2] if len(parts) > 2 else ""
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
if results:
|
|
452
|
+
citations = [
|
|
453
|
+
{"file": r["file"], "line": r["line"], "type": "reference"}
|
|
454
|
+
for r in results
|
|
455
|
+
]
|
|
456
|
+
return CodeQueryResult(
|
|
457
|
+
intent=CodeIntent.FIND_REFERENCES,
|
|
458
|
+
success=True,
|
|
459
|
+
lsp_used=False,
|
|
460
|
+
fallback_used=True,
|
|
461
|
+
data={"symbol": symbol, "references": results},
|
|
462
|
+
citations=citations
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
return CodeQueryResult(
|
|
466
|
+
intent=CodeIntent.FIND_REFERENCES,
|
|
467
|
+
success=False,
|
|
468
|
+
lsp_used=False,
|
|
469
|
+
fallback_used=True,
|
|
470
|
+
error=f"No references found for '{symbol}'"
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
except Exception as e:
|
|
474
|
+
return CodeQueryResult(
|
|
475
|
+
intent=CodeIntent.FIND_REFERENCES,
|
|
476
|
+
success=False,
|
|
477
|
+
lsp_used=False,
|
|
478
|
+
fallback_used=True,
|
|
479
|
+
error=str(e)
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
async def _handle_get_diagnostics(self, query: str, file_path: str = None) -> CodeQueryResult:
|
|
483
|
+
"""Handle get diagnostics query."""
|
|
484
|
+
if not file_path:
|
|
485
|
+
file_path = self._extract_file_from_query(query)
|
|
486
|
+
|
|
487
|
+
# Try LSP
|
|
488
|
+
if self.runtime.lsp_ready:
|
|
489
|
+
try:
|
|
490
|
+
diagnostics = await self.runtime.lsp_get_diagnostics(file_path)
|
|
491
|
+
citations = []
|
|
492
|
+
if diagnostics:
|
|
493
|
+
for d in diagnostics:
|
|
494
|
+
if isinstance(d, dict):
|
|
495
|
+
citations.append({
|
|
496
|
+
"file": file_path or "workspace",
|
|
497
|
+
"line": d.get("range", {}).get("start", {}).get("line", 0) + 1,
|
|
498
|
+
"type": "diagnostic",
|
|
499
|
+
"severity": d.get("severity", "unknown")
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
return CodeQueryResult(
|
|
503
|
+
intent=CodeIntent.GET_DIAGNOSTICS,
|
|
504
|
+
success=True,
|
|
505
|
+
lsp_used=True,
|
|
506
|
+
data=diagnostics,
|
|
507
|
+
citations=citations
|
|
508
|
+
)
|
|
509
|
+
except Exception as e:
|
|
510
|
+
logger.warning(f"LSP diagnostics failed: {e}")
|
|
511
|
+
|
|
512
|
+
return CodeQueryResult(
|
|
513
|
+
intent=CodeIntent.GET_DIAGNOSTICS,
|
|
514
|
+
success=False,
|
|
515
|
+
lsp_used=False,
|
|
516
|
+
error="LSP not available for diagnostics"
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
async def _handle_search_code(self, query: str, file_path: str = None) -> CodeQueryResult:
|
|
520
|
+
"""Handle code search query."""
|
|
521
|
+
# Extract search term
|
|
522
|
+
search_term = self._extract_search_term(query)
|
|
523
|
+
|
|
524
|
+
if not search_term:
|
|
525
|
+
return CodeQueryResult(
|
|
526
|
+
intent=CodeIntent.SEARCH_CODE,
|
|
527
|
+
success=False,
|
|
528
|
+
lsp_used=False,
|
|
529
|
+
error="Could not identify search term"
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
try:
|
|
533
|
+
import subprocess
|
|
534
|
+
workspace = self.runtime.config.workspace
|
|
535
|
+
|
|
536
|
+
cmd = ["grep", "-rn", search_term]
|
|
537
|
+
if file_path:
|
|
538
|
+
cmd.append(file_path)
|
|
539
|
+
else:
|
|
540
|
+
cmd.append(workspace)
|
|
541
|
+
|
|
542
|
+
result = subprocess.run(
|
|
543
|
+
cmd,
|
|
544
|
+
capture_output=True,
|
|
545
|
+
text=True,
|
|
546
|
+
timeout=10
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
results = []
|
|
550
|
+
if result.stdout:
|
|
551
|
+
for line in result.stdout.strip().split('\n')[:50]: # Limit results
|
|
552
|
+
if line:
|
|
553
|
+
parts = line.split(':', 2)
|
|
554
|
+
if len(parts) >= 2:
|
|
555
|
+
results.append({
|
|
556
|
+
"file": parts[0],
|
|
557
|
+
"line": int(parts[1]),
|
|
558
|
+
"content": parts[2] if len(parts) > 2 else ""
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
citations = [
|
|
562
|
+
{"file": r["file"], "line": r["line"], "type": "search_result"}
|
|
563
|
+
for r in results
|
|
564
|
+
]
|
|
565
|
+
|
|
566
|
+
return CodeQueryResult(
|
|
567
|
+
intent=CodeIntent.SEARCH_CODE,
|
|
568
|
+
success=True,
|
|
569
|
+
lsp_used=False,
|
|
570
|
+
data={"search_term": search_term, "results": results},
|
|
571
|
+
citations=citations
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
except Exception as e:
|
|
575
|
+
return CodeQueryResult(
|
|
576
|
+
intent=CodeIntent.SEARCH_CODE,
|
|
577
|
+
success=False,
|
|
578
|
+
lsp_used=False,
|
|
579
|
+
error=str(e)
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
def _extract_file_from_query(self, query: str) -> Optional[str]:
|
|
583
|
+
"""Extract file path from query."""
|
|
584
|
+
# Look for file patterns
|
|
585
|
+
patterns = [
|
|
586
|
+
r'(?:in|from|file)\s+["\']?([^\s"\']+\.\w+)["\']?',
|
|
587
|
+
r'([^\s]+\.(?:py|js|ts|tsx|jsx|go|rs|java|cpp|c|h))',
|
|
588
|
+
]
|
|
589
|
+
|
|
590
|
+
for pattern in patterns:
|
|
591
|
+
match = re.search(pattern, query, re.IGNORECASE)
|
|
592
|
+
if match:
|
|
593
|
+
return match.group(1)
|
|
594
|
+
|
|
595
|
+
return None
|
|
596
|
+
|
|
597
|
+
def _extract_symbol_from_query(self, query: str) -> Optional[str]:
|
|
598
|
+
"""Extract symbol name from query."""
|
|
599
|
+
# Common patterns for symbol extraction
|
|
600
|
+
patterns = [
|
|
601
|
+
r'(?:definition\s+of|where\s+is|find|show)\s+["\']?(\w+)["\']?',
|
|
602
|
+
r'["\'](\w+)["\']',
|
|
603
|
+
r'`(\w+)`',
|
|
604
|
+
r'(\w+)\s+(?:is\s+)?(?:defined|used)',
|
|
605
|
+
]
|
|
606
|
+
|
|
607
|
+
for pattern in patterns:
|
|
608
|
+
match = re.search(pattern, query, re.IGNORECASE)
|
|
609
|
+
if match:
|
|
610
|
+
symbol = match.group(1)
|
|
611
|
+
# Filter out common words
|
|
612
|
+
if symbol.lower() not in ['the', 'a', 'an', 'is', 'are', 'in', 'of', 'to', 'for']:
|
|
613
|
+
return symbol
|
|
614
|
+
|
|
615
|
+
return None
|
|
616
|
+
|
|
617
|
+
def _extract_search_term(self, query: str) -> Optional[str]:
|
|
618
|
+
"""Extract search term from query."""
|
|
619
|
+
patterns = [
|
|
620
|
+
r'search\s+(?:for\s+)?["\']([^"\']+)["\']',
|
|
621
|
+
r'find\s+["\']([^"\']+)["\']',
|
|
622
|
+
r'grep\s+["\']?([^\s"\']+)["\']?',
|
|
623
|
+
]
|
|
624
|
+
|
|
625
|
+
for pattern in patterns:
|
|
626
|
+
match = re.search(pattern, query, re.IGNORECASE)
|
|
627
|
+
if match:
|
|
628
|
+
return match.group(1)
|
|
629
|
+
|
|
630
|
+
return None
|
|
631
|
+
|
|
632
|
+
async def _find_symbol_position(self, file_path: str, symbol: str) -> tuple:
|
|
633
|
+
"""Find the position of a symbol in a file."""
|
|
634
|
+
try:
|
|
635
|
+
path = Path(file_path)
|
|
636
|
+
if not path.exists():
|
|
637
|
+
path = Path(self.runtime.config.workspace) / file_path
|
|
638
|
+
|
|
639
|
+
if not path.exists():
|
|
640
|
+
return None, None
|
|
641
|
+
|
|
642
|
+
content = path.read_text()
|
|
643
|
+
lines = content.split('\n')
|
|
644
|
+
|
|
645
|
+
for i, line in enumerate(lines):
|
|
646
|
+
col = line.find(symbol)
|
|
647
|
+
if col >= 0:
|
|
648
|
+
return i, col
|
|
649
|
+
|
|
650
|
+
return None, None
|
|
651
|
+
except Exception:
|
|
652
|
+
return None, None
|