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,564 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SQLite persistence layer for the PraisonAI Queue System.
|
|
3
|
+
|
|
4
|
+
Provides crash recovery, session persistence, and run history.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import sqlite3
|
|
11
|
+
import threading
|
|
12
|
+
import time
|
|
13
|
+
from contextlib import contextmanager
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
from .models import QueuedRun, RunState, RunPriority, QueueStats
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
SCHEMA_VERSION = "1.0"
|
|
22
|
+
|
|
23
|
+
SCHEMA_SQL = """
|
|
24
|
+
-- Schema version tracking
|
|
25
|
+
CREATE TABLE IF NOT EXISTS schema_version (
|
|
26
|
+
version TEXT PRIMARY KEY,
|
|
27
|
+
applied_at REAL NOT NULL
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
-- Queue runs
|
|
31
|
+
CREATE TABLE IF NOT EXISTS runs (
|
|
32
|
+
run_id TEXT PRIMARY KEY,
|
|
33
|
+
agent_name TEXT NOT NULL,
|
|
34
|
+
input_content TEXT,
|
|
35
|
+
output_content TEXT,
|
|
36
|
+
state TEXT NOT NULL DEFAULT 'queued',
|
|
37
|
+
priority INTEGER NOT NULL DEFAULT 1,
|
|
38
|
+
session_id TEXT,
|
|
39
|
+
trace_id TEXT,
|
|
40
|
+
workspace TEXT,
|
|
41
|
+
user_id TEXT,
|
|
42
|
+
created_at REAL NOT NULL,
|
|
43
|
+
started_at REAL,
|
|
44
|
+
ended_at REAL,
|
|
45
|
+
error TEXT,
|
|
46
|
+
retry_count INTEGER DEFAULT 0,
|
|
47
|
+
max_retries INTEGER DEFAULT 3,
|
|
48
|
+
parent_run_id TEXT,
|
|
49
|
+
config TEXT,
|
|
50
|
+
metrics TEXT,
|
|
51
|
+
recovered INTEGER DEFAULT 0
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_runs_state ON runs(state);
|
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_runs_session ON runs(session_id);
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_runs_priority_created ON runs(priority DESC, created_at ASC);
|
|
57
|
+
CREATE INDEX IF NOT EXISTS idx_runs_workspace ON runs(workspace);
|
|
58
|
+
|
|
59
|
+
-- Messages (chat history per run)
|
|
60
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
61
|
+
message_id TEXT PRIMARY KEY,
|
|
62
|
+
run_id TEXT REFERENCES runs(run_id),
|
|
63
|
+
role TEXT NOT NULL,
|
|
64
|
+
content TEXT NOT NULL,
|
|
65
|
+
timestamp REAL NOT NULL,
|
|
66
|
+
metadata TEXT
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
CREATE INDEX IF NOT EXISTS idx_messages_run ON messages(run_id);
|
|
70
|
+
|
|
71
|
+
-- Tool calls
|
|
72
|
+
CREATE TABLE IF NOT EXISTS tool_calls (
|
|
73
|
+
call_id TEXT PRIMARY KEY,
|
|
74
|
+
run_id TEXT REFERENCES runs(run_id),
|
|
75
|
+
tool_name TEXT NOT NULL,
|
|
76
|
+
args TEXT,
|
|
77
|
+
result TEXT,
|
|
78
|
+
started_at REAL NOT NULL,
|
|
79
|
+
ended_at REAL,
|
|
80
|
+
error TEXT
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
CREATE INDEX IF NOT EXISTS idx_tool_calls_run ON tool_calls(run_id);
|
|
84
|
+
|
|
85
|
+
-- Sessions
|
|
86
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
87
|
+
session_id TEXT PRIMARY KEY,
|
|
88
|
+
user_id TEXT,
|
|
89
|
+
created_at REAL NOT NULL,
|
|
90
|
+
updated_at REAL NOT NULL,
|
|
91
|
+
state TEXT,
|
|
92
|
+
config TEXT
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id);
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class QueuePersistence:
|
|
100
|
+
"""SQLite-backed persistence for the queue system."""
|
|
101
|
+
|
|
102
|
+
def __init__(self, db_path: str = ".praison/queue.db"):
|
|
103
|
+
"""
|
|
104
|
+
Initialize persistence layer.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
db_path: Path to SQLite database file.
|
|
108
|
+
"""
|
|
109
|
+
self.db_path = db_path
|
|
110
|
+
self._conn: Optional[sqlite3.Connection] = None
|
|
111
|
+
self._lock = threading.Lock()
|
|
112
|
+
self._initialized = False
|
|
113
|
+
|
|
114
|
+
def _ensure_dir(self) -> None:
|
|
115
|
+
"""Ensure database directory exists."""
|
|
116
|
+
db_dir = os.path.dirname(self.db_path)
|
|
117
|
+
if db_dir:
|
|
118
|
+
os.makedirs(db_dir, exist_ok=True)
|
|
119
|
+
|
|
120
|
+
def _get_connection(self) -> sqlite3.Connection:
|
|
121
|
+
"""Get or create database connection."""
|
|
122
|
+
if self._conn is None:
|
|
123
|
+
self._ensure_dir()
|
|
124
|
+
self._conn = sqlite3.connect(
|
|
125
|
+
self.db_path,
|
|
126
|
+
check_same_thread=False,
|
|
127
|
+
timeout=30.0
|
|
128
|
+
)
|
|
129
|
+
self._conn.row_factory = sqlite3.Row
|
|
130
|
+
# Enable WAL mode for better concurrency
|
|
131
|
+
self._conn.execute("PRAGMA journal_mode=WAL")
|
|
132
|
+
self._conn.execute("PRAGMA synchronous=NORMAL")
|
|
133
|
+
return self._conn
|
|
134
|
+
|
|
135
|
+
@contextmanager
|
|
136
|
+
def _transaction(self):
|
|
137
|
+
"""Context manager for database transactions."""
|
|
138
|
+
conn = self._get_connection()
|
|
139
|
+
with self._lock:
|
|
140
|
+
try:
|
|
141
|
+
yield conn
|
|
142
|
+
conn.commit()
|
|
143
|
+
except Exception:
|
|
144
|
+
conn.rollback()
|
|
145
|
+
raise
|
|
146
|
+
|
|
147
|
+
def initialize(self) -> None:
|
|
148
|
+
"""Initialize database schema."""
|
|
149
|
+
if self._initialized:
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
with self._transaction() as conn:
|
|
153
|
+
conn.executescript(SCHEMA_SQL)
|
|
154
|
+
|
|
155
|
+
# Check/set schema version
|
|
156
|
+
cursor = conn.execute(
|
|
157
|
+
"SELECT version FROM schema_version ORDER BY applied_at DESC LIMIT 1"
|
|
158
|
+
)
|
|
159
|
+
row = cursor.fetchone()
|
|
160
|
+
|
|
161
|
+
if row is None:
|
|
162
|
+
conn.execute(
|
|
163
|
+
"INSERT INTO schema_version (version, applied_at) VALUES (?, ?)",
|
|
164
|
+
(SCHEMA_VERSION, time.time())
|
|
165
|
+
)
|
|
166
|
+
elif row["version"] != SCHEMA_VERSION:
|
|
167
|
+
# Future: handle migrations
|
|
168
|
+
logger.warning(
|
|
169
|
+
f"Schema version mismatch: {row['version']} vs {SCHEMA_VERSION}"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
self._initialized = True
|
|
173
|
+
|
|
174
|
+
def close(self) -> None:
|
|
175
|
+
"""Close database connection."""
|
|
176
|
+
if self._conn is not None:
|
|
177
|
+
self._conn.close()
|
|
178
|
+
self._conn = None
|
|
179
|
+
self._initialized = False
|
|
180
|
+
|
|
181
|
+
# Run operations
|
|
182
|
+
|
|
183
|
+
def save_run(self, run: QueuedRun) -> None:
|
|
184
|
+
"""Save or update a run."""
|
|
185
|
+
self.initialize()
|
|
186
|
+
|
|
187
|
+
with self._transaction() as conn:
|
|
188
|
+
conn.execute("""
|
|
189
|
+
INSERT OR REPLACE INTO runs (
|
|
190
|
+
run_id, agent_name, input_content, output_content,
|
|
191
|
+
state, priority, session_id, trace_id, workspace, user_id,
|
|
192
|
+
created_at, started_at, ended_at, error,
|
|
193
|
+
retry_count, max_retries, parent_run_id,
|
|
194
|
+
config, metrics
|
|
195
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
196
|
+
""", (
|
|
197
|
+
run.run_id,
|
|
198
|
+
run.agent_name,
|
|
199
|
+
run.input_content,
|
|
200
|
+
run.output_content,
|
|
201
|
+
run.state.value,
|
|
202
|
+
int(run.priority),
|
|
203
|
+
run.session_id,
|
|
204
|
+
run.trace_id,
|
|
205
|
+
run.workspace,
|
|
206
|
+
run.user_id,
|
|
207
|
+
run.created_at,
|
|
208
|
+
run.started_at,
|
|
209
|
+
run.ended_at,
|
|
210
|
+
run.error,
|
|
211
|
+
run.retry_count,
|
|
212
|
+
run.max_retries,
|
|
213
|
+
run.parent_run_id,
|
|
214
|
+
json.dumps(run.config) if run.config else None,
|
|
215
|
+
json.dumps(run.metrics) if run.metrics else None,
|
|
216
|
+
))
|
|
217
|
+
|
|
218
|
+
def load_run(self, run_id: str) -> Optional[QueuedRun]:
|
|
219
|
+
"""Load a run by ID."""
|
|
220
|
+
self.initialize()
|
|
221
|
+
|
|
222
|
+
with self._transaction() as conn:
|
|
223
|
+
cursor = conn.execute(
|
|
224
|
+
"SELECT * FROM runs WHERE run_id = ?",
|
|
225
|
+
(run_id,)
|
|
226
|
+
)
|
|
227
|
+
row = cursor.fetchone()
|
|
228
|
+
|
|
229
|
+
if row is None:
|
|
230
|
+
return None
|
|
231
|
+
|
|
232
|
+
return self._row_to_run(row)
|
|
233
|
+
|
|
234
|
+
def list_runs(
|
|
235
|
+
self,
|
|
236
|
+
state: Optional[RunState] = None,
|
|
237
|
+
session_id: Optional[str] = None,
|
|
238
|
+
workspace: Optional[str] = None,
|
|
239
|
+
limit: int = 100,
|
|
240
|
+
offset: int = 0,
|
|
241
|
+
) -> List[QueuedRun]:
|
|
242
|
+
"""List runs with optional filters."""
|
|
243
|
+
self.initialize()
|
|
244
|
+
|
|
245
|
+
query = "SELECT * FROM runs WHERE 1=1"
|
|
246
|
+
params: List[Any] = []
|
|
247
|
+
|
|
248
|
+
if state is not None:
|
|
249
|
+
query += " AND state = ?"
|
|
250
|
+
params.append(state.value)
|
|
251
|
+
|
|
252
|
+
if session_id is not None:
|
|
253
|
+
query += " AND session_id = ?"
|
|
254
|
+
params.append(session_id)
|
|
255
|
+
|
|
256
|
+
if workspace is not None:
|
|
257
|
+
query += " AND workspace = ?"
|
|
258
|
+
params.append(workspace)
|
|
259
|
+
|
|
260
|
+
query += " ORDER BY priority DESC, created_at ASC LIMIT ? OFFSET ?"
|
|
261
|
+
params.extend([limit, offset])
|
|
262
|
+
|
|
263
|
+
with self._transaction() as conn:
|
|
264
|
+
cursor = conn.execute(query, params)
|
|
265
|
+
return [self._row_to_run(row) for row in cursor.fetchall()]
|
|
266
|
+
|
|
267
|
+
def delete_run(self, run_id: str) -> bool:
|
|
268
|
+
"""Delete a run and its related data."""
|
|
269
|
+
self.initialize()
|
|
270
|
+
|
|
271
|
+
with self._transaction() as conn:
|
|
272
|
+
# Delete related data first
|
|
273
|
+
conn.execute("DELETE FROM messages WHERE run_id = ?", (run_id,))
|
|
274
|
+
conn.execute("DELETE FROM tool_calls WHERE run_id = ?", (run_id,))
|
|
275
|
+
|
|
276
|
+
cursor = conn.execute("DELETE FROM runs WHERE run_id = ?", (run_id,))
|
|
277
|
+
return cursor.rowcount > 0
|
|
278
|
+
|
|
279
|
+
def update_run_state(
|
|
280
|
+
self,
|
|
281
|
+
run_id: str,
|
|
282
|
+
state: RunState,
|
|
283
|
+
error: Optional[str] = None,
|
|
284
|
+
output: Optional[str] = None,
|
|
285
|
+
) -> bool:
|
|
286
|
+
"""Update run state."""
|
|
287
|
+
self.initialize()
|
|
288
|
+
|
|
289
|
+
updates = ["state = ?"]
|
|
290
|
+
params: List[Any] = [state.value]
|
|
291
|
+
|
|
292
|
+
if state == RunState.RUNNING:
|
|
293
|
+
updates.append("started_at = ?")
|
|
294
|
+
params.append(time.time())
|
|
295
|
+
elif state.is_terminal():
|
|
296
|
+
updates.append("ended_at = ?")
|
|
297
|
+
params.append(time.time())
|
|
298
|
+
|
|
299
|
+
if error is not None:
|
|
300
|
+
updates.append("error = ?")
|
|
301
|
+
params.append(error)
|
|
302
|
+
|
|
303
|
+
if output is not None:
|
|
304
|
+
updates.append("output_content = ?")
|
|
305
|
+
params.append(output)
|
|
306
|
+
|
|
307
|
+
params.append(run_id)
|
|
308
|
+
|
|
309
|
+
with self._transaction() as conn:
|
|
310
|
+
cursor = conn.execute(
|
|
311
|
+
f"UPDATE runs SET {', '.join(updates)} WHERE run_id = ?",
|
|
312
|
+
params
|
|
313
|
+
)
|
|
314
|
+
return cursor.rowcount > 0
|
|
315
|
+
|
|
316
|
+
# Crash recovery
|
|
317
|
+
|
|
318
|
+
def load_pending_runs(self) -> List[QueuedRun]:
|
|
319
|
+
"""Load runs that were QUEUED or RUNNING at crash."""
|
|
320
|
+
self.initialize()
|
|
321
|
+
|
|
322
|
+
with self._transaction() as conn:
|
|
323
|
+
cursor = conn.execute("""
|
|
324
|
+
SELECT * FROM runs
|
|
325
|
+
WHERE state IN ('queued', 'running')
|
|
326
|
+
ORDER BY priority DESC, created_at ASC
|
|
327
|
+
""")
|
|
328
|
+
return [self._row_to_run(row) for row in cursor.fetchall()]
|
|
329
|
+
|
|
330
|
+
def mark_recovered(self, run_id: str) -> None:
|
|
331
|
+
"""Mark a run as recovered after restart."""
|
|
332
|
+
self.initialize()
|
|
333
|
+
|
|
334
|
+
with self._transaction() as conn:
|
|
335
|
+
conn.execute(
|
|
336
|
+
"UPDATE runs SET recovered = 1 WHERE run_id = ?",
|
|
337
|
+
(run_id,)
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
def mark_interrupted_as_failed(self) -> int:
|
|
341
|
+
"""Mark all RUNNING runs as FAILED (for crash recovery)."""
|
|
342
|
+
self.initialize()
|
|
343
|
+
|
|
344
|
+
with self._transaction() as conn:
|
|
345
|
+
cursor = conn.execute("""
|
|
346
|
+
UPDATE runs
|
|
347
|
+
SET state = 'failed',
|
|
348
|
+
error = 'Interrupted by crash/restart',
|
|
349
|
+
ended_at = ?
|
|
350
|
+
WHERE state = 'running'
|
|
351
|
+
""", (time.time(),))
|
|
352
|
+
return cursor.rowcount
|
|
353
|
+
|
|
354
|
+
# Statistics
|
|
355
|
+
|
|
356
|
+
def get_stats(self, session_id: Optional[str] = None) -> QueueStats:
|
|
357
|
+
"""Get queue statistics."""
|
|
358
|
+
self.initialize()
|
|
359
|
+
|
|
360
|
+
with self._transaction() as conn:
|
|
361
|
+
where = ""
|
|
362
|
+
params: List[Any] = []
|
|
363
|
+
if session_id:
|
|
364
|
+
where = "WHERE session_id = ?"
|
|
365
|
+
params = [session_id]
|
|
366
|
+
|
|
367
|
+
# Count by state
|
|
368
|
+
cursor = conn.execute(f"""
|
|
369
|
+
SELECT state, COUNT(*) as count
|
|
370
|
+
FROM runs {where}
|
|
371
|
+
GROUP BY state
|
|
372
|
+
""", params)
|
|
373
|
+
|
|
374
|
+
counts = {row["state"]: row["count"] for row in cursor.fetchall()}
|
|
375
|
+
|
|
376
|
+
# Average wait time
|
|
377
|
+
cursor = conn.execute(f"""
|
|
378
|
+
SELECT AVG(started_at - created_at) as avg_wait
|
|
379
|
+
FROM runs
|
|
380
|
+
{where + ' AND' if where else 'WHERE'} started_at IS NOT NULL
|
|
381
|
+
""", params)
|
|
382
|
+
row = cursor.fetchone()
|
|
383
|
+
avg_wait = row["avg_wait"] or 0.0
|
|
384
|
+
|
|
385
|
+
# Average duration
|
|
386
|
+
cursor = conn.execute(f"""
|
|
387
|
+
SELECT AVG(ended_at - started_at) as avg_duration
|
|
388
|
+
FROM runs
|
|
389
|
+
{where + ' AND' if where else 'WHERE'} ended_at IS NOT NULL AND started_at IS NOT NULL
|
|
390
|
+
""", params)
|
|
391
|
+
row = cursor.fetchone()
|
|
392
|
+
avg_duration = row["avg_duration"] or 0.0
|
|
393
|
+
|
|
394
|
+
return QueueStats(
|
|
395
|
+
queued_count=counts.get("queued", 0),
|
|
396
|
+
running_count=counts.get("running", 0),
|
|
397
|
+
succeeded_count=counts.get("succeeded", 0),
|
|
398
|
+
failed_count=counts.get("failed", 0),
|
|
399
|
+
cancelled_count=counts.get("cancelled", 0),
|
|
400
|
+
total_runs=sum(counts.values()),
|
|
401
|
+
avg_wait_seconds=avg_wait,
|
|
402
|
+
avg_duration_seconds=avg_duration,
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
# Session operations
|
|
406
|
+
|
|
407
|
+
def save_session(
|
|
408
|
+
self,
|
|
409
|
+
session_id: str,
|
|
410
|
+
user_id: Optional[str] = None,
|
|
411
|
+
state: Optional[Dict[str, Any]] = None,
|
|
412
|
+
config: Optional[Dict[str, Any]] = None,
|
|
413
|
+
) -> None:
|
|
414
|
+
"""Save or update a session."""
|
|
415
|
+
self.initialize()
|
|
416
|
+
|
|
417
|
+
now = time.time()
|
|
418
|
+
|
|
419
|
+
with self._transaction() as conn:
|
|
420
|
+
# Check if exists
|
|
421
|
+
cursor = conn.execute(
|
|
422
|
+
"SELECT session_id FROM sessions WHERE session_id = ?",
|
|
423
|
+
(session_id,)
|
|
424
|
+
)
|
|
425
|
+
exists = cursor.fetchone() is not None
|
|
426
|
+
|
|
427
|
+
if exists:
|
|
428
|
+
conn.execute("""
|
|
429
|
+
UPDATE sessions SET
|
|
430
|
+
user_id = COALESCE(?, user_id),
|
|
431
|
+
updated_at = ?,
|
|
432
|
+
state = COALESCE(?, state),
|
|
433
|
+
config = COALESCE(?, config)
|
|
434
|
+
WHERE session_id = ?
|
|
435
|
+
""", (
|
|
436
|
+
user_id,
|
|
437
|
+
now,
|
|
438
|
+
json.dumps(state) if state else None,
|
|
439
|
+
json.dumps(config) if config else None,
|
|
440
|
+
session_id,
|
|
441
|
+
))
|
|
442
|
+
else:
|
|
443
|
+
conn.execute("""
|
|
444
|
+
INSERT INTO sessions (session_id, user_id, created_at, updated_at, state, config)
|
|
445
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
446
|
+
""", (
|
|
447
|
+
session_id,
|
|
448
|
+
user_id,
|
|
449
|
+
now,
|
|
450
|
+
now,
|
|
451
|
+
json.dumps(state) if state else None,
|
|
452
|
+
json.dumps(config) if config else None,
|
|
453
|
+
))
|
|
454
|
+
|
|
455
|
+
def load_session(self, session_id: str) -> Optional[Dict[str, Any]]:
|
|
456
|
+
"""Load a session."""
|
|
457
|
+
self.initialize()
|
|
458
|
+
|
|
459
|
+
with self._transaction() as conn:
|
|
460
|
+
cursor = conn.execute(
|
|
461
|
+
"SELECT * FROM sessions WHERE session_id = ?",
|
|
462
|
+
(session_id,)
|
|
463
|
+
)
|
|
464
|
+
row = cursor.fetchone()
|
|
465
|
+
|
|
466
|
+
if row is None:
|
|
467
|
+
return None
|
|
468
|
+
|
|
469
|
+
return {
|
|
470
|
+
"session_id": row["session_id"],
|
|
471
|
+
"user_id": row["user_id"],
|
|
472
|
+
"created_at": row["created_at"],
|
|
473
|
+
"updated_at": row["updated_at"],
|
|
474
|
+
"state": json.loads(row["state"]) if row["state"] else None,
|
|
475
|
+
"config": json.loads(row["config"]) if row["config"] else None,
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
def list_sessions(self, limit: int = 50) -> List[Dict[str, Any]]:
|
|
479
|
+
"""List recent sessions."""
|
|
480
|
+
self.initialize()
|
|
481
|
+
|
|
482
|
+
with self._transaction() as conn:
|
|
483
|
+
cursor = conn.execute("""
|
|
484
|
+
SELECT * FROM sessions
|
|
485
|
+
ORDER BY updated_at DESC
|
|
486
|
+
LIMIT ?
|
|
487
|
+
""", (limit,))
|
|
488
|
+
|
|
489
|
+
return [
|
|
490
|
+
{
|
|
491
|
+
"session_id": row["session_id"],
|
|
492
|
+
"user_id": row["user_id"],
|
|
493
|
+
"created_at": row["created_at"],
|
|
494
|
+
"updated_at": row["updated_at"],
|
|
495
|
+
"state": json.loads(row["state"]) if row["state"] else None,
|
|
496
|
+
"config": json.loads(row["config"]) if row["config"] else None,
|
|
497
|
+
}
|
|
498
|
+
for row in cursor.fetchall()
|
|
499
|
+
]
|
|
500
|
+
|
|
501
|
+
# Helper methods
|
|
502
|
+
|
|
503
|
+
def _row_to_run(self, row: sqlite3.Row) -> QueuedRun:
|
|
504
|
+
"""Convert database row to QueuedRun."""
|
|
505
|
+
return QueuedRun(
|
|
506
|
+
run_id=row["run_id"],
|
|
507
|
+
agent_name=row["agent_name"],
|
|
508
|
+
input_content=row["input_content"] or "",
|
|
509
|
+
output_content=row["output_content"],
|
|
510
|
+
state=RunState(row["state"]),
|
|
511
|
+
priority=RunPriority(row["priority"]),
|
|
512
|
+
session_id=row["session_id"],
|
|
513
|
+
trace_id=row["trace_id"],
|
|
514
|
+
workspace=row["workspace"],
|
|
515
|
+
user_id=row["user_id"],
|
|
516
|
+
created_at=row["created_at"],
|
|
517
|
+
started_at=row["started_at"],
|
|
518
|
+
ended_at=row["ended_at"],
|
|
519
|
+
error=row["error"],
|
|
520
|
+
retry_count=row["retry_count"] or 0,
|
|
521
|
+
max_retries=row["max_retries"] or 3,
|
|
522
|
+
parent_run_id=row["parent_run_id"],
|
|
523
|
+
config=json.loads(row["config"]) if row["config"] else {},
|
|
524
|
+
metrics=json.loads(row["metrics"]) if row["metrics"] else {},
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
# Cleanup
|
|
528
|
+
|
|
529
|
+
def cleanup_old_runs(self, days: int = 30) -> int:
|
|
530
|
+
"""Delete runs older than specified days."""
|
|
531
|
+
self.initialize()
|
|
532
|
+
|
|
533
|
+
cutoff = time.time() - (days * 24 * 60 * 60)
|
|
534
|
+
|
|
535
|
+
with self._transaction() as conn:
|
|
536
|
+
# Get run IDs to delete
|
|
537
|
+
cursor = conn.execute("""
|
|
538
|
+
SELECT run_id FROM runs
|
|
539
|
+
WHERE created_at < ? AND state IN ('succeeded', 'failed', 'cancelled')
|
|
540
|
+
""", (cutoff,))
|
|
541
|
+
run_ids = [row["run_id"] for row in cursor.fetchall()]
|
|
542
|
+
|
|
543
|
+
if not run_ids:
|
|
544
|
+
return 0
|
|
545
|
+
|
|
546
|
+
placeholders = ",".join("?" * len(run_ids))
|
|
547
|
+
|
|
548
|
+
# Delete related data
|
|
549
|
+
conn.execute(
|
|
550
|
+
f"DELETE FROM messages WHERE run_id IN ({placeholders})",
|
|
551
|
+
run_ids
|
|
552
|
+
)
|
|
553
|
+
conn.execute(
|
|
554
|
+
f"DELETE FROM tool_calls WHERE run_id IN ({placeholders})",
|
|
555
|
+
run_ids
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
# Delete runs
|
|
559
|
+
cursor = conn.execute(
|
|
560
|
+
f"DELETE FROM runs WHERE run_id IN ({placeholders})",
|
|
561
|
+
run_ids
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
return cursor.rowcount
|