alita-sdk 0.3.462__py3-none-any.whl → 0.3.627__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.
- alita_sdk/cli/agent/__init__.py +5 -0
- alita_sdk/cli/agent/default.py +258 -0
- alita_sdk/cli/agent_executor.py +15 -3
- alita_sdk/cli/agent_loader.py +56 -8
- alita_sdk/cli/agent_ui.py +93 -31
- alita_sdk/cli/agents.py +2274 -230
- alita_sdk/cli/callbacks.py +96 -25
- alita_sdk/cli/cli.py +10 -1
- alita_sdk/cli/config.py +162 -9
- alita_sdk/cli/context/__init__.py +30 -0
- alita_sdk/cli/context/cleanup.py +198 -0
- alita_sdk/cli/context/manager.py +731 -0
- alita_sdk/cli/context/message.py +285 -0
- alita_sdk/cli/context/strategies.py +289 -0
- alita_sdk/cli/context/token_estimation.py +127 -0
- alita_sdk/cli/input_handler.py +419 -0
- alita_sdk/cli/inventory.py +1073 -0
- alita_sdk/cli/testcases/__init__.py +94 -0
- alita_sdk/cli/testcases/data_generation.py +119 -0
- alita_sdk/cli/testcases/discovery.py +96 -0
- alita_sdk/cli/testcases/executor.py +84 -0
- alita_sdk/cli/testcases/logger.py +85 -0
- alita_sdk/cli/testcases/parser.py +172 -0
- alita_sdk/cli/testcases/prompts.py +91 -0
- alita_sdk/cli/testcases/reporting.py +125 -0
- alita_sdk/cli/testcases/setup.py +108 -0
- alita_sdk/cli/testcases/test_runner.py +282 -0
- alita_sdk/cli/testcases/utils.py +39 -0
- alita_sdk/cli/testcases/validation.py +90 -0
- alita_sdk/cli/testcases/workflow.py +196 -0
- alita_sdk/cli/toolkit.py +14 -17
- alita_sdk/cli/toolkit_loader.py +35 -5
- alita_sdk/cli/tools/__init__.py +36 -2
- alita_sdk/cli/tools/approval.py +224 -0
- alita_sdk/cli/tools/filesystem.py +910 -64
- alita_sdk/cli/tools/planning.py +389 -0
- alita_sdk/cli/tools/terminal.py +414 -0
- alita_sdk/community/__init__.py +72 -12
- alita_sdk/community/inventory/__init__.py +236 -0
- alita_sdk/community/inventory/config.py +257 -0
- alita_sdk/community/inventory/enrichment.py +2137 -0
- alita_sdk/community/inventory/extractors.py +1469 -0
- alita_sdk/community/inventory/ingestion.py +3172 -0
- alita_sdk/community/inventory/knowledge_graph.py +1457 -0
- alita_sdk/community/inventory/parsers/__init__.py +218 -0
- alita_sdk/community/inventory/parsers/base.py +295 -0
- alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
- alita_sdk/community/inventory/parsers/go_parser.py +851 -0
- alita_sdk/community/inventory/parsers/html_parser.py +389 -0
- alita_sdk/community/inventory/parsers/java_parser.py +593 -0
- alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
- alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
- alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
- alita_sdk/community/inventory/parsers/python_parser.py +604 -0
- alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
- alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
- alita_sdk/community/inventory/parsers/text_parser.py +322 -0
- alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
- alita_sdk/community/inventory/patterns/__init__.py +61 -0
- alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
- alita_sdk/community/inventory/patterns/loader.py +348 -0
- alita_sdk/community/inventory/patterns/registry.py +198 -0
- alita_sdk/community/inventory/presets.py +535 -0
- alita_sdk/community/inventory/retrieval.py +1403 -0
- alita_sdk/community/inventory/toolkit.py +173 -0
- alita_sdk/community/inventory/toolkit_utils.py +176 -0
- alita_sdk/community/inventory/visualize.py +1370 -0
- alita_sdk/configurations/__init__.py +1 -1
- alita_sdk/configurations/ado.py +141 -20
- alita_sdk/configurations/bitbucket.py +0 -3
- alita_sdk/configurations/confluence.py +76 -42
- alita_sdk/configurations/figma.py +76 -0
- alita_sdk/configurations/gitlab.py +17 -5
- alita_sdk/configurations/openapi.py +329 -0
- alita_sdk/configurations/qtest.py +72 -1
- alita_sdk/configurations/report_portal.py +96 -0
- alita_sdk/configurations/sharepoint.py +148 -0
- alita_sdk/configurations/testio.py +83 -0
- alita_sdk/runtime/clients/artifact.py +3 -3
- alita_sdk/runtime/clients/client.py +353 -48
- alita_sdk/runtime/clients/sandbox_client.py +0 -21
- alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
- alita_sdk/runtime/langchain/assistant.py +123 -26
- alita_sdk/runtime/langchain/constants.py +642 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +6 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
- alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
- alita_sdk/runtime/langchain/document_loaders/constants.py +12 -7
- alita_sdk/runtime/langchain/langraph_agent.py +279 -73
- alita_sdk/runtime/langchain/utils.py +82 -15
- alita_sdk/runtime/llms/preloaded.py +2 -6
- alita_sdk/runtime/skills/__init__.py +91 -0
- alita_sdk/runtime/skills/callbacks.py +498 -0
- alita_sdk/runtime/skills/discovery.py +540 -0
- alita_sdk/runtime/skills/executor.py +610 -0
- alita_sdk/runtime/skills/input_builder.py +371 -0
- alita_sdk/runtime/skills/models.py +330 -0
- alita_sdk/runtime/skills/registry.py +355 -0
- alita_sdk/runtime/skills/skill_runner.py +330 -0
- alita_sdk/runtime/toolkits/__init__.py +7 -0
- alita_sdk/runtime/toolkits/application.py +21 -9
- alita_sdk/runtime/toolkits/artifact.py +15 -5
- alita_sdk/runtime/toolkits/datasource.py +13 -6
- alita_sdk/runtime/toolkits/mcp.py +139 -251
- alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
- alita_sdk/runtime/toolkits/planning.py +178 -0
- alita_sdk/runtime/toolkits/skill_router.py +238 -0
- alita_sdk/runtime/toolkits/subgraph.py +251 -6
- alita_sdk/runtime/toolkits/tools.py +238 -32
- alita_sdk/runtime/toolkits/vectorstore.py +11 -5
- alita_sdk/runtime/tools/__init__.py +3 -1
- alita_sdk/runtime/tools/application.py +20 -6
- alita_sdk/runtime/tools/artifact.py +511 -28
- alita_sdk/runtime/tools/data_analysis.py +183 -0
- alita_sdk/runtime/tools/function.py +43 -15
- alita_sdk/runtime/tools/image_generation.py +50 -44
- alita_sdk/runtime/tools/llm.py +852 -67
- alita_sdk/runtime/tools/loop.py +3 -1
- alita_sdk/runtime/tools/loop_output.py +3 -1
- alita_sdk/runtime/tools/mcp_remote_tool.py +25 -10
- alita_sdk/runtime/tools/mcp_server_tool.py +7 -6
- alita_sdk/runtime/tools/planning/__init__.py +36 -0
- alita_sdk/runtime/tools/planning/models.py +246 -0
- alita_sdk/runtime/tools/planning/wrapper.py +607 -0
- alita_sdk/runtime/tools/router.py +2 -4
- alita_sdk/runtime/tools/sandbox.py +9 -6
- alita_sdk/runtime/tools/skill_router.py +776 -0
- alita_sdk/runtime/tools/tool.py +3 -1
- alita_sdk/runtime/tools/vectorstore.py +7 -2
- alita_sdk/runtime/tools/vectorstore_base.py +51 -11
- alita_sdk/runtime/utils/AlitaCallback.py +137 -21
- alita_sdk/runtime/utils/constants.py +5 -1
- alita_sdk/runtime/utils/mcp_client.py +492 -0
- alita_sdk/runtime/utils/mcp_oauth.py +202 -5
- alita_sdk/runtime/utils/mcp_sse_client.py +36 -7
- alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
- alita_sdk/runtime/utils/serialization.py +155 -0
- alita_sdk/runtime/utils/streamlit.py +6 -10
- alita_sdk/runtime/utils/toolkit_utils.py +16 -5
- alita_sdk/runtime/utils/utils.py +36 -0
- alita_sdk/tools/__init__.py +113 -29
- alita_sdk/tools/ado/repos/__init__.py +51 -33
- alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
- alita_sdk/tools/ado/test_plan/__init__.py +25 -9
- alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
- alita_sdk/tools/ado/utils.py +1 -18
- alita_sdk/tools/ado/wiki/__init__.py +25 -8
- alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
- alita_sdk/tools/ado/work_item/__init__.py +26 -9
- alita_sdk/tools/ado/work_item/ado_wrapper.py +56 -3
- alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
- alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
- alita_sdk/tools/aws/delta_lake/tool.py +5 -1
- alita_sdk/tools/azure_ai/search/__init__.py +11 -8
- alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
- alita_sdk/tools/base/tool.py +5 -1
- alita_sdk/tools/base_indexer_toolkit.py +170 -45
- alita_sdk/tools/bitbucket/__init__.py +17 -12
- alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
- alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
- alita_sdk/tools/browser/__init__.py +5 -4
- alita_sdk/tools/carrier/__init__.py +5 -6
- alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
- alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
- alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
- alita_sdk/tools/chunkers/__init__.py +3 -1
- alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
- alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
- alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
- alita_sdk/tools/chunkers/universal_chunker.py +270 -0
- alita_sdk/tools/cloud/aws/__init__.py +10 -7
- alita_sdk/tools/cloud/azure/__init__.py +10 -7
- alita_sdk/tools/cloud/gcp/__init__.py +10 -7
- alita_sdk/tools/cloud/k8s/__init__.py +10 -7
- alita_sdk/tools/code/linter/__init__.py +10 -8
- alita_sdk/tools/code/loaders/codesearcher.py +3 -2
- alita_sdk/tools/code/sonar/__init__.py +10 -7
- alita_sdk/tools/code_indexer_toolkit.py +73 -23
- alita_sdk/tools/confluence/__init__.py +21 -15
- alita_sdk/tools/confluence/api_wrapper.py +78 -23
- alita_sdk/tools/confluence/loader.py +4 -2
- alita_sdk/tools/custom_open_api/__init__.py +12 -5
- alita_sdk/tools/elastic/__init__.py +11 -8
- alita_sdk/tools/elitea_base.py +493 -30
- alita_sdk/tools/figma/__init__.py +58 -11
- alita_sdk/tools/figma/api_wrapper.py +1235 -143
- alita_sdk/tools/figma/figma_client.py +73 -0
- alita_sdk/tools/figma/toon_tools.py +2748 -0
- alita_sdk/tools/github/__init__.py +13 -14
- alita_sdk/tools/github/github_client.py +224 -100
- alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
- alita_sdk/tools/github/schemas.py +14 -5
- alita_sdk/tools/github/tool.py +5 -1
- alita_sdk/tools/github/tool_prompts.py +9 -22
- alita_sdk/tools/gitlab/__init__.py +15 -11
- alita_sdk/tools/gitlab/api_wrapper.py +207 -41
- alita_sdk/tools/gitlab_org/__init__.py +10 -8
- alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
- alita_sdk/tools/google/bigquery/__init__.py +13 -12
- alita_sdk/tools/google/bigquery/tool.py +5 -1
- alita_sdk/tools/google_places/__init__.py +10 -8
- alita_sdk/tools/google_places/api_wrapper.py +1 -1
- alita_sdk/tools/jira/__init__.py +17 -11
- alita_sdk/tools/jira/api_wrapper.py +91 -40
- alita_sdk/tools/keycloak/__init__.py +11 -8
- alita_sdk/tools/localgit/__init__.py +9 -3
- alita_sdk/tools/localgit/local_git.py +62 -54
- alita_sdk/tools/localgit/tool.py +5 -1
- alita_sdk/tools/memory/__init__.py +11 -3
- alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
- alita_sdk/tools/ocr/__init__.py +11 -8
- alita_sdk/tools/openapi/__init__.py +490 -114
- alita_sdk/tools/openapi/api_wrapper.py +1368 -0
- alita_sdk/tools/openapi/tool.py +20 -0
- alita_sdk/tools/pandas/__init__.py +20 -12
- alita_sdk/tools/pandas/api_wrapper.py +38 -25
- alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
- alita_sdk/tools/postman/__init__.py +11 -11
- alita_sdk/tools/pptx/__init__.py +10 -9
- alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
- alita_sdk/tools/qtest/__init__.py +30 -10
- alita_sdk/tools/qtest/api_wrapper.py +430 -13
- alita_sdk/tools/rally/__init__.py +10 -8
- alita_sdk/tools/rally/api_wrapper.py +1 -1
- alita_sdk/tools/report_portal/__init__.py +12 -9
- alita_sdk/tools/salesforce/__init__.py +10 -9
- alita_sdk/tools/servicenow/__init__.py +17 -14
- alita_sdk/tools/servicenow/api_wrapper.py +1 -1
- alita_sdk/tools/sharepoint/__init__.py +10 -8
- alita_sdk/tools/sharepoint/api_wrapper.py +4 -4
- alita_sdk/tools/slack/__init__.py +10 -8
- alita_sdk/tools/slack/api_wrapper.py +2 -2
- alita_sdk/tools/sql/__init__.py +11 -9
- alita_sdk/tools/testio/__init__.py +10 -8
- alita_sdk/tools/testrail/__init__.py +11 -8
- alita_sdk/tools/testrail/api_wrapper.py +1 -1
- alita_sdk/tools/utils/__init__.py +9 -4
- alita_sdk/tools/utils/content_parser.py +77 -3
- alita_sdk/tools/utils/text_operations.py +410 -0
- alita_sdk/tools/utils/tool_prompts.py +79 -0
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +17 -13
- alita_sdk/tools/xray/__init__.py +12 -9
- alita_sdk/tools/yagmail/__init__.py +9 -3
- alita_sdk/tools/zephyr/__init__.py +9 -7
- alita_sdk/tools/zephyr_enterprise/__init__.py +11 -8
- alita_sdk/tools/zephyr_essential/__init__.py +10 -8
- alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
- alita_sdk/tools/zephyr_essential/client.py +2 -2
- alita_sdk/tools/zephyr_scale/__init__.py +11 -9
- alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
- alita_sdk/tools/zephyr_squad/__init__.py +10 -8
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +147 -7
- alita_sdk-0.3.627.dist-info/RECORD +468 -0
- alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
- alita_sdk-0.3.462.dist-info/RECORD +0 -384
- alita_sdk-0.3.462.dist-info/entry_points.txt +0 -2
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PlanningWrapper - Adaptive API wrapper for plan CRUD operations.
|
|
3
|
+
|
|
4
|
+
Supports two storage backends:
|
|
5
|
+
1. PostgreSQL - when connection_string is provided (production/indexer_worker)
|
|
6
|
+
2. Filesystem - when no connection string (local CLI usage)
|
|
7
|
+
|
|
8
|
+
Plans are scoped by conversation_id (from server) or session_id (from CLI).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import List, Dict, Any, Optional
|
|
17
|
+
from pydantic import BaseModel, Field, model_validator
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PlanStep(BaseModel):
|
|
23
|
+
"""A single step in a plan."""
|
|
24
|
+
description: str = Field(description="Step description")
|
|
25
|
+
completed: bool = Field(default=False, description="Whether step is completed")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class PlanState(BaseModel):
|
|
29
|
+
"""Current plan state."""
|
|
30
|
+
title: str = Field(default="", description="Plan title")
|
|
31
|
+
steps: List[PlanStep] = Field(default_factory=list, description="List of steps")
|
|
32
|
+
status: str = Field(default="in_progress", description="Plan status")
|
|
33
|
+
conversation_id: Optional[str] = Field(default=None, description="Conversation ID for scoping")
|
|
34
|
+
|
|
35
|
+
def render(self) -> str:
|
|
36
|
+
"""Render plan as formatted string with checkboxes."""
|
|
37
|
+
if not self.steps:
|
|
38
|
+
return "No plan created yet."
|
|
39
|
+
|
|
40
|
+
lines = []
|
|
41
|
+
if self.title:
|
|
42
|
+
lines.append(f"📋 {self.title}")
|
|
43
|
+
|
|
44
|
+
completed_count = sum(1 for s in self.steps if s.completed)
|
|
45
|
+
total_count = len(self.steps)
|
|
46
|
+
lines.append(f" Progress: {completed_count}/{total_count} steps completed")
|
|
47
|
+
lines.append("")
|
|
48
|
+
|
|
49
|
+
for i, step in enumerate(self.steps, 1):
|
|
50
|
+
checkbox = "☑" if step.completed else "☐"
|
|
51
|
+
status = " ✓" if step.completed else ""
|
|
52
|
+
lines.append(f" {checkbox} {i}. {step.description}{status}")
|
|
53
|
+
|
|
54
|
+
return "\n".join(lines)
|
|
55
|
+
|
|
56
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
57
|
+
"""Convert to dictionary for serialization."""
|
|
58
|
+
return {
|
|
59
|
+
"title": self.title,
|
|
60
|
+
"steps": [{"description": s.description, "completed": s.completed} for s in self.steps],
|
|
61
|
+
"status": self.status,
|
|
62
|
+
"conversation_id": self.conversation_id
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def from_dict(cls, data: Dict[str, Any]) -> "PlanState":
|
|
67
|
+
"""Create from dictionary."""
|
|
68
|
+
steps = [PlanStep(**s) for s in data.get("steps", [])]
|
|
69
|
+
return cls(
|
|
70
|
+
title=data.get("title", ""),
|
|
71
|
+
steps=steps,
|
|
72
|
+
status=data.get("status", "in_progress"),
|
|
73
|
+
conversation_id=data.get("conversation_id")
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class FilesystemStorage:
|
|
78
|
+
"""Filesystem-based plan storage for local CLI usage."""
|
|
79
|
+
|
|
80
|
+
def __init__(self, base_dir: Optional[str] = None):
|
|
81
|
+
"""
|
|
82
|
+
Initialize filesystem storage.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
base_dir: Base directory for plan storage.
|
|
86
|
+
Defaults to $ALITA_DIR/plans or .alita/plans
|
|
87
|
+
"""
|
|
88
|
+
if base_dir:
|
|
89
|
+
self.base_dir = Path(base_dir)
|
|
90
|
+
else:
|
|
91
|
+
alita_dir = os.environ.get('ALITA_DIR', '.alita')
|
|
92
|
+
self.base_dir = Path(alita_dir) / 'plans'
|
|
93
|
+
|
|
94
|
+
self.base_dir.mkdir(parents=True, exist_ok=True)
|
|
95
|
+
logger.debug(f"Filesystem storage initialized at {self.base_dir}")
|
|
96
|
+
|
|
97
|
+
def _get_plan_path(self, conversation_id: str) -> Path:
|
|
98
|
+
"""Get the path to a plan file."""
|
|
99
|
+
# Sanitize conversation_id for filesystem
|
|
100
|
+
safe_id = conversation_id.replace('/', '_').replace('\\', '_')
|
|
101
|
+
return self.base_dir / f"{safe_id}.json"
|
|
102
|
+
|
|
103
|
+
def get_plan(self, conversation_id: str) -> Optional[PlanState]:
|
|
104
|
+
"""Load plan from filesystem."""
|
|
105
|
+
plan_path = self._get_plan_path(conversation_id)
|
|
106
|
+
if plan_path.exists():
|
|
107
|
+
try:
|
|
108
|
+
data = json.loads(plan_path.read_text())
|
|
109
|
+
return PlanState.from_dict(data)
|
|
110
|
+
except Exception as e:
|
|
111
|
+
logger.error(f"Failed to load plan from {plan_path}: {e}")
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
def save_plan(self, conversation_id: str, plan: PlanState) -> bool:
|
|
115
|
+
"""Save plan to filesystem."""
|
|
116
|
+
try:
|
|
117
|
+
plan_path = self._get_plan_path(conversation_id)
|
|
118
|
+
plan.conversation_id = conversation_id
|
|
119
|
+
plan_path.write_text(json.dumps(plan.to_dict(), indent=2))
|
|
120
|
+
logger.debug(f"Saved plan to {plan_path}")
|
|
121
|
+
return True
|
|
122
|
+
except Exception as e:
|
|
123
|
+
logger.error(f"Failed to save plan: {e}")
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
def delete_plan(self, conversation_id: str) -> bool:
|
|
127
|
+
"""Delete plan from filesystem."""
|
|
128
|
+
try:
|
|
129
|
+
plan_path = self._get_plan_path(conversation_id)
|
|
130
|
+
if plan_path.exists():
|
|
131
|
+
plan_path.unlink()
|
|
132
|
+
logger.debug(f"Deleted plan at {plan_path}")
|
|
133
|
+
return True
|
|
134
|
+
except Exception as e:
|
|
135
|
+
logger.error(f"Failed to delete plan: {e}")
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class PostgresStorage:
|
|
140
|
+
"""PostgreSQL-based plan storage for production usage."""
|
|
141
|
+
|
|
142
|
+
def __init__(self, connection_string: str, conversation_id: Optional[str] = None):
|
|
143
|
+
"""
|
|
144
|
+
Initialize PostgreSQL storage.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
connection_string: PostgreSQL connection string
|
|
148
|
+
conversation_id: Conversation ID for scoping plans (from server or CLI session_id)
|
|
149
|
+
"""
|
|
150
|
+
from sqlalchemy import create_engine
|
|
151
|
+
from sqlalchemy.orm import Session
|
|
152
|
+
|
|
153
|
+
self.connection_string = connection_string
|
|
154
|
+
self.conversation_id = conversation_id
|
|
155
|
+
self._engine = None
|
|
156
|
+
self._ensure_tables()
|
|
157
|
+
|
|
158
|
+
def _ensure_tables(self):
|
|
159
|
+
"""Ensure the agent_plans table exists."""
|
|
160
|
+
from .models import ensure_plan_tables
|
|
161
|
+
ensure_plan_tables(self.connection_string)
|
|
162
|
+
|
|
163
|
+
def _get_engine(self):
|
|
164
|
+
"""Get or create SQLAlchemy engine."""
|
|
165
|
+
if self._engine is None:
|
|
166
|
+
from sqlalchemy import create_engine
|
|
167
|
+
self._engine = create_engine(self.connection_string)
|
|
168
|
+
return self._engine
|
|
169
|
+
|
|
170
|
+
def _get_session(self):
|
|
171
|
+
"""Get a database session."""
|
|
172
|
+
from sqlalchemy.orm import Session
|
|
173
|
+
return Session(self._get_engine())
|
|
174
|
+
|
|
175
|
+
def get_plan(self, conversation_id: str) -> Optional[PlanState]:
|
|
176
|
+
"""
|
|
177
|
+
Load plan from PostgreSQL.
|
|
178
|
+
|
|
179
|
+
Uses conversation_id for scoping. Server provides conversation_id,
|
|
180
|
+
CLI provides session_id as conversation_id.
|
|
181
|
+
"""
|
|
182
|
+
from .models import AgentPlan
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
session = self._get_session()
|
|
186
|
+
|
|
187
|
+
# Use conversation_id for querying (set during initialization from server/CLI)
|
|
188
|
+
query_id = self.conversation_id or conversation_id
|
|
189
|
+
|
|
190
|
+
plan = session.query(AgentPlan).filter(
|
|
191
|
+
AgentPlan.conversation_id == query_id
|
|
192
|
+
).first()
|
|
193
|
+
|
|
194
|
+
if plan:
|
|
195
|
+
steps = [
|
|
196
|
+
PlanStep(
|
|
197
|
+
description=s.get("description", ""),
|
|
198
|
+
completed=s.get("completed", False)
|
|
199
|
+
)
|
|
200
|
+
for s in plan.plan_data.get("steps", [])
|
|
201
|
+
]
|
|
202
|
+
result = PlanState(
|
|
203
|
+
title=plan.title,
|
|
204
|
+
steps=steps,
|
|
205
|
+
status=plan.status,
|
|
206
|
+
conversation_id=query_id
|
|
207
|
+
)
|
|
208
|
+
session.close()
|
|
209
|
+
return result
|
|
210
|
+
|
|
211
|
+
session.close()
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
except Exception as e:
|
|
215
|
+
logger.error(f"Failed to load plan from database: {e}")
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
def save_plan(self, conversation_id: str, plan: PlanState) -> bool:
|
|
219
|
+
"""
|
|
220
|
+
Save plan to PostgreSQL.
|
|
221
|
+
|
|
222
|
+
Uses conversation_id for scoping.
|
|
223
|
+
"""
|
|
224
|
+
from .models import AgentPlan
|
|
225
|
+
|
|
226
|
+
try:
|
|
227
|
+
session = self._get_session()
|
|
228
|
+
|
|
229
|
+
# Use conversation_id for querying and storing
|
|
230
|
+
query_id = self.conversation_id or conversation_id
|
|
231
|
+
|
|
232
|
+
existing = session.query(AgentPlan).filter(
|
|
233
|
+
AgentPlan.conversation_id == query_id
|
|
234
|
+
).first()
|
|
235
|
+
|
|
236
|
+
plan_data = {
|
|
237
|
+
"steps": [{"description": s.description, "completed": s.completed} for s in plan.steps]
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if existing:
|
|
241
|
+
existing.title = plan.title
|
|
242
|
+
existing.plan_data = plan_data
|
|
243
|
+
existing.status = plan.status
|
|
244
|
+
existing.updated_at = datetime.utcnow()
|
|
245
|
+
else:
|
|
246
|
+
new_plan = AgentPlan(
|
|
247
|
+
conversation_id=query_id,
|
|
248
|
+
title=plan.title,
|
|
249
|
+
plan_data=plan_data,
|
|
250
|
+
status=plan.status
|
|
251
|
+
)
|
|
252
|
+
session.add(new_plan)
|
|
253
|
+
|
|
254
|
+
session.commit()
|
|
255
|
+
session.close()
|
|
256
|
+
return True
|
|
257
|
+
|
|
258
|
+
except Exception as e:
|
|
259
|
+
logger.error(f"Failed to save plan to database: {e}")
|
|
260
|
+
return False
|
|
261
|
+
|
|
262
|
+
def delete_plan(self, conversation_id: str) -> bool:
|
|
263
|
+
"""
|
|
264
|
+
Delete plan from PostgreSQL.
|
|
265
|
+
|
|
266
|
+
Uses conversation_id for scoping.
|
|
267
|
+
"""
|
|
268
|
+
from .models import AgentPlan
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
session = self._get_session()
|
|
272
|
+
|
|
273
|
+
# Use conversation_id for querying
|
|
274
|
+
query_id = self.conversation_id or conversation_id
|
|
275
|
+
|
|
276
|
+
session.query(AgentPlan).filter(
|
|
277
|
+
AgentPlan.conversation_id == query_id
|
|
278
|
+
).delete()
|
|
279
|
+
session.commit()
|
|
280
|
+
session.close()
|
|
281
|
+
return True
|
|
282
|
+
|
|
283
|
+
except Exception as e:
|
|
284
|
+
logger.error(f"Failed to delete plan from database: {e}")
|
|
285
|
+
return False
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class PlanningWrapper(BaseModel):
|
|
289
|
+
"""
|
|
290
|
+
Adaptive wrapper for plan management operations.
|
|
291
|
+
|
|
292
|
+
Automatically selects storage backend:
|
|
293
|
+
- PostgreSQL when connection_string is provided
|
|
294
|
+
- Filesystem when no connection_string (local usage)
|
|
295
|
+
|
|
296
|
+
Conversation ID can be:
|
|
297
|
+
1. Passed explicitly to each method
|
|
298
|
+
2. Set via conversation_id field (from server payload or CLI session_id)
|
|
299
|
+
"""
|
|
300
|
+
connection_string: Optional[str] = Field(
|
|
301
|
+
default=None,
|
|
302
|
+
description="PostgreSQL connection string. If not provided, uses filesystem storage."
|
|
303
|
+
)
|
|
304
|
+
conversation_id: Optional[str] = Field(
|
|
305
|
+
default=None,
|
|
306
|
+
description="Optional conversation ID for scoping"
|
|
307
|
+
)
|
|
308
|
+
storage_dir: Optional[str] = Field(
|
|
309
|
+
default=None,
|
|
310
|
+
description="Directory for filesystem storage (when no connection_string)"
|
|
311
|
+
)
|
|
312
|
+
plan_callback: Optional[Any] = Field(
|
|
313
|
+
default=None,
|
|
314
|
+
description="Optional callback function called when plan changes (for CLI UI updates)"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
# Runtime state
|
|
318
|
+
_storage: Any = None
|
|
319
|
+
_use_postgres: bool = False
|
|
320
|
+
|
|
321
|
+
class Config:
|
|
322
|
+
arbitrary_types_allowed = True
|
|
323
|
+
|
|
324
|
+
@model_validator(mode='after')
|
|
325
|
+
def setup_storage(self):
|
|
326
|
+
"""Initialize the appropriate storage backend."""
|
|
327
|
+
conn_str = self.connection_string
|
|
328
|
+
if hasattr(conn_str, 'get_secret_value'):
|
|
329
|
+
conn_str = conn_str.get_secret_value()
|
|
330
|
+
|
|
331
|
+
if conn_str:
|
|
332
|
+
# Use PostgreSQL storage
|
|
333
|
+
try:
|
|
334
|
+
storage = PostgresStorage(conn_str, self.conversation_id)
|
|
335
|
+
object.__setattr__(self, '_storage', storage)
|
|
336
|
+
object.__setattr__(self, '_use_postgres', True)
|
|
337
|
+
logger.info("Planning toolkit using PostgreSQL storage")
|
|
338
|
+
except Exception as e:
|
|
339
|
+
logger.warning(f"Failed to initialize PostgreSQL storage, falling back to filesystem: {e}")
|
|
340
|
+
storage = FilesystemStorage(self.storage_dir)
|
|
341
|
+
object.__setattr__(self, '_storage', storage)
|
|
342
|
+
object.__setattr__(self, '_use_postgres', False)
|
|
343
|
+
else:
|
|
344
|
+
# Use filesystem storage
|
|
345
|
+
storage = FilesystemStorage(self.storage_dir)
|
|
346
|
+
object.__setattr__(self, '_storage', storage)
|
|
347
|
+
object.__setattr__(self, '_use_postgres', False)
|
|
348
|
+
logger.info("Planning toolkit using filesystem storage")
|
|
349
|
+
|
|
350
|
+
return self
|
|
351
|
+
|
|
352
|
+
def run(self, action: str, *args, **kwargs) -> str:
|
|
353
|
+
"""Execute an action by name (called by BaseAction)."""
|
|
354
|
+
# Strip toolkit prefix if present (e.g., "Plan___update_plan" -> "update_plan")
|
|
355
|
+
if '___' in action:
|
|
356
|
+
action = action.split('___')[-1]
|
|
357
|
+
|
|
358
|
+
action_map = {
|
|
359
|
+
"update_plan": self.update_plan,
|
|
360
|
+
"complete_step": self.complete_step,
|
|
361
|
+
"get_plan_status": self.get_plan_status,
|
|
362
|
+
"delete_plan": self.delete_plan,
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if action not in action_map:
|
|
366
|
+
return f"Unknown action: {action}"
|
|
367
|
+
|
|
368
|
+
return action_map[action](*args, **kwargs)
|
|
369
|
+
|
|
370
|
+
def update_plan(self, title: str, steps: List[str], conversation_id: Optional[str] = None) -> str:
|
|
371
|
+
"""
|
|
372
|
+
Create or update an execution plan.
|
|
373
|
+
|
|
374
|
+
If a plan exists for the conversation_id, it will be replaced.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
title: Plan title
|
|
378
|
+
steps: List of step descriptions
|
|
379
|
+
conversation_id: Conversation ID for scoping. Uses wrapper's conversation_id if not provided.
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
Formatted plan state string
|
|
383
|
+
"""
|
|
384
|
+
conversation_id = conversation_id or self.conversation_id
|
|
385
|
+
if not conversation_id:
|
|
386
|
+
return "❌ Error: conversation_id is required (from server or session_id from CLI)"
|
|
387
|
+
|
|
388
|
+
try:
|
|
389
|
+
plan = PlanState(
|
|
390
|
+
title=title,
|
|
391
|
+
steps=[PlanStep(description=s, completed=False) for s in steps],
|
|
392
|
+
status="in_progress",
|
|
393
|
+
conversation_id=conversation_id
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
existing = self._storage.get_plan(conversation_id)
|
|
397
|
+
if self._storage.save_plan(conversation_id, plan):
|
|
398
|
+
action = "updated" if existing else "created"
|
|
399
|
+
|
|
400
|
+
# Notify callback if set (for CLI UI updates)
|
|
401
|
+
if self.plan_callback:
|
|
402
|
+
self.plan_callback(plan)
|
|
403
|
+
|
|
404
|
+
return f"✓ Plan {action}:\n\n{plan.render()}"
|
|
405
|
+
else:
|
|
406
|
+
return "❌ Error: Failed to save plan"
|
|
407
|
+
|
|
408
|
+
except Exception as e:
|
|
409
|
+
logger.error(f"Failed to update plan: {e}")
|
|
410
|
+
return f"❌ Error updating plan: {str(e)}"
|
|
411
|
+
|
|
412
|
+
def complete_step(self, step_number: int, conversation_id: Optional[str] = None) -> str:
|
|
413
|
+
"""
|
|
414
|
+
Mark a step as completed.
|
|
415
|
+
|
|
416
|
+
Args:
|
|
417
|
+
step_number: Step number (1-indexed)
|
|
418
|
+
conversation_id: Conversation ID for scoping. Uses wrapper's conversation_id if not provided.
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
Updated plan state string
|
|
422
|
+
"""
|
|
423
|
+
conversation_id = conversation_id or self.conversation_id
|
|
424
|
+
if not conversation_id:
|
|
425
|
+
return "❌ Error: conversation_id is required (from server or session_id from CLI)"
|
|
426
|
+
|
|
427
|
+
try:
|
|
428
|
+
plan = self._storage.get_plan(conversation_id)
|
|
429
|
+
|
|
430
|
+
if not plan or not plan.steps:
|
|
431
|
+
return "❌ No plan exists. Use update_plan first to create a plan."
|
|
432
|
+
|
|
433
|
+
if step_number < 1 or step_number > len(plan.steps):
|
|
434
|
+
return f"❌ Invalid step number. Plan has {len(plan.steps)} steps (1-{len(plan.steps)})."
|
|
435
|
+
|
|
436
|
+
step = plan.steps[step_number - 1]
|
|
437
|
+
if step.completed:
|
|
438
|
+
return f"Step {step_number} was already completed.\n\n{plan.render()}"
|
|
439
|
+
|
|
440
|
+
step.completed = True
|
|
441
|
+
|
|
442
|
+
# Check if all steps completed
|
|
443
|
+
all_completed = all(s.completed for s in plan.steps)
|
|
444
|
+
if all_completed:
|
|
445
|
+
plan.status = "completed"
|
|
446
|
+
|
|
447
|
+
if self._storage.save_plan(conversation_id, plan):
|
|
448
|
+
# Notify callback if set (for CLI UI updates)
|
|
449
|
+
if self.plan_callback:
|
|
450
|
+
self.plan_callback(plan)
|
|
451
|
+
|
|
452
|
+
completed = sum(1 for s in plan.steps if s.completed)
|
|
453
|
+
total = len(plan.steps)
|
|
454
|
+
return f"✓ Step {step_number} completed ({completed}/{total} done)\n\n{plan.render()}"
|
|
455
|
+
else:
|
|
456
|
+
return "❌ Error: Failed to save plan progress"
|
|
457
|
+
|
|
458
|
+
except Exception as e:
|
|
459
|
+
logger.error(f"Failed to complete step: {e}")
|
|
460
|
+
return f"❌ Error completing step: {str(e)}"
|
|
461
|
+
|
|
462
|
+
def get_plan_status(self, conversation_id: Optional[str] = None) -> str:
|
|
463
|
+
"""
|
|
464
|
+
Get the current plan status.
|
|
465
|
+
|
|
466
|
+
Args:
|
|
467
|
+
conversation_id: Conversation ID for scoping. Uses wrapper's conversation_id if not provided.
|
|
468
|
+
|
|
469
|
+
Returns:
|
|
470
|
+
Formatted plan state or message if no plan exists
|
|
471
|
+
"""
|
|
472
|
+
conversation_id = conversation_id or self.conversation_id
|
|
473
|
+
if not conversation_id:
|
|
474
|
+
return "❌ Error: conversation_id is required (from server or session_id from CLI)"
|
|
475
|
+
|
|
476
|
+
try:
|
|
477
|
+
plan = self._storage.get_plan(conversation_id)
|
|
478
|
+
|
|
479
|
+
if not plan:
|
|
480
|
+
return "No plan exists for the current conversation. Use update_plan to create one."
|
|
481
|
+
|
|
482
|
+
return plan.render()
|
|
483
|
+
|
|
484
|
+
except Exception as e:
|
|
485
|
+
logger.error(f"Failed to get plan status: {e}")
|
|
486
|
+
return f"❌ Error getting plan status: {str(e)}"
|
|
487
|
+
|
|
488
|
+
def delete_plan(self, conversation_id: Optional[str] = None) -> str:
|
|
489
|
+
"""
|
|
490
|
+
Delete the current plan.
|
|
491
|
+
|
|
492
|
+
Args:
|
|
493
|
+
conversation_id: Conversation ID for scoping. Uses wrapper's conversation_id if not provided.
|
|
494
|
+
|
|
495
|
+
Returns:
|
|
496
|
+
Confirmation message
|
|
497
|
+
"""
|
|
498
|
+
conversation_id = conversation_id or self.conversation_id
|
|
499
|
+
if not conversation_id:
|
|
500
|
+
return "❌ Error: conversation_id is required (from server or session_id from CLI)"
|
|
501
|
+
|
|
502
|
+
try:
|
|
503
|
+
plan = self._storage.get_plan(conversation_id)
|
|
504
|
+
|
|
505
|
+
if not plan:
|
|
506
|
+
return "No plan exists for the current conversation."
|
|
507
|
+
|
|
508
|
+
if self._storage.delete_plan(conversation_id):
|
|
509
|
+
return f"✓ Plan '{plan.title}' deleted successfully."
|
|
510
|
+
else:
|
|
511
|
+
return "❌ Error: Failed to delete plan"
|
|
512
|
+
|
|
513
|
+
except Exception as e:
|
|
514
|
+
logger.error(f"Failed to delete plan: {e}")
|
|
515
|
+
return f"❌ Error deleting plan: {str(e)}"
|
|
516
|
+
|
|
517
|
+
def get_available_tools(self) -> List[Dict[str, Any]]:
|
|
518
|
+
"""
|
|
519
|
+
Return list of available planning tools with their schemas.
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
List of tool definitions with name, description, and args_schema
|
|
523
|
+
"""
|
|
524
|
+
# Define input schemas for tools
|
|
525
|
+
# conversation_id is optional when set on the wrapper instance
|
|
526
|
+
UpdatePlanInput = create_model(
|
|
527
|
+
'UpdatePlanInput',
|
|
528
|
+
title=(str, Field(description="Title for the plan (e.g., 'Test Investigation Plan')")),
|
|
529
|
+
steps=(List[str], Field(description="List of step descriptions in order")),
|
|
530
|
+
conversation_id=(Optional[str], Field(default=None, description="Conversation ID (optional - uses default if not provided)"))
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
CompleteStepInput = create_model(
|
|
534
|
+
'CompleteStepInput',
|
|
535
|
+
step_number=(int, Field(description="Step number to mark as complete (1-indexed)")),
|
|
536
|
+
conversation_id=(Optional[str], Field(default=None, description="Conversation ID (optional - uses default if not provided)"))
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
GetPlanStatusInput = create_model(
|
|
540
|
+
'GetPlanStatusInput',
|
|
541
|
+
conversation_id=(Optional[str], Field(default=None, description="Conversation ID (optional - uses default if not provided)"))
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
DeletePlanInput = create_model(
|
|
545
|
+
'DeletePlanInput',
|
|
546
|
+
conversation_id=(Optional[str], Field(default=None, description="Conversation ID (optional - uses default if not provided)"))
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
return [
|
|
550
|
+
{
|
|
551
|
+
"name": "update_plan",
|
|
552
|
+
"description": """Create or replace the current execution plan.
|
|
553
|
+
|
|
554
|
+
Use this when:
|
|
555
|
+
- Starting a multi-step task that needs tracking
|
|
556
|
+
- The sequence of activities matters
|
|
557
|
+
- Breaking down a complex task into phases
|
|
558
|
+
|
|
559
|
+
The plan will be displayed and you can mark steps complete as you progress.
|
|
560
|
+
|
|
561
|
+
Example:
|
|
562
|
+
update_plan(
|
|
563
|
+
title="API Test Investigation",
|
|
564
|
+
steps=[
|
|
565
|
+
"Reproduce the failing test locally",
|
|
566
|
+
"Capture error logs and stack trace",
|
|
567
|
+
"Identify root cause",
|
|
568
|
+
"Apply fix",
|
|
569
|
+
"Re-run test suite"
|
|
570
|
+
]
|
|
571
|
+
)""",
|
|
572
|
+
"args_schema": UpdatePlanInput
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
"name": "complete_step",
|
|
576
|
+
"description": """Mark a step in the current plan as completed.
|
|
577
|
+
|
|
578
|
+
Use this after finishing a step to update progress.
|
|
579
|
+
Step numbers are 1-indexed (first step is 1, not 0).
|
|
580
|
+
|
|
581
|
+
Example:
|
|
582
|
+
complete_step(step_number=1) # Mark first step as done""",
|
|
583
|
+
"args_schema": CompleteStepInput
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
"name": "get_plan_status",
|
|
587
|
+
"description": """Get the current plan status and progress.
|
|
588
|
+
|
|
589
|
+
Shows the plan title, all steps with completion status, and overall progress.
|
|
590
|
+
Use this to review what needs to be done or verify progress.""",
|
|
591
|
+
"args_schema": GetPlanStatusInput
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
"name": "delete_plan",
|
|
595
|
+
"description": """Delete the current plan.
|
|
596
|
+
|
|
597
|
+
Use this when:
|
|
598
|
+
- The plan is complete and no longer needed
|
|
599
|
+
- You want to start fresh with a new plan
|
|
600
|
+
- The current plan is no longer relevant""",
|
|
601
|
+
"args_schema": DeletePlanInput
|
|
602
|
+
}
|
|
603
|
+
]
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
# Import create_model for get_available_tools
|
|
607
|
+
from pydantic import create_model
|
|
@@ -27,10 +27,8 @@ class RouterNode(BaseTool):
|
|
|
27
27
|
if result in [clean_string(formatted_result) for formatted_result in self.routes]:
|
|
28
28
|
# If the result is one of the routes, return it
|
|
29
29
|
return {"router_output": result}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return {"router_output": clean_string(self.default_output)}
|
|
33
|
-
return {"router_output": 'END'}
|
|
30
|
+
# For any unmatched condition (including empty string), use the configured default_output
|
|
31
|
+
return {"router_output": clean_string(self.default_output)}
|
|
34
32
|
|
|
35
33
|
def _run(self, *args, **kwargs):
|
|
36
34
|
return self.invoke(**kwargs)
|
|
@@ -326,12 +326,15 @@ class SandboxToolkit(BaseToolkit):
|
|
|
326
326
|
|
|
327
327
|
@staticmethod
|
|
328
328
|
def toolkit_config_schema() -> Type[BaseModel]:
|
|
329
|
-
#
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
329
|
+
# Get tool schemas without instantiating the tools (avoids Deno requirement)
|
|
330
|
+
try:
|
|
331
|
+
selected_tools = {
|
|
332
|
+
"pyodide_sandbox": sandbox_tool_input.model_json_schema(),
|
|
333
|
+
"stateful_pyodide_sandbox": sandbox_tool_input.model_json_schema(),
|
|
334
|
+
}
|
|
335
|
+
except Exception as e:
|
|
336
|
+
logger.warning(f"Could not generate sandbox tool schemas: {e}")
|
|
337
|
+
selected_tools = {}
|
|
335
338
|
|
|
336
339
|
return create_model(
|
|
337
340
|
'sandbox',
|