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,391 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Template Discovery
|
|
3
|
+
|
|
4
|
+
Discovers templates from multiple directories with precedence support.
|
|
5
|
+
Supports custom user directories, built-in templates, and package templates.
|
|
6
|
+
Includes caching to avoid repeated filesystem scans.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import time
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Dict, List, Optional, Tuple
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class DiscoveredTemplate:
|
|
18
|
+
"""Information about a discovered template."""
|
|
19
|
+
name: str
|
|
20
|
+
path: Path
|
|
21
|
+
source: str # 'custom', 'builtin', 'package'
|
|
22
|
+
priority: int # Lower = higher priority
|
|
23
|
+
|
|
24
|
+
# Optional metadata (loaded lazily)
|
|
25
|
+
description: Optional[str] = None
|
|
26
|
+
version: Optional[str] = None
|
|
27
|
+
author: Optional[str] = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class _CacheEntry:
|
|
32
|
+
"""Cache entry for template discovery results."""
|
|
33
|
+
templates: Dict[str, 'DiscoveredTemplate']
|
|
34
|
+
timestamp: float
|
|
35
|
+
mtimes: Dict[str, float] = field(default_factory=dict) # path -> mtime for invalidation
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TemplateDiscovery:
|
|
39
|
+
"""
|
|
40
|
+
Discovers templates from multiple directories with precedence.
|
|
41
|
+
|
|
42
|
+
Search order (highest to lowest priority):
|
|
43
|
+
1. User custom directory (~/.praison/templates)
|
|
44
|
+
2. XDG config directory (~/.config/praison/templates)
|
|
45
|
+
3. Project local directory (./.praison/templates)
|
|
46
|
+
4. Built-in package templates (agent_recipes)
|
|
47
|
+
|
|
48
|
+
Templates in higher priority directories override those in lower priority.
|
|
49
|
+
|
|
50
|
+
Caching:
|
|
51
|
+
- Results are cached to avoid repeated filesystem scans
|
|
52
|
+
- Cache is invalidated when directory mtimes change
|
|
53
|
+
- Use refresh=True to force a fresh scan
|
|
54
|
+
- Default TTL is 300 seconds (5 minutes)
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
# Default search paths (in priority order)
|
|
58
|
+
DEFAULT_SEARCH_PATHS = [
|
|
59
|
+
("~/.praison/templates", "custom", 1),
|
|
60
|
+
("~/.config/praison/templates", "custom", 2),
|
|
61
|
+
("./.praison/templates", "project", 3),
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
TEMPLATE_FILE = "TEMPLATE.yaml"
|
|
65
|
+
|
|
66
|
+
# Cache TTL in seconds (5 minutes default)
|
|
67
|
+
CACHE_TTL = 300
|
|
68
|
+
|
|
69
|
+
# Class-level cache shared across instances with same search paths
|
|
70
|
+
_cache: Dict[str, _CacheEntry] = {}
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
custom_dirs: Optional[List[str]] = None,
|
|
75
|
+
include_package: bool = True,
|
|
76
|
+
include_defaults: bool = True,
|
|
77
|
+
cache_ttl: Optional[int] = None
|
|
78
|
+
):
|
|
79
|
+
"""
|
|
80
|
+
Initialize template discovery.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
custom_dirs: Additional custom directories to search (highest priority)
|
|
84
|
+
include_package: Whether to include package templates (agent_recipes)
|
|
85
|
+
include_defaults: Whether to include default search paths
|
|
86
|
+
cache_ttl: Cache time-to-live in seconds (default: 300)
|
|
87
|
+
"""
|
|
88
|
+
self.search_paths: List[Tuple[Path, str, int]] = []
|
|
89
|
+
|
|
90
|
+
# Add custom directories first (highest priority)
|
|
91
|
+
if custom_dirs:
|
|
92
|
+
for i, dir_path in enumerate(custom_dirs):
|
|
93
|
+
expanded = Path(os.path.expanduser(dir_path))
|
|
94
|
+
self.search_paths.append((expanded, "custom", i))
|
|
95
|
+
|
|
96
|
+
# Add default search paths
|
|
97
|
+
if include_defaults:
|
|
98
|
+
base_priority = len(custom_dirs) if custom_dirs else 0
|
|
99
|
+
for path, source, priority in self.DEFAULT_SEARCH_PATHS:
|
|
100
|
+
expanded = Path(os.path.expanduser(path))
|
|
101
|
+
self.search_paths.append((expanded, source, base_priority + priority))
|
|
102
|
+
|
|
103
|
+
self.include_package = include_package
|
|
104
|
+
self._package_path: Optional[Path] = None
|
|
105
|
+
self._cache_ttl = cache_ttl if cache_ttl is not None else self.CACHE_TTL
|
|
106
|
+
|
|
107
|
+
# Generate cache key based on search paths configuration
|
|
108
|
+
self._cache_key = self._generate_cache_key()
|
|
109
|
+
|
|
110
|
+
def _generate_cache_key(self) -> str:
|
|
111
|
+
"""Generate a unique cache key based on search paths."""
|
|
112
|
+
paths_str = "|".join(
|
|
113
|
+
f"{p}:{s}:{pr}" for p, s, pr in self.search_paths
|
|
114
|
+
)
|
|
115
|
+
return f"{paths_str}|pkg={self.include_package}"
|
|
116
|
+
|
|
117
|
+
def _get_dir_mtime(self, path: Path) -> float:
|
|
118
|
+
"""Get directory modification time, or 0 if not accessible."""
|
|
119
|
+
try:
|
|
120
|
+
if path.exists():
|
|
121
|
+
return path.stat().st_mtime
|
|
122
|
+
except (OSError, PermissionError):
|
|
123
|
+
pass
|
|
124
|
+
return 0.0
|
|
125
|
+
|
|
126
|
+
def _is_cache_valid(self, entry: _CacheEntry) -> bool:
|
|
127
|
+
"""Check if cache entry is still valid."""
|
|
128
|
+
# Check TTL
|
|
129
|
+
if time.time() - entry.timestamp > self._cache_ttl:
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
# Check if any directory mtimes have changed
|
|
133
|
+
for path_str, cached_mtime in entry.mtimes.items():
|
|
134
|
+
current_mtime = self._get_dir_mtime(Path(path_str))
|
|
135
|
+
if current_mtime != cached_mtime:
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
return True
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def package_templates_path(self) -> Optional[Path]:
|
|
142
|
+
"""Get path to package templates (lazy loaded)."""
|
|
143
|
+
if self._package_path is None and self.include_package:
|
|
144
|
+
try:
|
|
145
|
+
import importlib.resources
|
|
146
|
+
try:
|
|
147
|
+
# Python 3.9+
|
|
148
|
+
ref = importlib.resources.files("agent_recipes") / "templates"
|
|
149
|
+
self._package_path = Path(str(ref))
|
|
150
|
+
except (TypeError, AttributeError):
|
|
151
|
+
# Fallback for older Python
|
|
152
|
+
import agent_recipes
|
|
153
|
+
self._package_path = Path(agent_recipes.__file__).parent / "templates"
|
|
154
|
+
except ImportError:
|
|
155
|
+
pass
|
|
156
|
+
return self._package_path
|
|
157
|
+
|
|
158
|
+
def discover_all(self, refresh: bool = False) -> Dict[str, DiscoveredTemplate]:
|
|
159
|
+
"""
|
|
160
|
+
Discover all templates from all search paths.
|
|
161
|
+
|
|
162
|
+
Results are cached to avoid repeated filesystem scans.
|
|
163
|
+
Cache is invalidated when directory mtimes change or TTL expires.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
refresh: If True, bypass cache and force a fresh scan
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Dict mapping template name to DiscoveredTemplate.
|
|
170
|
+
When duplicates exist, higher priority wins.
|
|
171
|
+
"""
|
|
172
|
+
# Check cache first (unless refresh requested)
|
|
173
|
+
if not refresh and self._cache_key in self._cache:
|
|
174
|
+
entry = self._cache[self._cache_key]
|
|
175
|
+
if self._is_cache_valid(entry):
|
|
176
|
+
return entry.templates.copy()
|
|
177
|
+
|
|
178
|
+
templates: Dict[str, DiscoveredTemplate] = {}
|
|
179
|
+
mtimes: Dict[str, float] = {}
|
|
180
|
+
|
|
181
|
+
# Scan search paths in reverse priority order (lowest first)
|
|
182
|
+
# so that higher priority overwrites lower
|
|
183
|
+
all_paths = list(self.search_paths)
|
|
184
|
+
|
|
185
|
+
# Add package templates with lowest priority
|
|
186
|
+
if self.include_package and self.package_templates_path:
|
|
187
|
+
max_priority = max((p[2] for p in all_paths), default=0) + 1
|
|
188
|
+
all_paths.append((self.package_templates_path, "package", max_priority))
|
|
189
|
+
|
|
190
|
+
# Sort by priority descending (lowest priority first)
|
|
191
|
+
all_paths.sort(key=lambda x: x[2], reverse=True)
|
|
192
|
+
|
|
193
|
+
for dir_path, source, priority in all_paths:
|
|
194
|
+
# Record mtime for cache invalidation
|
|
195
|
+
mtimes[str(dir_path)] = self._get_dir_mtime(dir_path)
|
|
196
|
+
|
|
197
|
+
if dir_path.exists() and dir_path.is_dir():
|
|
198
|
+
discovered = self._scan_directory(dir_path, source, priority)
|
|
199
|
+
# Higher priority overwrites lower
|
|
200
|
+
templates.update(discovered)
|
|
201
|
+
|
|
202
|
+
# Store in cache
|
|
203
|
+
self._cache[self._cache_key] = _CacheEntry(
|
|
204
|
+
templates=templates.copy(),
|
|
205
|
+
timestamp=time.time(),
|
|
206
|
+
mtimes=mtimes
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
return templates
|
|
210
|
+
|
|
211
|
+
def _scan_directory(
|
|
212
|
+
self,
|
|
213
|
+
directory: Path,
|
|
214
|
+
source: str,
|
|
215
|
+
priority: int
|
|
216
|
+
) -> Dict[str, DiscoveredTemplate]:
|
|
217
|
+
"""
|
|
218
|
+
Scan a directory for templates (shallow scan).
|
|
219
|
+
|
|
220
|
+
Only looks at direct subdirectories containing TEMPLATE.yaml.
|
|
221
|
+
Does NOT recursively scan nested directories.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
directory: Directory to scan
|
|
225
|
+
source: Source identifier ('custom', 'builtin', 'package')
|
|
226
|
+
priority: Priority level
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Dict mapping template name to DiscoveredTemplate
|
|
230
|
+
"""
|
|
231
|
+
templates = {}
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
for item in directory.iterdir():
|
|
235
|
+
if item.is_dir():
|
|
236
|
+
template_file = item / self.TEMPLATE_FILE
|
|
237
|
+
workflow_file = item / "workflow.yaml"
|
|
238
|
+
|
|
239
|
+
# A valid template has TEMPLATE.yaml or workflow.yaml
|
|
240
|
+
if template_file.exists() or workflow_file.exists():
|
|
241
|
+
template = DiscoveredTemplate(
|
|
242
|
+
name=item.name,
|
|
243
|
+
path=item,
|
|
244
|
+
source=source,
|
|
245
|
+
priority=priority
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# Load basic metadata if TEMPLATE.yaml exists
|
|
249
|
+
if template_file.exists():
|
|
250
|
+
self._load_template_metadata(template, template_file)
|
|
251
|
+
|
|
252
|
+
templates[item.name] = template
|
|
253
|
+
except PermissionError:
|
|
254
|
+
pass # Skip directories we can't read
|
|
255
|
+
|
|
256
|
+
return templates
|
|
257
|
+
|
|
258
|
+
def _load_template_metadata(
|
|
259
|
+
self,
|
|
260
|
+
template: DiscoveredTemplate,
|
|
261
|
+
template_file: Path
|
|
262
|
+
) -> None:
|
|
263
|
+
"""Load basic metadata from TEMPLATE.yaml."""
|
|
264
|
+
try:
|
|
265
|
+
import yaml
|
|
266
|
+
with open(template_file) as f:
|
|
267
|
+
data = yaml.safe_load(f) or {}
|
|
268
|
+
|
|
269
|
+
template.description = data.get("description", "")
|
|
270
|
+
template.version = data.get("version", "1.0.0")
|
|
271
|
+
template.author = data.get("author")
|
|
272
|
+
except Exception:
|
|
273
|
+
pass # Metadata loading is optional
|
|
274
|
+
|
|
275
|
+
def find_template(self, name: str, refresh: bool = False) -> Optional[DiscoveredTemplate]:
|
|
276
|
+
"""
|
|
277
|
+
Find a template by name.
|
|
278
|
+
|
|
279
|
+
Searches all directories in priority order and returns the
|
|
280
|
+
highest priority match.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
name: Template name to find
|
|
284
|
+
refresh: If True, bypass cache and force a fresh scan
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
DiscoveredTemplate if found, None otherwise
|
|
288
|
+
"""
|
|
289
|
+
all_templates = self.discover_all(refresh=refresh)
|
|
290
|
+
return all_templates.get(name)
|
|
291
|
+
|
|
292
|
+
def resolve_template_path(self, name: str) -> Optional[Path]:
|
|
293
|
+
"""
|
|
294
|
+
Resolve a template name to its path.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
name: Template name
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Path to template directory, or None if not found
|
|
301
|
+
"""
|
|
302
|
+
template = self.find_template(name)
|
|
303
|
+
return template.path if template else None
|
|
304
|
+
|
|
305
|
+
def list_templates(
|
|
306
|
+
self,
|
|
307
|
+
source_filter: Optional[str] = None,
|
|
308
|
+
refresh: bool = False
|
|
309
|
+
) -> List[DiscoveredTemplate]:
|
|
310
|
+
"""
|
|
311
|
+
List all discovered templates.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
source_filter: Optional filter by source ('custom', 'package', etc.)
|
|
315
|
+
refresh: If True, bypass cache and force a fresh scan
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
List of DiscoveredTemplate objects
|
|
319
|
+
"""
|
|
320
|
+
templates = self.discover_all(refresh=refresh)
|
|
321
|
+
|
|
322
|
+
if source_filter:
|
|
323
|
+
return [t for t in templates.values() if t.source == source_filter]
|
|
324
|
+
|
|
325
|
+
return list(templates.values())
|
|
326
|
+
|
|
327
|
+
def clear_cache(self) -> None:
|
|
328
|
+
"""Clear the template discovery cache."""
|
|
329
|
+
if self._cache_key in self._cache:
|
|
330
|
+
del self._cache[self._cache_key]
|
|
331
|
+
|
|
332
|
+
@classmethod
|
|
333
|
+
def clear_all_caches(cls) -> None:
|
|
334
|
+
"""Clear all template discovery caches."""
|
|
335
|
+
cls._cache.clear()
|
|
336
|
+
|
|
337
|
+
def get_search_paths(self) -> List[Tuple[str, str, bool]]:
|
|
338
|
+
"""
|
|
339
|
+
Get all search paths with their status.
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
List of (path, source, exists) tuples
|
|
343
|
+
"""
|
|
344
|
+
paths = []
|
|
345
|
+
|
|
346
|
+
for dir_path, source, _ in self.search_paths:
|
|
347
|
+
paths.append((str(dir_path), source, dir_path.exists()))
|
|
348
|
+
|
|
349
|
+
if self.include_package and self.package_templates_path:
|
|
350
|
+
paths.append((
|
|
351
|
+
str(self.package_templates_path),
|
|
352
|
+
"package",
|
|
353
|
+
self.package_templates_path.exists()
|
|
354
|
+
))
|
|
355
|
+
|
|
356
|
+
return paths
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
# Convenience functions
|
|
360
|
+
def discover_templates(
|
|
361
|
+
custom_dirs: Optional[List[str]] = None
|
|
362
|
+
) -> Dict[str, DiscoveredTemplate]:
|
|
363
|
+
"""
|
|
364
|
+
Discover all templates from default and custom directories.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
custom_dirs: Additional custom directories to search
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
Dict mapping template name to DiscoveredTemplate
|
|
371
|
+
"""
|
|
372
|
+
discovery = TemplateDiscovery(custom_dirs=custom_dirs)
|
|
373
|
+
return discovery.discover_all()
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def find_template_path(
|
|
377
|
+
name: str,
|
|
378
|
+
custom_dirs: Optional[List[str]] = None
|
|
379
|
+
) -> Optional[Path]:
|
|
380
|
+
"""
|
|
381
|
+
Find a template by name and return its path.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
name: Template name
|
|
385
|
+
custom_dirs: Additional custom directories to search
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
Path to template directory, or None if not found
|
|
389
|
+
"""
|
|
390
|
+
discovery = TemplateDiscovery(custom_dirs=custom_dirs)
|
|
391
|
+
return discovery.resolve_template_path(name)
|