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,284 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import asyncio
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import difflib
|
|
5
|
+
import platform
|
|
6
|
+
from typing import Dict, Any
|
|
7
|
+
from litellm import acompletion
|
|
8
|
+
import json
|
|
9
|
+
import dotenv
|
|
10
|
+
from tavily import TavilyClient
|
|
11
|
+
from crawl4ai import AsyncAsyncWebCrawler
|
|
12
|
+
|
|
13
|
+
dotenv.load_dotenv()
|
|
14
|
+
|
|
15
|
+
class AICoder:
|
|
16
|
+
def __init__(self, cwd: str = None, tavily_api_key: str = None):
|
|
17
|
+
self.cwd = cwd or os.getcwd()
|
|
18
|
+
self.tools = [
|
|
19
|
+
{
|
|
20
|
+
"type": "function",
|
|
21
|
+
"function": {
|
|
22
|
+
"name": "write_to_file",
|
|
23
|
+
"description": "Write content to a file at the specified path. If the file exists, it will be overwritten.",
|
|
24
|
+
"parameters": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"properties": {
|
|
27
|
+
"path": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "The path of the file to write to."
|
|
30
|
+
},
|
|
31
|
+
"content": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": "The content to write to the file."
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"required": ["path", "content"]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"type": "function",
|
|
42
|
+
"function": {
|
|
43
|
+
"name": "execute_command",
|
|
44
|
+
"description": "Execute a CLI command on the system.",
|
|
45
|
+
"parameters": {
|
|
46
|
+
"type": "object",
|
|
47
|
+
"properties": {
|
|
48
|
+
"command": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"description": "The CLI command to execute."
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"required": ["command"]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"type": "function",
|
|
59
|
+
"function": {
|
|
60
|
+
"name": "read_file",
|
|
61
|
+
"description": "Read the contents of a file at the specified path.",
|
|
62
|
+
"parameters": {
|
|
63
|
+
"type": "object",
|
|
64
|
+
"properties": {
|
|
65
|
+
"path": {
|
|
66
|
+
"type": "string",
|
|
67
|
+
"description": "The path of the file to read."
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"required": ["path"]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
self.tavily_api_key = tavily_api_key
|
|
77
|
+
if self.tavily_api_key:
|
|
78
|
+
self.tavily_client = TavilyClient(api_key=self.tavily_api_key)
|
|
79
|
+
self.tools.append({
|
|
80
|
+
"type": "function",
|
|
81
|
+
"function": {
|
|
82
|
+
"name": "tavily_web_search",
|
|
83
|
+
"description": "Search the web using Tavily API and crawl the resulting URLs",
|
|
84
|
+
"parameters": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"properties": {
|
|
87
|
+
"query": {"type": "string", "description": "Search query"}
|
|
88
|
+
},
|
|
89
|
+
"required": ["query"]
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
else:
|
|
94
|
+
self.tavily_client = None
|
|
95
|
+
|
|
96
|
+
async def create_directories(self, file_path):
|
|
97
|
+
file_path_obj = Path(file_path)
|
|
98
|
+
dir_path = file_path_obj.parent
|
|
99
|
+
if not dir_path.exists():
|
|
100
|
+
os.makedirs(dir_path, exist_ok=True)
|
|
101
|
+
return dir_path
|
|
102
|
+
|
|
103
|
+
async def file_exists(self, file_path):
|
|
104
|
+
return Path(file_path).exists()
|
|
105
|
+
|
|
106
|
+
async def write_to_file(self, file_path, content, existing=False):
|
|
107
|
+
if not existing:
|
|
108
|
+
await self.create_directories(file_path)
|
|
109
|
+
try:
|
|
110
|
+
with open(file_path, 'w') as file:
|
|
111
|
+
file.write(content)
|
|
112
|
+
return True
|
|
113
|
+
except Exception as e:
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
async def read_file(self, file_path):
|
|
117
|
+
try:
|
|
118
|
+
with open(file_path, 'r') as file:
|
|
119
|
+
return file.read()
|
|
120
|
+
except:
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
def get_shell_command(self, command: str) -> str:
|
|
124
|
+
"""
|
|
125
|
+
Convert command to be cross-platform compatible.
|
|
126
|
+
On Windows, use cmd /c for shell commands.
|
|
127
|
+
On Unix-like systems, use the command as-is.
|
|
128
|
+
"""
|
|
129
|
+
if platform.system() == "Windows":
|
|
130
|
+
# For Windows, escape quotes and wrap command in cmd /c
|
|
131
|
+
escaped_command = command.replace('"', '\\"')
|
|
132
|
+
return f'cmd /c "{escaped_command}"'
|
|
133
|
+
return command
|
|
134
|
+
|
|
135
|
+
async def execute_command(self, command: str):
|
|
136
|
+
try:
|
|
137
|
+
# Make command cross-platform compatible
|
|
138
|
+
shell_command = self.get_shell_command(command)
|
|
139
|
+
process = await asyncio.create_subprocess_shell(
|
|
140
|
+
shell_command,
|
|
141
|
+
stdout=asyncio.subprocess.PIPE,
|
|
142
|
+
stderr=asyncio.subprocess.PIPE,
|
|
143
|
+
cwd=self.cwd
|
|
144
|
+
)
|
|
145
|
+
stdout, stderr = await process.communicate()
|
|
146
|
+
if stdout:
|
|
147
|
+
return f"Command output:\n{stdout.decode()}"
|
|
148
|
+
if stderr:
|
|
149
|
+
return f"Command error:\n{stderr.decode()}"
|
|
150
|
+
return process.returncode == 0
|
|
151
|
+
except Exception as e:
|
|
152
|
+
return f"Error executing command: {str(e)}"
|
|
153
|
+
|
|
154
|
+
async def tavily_web_search(self, query):
|
|
155
|
+
if not self.tavily_client:
|
|
156
|
+
return json.dumps({
|
|
157
|
+
"query": query,
|
|
158
|
+
"error": "Tavily API key is not set. Web search is unavailable."
|
|
159
|
+
})
|
|
160
|
+
response = self.tavily_client.search(query)
|
|
161
|
+
results = []
|
|
162
|
+
async with AsyncAsyncWebCrawler() as crawler:
|
|
163
|
+
for result in response.get('results', []):
|
|
164
|
+
url = result.get('url')
|
|
165
|
+
if url:
|
|
166
|
+
try:
|
|
167
|
+
crawl_result = await crawler.arun(url=url)
|
|
168
|
+
results.append({
|
|
169
|
+
"content": result.get('content'),
|
|
170
|
+
"url": url,
|
|
171
|
+
"full_content": crawl_result.markdown
|
|
172
|
+
})
|
|
173
|
+
except Exception:
|
|
174
|
+
results.append({
|
|
175
|
+
"content": result.get('content'),
|
|
176
|
+
"url": url,
|
|
177
|
+
"full_content": "Error: Unable to crawl this URL"
|
|
178
|
+
})
|
|
179
|
+
return json.dumps({
|
|
180
|
+
"query": query,
|
|
181
|
+
"results": results
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
def generate_diff(self, original_content: str, new_content: str, filename="file.txt"):
|
|
185
|
+
diff_lines = difflib.unified_diff(
|
|
186
|
+
original_content.splitlines(keepends=True),
|
|
187
|
+
new_content.splitlines(keepends=True),
|
|
188
|
+
fromfile=f"original_{filename}",
|
|
189
|
+
tofile=f"modified_{filename}"
|
|
190
|
+
)
|
|
191
|
+
return "".join(diff_lines)
|
|
192
|
+
|
|
193
|
+
def parse_json_response(self, json_object: Dict) -> Dict[str, Any]:
|
|
194
|
+
if 'choices' in json_object and json_object['choices'][0]['message']:
|
|
195
|
+
message = json_object['choices'][0]['message']
|
|
196
|
+
if 'tool_calls' in message and message['tool_calls']:
|
|
197
|
+
return {"type": "tool_calls", "data": message['tool_calls']}
|
|
198
|
+
return {"type": "content", "data": message.get('content', "")}
|
|
199
|
+
return {"type": "content", "data": json.dumps(json_object)}
|
|
200
|
+
|
|
201
|
+
def parse_llm_response(self, response: Any) -> Dict[str, Any]:
|
|
202
|
+
if response is None:
|
|
203
|
+
return {"type": "content", "data": ""}
|
|
204
|
+
if isinstance(response, str):
|
|
205
|
+
try:
|
|
206
|
+
json_object = json.loads(response)
|
|
207
|
+
if isinstance(json_object, dict):
|
|
208
|
+
return self.parse_json_response(json_object)
|
|
209
|
+
except json.JSONDecodeError:
|
|
210
|
+
return {"type": "content", "data": response}
|
|
211
|
+
if hasattr(response, 'choices') and response.choices:
|
|
212
|
+
message = response.choices[0].message
|
|
213
|
+
if hasattr(message, 'tool_calls') and message.tool_calls:
|
|
214
|
+
tool_calls_data = []
|
|
215
|
+
for tool_call in message.tool_calls:
|
|
216
|
+
tool_calls_data.append({
|
|
217
|
+
'id': tool_call.id,
|
|
218
|
+
'type': tool_call.type,
|
|
219
|
+
'function': {
|
|
220
|
+
'name': tool_call.function.name,
|
|
221
|
+
'arguments': tool_call.function.arguments
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
return {"type": "tool_calls", "data": tool_calls_data}
|
|
225
|
+
return {"type": "content", "data": message.content or ""}
|
|
226
|
+
return {"type": "content", "data": str(response)}
|
|
227
|
+
|
|
228
|
+
async def apply_llm_response(self, task, llm_response):
|
|
229
|
+
parsed_response = self.parse_llm_response(llm_response)
|
|
230
|
+
if parsed_response["type"] == "tool_calls":
|
|
231
|
+
for tool_call in parsed_response["data"]:
|
|
232
|
+
if tool_call["function"]["name"] == "write_to_file":
|
|
233
|
+
args = json.loads(tool_call["function"]["arguments"])
|
|
234
|
+
file_path = os.path.join(self.cwd, args["path"].strip())
|
|
235
|
+
content = args["content"]
|
|
236
|
+
if await self.file_exists(file_path):
|
|
237
|
+
original_content = await self.read_file(file_path)
|
|
238
|
+
file_diff = self.generate_diff(original_content, content, os.path.basename(file_path))
|
|
239
|
+
# Interaction with user removed for automation context
|
|
240
|
+
return await self.write_to_file(file_path, content, True)
|
|
241
|
+
else:
|
|
242
|
+
return await self.write_to_file(file_path, content)
|
|
243
|
+
elif tool_call["function"]["name"] == "execute_command":
|
|
244
|
+
args = json.loads(tool_call["function"]["arguments"])
|
|
245
|
+
command = args.get("command", "").strip()
|
|
246
|
+
if command:
|
|
247
|
+
return await self.execute_command(command)
|
|
248
|
+
else:
|
|
249
|
+
return False
|
|
250
|
+
elif tool_call["function"]["name"] == "read_file":
|
|
251
|
+
args = json.loads(tool_call["function"]["arguments"])
|
|
252
|
+
file_path = args.get("path", "").strip()
|
|
253
|
+
if file_path:
|
|
254
|
+
content = await self.read_file(os.path.join(self.cwd, file_path))
|
|
255
|
+
return True if content is not None else False
|
|
256
|
+
else:
|
|
257
|
+
return False
|
|
258
|
+
elif tool_call["function"]["name"] == "tavily_web_search":
|
|
259
|
+
args = json.loads(tool_call["function"]["arguments"])
|
|
260
|
+
return await self.tavily_web_search(args.get("query"))
|
|
261
|
+
else:
|
|
262
|
+
return False
|
|
263
|
+
return True
|
|
264
|
+
|
|
265
|
+
async def process_task(self, task: str):
|
|
266
|
+
llm_response = await acompletion(
|
|
267
|
+
model="gpt-4",
|
|
268
|
+
messages=[
|
|
269
|
+
{"role": "user", "content": task}
|
|
270
|
+
],
|
|
271
|
+
tools=self.tools,
|
|
272
|
+
tool_choice="auto"
|
|
273
|
+
)
|
|
274
|
+
return await self.apply_llm_response(task, llm_response)
|
|
275
|
+
|
|
276
|
+
async def main():
|
|
277
|
+
ai_coder = AICoder()
|
|
278
|
+
await ai_coder.process_task("Create a file called `hello.txt` with the content 'Hello, world!'")
|
|
279
|
+
await ai_coder.process_task("Edit the file called `hello.txt` and change the content to 'Hello again, world!'")
|
|
280
|
+
await ai_coder.process_task("Show me the current working directory")
|
|
281
|
+
await ai_coder.process_task("Read the contents of hello.txt")
|
|
282
|
+
|
|
283
|
+
if __name__ == "__main__":
|
|
284
|
+
asyncio.run(main())
|
praisonai/ui/context.py
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import fnmatch
|
|
3
|
+
import yaml
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
# Set up logging
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
log_level = os.getenv("LOGLEVEL", "INFO").upper() or "INFO"
|
|
10
|
+
logger.handlers = []
|
|
11
|
+
|
|
12
|
+
# Set up logging to console
|
|
13
|
+
console_handler = logging.StreamHandler()
|
|
14
|
+
console_handler.setLevel(log_level)
|
|
15
|
+
console_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
16
|
+
console_handler.setFormatter(console_formatter)
|
|
17
|
+
logger.addHandler(console_handler)
|
|
18
|
+
|
|
19
|
+
# Set the logging level for the logger
|
|
20
|
+
logger.setLevel(log_level)
|
|
21
|
+
|
|
22
|
+
class ContextGatherer:
|
|
23
|
+
def __init__(self, directory='.', output_file='context.txt',
|
|
24
|
+
relevant_extensions=None, max_file_size=1_000_000, max_tokens=900000):
|
|
25
|
+
self.directory = directory
|
|
26
|
+
self.output_file = output_file
|
|
27
|
+
self.relevant_extensions = relevant_extensions or [
|
|
28
|
+
'.py', '.js', '.ts', '.java', '.rb', '.php', '.pl', '.pm', '.c', '.h',
|
|
29
|
+
'.cpp', '.hpp', '.cs', '.vb', '.swift', '.kt', '.m', '.mm', '.go', '.rs',
|
|
30
|
+
'.hs', '.r', '.lua', '.sh', '.bat', '.clj', '.scala', '.erl', '.ex',
|
|
31
|
+
'.ml', '.fs', '.groovy', '.jsm', '.jsx', '.tsx', '.yaml'
|
|
32
|
+
]
|
|
33
|
+
self.max_file_size = max_file_size
|
|
34
|
+
self.max_tokens = int(os.getenv("PRAISONAI_MAX_TOKENS", max_tokens))
|
|
35
|
+
self.ignore_patterns = self.get_ignore_patterns()
|
|
36
|
+
self.include_paths = self.get_include_paths()
|
|
37
|
+
self.included_files = []
|
|
38
|
+
|
|
39
|
+
def get_ignore_patterns(self):
|
|
40
|
+
"""
|
|
41
|
+
Loads ignore patterns from various sources, prioritizing them in
|
|
42
|
+
the following order:
|
|
43
|
+
1. .praisonignore
|
|
44
|
+
2. settings.yaml (under code.ignore_files)
|
|
45
|
+
3. PRAISONAI_IGNORE_FILES environment variable
|
|
46
|
+
4. .gitignore
|
|
47
|
+
5. Default patterns
|
|
48
|
+
"""
|
|
49
|
+
ignore_patterns = []
|
|
50
|
+
|
|
51
|
+
def load_from_file(filepath):
|
|
52
|
+
if os.path.exists(filepath):
|
|
53
|
+
with open(filepath, 'r') as f:
|
|
54
|
+
ignore_patterns.extend(
|
|
55
|
+
line.strip() for line in f
|
|
56
|
+
if line.strip() and not line.startswith('#')
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# 1. Load from .praisonignore
|
|
60
|
+
load_from_file(os.path.join(self.directory, '.praisonignore'))
|
|
61
|
+
|
|
62
|
+
# 2. Load from settings.yaml
|
|
63
|
+
settings_path = os.path.join(self.directory, 'settings.yaml')
|
|
64
|
+
if os.path.exists(settings_path):
|
|
65
|
+
with open(settings_path, 'r') as f:
|
|
66
|
+
settings = yaml.safe_load(f)
|
|
67
|
+
if 'code' in settings and 'ignore_files' in settings['code']:
|
|
68
|
+
ignore_patterns.extend(settings['code']['ignore_files'])
|
|
69
|
+
|
|
70
|
+
# 3. Load from environment variable
|
|
71
|
+
ignore_files_env = os.getenv("PRAISONAI_IGNORE_FILES")
|
|
72
|
+
if ignore_files_env:
|
|
73
|
+
ignore_patterns.extend(ignore_files_env.split(","))
|
|
74
|
+
|
|
75
|
+
# 4. Load from .gitignore
|
|
76
|
+
load_from_file(os.path.join(self.directory, '.gitignore'))
|
|
77
|
+
|
|
78
|
+
# 5. Default patterns (only if no patterns loaded from above sources)
|
|
79
|
+
if not ignore_patterns:
|
|
80
|
+
ignore_patterns = [
|
|
81
|
+
".*", "*.pyc", "__pycache__", ".git", ".gitignore", ".vscode",
|
|
82
|
+
".idea", ".DS_Store", "*.lock", "*.pyc", ".env", "docs", "tests",
|
|
83
|
+
"test", "tmp", "temp", "*.txt", "*.md", "*.json", "*.csv", "*.tsv",
|
|
84
|
+
"public", "*.sql", "*.sqlite", "*.db", "*.db3", "*.sqlite3",
|
|
85
|
+
"*.log", "*.zip", "*.gz", "*.tar", "*.rar", "*.7z", "*.pdf",
|
|
86
|
+
"*.jpg", "*.jpeg", "*.png", "*.gif", "*.svg", "cookbooks",
|
|
87
|
+
"assets", "__pycache__", "dist", "build", "node_modules", "venv"
|
|
88
|
+
]
|
|
89
|
+
logger.debug(f"Using default ignore patterns: {ignore_patterns}")
|
|
90
|
+
|
|
91
|
+
# Modify patterns to match directories and add leading '*' if necessary
|
|
92
|
+
modified_ignore_patterns = [
|
|
93
|
+
'*' + pattern if not pattern.startswith('.') and not pattern.startswith('*') else pattern
|
|
94
|
+
for pattern in ignore_patterns
|
|
95
|
+
]
|
|
96
|
+
logger.debug(f"Final ignore patterns: {modified_ignore_patterns}")
|
|
97
|
+
return modified_ignore_patterns
|
|
98
|
+
|
|
99
|
+
def get_include_paths(self):
|
|
100
|
+
"""
|
|
101
|
+
Loads include paths from:
|
|
102
|
+
1. .praisoninclude (includes ONLY files/directories listed)
|
|
103
|
+
2. .praisoncontext (if .praisoninclude doesn't exist, this is used
|
|
104
|
+
to include all other relevant files, excluding ignore patterns)
|
|
105
|
+
"""
|
|
106
|
+
include_paths = []
|
|
107
|
+
include_all = False # Flag to indicate if we need to include all files
|
|
108
|
+
|
|
109
|
+
include_file = os.path.join(self.directory, '.praisoncontext')
|
|
110
|
+
if os.path.exists(include_file):
|
|
111
|
+
with open(include_file, 'r') as f:
|
|
112
|
+
include_paths.extend(
|
|
113
|
+
line.strip() for line in f
|
|
114
|
+
if line.strip() and not line.startswith('#')
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# If .praisoncontext doesn't exist, fall back to .praisoninclude
|
|
118
|
+
# for including all relevant files
|
|
119
|
+
if not include_paths:
|
|
120
|
+
include_file = os.path.join(self.directory, '.praisoninclude')
|
|
121
|
+
if os.path.exists(include_file):
|
|
122
|
+
with open(include_file, 'r') as f:
|
|
123
|
+
include_paths.extend(
|
|
124
|
+
line.strip() for line in f
|
|
125
|
+
if line.strip() and not line.startswith('#')
|
|
126
|
+
)
|
|
127
|
+
include_all = True # Include all files along with specified paths
|
|
128
|
+
|
|
129
|
+
return include_paths, include_all
|
|
130
|
+
|
|
131
|
+
def should_ignore(self, file_path):
|
|
132
|
+
"""
|
|
133
|
+
Check if a file or directory should be ignored based on patterns.
|
|
134
|
+
Handles both file names and directory names for more comprehensive filtering.
|
|
135
|
+
"""
|
|
136
|
+
relative_path = os.path.relpath(file_path, self.directory)
|
|
137
|
+
if relative_path.startswith('.'):
|
|
138
|
+
return True
|
|
139
|
+
for pattern in self.ignore_patterns:
|
|
140
|
+
if fnmatch.fnmatch(relative_path, pattern) or \
|
|
141
|
+
fnmatch.fnmatch(os.path.basename(file_path), pattern):
|
|
142
|
+
return True
|
|
143
|
+
return False
|
|
144
|
+
|
|
145
|
+
def is_relevant_file(self, file_path):
|
|
146
|
+
"""Determine if a file is relevant for the context."""
|
|
147
|
+
return os.path.isfile(file_path) and \
|
|
148
|
+
os.path.getsize(file_path) <= self.max_file_size and \
|
|
149
|
+
any(file_path.endswith(ext) for ext in self.relevant_extensions)
|
|
150
|
+
|
|
151
|
+
def gather_context(self):
|
|
152
|
+
"""
|
|
153
|
+
Gather context from relevant files, respecting ignore patterns
|
|
154
|
+
and include options from .praisoninclude and .praisoncontext.
|
|
155
|
+
"""
|
|
156
|
+
context = []
|
|
157
|
+
total_files = 0
|
|
158
|
+
processed_files = 0
|
|
159
|
+
self.include_paths, include_all = self.get_include_paths()
|
|
160
|
+
|
|
161
|
+
def add_file_content(file_path):
|
|
162
|
+
"""Helper function to add file content to context."""
|
|
163
|
+
try:
|
|
164
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
165
|
+
content = f.read()
|
|
166
|
+
context.append(
|
|
167
|
+
f"File: {file_path}\n\n{content}\n\n{'=' * 50}\n"
|
|
168
|
+
)
|
|
169
|
+
self.included_files.append(
|
|
170
|
+
Path(file_path).relative_to(self.directory)
|
|
171
|
+
)
|
|
172
|
+
except Exception as e:
|
|
173
|
+
logger.error(f"Error reading {file_path}: {e}")
|
|
174
|
+
|
|
175
|
+
def process_path(path):
|
|
176
|
+
"""Helper function to process a single path (file or directory)."""
|
|
177
|
+
nonlocal total_files, processed_files
|
|
178
|
+
if os.path.isdir(path):
|
|
179
|
+
for root, dirs, files in os.walk(path):
|
|
180
|
+
total_files += len(files)
|
|
181
|
+
dirs[:] = [
|
|
182
|
+
d
|
|
183
|
+
for d in dirs
|
|
184
|
+
if not self.should_ignore(os.path.join(root, d))
|
|
185
|
+
]
|
|
186
|
+
for file in files:
|
|
187
|
+
file_path = os.path.join(root, file)
|
|
188
|
+
if not self.should_ignore(file_path) and self.is_relevant_file(file_path):
|
|
189
|
+
add_file_content(file_path)
|
|
190
|
+
processed_files += 1
|
|
191
|
+
print(
|
|
192
|
+
f"\rProcessed {processed_files}/{total_files} files",
|
|
193
|
+
end="",
|
|
194
|
+
flush=True,
|
|
195
|
+
)
|
|
196
|
+
elif os.path.isfile(path) and self.is_relevant_file(path):
|
|
197
|
+
add_file_content(path)
|
|
198
|
+
processed_files += 1
|
|
199
|
+
print(
|
|
200
|
+
f"\rProcessed {processed_files}/1 files",
|
|
201
|
+
end="",
|
|
202
|
+
flush=True,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
if include_all:
|
|
206
|
+
# Include ALL relevant files from the entire directory
|
|
207
|
+
process_path(self.directory)
|
|
208
|
+
|
|
209
|
+
# Include files from .praisoninclude specifically
|
|
210
|
+
for include_path in self.include_paths:
|
|
211
|
+
full_path = os.path.join(self.directory, include_path)
|
|
212
|
+
process_path(full_path)
|
|
213
|
+
elif self.include_paths:
|
|
214
|
+
# Include only files specified in .praisoncontext
|
|
215
|
+
for include_path in self.include_paths:
|
|
216
|
+
full_path = os.path.join(self.directory, include_path)
|
|
217
|
+
process_path(full_path)
|
|
218
|
+
else:
|
|
219
|
+
# No include options, process the entire directory
|
|
220
|
+
process_path(self.directory)
|
|
221
|
+
|
|
222
|
+
print() # New line after progress indicator
|
|
223
|
+
return "\n".join(context)
|
|
224
|
+
|
|
225
|
+
def count_tokens(self, text):
|
|
226
|
+
"""Count tokens using a simple whitespace-based tokenizer."""
|
|
227
|
+
return len(text.split())
|
|
228
|
+
|
|
229
|
+
def truncate_context(self, context):
|
|
230
|
+
"""Truncate context to stay within the token limit."""
|
|
231
|
+
tokens = context.split()
|
|
232
|
+
if len(tokens) > self.max_tokens:
|
|
233
|
+
truncated_context = ' '.join(tokens[:self.max_tokens])
|
|
234
|
+
logger.warning("Context truncated due to token limit.")
|
|
235
|
+
return truncated_context
|
|
236
|
+
return context
|
|
237
|
+
|
|
238
|
+
def save_context(self, context):
|
|
239
|
+
"""Save the gathered context to a file."""
|
|
240
|
+
with open(self.output_file, 'w', encoding='utf-8') as f:
|
|
241
|
+
f.write(context)
|
|
242
|
+
|
|
243
|
+
def get_context_tree(self):
|
|
244
|
+
"""Generate a formatted tree structure of included files and folders."""
|
|
245
|
+
tree = []
|
|
246
|
+
start_dir = Path(self.directory)
|
|
247
|
+
|
|
248
|
+
def add_to_tree(path, prefix=''):
|
|
249
|
+
contents = sorted(path.iterdir())
|
|
250
|
+
pointers = [('└── ' if i == len(contents) - 1 else '├── ') for i in range(len(contents))]
|
|
251
|
+
for pointer, item in zip(pointers, contents):
|
|
252
|
+
rel_path = item.relative_to(start_dir)
|
|
253
|
+
if rel_path in self.included_files:
|
|
254
|
+
tree.append(f"{prefix}{pointer}{rel_path}")
|
|
255
|
+
|
|
256
|
+
if item.is_dir():
|
|
257
|
+
add_to_tree(item, prefix + (' ' if pointer == '└── ' else '│ '))
|
|
258
|
+
|
|
259
|
+
add_to_tree(start_dir)
|
|
260
|
+
return '\n'.join(tree)
|
|
261
|
+
|
|
262
|
+
def run(self):
|
|
263
|
+
"""Execute the context gathering, truncation, and reporting."""
|
|
264
|
+
context = self.gather_context()
|
|
265
|
+
context = self.truncate_context(context)
|
|
266
|
+
token_count = self.count_tokens(context)
|
|
267
|
+
print(f"Context gathered successfully.")
|
|
268
|
+
print(f"Total number of tokens (estimated): {token_count}")
|
|
269
|
+
# self.save_context(context)
|
|
270
|
+
context_tree = self.get_context_tree()
|
|
271
|
+
logger.debug(f"Context tree:\n{context_tree}")
|
|
272
|
+
return context, token_count, context_tree
|
|
273
|
+
|
|
274
|
+
def main():
|
|
275
|
+
gatherer = ContextGatherer()
|
|
276
|
+
context, token_count, context_tree = gatherer.run()
|
|
277
|
+
print(context_tree)
|
|
278
|
+
print(f"\nThe context contains approximately {token_count} tokens.")
|
|
279
|
+
print("First 500 characters of context:")
|
|
280
|
+
print(context[:500] + "...")
|
|
281
|
+
|
|
282
|
+
if __name__ == "__main__":
|
|
283
|
+
main()
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Database configuration utilities for PraisonAI UI components.
|
|
3
|
+
|
|
4
|
+
This module provides centralized database configuration functionality,
|
|
5
|
+
particularly for handling the FORCE_SQLITE environment variable.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from typing import Optional, Tuple
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def should_force_sqlite() -> bool:
|
|
13
|
+
"""
|
|
14
|
+
Check if FORCE_SQLITE environment variable is set to true.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
bool: True if FORCE_SQLITE is set to "true" (case-insensitive), False otherwise.
|
|
18
|
+
"""
|
|
19
|
+
return os.getenv("FORCE_SQLITE", "false").lower() == "true"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_database_url_with_sqlite_override() -> Optional[str]:
|
|
23
|
+
"""
|
|
24
|
+
Get database URL respecting FORCE_SQLITE flag.
|
|
25
|
+
|
|
26
|
+
When FORCE_SQLITE=true, this function returns None to force SQLite usage.
|
|
27
|
+
Otherwise, it returns the appropriate database URL from environment variables.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Optional[str]: Database URL if external database should be used, None for SQLite.
|
|
31
|
+
"""
|
|
32
|
+
if should_force_sqlite():
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
database_url = os.getenv("DATABASE_URL")
|
|
36
|
+
supabase_url = os.getenv("SUPABASE_DATABASE_URL")
|
|
37
|
+
return supabase_url if supabase_url else database_url
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_database_config_for_sqlalchemy() -> Tuple[Optional[str], Optional[str]]:
|
|
41
|
+
"""
|
|
42
|
+
Get database configuration for SQLAlchemy module respecting FORCE_SQLITE flag.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Tuple[Optional[str], Optional[str]]: (DATABASE_URL, SUPABASE_DATABASE_URL)
|
|
46
|
+
Both will be None if FORCE_SQLITE=true, otherwise original values.
|
|
47
|
+
"""
|
|
48
|
+
if should_force_sqlite():
|
|
49
|
+
return None, None
|
|
50
|
+
else:
|
|
51
|
+
database_url = os.getenv("DATABASE_URL")
|
|
52
|
+
supabase_url = os.getenv("SUPABASE_DATABASE_URL")
|
|
53
|
+
# Apply Supabase override logic
|
|
54
|
+
if supabase_url:
|
|
55
|
+
database_url = supabase_url
|
|
56
|
+
return database_url, supabase_url
|