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,231 @@
|
|
|
1
|
+
"""
|
|
2
|
+
External Agents Handler for CLI.
|
|
3
|
+
|
|
4
|
+
Provides integration with external AI coding CLI tools:
|
|
5
|
+
- Claude Code CLI
|
|
6
|
+
- Gemini CLI
|
|
7
|
+
- OpenAI Codex CLI
|
|
8
|
+
- Cursor CLI
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
praisonai "Refactor the code" --external-agent claude
|
|
12
|
+
praisonai "Analyze codebase" --external-agent gemini
|
|
13
|
+
praisonai "Fix bugs" --external-agent codex
|
|
14
|
+
praisonai "Add tests" --external-agent cursor
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from typing import Dict, Any, List, Optional, Tuple
|
|
18
|
+
|
|
19
|
+
from .base import FlagHandler
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ExternalAgentsHandler(FlagHandler):
|
|
23
|
+
"""
|
|
24
|
+
Handler for --external-agent flag.
|
|
25
|
+
|
|
26
|
+
Integrates external AI coding CLI tools as agent tools.
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
praisonai "Refactor the auth module" --external-agent claude
|
|
30
|
+
praisonai "Analyze this codebase" --external-agent gemini
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
# Mapping of integration names to their module paths
|
|
34
|
+
INTEGRATIONS = {
|
|
35
|
+
"claude": "claude_code.ClaudeCodeIntegration",
|
|
36
|
+
"gemini": "gemini_cli.GeminiCLIIntegration",
|
|
37
|
+
"codex": "codex_cli.CodexCLIIntegration",
|
|
38
|
+
"cursor": "cursor_cli.CursorCLIIntegration",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
def __init__(self, verbose: bool = False):
|
|
42
|
+
"""Initialize the handler."""
|
|
43
|
+
super().__init__(verbose)
|
|
44
|
+
self._integration_cache: Dict[str, Any] = {}
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def feature_name(self) -> str:
|
|
48
|
+
"""Return the feature name."""
|
|
49
|
+
return "external_agents"
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def flag_name(self) -> str:
|
|
53
|
+
"""Return the flag name."""
|
|
54
|
+
return "external-agent"
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def flag_help(self) -> str:
|
|
58
|
+
"""Return the flag help text."""
|
|
59
|
+
return "External AI CLI tool to use (claude, gemini, codex, cursor)"
|
|
60
|
+
|
|
61
|
+
def check_dependencies(self) -> Tuple[bool, str]:
|
|
62
|
+
"""Check if integrations module is available."""
|
|
63
|
+
try:
|
|
64
|
+
import importlib.util
|
|
65
|
+
if importlib.util.find_spec("praisonai.integrations") is not None:
|
|
66
|
+
return True, ""
|
|
67
|
+
return False, "Integrations module not available"
|
|
68
|
+
except ImportError:
|
|
69
|
+
return False, "Integrations module not available"
|
|
70
|
+
|
|
71
|
+
def get_integration(self, name: str, **options):
|
|
72
|
+
"""
|
|
73
|
+
Get an integration instance by name.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
name: Integration name (claude, gemini, codex, cursor)
|
|
77
|
+
**options: Options to pass to the integration
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Integration instance
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
ValueError: If integration name is invalid
|
|
84
|
+
"""
|
|
85
|
+
if name not in self.INTEGRATIONS:
|
|
86
|
+
raise ValueError(f"Unknown integration: {name}. Available: {list(self.INTEGRATIONS.keys())}")
|
|
87
|
+
|
|
88
|
+
# Check cache first
|
|
89
|
+
cache_key = f"{name}_{hash(frozenset(options.items()))}"
|
|
90
|
+
if cache_key in self._integration_cache:
|
|
91
|
+
return self._integration_cache[cache_key]
|
|
92
|
+
|
|
93
|
+
# Lazy import the integration
|
|
94
|
+
module_path = self.INTEGRATIONS[name]
|
|
95
|
+
module_name, class_name = module_path.rsplit(".", 1)
|
|
96
|
+
|
|
97
|
+
import importlib
|
|
98
|
+
module = importlib.import_module(f"praisonai.integrations.{module_name}")
|
|
99
|
+
integration_class = getattr(module, class_name)
|
|
100
|
+
|
|
101
|
+
# Create instance with options
|
|
102
|
+
integration = integration_class(**options)
|
|
103
|
+
|
|
104
|
+
# Cache it
|
|
105
|
+
self._integration_cache[cache_key] = integration
|
|
106
|
+
|
|
107
|
+
return integration
|
|
108
|
+
|
|
109
|
+
def list_integrations(self) -> List[str]:
|
|
110
|
+
"""
|
|
111
|
+
List all available integration names.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
List of integration names
|
|
115
|
+
"""
|
|
116
|
+
return list(self.INTEGRATIONS.keys())
|
|
117
|
+
|
|
118
|
+
def check_availability(self) -> Dict[str, bool]:
|
|
119
|
+
"""
|
|
120
|
+
Check availability of all integrations.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Dict mapping integration name to availability
|
|
124
|
+
"""
|
|
125
|
+
availability = {}
|
|
126
|
+
for name in self.INTEGRATIONS:
|
|
127
|
+
try:
|
|
128
|
+
integration = self.get_integration(name)
|
|
129
|
+
availability[name] = integration.is_available
|
|
130
|
+
except Exception:
|
|
131
|
+
availability[name] = False
|
|
132
|
+
return availability
|
|
133
|
+
|
|
134
|
+
def apply_to_agent_config(self, config: Dict[str, Any], flag_value: Any) -> Dict[str, Any]:
|
|
135
|
+
"""
|
|
136
|
+
Apply external agent configuration to agent config.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
config: Agent configuration dictionary
|
|
140
|
+
flag_value: Integration name or dict with name and options
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Modified configuration with external agent tool
|
|
144
|
+
"""
|
|
145
|
+
if not flag_value:
|
|
146
|
+
return config
|
|
147
|
+
|
|
148
|
+
# Handle both string and dict formats
|
|
149
|
+
if isinstance(flag_value, str):
|
|
150
|
+
name = flag_value
|
|
151
|
+
options = {}
|
|
152
|
+
elif isinstance(flag_value, dict):
|
|
153
|
+
name = flag_value.get("name", "")
|
|
154
|
+
options = {k: v for k, v in flag_value.items() if k != "name"}
|
|
155
|
+
else:
|
|
156
|
+
return config
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
integration = self.get_integration(name, **options)
|
|
160
|
+
|
|
161
|
+
if integration.is_available:
|
|
162
|
+
# Add tool to existing tools
|
|
163
|
+
existing_tools = config.get("tools", [])
|
|
164
|
+
if isinstance(existing_tools, list):
|
|
165
|
+
existing_tools.append(integration.as_tool())
|
|
166
|
+
else:
|
|
167
|
+
existing_tools = [integration.as_tool()]
|
|
168
|
+
config["tools"] = existing_tools
|
|
169
|
+
|
|
170
|
+
self.print_status(f"🔌 External agent connected: {name}", "success")
|
|
171
|
+
else:
|
|
172
|
+
self.print_status(f"⚠️ External agent not available: {name}", "warning")
|
|
173
|
+
|
|
174
|
+
except ValueError as e:
|
|
175
|
+
self.print_status(str(e), "error")
|
|
176
|
+
except Exception as e:
|
|
177
|
+
self.print_status(f"Failed to connect external agent: {e}", "error")
|
|
178
|
+
|
|
179
|
+
return config
|
|
180
|
+
|
|
181
|
+
def execute(
|
|
182
|
+
self,
|
|
183
|
+
integration_name: str = None,
|
|
184
|
+
**options
|
|
185
|
+
) -> Optional[Any]:
|
|
186
|
+
"""
|
|
187
|
+
Execute external agent setup.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
integration_name: Name of the integration
|
|
191
|
+
**options: Options for the integration
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Integration instance or None
|
|
195
|
+
"""
|
|
196
|
+
if not integration_name:
|
|
197
|
+
# List available integrations
|
|
198
|
+
self.print_status("\n🔧 Available External Agents:", "info")
|
|
199
|
+
self.print_status("-" * 40, "info")
|
|
200
|
+
|
|
201
|
+
availability = self.check_availability()
|
|
202
|
+
for name, available in availability.items():
|
|
203
|
+
status = "✅ Available" if available else "❌ Not installed"
|
|
204
|
+
self.print_status(f" {name}: {status}", "info")
|
|
205
|
+
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
integration = self.get_integration(integration_name, **options)
|
|
210
|
+
|
|
211
|
+
if integration.is_available:
|
|
212
|
+
self.print_status(f"🔌 External agent ready: {integration_name}", "success")
|
|
213
|
+
return integration
|
|
214
|
+
else:
|
|
215
|
+
self.print_status(f"⚠️ External agent not installed: {integration_name}", "warning")
|
|
216
|
+
self.print_status(f" Install with: {self._get_install_instructions(integration_name)}", "info")
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
except ValueError as e:
|
|
220
|
+
self.print_status(str(e), "error")
|
|
221
|
+
return None
|
|
222
|
+
|
|
223
|
+
def _get_install_instructions(self, name: str) -> str:
|
|
224
|
+
"""Get installation instructions for an integration."""
|
|
225
|
+
instructions = {
|
|
226
|
+
"claude": "npm install -g @anthropic-ai/claude-code",
|
|
227
|
+
"gemini": "npm install -g @anthropic-ai/gemini-cli",
|
|
228
|
+
"codex": "npm install -g @openai/codex",
|
|
229
|
+
"cursor": "Download from cursor.com",
|
|
230
|
+
}
|
|
231
|
+
return instructions.get(name, "See documentation")
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Fast Context Handler for CLI.
|
|
3
|
+
|
|
4
|
+
Provides codebase search capability using FastContext.
|
|
5
|
+
Usage: praisonai "Find authentication code" --fast-context ./src
|
|
6
|
+
|
|
7
|
+
Fast Context Strategy:
|
|
8
|
+
1. Extract search keywords from natural language query using LLM
|
|
9
|
+
2. Provide folder tree context to help understand codebase structure
|
|
10
|
+
3. Use both file name search AND content search (ripgrep-style)
|
|
11
|
+
4. Return relevant code snippets for context
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import logging
|
|
16
|
+
from typing import Any, Dict, Tuple, List
|
|
17
|
+
from .base import FlagHandler
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FastContextHandler(FlagHandler):
|
|
23
|
+
"""
|
|
24
|
+
Handler for --fast-context flag.
|
|
25
|
+
|
|
26
|
+
Searches codebase for relevant context before agent execution.
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
praisonai "Find authentication code" --fast-context ./src
|
|
30
|
+
praisonai "Explain the database schema" --fast-context /path/to/project
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def feature_name(self) -> str:
|
|
35
|
+
return "fast_context"
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def flag_name(self) -> str:
|
|
39
|
+
return "fast-context"
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def flag_help(self) -> str:
|
|
43
|
+
return "Path to search for relevant code context"
|
|
44
|
+
|
|
45
|
+
def check_dependencies(self) -> Tuple[bool, str]:
|
|
46
|
+
"""Check if FastContext is available."""
|
|
47
|
+
try:
|
|
48
|
+
import importlib.util
|
|
49
|
+
if importlib.util.find_spec("praisonaiagents") is not None:
|
|
50
|
+
return True, ""
|
|
51
|
+
return False, "praisonaiagents not installed"
|
|
52
|
+
except ImportError:
|
|
53
|
+
return False, "praisonaiagents not installed. Install with: pip install praisonaiagents"
|
|
54
|
+
|
|
55
|
+
def validate_search_path(self, path: str) -> Tuple[bool, str]:
|
|
56
|
+
"""
|
|
57
|
+
Validate that the search path exists.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
path: Path to search directory
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Tuple of (is_valid, error_message)
|
|
64
|
+
"""
|
|
65
|
+
if not path:
|
|
66
|
+
return False, "No search path provided"
|
|
67
|
+
|
|
68
|
+
if not os.path.isdir(path):
|
|
69
|
+
return False, f"Directory not found: {path}"
|
|
70
|
+
|
|
71
|
+
return True, ""
|
|
72
|
+
|
|
73
|
+
def _count_files_quick(self, path: str, max_count: int = 1000) -> int:
|
|
74
|
+
"""Quickly count files in directory, stopping at max_count."""
|
|
75
|
+
count = 0
|
|
76
|
+
ignore_dirs = {'.git', '__pycache__', 'node_modules', '.venv', 'venv', '.cache', 'dist', 'build'}
|
|
77
|
+
|
|
78
|
+
for root, dirs, files in os.walk(path):
|
|
79
|
+
dirs[:] = [d for d in dirs if d not in ignore_dirs and not d.startswith('.')]
|
|
80
|
+
count += len(files)
|
|
81
|
+
if count >= max_count:
|
|
82
|
+
return count
|
|
83
|
+
return count
|
|
84
|
+
|
|
85
|
+
def _get_folder_tree(self, path: str, max_depth: int = 2) -> str:
|
|
86
|
+
"""
|
|
87
|
+
Get a compact folder tree structure for context.
|
|
88
|
+
|
|
89
|
+
Optimized for large directories:
|
|
90
|
+
- Limits depth to max_depth (default 2)
|
|
91
|
+
- Shows max 10 files per directory
|
|
92
|
+
- Truncates total output to 50 lines
|
|
93
|
+
- Skips common non-code directories
|
|
94
|
+
"""
|
|
95
|
+
# Quick check for very large directories
|
|
96
|
+
file_count = self._count_files_quick(path, 500)
|
|
97
|
+
if file_count >= 500:
|
|
98
|
+
# For large directories, reduce depth and show summary
|
|
99
|
+
max_depth = 1
|
|
100
|
+
logger.debug(f"Large directory detected ({file_count}+ files), reducing tree depth")
|
|
101
|
+
|
|
102
|
+
tree_lines = []
|
|
103
|
+
ignore_dirs = {'.git', '__pycache__', 'node_modules', '.venv', 'venv', '.cache', 'dist', 'build', 'coverage', '.pytest_cache', '.mypy_cache', 'eggs', '*.egg-info'}
|
|
104
|
+
ignore_exts = {'.pyc', '.pyo', '.so', '.o', '.a', '.dylib', '.class', '.jar'}
|
|
105
|
+
total_files = 0
|
|
106
|
+
max_total_files = 100 # Stop after seeing this many files
|
|
107
|
+
|
|
108
|
+
def walk_tree(current_path, prefix="", depth=0):
|
|
109
|
+
nonlocal total_files
|
|
110
|
+
if depth > max_depth or total_files >= max_total_files:
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
entries = sorted(os.listdir(current_path))
|
|
115
|
+
except PermissionError:
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
dirs = []
|
|
119
|
+
files = []
|
|
120
|
+
for entry in entries:
|
|
121
|
+
full_path = os.path.join(current_path, entry)
|
|
122
|
+
if os.path.isdir(full_path):
|
|
123
|
+
if entry not in ignore_dirs and not entry.startswith('.'):
|
|
124
|
+
dirs.append(entry)
|
|
125
|
+
else:
|
|
126
|
+
ext = os.path.splitext(entry)[1]
|
|
127
|
+
if ext not in ignore_exts and not entry.startswith('.'):
|
|
128
|
+
files.append(entry)
|
|
129
|
+
|
|
130
|
+
# Limit directories shown at each level
|
|
131
|
+
dirs_to_show = dirs[:15]
|
|
132
|
+
for i, d in enumerate(dirs_to_show):
|
|
133
|
+
is_last = (i == len(dirs_to_show) - 1) and not files
|
|
134
|
+
tree_lines.append(f"{prefix}{'└── ' if is_last else '├── '}{d}/")
|
|
135
|
+
walk_tree(os.path.join(current_path, d),
|
|
136
|
+
prefix + (' ' if is_last else '│ '), depth + 1)
|
|
137
|
+
if len(dirs) > 15:
|
|
138
|
+
tree_lines.append(f"{prefix} ... and {len(dirs) - 15} more directories")
|
|
139
|
+
|
|
140
|
+
# Limit files shown
|
|
141
|
+
files_to_show = files[:10]
|
|
142
|
+
total_files += len(files)
|
|
143
|
+
for i, f in enumerate(files_to_show):
|
|
144
|
+
is_last = i == len(files_to_show) - 1
|
|
145
|
+
tree_lines.append(f"{prefix}{'└── ' if is_last else '├── '}{f}")
|
|
146
|
+
if len(files) > 10:
|
|
147
|
+
tree_lines.append(f"{prefix} ... and {len(files) - 10} more files")
|
|
148
|
+
|
|
149
|
+
tree_lines.append(os.path.basename(path) + "/")
|
|
150
|
+
walk_tree(path)
|
|
151
|
+
return "\n".join(tree_lines[:50]) # Limit total lines
|
|
152
|
+
|
|
153
|
+
def _extract_keywords(self, query: str, folder_tree: str) -> List[str]:
|
|
154
|
+
"""Extract search keywords from natural language query using LLM."""
|
|
155
|
+
try:
|
|
156
|
+
from praisonaiagents import Agent
|
|
157
|
+
|
|
158
|
+
extractor = Agent(
|
|
159
|
+
name="KeywordExtractor",
|
|
160
|
+
role="Search Query Analyzer",
|
|
161
|
+
goal="Extract file search keywords from natural language",
|
|
162
|
+
backstory="Expert at understanding code search queries",
|
|
163
|
+
llm="gpt-4o-mini",
|
|
164
|
+
verbose=False
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
prompt = f"""Given this folder structure:
|
|
168
|
+
{folder_tree}
|
|
169
|
+
|
|
170
|
+
And this search query: "{query}"
|
|
171
|
+
|
|
172
|
+
Extract 1-5 specific keywords/patterns to search for in file names and content.
|
|
173
|
+
Return ONLY the keywords, one per line, no explanations.
|
|
174
|
+
Focus on: file names, function names, class names, variable names that might match.
|
|
175
|
+
If the query mentions a folder name that exists, include files from that folder."""
|
|
176
|
+
|
|
177
|
+
response = extractor.chat(prompt, stream=False)
|
|
178
|
+
keywords = [k.strip() for k in str(response).strip().split('\n') if k.strip()]
|
|
179
|
+
logger.debug(f"Extracted keywords: {keywords}")
|
|
180
|
+
return keywords[:5] # Limit to 5 keywords
|
|
181
|
+
except Exception as e:
|
|
182
|
+
logger.debug(f"Keyword extraction failed: {e}")
|
|
183
|
+
# Fallback: extract words from query
|
|
184
|
+
return [w for w in query.split() if len(w) > 2][:3]
|
|
185
|
+
|
|
186
|
+
def _search_files_by_name(self, path: str, keywords: List[str]) -> List[Dict[str, Any]]:
|
|
187
|
+
"""Search for files matching keywords in their names."""
|
|
188
|
+
matches = []
|
|
189
|
+
path = os.path.abspath(path)
|
|
190
|
+
|
|
191
|
+
ignore_dirs = {'.git', '__pycache__', 'node_modules', '.venv', 'venv'}
|
|
192
|
+
|
|
193
|
+
for root, dirs, files in os.walk(path):
|
|
194
|
+
dirs[:] = [d for d in dirs if d not in ignore_dirs]
|
|
195
|
+
|
|
196
|
+
for filename in files:
|
|
197
|
+
filepath = os.path.join(root, filename)
|
|
198
|
+
rel_path = os.path.relpath(filepath, path)
|
|
199
|
+
|
|
200
|
+
# Check if any keyword matches filename or path
|
|
201
|
+
for kw in keywords:
|
|
202
|
+
kw_lower = kw.lower()
|
|
203
|
+
if kw_lower in filename.lower() or kw_lower in rel_path.lower():
|
|
204
|
+
matches.append({
|
|
205
|
+
'file': rel_path,
|
|
206
|
+
'lines': [(1, 50)], # First 50 lines
|
|
207
|
+
'relevance': 1.0,
|
|
208
|
+
'match_type': 'filename'
|
|
209
|
+
})
|
|
210
|
+
break
|
|
211
|
+
|
|
212
|
+
return matches[:20] # Limit results
|
|
213
|
+
|
|
214
|
+
def _search_files_by_content(self, path: str, keywords: List[str]) -> List[Dict[str, Any]]:
|
|
215
|
+
"""Search for files containing keywords in content."""
|
|
216
|
+
matches = []
|
|
217
|
+
path = os.path.abspath(path)
|
|
218
|
+
|
|
219
|
+
ignore_dirs = {'.git', '__pycache__', 'node_modules', '.venv', 'venv'}
|
|
220
|
+
code_exts = {'.py', '.js', '.ts', '.jsx', '.tsx', '.java', '.go', '.rs', '.rb', '.sh', '.yaml', '.yml', '.json', '.md', '.txt'}
|
|
221
|
+
|
|
222
|
+
for root, dirs, files in os.walk(path):
|
|
223
|
+
dirs[:] = [d for d in dirs if d not in ignore_dirs]
|
|
224
|
+
|
|
225
|
+
for filename in files:
|
|
226
|
+
ext = os.path.splitext(filename)[1]
|
|
227
|
+
if ext not in code_exts:
|
|
228
|
+
continue
|
|
229
|
+
|
|
230
|
+
filepath = os.path.join(root, filename)
|
|
231
|
+
rel_path = os.path.relpath(filepath, path)
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
|
|
235
|
+
content = f.read(50000) # Read first 50KB
|
|
236
|
+
|
|
237
|
+
content_lower = content.lower()
|
|
238
|
+
for kw in keywords:
|
|
239
|
+
if kw.lower() in content_lower:
|
|
240
|
+
# Find line numbers with matches
|
|
241
|
+
lines = content.split('\n')
|
|
242
|
+
match_lines = []
|
|
243
|
+
for i, line in enumerate(lines[:200], 1): # Check first 200 lines
|
|
244
|
+
if kw.lower() in line.lower():
|
|
245
|
+
match_lines.append((max(1, i-2), min(len(lines), i+2)))
|
|
246
|
+
|
|
247
|
+
if match_lines:
|
|
248
|
+
matches.append({
|
|
249
|
+
'file': rel_path,
|
|
250
|
+
'lines': match_lines[:5], # Limit line ranges
|
|
251
|
+
'relevance': 0.8,
|
|
252
|
+
'match_type': 'content'
|
|
253
|
+
})
|
|
254
|
+
break
|
|
255
|
+
except Exception:
|
|
256
|
+
continue
|
|
257
|
+
|
|
258
|
+
return matches[:15] # Limit results
|
|
259
|
+
|
|
260
|
+
def search_context(self, query: str, path: str, **kwargs) -> List[Dict[str, Any]]:
|
|
261
|
+
"""
|
|
262
|
+
Search for relevant context in the codebase.
|
|
263
|
+
|
|
264
|
+
Uses a smart multi-step approach:
|
|
265
|
+
1. Get folder tree structure
|
|
266
|
+
2. Use LLM to extract search keywords from query
|
|
267
|
+
3. Search by filename AND content
|
|
268
|
+
4. Return combined results
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
query: Natural language search query
|
|
272
|
+
path: Path to search
|
|
273
|
+
**kwargs: Additional search options
|
|
274
|
+
- use_llm_keywords: Use LLM to extract keywords (default: True)
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
List of matching results
|
|
278
|
+
"""
|
|
279
|
+
available, msg = self.check_dependencies()
|
|
280
|
+
if not available:
|
|
281
|
+
self.print_status(msg, "error")
|
|
282
|
+
return []
|
|
283
|
+
|
|
284
|
+
valid, msg = self.validate_search_path(path)
|
|
285
|
+
if not valid:
|
|
286
|
+
self.print_status(msg, "error")
|
|
287
|
+
return []
|
|
288
|
+
|
|
289
|
+
try:
|
|
290
|
+
use_llm_keywords = kwargs.get('use_llm_keywords', True)
|
|
291
|
+
|
|
292
|
+
# Step 1: Get folder tree for context
|
|
293
|
+
logger.debug(f"Getting folder tree for: {path}")
|
|
294
|
+
folder_tree = self._get_folder_tree(path)
|
|
295
|
+
logger.debug(f"Folder tree:\n{folder_tree}")
|
|
296
|
+
|
|
297
|
+
# Step 2: Extract keywords
|
|
298
|
+
if use_llm_keywords:
|
|
299
|
+
logger.debug(f"Extracting keywords from query: {query}")
|
|
300
|
+
keywords = self._extract_keywords(query, folder_tree)
|
|
301
|
+
else:
|
|
302
|
+
keywords = [w for w in query.split() if len(w) > 2][:3]
|
|
303
|
+
|
|
304
|
+
logger.debug(f"Search keywords: {keywords}")
|
|
305
|
+
|
|
306
|
+
if not keywords:
|
|
307
|
+
self.print_status("Could not extract search keywords", "warning")
|
|
308
|
+
return []
|
|
309
|
+
|
|
310
|
+
# Step 3: Search by filename
|
|
311
|
+
logger.debug("Searching by filename...")
|
|
312
|
+
filename_matches = self._search_files_by_name(path, keywords)
|
|
313
|
+
logger.debug(f"Found {len(filename_matches)} filename matches")
|
|
314
|
+
|
|
315
|
+
# Step 4: Search by content
|
|
316
|
+
logger.debug("Searching by content...")
|
|
317
|
+
content_matches = self._search_files_by_content(path, keywords)
|
|
318
|
+
logger.debug(f"Found {len(content_matches)} content matches")
|
|
319
|
+
|
|
320
|
+
# Combine and deduplicate
|
|
321
|
+
seen_files = set()
|
|
322
|
+
matches = []
|
|
323
|
+
|
|
324
|
+
for m in filename_matches + content_matches:
|
|
325
|
+
if m['file'] not in seen_files:
|
|
326
|
+
seen_files.add(m['file'])
|
|
327
|
+
matches.append(m)
|
|
328
|
+
|
|
329
|
+
self.print_status(f"🔍 Found {len(matches)} relevant files", "success")
|
|
330
|
+
return matches
|
|
331
|
+
|
|
332
|
+
except Exception as e:
|
|
333
|
+
logger.error(f"FastContext search error: {e}")
|
|
334
|
+
self.log(f"FastContext search error: {e}", "error")
|
|
335
|
+
return []
|
|
336
|
+
|
|
337
|
+
def format_context_for_prompt(self, matches: List[Dict[str, Any]], max_chars: int = 10000) -> str:
|
|
338
|
+
"""
|
|
339
|
+
Format search results as context for the prompt.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
matches: List of search results
|
|
343
|
+
max_chars: Maximum characters to include
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
Formatted context string
|
|
347
|
+
"""
|
|
348
|
+
if not matches:
|
|
349
|
+
return ""
|
|
350
|
+
|
|
351
|
+
context_parts = ["## Relevant Code Context\n"]
|
|
352
|
+
total_chars = 0
|
|
353
|
+
|
|
354
|
+
for match in matches:
|
|
355
|
+
file_path = match.get('file', '')
|
|
356
|
+
lines = match.get('lines', [])
|
|
357
|
+
|
|
358
|
+
if not file_path or not os.path.exists(file_path):
|
|
359
|
+
continue
|
|
360
|
+
|
|
361
|
+
try:
|
|
362
|
+
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
363
|
+
content = f.readlines()
|
|
364
|
+
|
|
365
|
+
for start, end in lines:
|
|
366
|
+
snippet = ''.join(content[max(0, start-1):end])
|
|
367
|
+
|
|
368
|
+
if total_chars + len(snippet) > max_chars:
|
|
369
|
+
break
|
|
370
|
+
|
|
371
|
+
context_parts.append(f"\n### {file_path} (lines {start}-{end})\n```\n{snippet}```\n")
|
|
372
|
+
total_chars += len(snippet)
|
|
373
|
+
|
|
374
|
+
except Exception:
|
|
375
|
+
continue
|
|
376
|
+
|
|
377
|
+
return '\n'.join(context_parts)
|
|
378
|
+
|
|
379
|
+
def apply_to_agent_config(self, config: Dict[str, Any], flag_value: Any) -> Dict[str, Any]:
|
|
380
|
+
"""
|
|
381
|
+
Apply fast context configuration.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
config: Agent configuration dictionary
|
|
385
|
+
flag_value: Search path
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
Modified configuration
|
|
389
|
+
"""
|
|
390
|
+
if flag_value:
|
|
391
|
+
config['fast_context'] = True
|
|
392
|
+
config['fast_context_path'] = flag_value
|
|
393
|
+
return config
|
|
394
|
+
|
|
395
|
+
def execute(self, query: str = None, path: str = None, **kwargs) -> str:
|
|
396
|
+
"""
|
|
397
|
+
Execute fast context search and return formatted context.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
query: Search query
|
|
401
|
+
path: Search path
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
Formatted context string
|
|
405
|
+
"""
|
|
406
|
+
if not query or not path:
|
|
407
|
+
return ""
|
|
408
|
+
|
|
409
|
+
matches = self.search_context(query, path, **kwargs)
|
|
410
|
+
return self.format_context_for_prompt(matches)
|