alita-sdk 0.3.465__py3-none-any.whl → 0.3.497__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.
Potentially problematic release.
This version of alita-sdk might be problematic. Click here for more details.
- alita_sdk/cli/agent/__init__.py +5 -0
- alita_sdk/cli/agent/default.py +83 -1
- alita_sdk/cli/agent_loader.py +22 -4
- alita_sdk/cli/agent_ui.py +13 -3
- alita_sdk/cli/agents.py +1876 -186
- alita_sdk/cli/callbacks.py +96 -25
- alita_sdk/cli/cli.py +10 -1
- alita_sdk/cli/config.py +151 -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 +167 -4
- alita_sdk/cli/inventory.py +1256 -0
- alita_sdk/cli/toolkit.py +14 -17
- alita_sdk/cli/toolkit_loader.py +35 -5
- alita_sdk/cli/tools/__init__.py +8 -1
- alita_sdk/cli/tools/filesystem.py +910 -64
- alita_sdk/cli/tools/planning.py +143 -157
- alita_sdk/cli/tools/terminal.py +154 -20
- alita_sdk/community/__init__.py +64 -8
- alita_sdk/community/inventory/__init__.py +224 -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 +169 -0
- alita_sdk/community/inventory/visualize.py +1370 -0
- alita_sdk/configurations/bitbucket.py +0 -3
- alita_sdk/runtime/clients/client.py +108 -31
- alita_sdk/runtime/langchain/assistant.py +4 -2
- alita_sdk/runtime/langchain/constants.py +3 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
- alita_sdk/runtime/langchain/document_loaders/constants.py +10 -6
- alita_sdk/runtime/langchain/langraph_agent.py +123 -31
- alita_sdk/runtime/llms/preloaded.py +2 -6
- alita_sdk/runtime/toolkits/__init__.py +2 -0
- alita_sdk/runtime/toolkits/application.py +1 -1
- alita_sdk/runtime/toolkits/mcp.py +107 -91
- alita_sdk/runtime/toolkits/planning.py +173 -0
- alita_sdk/runtime/toolkits/tools.py +59 -7
- alita_sdk/runtime/tools/artifact.py +46 -17
- alita_sdk/runtime/tools/function.py +2 -1
- alita_sdk/runtime/tools/llm.py +320 -32
- alita_sdk/runtime/tools/mcp_remote_tool.py +23 -7
- 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/vectorstore_base.py +44 -9
- alita_sdk/runtime/utils/AlitaCallback.py +106 -20
- alita_sdk/runtime/utils/mcp_client.py +465 -0
- alita_sdk/runtime/utils/mcp_oauth.py +80 -0
- alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
- alita_sdk/runtime/utils/streamlit.py +6 -10
- alita_sdk/runtime/utils/toolkit_utils.py +14 -5
- alita_sdk/tools/__init__.py +54 -27
- alita_sdk/tools/ado/repos/repos_wrapper.py +1 -2
- alita_sdk/tools/base_indexer_toolkit.py +99 -20
- alita_sdk/tools/bitbucket/__init__.py +2 -2
- alita_sdk/tools/chunkers/__init__.py +3 -1
- 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/code/loaders/codesearcher.py +3 -2
- alita_sdk/tools/code_indexer_toolkit.py +55 -22
- alita_sdk/tools/confluence/api_wrapper.py +63 -14
- alita_sdk/tools/elitea_base.py +86 -21
- alita_sdk/tools/jira/__init__.py +1 -1
- alita_sdk/tools/jira/api_wrapper.py +91 -40
- alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
- alita_sdk/tools/qtest/__init__.py +1 -1
- alita_sdk/tools/sharepoint/api_wrapper.py +2 -2
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +17 -13
- alita_sdk/tools/zephyr_essential/api_wrapper.py +12 -13
- {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.497.dist-info}/METADATA +2 -1
- {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.497.dist-info}/RECORD +103 -61
- {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.497.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.497.dist-info}/entry_points.txt +0 -0
- {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.497.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.497.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PlanningToolkit - Runtime toolkit for agent plan management.
|
|
3
|
+
|
|
4
|
+
Provides tools for creating, tracking, and completing multi-step execution plans.
|
|
5
|
+
Supports two storage backends:
|
|
6
|
+
1. PostgreSQL - when pgvector_configuration with connection_string is provided
|
|
7
|
+
2. Filesystem - when no connection string (local CLI usage)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import ClassVar, List, Any, Literal, Optional, Callable
|
|
11
|
+
|
|
12
|
+
from langchain_community.agent_toolkits.base import BaseToolkit
|
|
13
|
+
from langchain_core.tools import BaseTool
|
|
14
|
+
from pydantic import create_model, BaseModel, ConfigDict, Field
|
|
15
|
+
from pydantic.fields import FieldInfo
|
|
16
|
+
|
|
17
|
+
from ..tools.planning import PlanningWrapper
|
|
18
|
+
from ...tools.base.tool import BaseAction
|
|
19
|
+
from ...tools.utils import clean_string, TOOLKIT_SPLITTER, get_max_toolkit_length
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PlanningToolkit(BaseToolkit):
|
|
23
|
+
"""
|
|
24
|
+
Toolkit for agent plan management.
|
|
25
|
+
|
|
26
|
+
Provides tools for creating, updating, and tracking execution plans.
|
|
27
|
+
Supports PostgreSQL (production) and filesystem (local) storage backends.
|
|
28
|
+
Plans are scoped by conversation_id.
|
|
29
|
+
"""
|
|
30
|
+
tools: List[BaseTool] = []
|
|
31
|
+
_toolkit_max_length: ClassVar[int] = 50 # Use ClassVar to avoid Pydantic treating it as field
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def toolkit_config_schema() -> BaseModel:
|
|
35
|
+
"""
|
|
36
|
+
Returns the configuration schema for the Planning toolkit.
|
|
37
|
+
|
|
38
|
+
Used by the UI to generate the toolkit configuration form.
|
|
39
|
+
"""
|
|
40
|
+
# Define available tools
|
|
41
|
+
selected_tools = {
|
|
42
|
+
'update_plan': {
|
|
43
|
+
'title': 'UpdatePlanInput',
|
|
44
|
+
'type': 'object',
|
|
45
|
+
'properties': {
|
|
46
|
+
'title': {'type': 'string', 'description': "Title for the plan"},
|
|
47
|
+
'steps': {'type': 'array', 'items': {'type': 'string'}, 'description': "List of step descriptions"},
|
|
48
|
+
'conversation_id': {'type': 'string', 'description': "Conversation ID (auto-injected)"}
|
|
49
|
+
},
|
|
50
|
+
'required': ['title', 'steps', 'conversation_id']
|
|
51
|
+
},
|
|
52
|
+
'complete_step': {
|
|
53
|
+
'title': 'CompleteStepInput',
|
|
54
|
+
'type': 'object',
|
|
55
|
+
'properties': {
|
|
56
|
+
'step_number': {'type': 'integer', 'description': "Step number to complete (1-indexed)"},
|
|
57
|
+
'conversation_id': {'type': 'string', 'description': "Conversation ID (auto-injected)"}
|
|
58
|
+
},
|
|
59
|
+
'required': ['step_number', 'conversation_id']
|
|
60
|
+
},
|
|
61
|
+
'get_plan_status': {
|
|
62
|
+
'title': 'GetPlanStatusInput',
|
|
63
|
+
'type': 'object',
|
|
64
|
+
'properties': {
|
|
65
|
+
'conversation_id': {'type': 'string', 'description': "Conversation ID (auto-injected)"}
|
|
66
|
+
},
|
|
67
|
+
'required': ['conversation_id']
|
|
68
|
+
},
|
|
69
|
+
'delete_plan': {
|
|
70
|
+
'title': 'DeletePlanInput',
|
|
71
|
+
'type': 'object',
|
|
72
|
+
'properties': {
|
|
73
|
+
'conversation_id': {'type': 'string', 'description': "Conversation ID (auto-injected)"}
|
|
74
|
+
},
|
|
75
|
+
'required': ['conversation_id']
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
PlanningToolkit._toolkit_max_length = get_max_toolkit_length(selected_tools)
|
|
80
|
+
|
|
81
|
+
return create_model(
|
|
82
|
+
"planning",
|
|
83
|
+
# Tool selection
|
|
84
|
+
selected_tools=(
|
|
85
|
+
List[Literal[tuple(selected_tools)]],
|
|
86
|
+
Field(
|
|
87
|
+
default=list(selected_tools.keys()),
|
|
88
|
+
json_schema_extra={'args_schemas': selected_tools}
|
|
89
|
+
)
|
|
90
|
+
),
|
|
91
|
+
__config__=ConfigDict(
|
|
92
|
+
json_schema_extra={
|
|
93
|
+
'metadata': {
|
|
94
|
+
"label": "Planning",
|
|
95
|
+
"description": "Tools for managing multi-step execution plans with progress tracking. Uses PostgreSQL when configured, filesystem otherwise.",
|
|
96
|
+
"icon_url": None,
|
|
97
|
+
"max_length": PlanningToolkit._toolkit_max_length,
|
|
98
|
+
"categories": ["planning", "internal_tool"],
|
|
99
|
+
"extra_categories": ["task management", "todo", "progress tracking"]
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def get_toolkit(
|
|
107
|
+
cls,
|
|
108
|
+
toolkit_name: Optional[str] = None,
|
|
109
|
+
selected_tools: Optional[List[str]] = None,
|
|
110
|
+
pgvector_configuration: Optional[dict] = None,
|
|
111
|
+
storage_dir: Optional[str] = None,
|
|
112
|
+
plan_callback: Optional[Any] = None,
|
|
113
|
+
conversation_id: Optional[str] = None,
|
|
114
|
+
**kwargs
|
|
115
|
+
):
|
|
116
|
+
"""
|
|
117
|
+
Create a PlanningToolkit instance with configured tools.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
toolkit_name: Optional name prefix for tools
|
|
121
|
+
selected_tools: List of tool names to include (default: all)
|
|
122
|
+
pgvector_configuration: PostgreSQL configuration dict with connection_string.
|
|
123
|
+
If not provided, uses filesystem storage.
|
|
124
|
+
storage_dir: Directory for filesystem storage (when no pgvector_configuration)
|
|
125
|
+
plan_callback: Optional callback function called when plan changes (for CLI UI)
|
|
126
|
+
conversation_id: Conversation ID for scoping plans.
|
|
127
|
+
For server: from elitea_core payload. For CLI: session_id.
|
|
128
|
+
**kwargs: Additional configuration options
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
PlanningToolkit instance with configured tools
|
|
132
|
+
"""
|
|
133
|
+
if selected_tools is None:
|
|
134
|
+
selected_tools = ['update_plan', 'complete_step', 'get_plan_status', 'delete_plan']
|
|
135
|
+
|
|
136
|
+
tools = []
|
|
137
|
+
|
|
138
|
+
# Extract connection string from pgvector configuration (if provided)
|
|
139
|
+
connection_string = None
|
|
140
|
+
if pgvector_configuration:
|
|
141
|
+
connection_string = pgvector_configuration.get('connection_string', '')
|
|
142
|
+
if hasattr(connection_string, 'get_secret_value'):
|
|
143
|
+
connection_string = connection_string.get_secret_value()
|
|
144
|
+
|
|
145
|
+
# Create wrapper - it will auto-select storage backend
|
|
146
|
+
wrapper = PlanningWrapper(
|
|
147
|
+
connection_string=connection_string if connection_string else None,
|
|
148
|
+
conversation_id=conversation_id,
|
|
149
|
+
storage_dir=storage_dir,
|
|
150
|
+
plan_callback=plan_callback,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Build tool name prefix
|
|
154
|
+
prefix = clean_string(toolkit_name, cls._toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
|
|
155
|
+
|
|
156
|
+
# Create tools from wrapper
|
|
157
|
+
available_tools = wrapper.get_available_tools()
|
|
158
|
+
for tool in available_tools:
|
|
159
|
+
if tool["name"] not in selected_tools:
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
tools.append(BaseAction(
|
|
163
|
+
api_wrapper=wrapper,
|
|
164
|
+
name=prefix + tool["name"],
|
|
165
|
+
description=tool["description"],
|
|
166
|
+
args_schema=tool["args_schema"]
|
|
167
|
+
))
|
|
168
|
+
|
|
169
|
+
return cls(tools=tools)
|
|
170
|
+
|
|
171
|
+
def get_tools(self) -> List[BaseTool]:
|
|
172
|
+
"""Return the list of configured tools."""
|
|
173
|
+
return self.tools
|
|
@@ -9,6 +9,7 @@ from alita_sdk.tools import get_tools as alita_tools
|
|
|
9
9
|
from .application import ApplicationToolkit
|
|
10
10
|
from .artifact import ArtifactToolkit
|
|
11
11
|
from .datasource import DatasourcesToolkit
|
|
12
|
+
from .planning import PlanningToolkit
|
|
12
13
|
from .prompt import PromptToolkit
|
|
13
14
|
from .subgraph import SubgraphToolkit
|
|
14
15
|
from .vectorstore import VectorStoreToolkit
|
|
@@ -21,6 +22,7 @@ from ...community import get_toolkits as community_toolkits, get_tools as commun
|
|
|
21
22
|
from ...tools.memory import MemoryToolkit
|
|
22
23
|
from ..utils.mcp_oauth import canonical_resource, McpAuthorizationRequired
|
|
23
24
|
from ...tools.utils import TOOLKIT_SPLITTER
|
|
25
|
+
from alita_sdk.tools import _inject_toolkit_id
|
|
24
26
|
|
|
25
27
|
logger = logging.getLogger(__name__)
|
|
26
28
|
|
|
@@ -29,6 +31,7 @@ def get_toolkits():
|
|
|
29
31
|
core_toolkits = [
|
|
30
32
|
ArtifactToolkit.toolkit_config_schema(),
|
|
31
33
|
MemoryToolkit.toolkit_config_schema(),
|
|
34
|
+
PlanningToolkit.toolkit_config_schema(),
|
|
32
35
|
VectorStoreToolkit.toolkit_config_schema(),
|
|
33
36
|
SandboxToolkit.toolkit_config_schema(),
|
|
34
37
|
ImageGenerationToolkit.toolkit_config_schema(),
|
|
@@ -38,7 +41,7 @@ def get_toolkits():
|
|
|
38
41
|
return core_toolkits + community_toolkits() + alita_toolkits()
|
|
39
42
|
|
|
40
43
|
|
|
41
|
-
def get_tools(tools_list: list, alita_client, llm, memory_store: BaseStore = None, debug_mode: Optional[bool] = False, mcp_tokens: Optional[dict] = None) -> list:
|
|
44
|
+
def get_tools(tools_list: list, alita_client=None, llm=None, memory_store: BaseStore = None, debug_mode: Optional[bool] = False, mcp_tokens: Optional[dict] = None, conversation_id: Optional[str] = None) -> list:
|
|
42
45
|
prompts = []
|
|
43
46
|
tools = []
|
|
44
47
|
|
|
@@ -91,8 +94,13 @@ def get_tools(tools_list: list, alita_client, llm, memory_store: BaseStore = Non
|
|
|
91
94
|
else:
|
|
92
95
|
logger.warning("Image generation internal tool requested "
|
|
93
96
|
"but no image generation model configured")
|
|
97
|
+
elif tool['name'] == 'planner':
|
|
98
|
+
tools += PlanningToolkit.get_toolkit(
|
|
99
|
+
pgvector_configuration=tool.get('settings', {}).get('pgvector_configuration'),
|
|
100
|
+
conversation_id=conversation_id,
|
|
101
|
+
).get_tools()
|
|
94
102
|
elif tool['type'] == 'artifact':
|
|
95
|
-
|
|
103
|
+
toolkit_tools = ArtifactToolkit.get_toolkit(
|
|
96
104
|
client=alita_client,
|
|
97
105
|
bucket=tool['settings']['bucket'],
|
|
98
106
|
toolkit_name=tool.get('toolkit_name', ''),
|
|
@@ -102,13 +110,56 @@ def get_tools(tools_list: list, alita_client, llm, memory_store: BaseStore = Non
|
|
|
102
110
|
pgvector_configuration=tool['settings'].get('pgvector_configuration', {}),
|
|
103
111
|
embedding_model=tool['settings'].get('embedding_model'),
|
|
104
112
|
collection_name=f"{tool.get('toolkit_name')}",
|
|
105
|
-
collection_schema
|
|
106
|
-
).get_tools()
|
|
113
|
+
collection_schema=str(tool['id']),
|
|
114
|
+
).get_tools()
|
|
115
|
+
# Inject toolkit_id for artifact tools as well
|
|
116
|
+
_inject_toolkit_id(tool, toolkit_tools)
|
|
117
|
+
tools.extend(toolkit_tools)
|
|
118
|
+
|
|
107
119
|
elif tool['type'] == 'vectorstore':
|
|
108
120
|
tools.extend(VectorStoreToolkit.get_toolkit(
|
|
109
121
|
llm=llm,
|
|
110
122
|
toolkit_name=tool.get('toolkit_name', ''),
|
|
111
123
|
**tool['settings']).get_tools())
|
|
124
|
+
elif tool['type'] == 'planning':
|
|
125
|
+
# Planning toolkit for multi-step task tracking
|
|
126
|
+
settings = tool.get('settings', {})
|
|
127
|
+
|
|
128
|
+
# Check if local mode is enabled (uses filesystem storage, ignores pgvector)
|
|
129
|
+
use_local = settings.get('local', False)
|
|
130
|
+
|
|
131
|
+
if use_local:
|
|
132
|
+
# Local mode - use filesystem storage
|
|
133
|
+
logger.info("Planning toolkit using local filesystem storage (local=true)")
|
|
134
|
+
pgvector_config = {}
|
|
135
|
+
else:
|
|
136
|
+
# Check if explicit connection_string is provided in pgvector_configuration
|
|
137
|
+
explicit_pgvector_config = settings.get('pgvector_configuration', {})
|
|
138
|
+
explicit_connstr = explicit_pgvector_config.get('connection_string') if explicit_pgvector_config else None
|
|
139
|
+
|
|
140
|
+
if explicit_connstr:
|
|
141
|
+
# Use explicitly provided connection string (overrides project secrets)
|
|
142
|
+
logger.info("Using explicit connection_string for planning toolkit")
|
|
143
|
+
pgvector_config = explicit_pgvector_config
|
|
144
|
+
else:
|
|
145
|
+
# Try to fetch pgvector_project_connstr from project secrets
|
|
146
|
+
pgvector_connstr = None
|
|
147
|
+
if alita_client:
|
|
148
|
+
try:
|
|
149
|
+
pgvector_connstr = alita_client.unsecret('pgvector_project_connstr')
|
|
150
|
+
if pgvector_connstr:
|
|
151
|
+
logger.info("Using pgvector_project_connstr for planning toolkit")
|
|
152
|
+
except Exception as e:
|
|
153
|
+
logger.debug(f"pgvector_project_connstr not available: {e}")
|
|
154
|
+
|
|
155
|
+
pgvector_config = {'connection_string': pgvector_connstr} if pgvector_connstr else {}
|
|
156
|
+
|
|
157
|
+
tools.extend(PlanningToolkit.get_toolkit(
|
|
158
|
+
toolkit_name=tool.get('toolkit_name', ''),
|
|
159
|
+
selected_tools=settings.get('selected_tools', []),
|
|
160
|
+
pgvector_configuration=pgvector_config,
|
|
161
|
+
conversation_id=conversation_id or settings.get('conversation_id'),
|
|
162
|
+
).get_tools())
|
|
112
163
|
elif tool['type'] == 'mcp':
|
|
113
164
|
# remote mcp tool initialization with token injection
|
|
114
165
|
settings = dict(tool['settings'])
|
|
@@ -153,9 +204,10 @@ def get_tools(tools_list: list, alita_client, llm, memory_store: BaseStore = Non
|
|
|
153
204
|
toolkit_name=tool.get('toolkit_name', ''),
|
|
154
205
|
client=alita_client,
|
|
155
206
|
**settings).get_tools())
|
|
207
|
+
except McpAuthorizationRequired:
|
|
208
|
+
# Re-raise auth required exceptions directly
|
|
209
|
+
raise
|
|
156
210
|
except Exception as e:
|
|
157
|
-
if isinstance(e, McpAuthorizationRequired):
|
|
158
|
-
raise
|
|
159
211
|
logger.error(f"Error initializing toolkit for tool '{tool.get('name', 'unknown')}': {e}", exc_info=True)
|
|
160
212
|
if debug_mode:
|
|
161
213
|
logger.info("Skipping tool initialization error due to debug mode.")
|
|
@@ -271,7 +323,7 @@ def _init_single_mcp_tool(server_toolkit_name, toolkit_name, available_tool, ali
|
|
|
271
323
|
tool_name = f'{toolkit_name}{TOOLKIT_SPLITTER}{available_tool["name"]}'
|
|
272
324
|
return McpServerTool(
|
|
273
325
|
name=tool_name,
|
|
274
|
-
description=f"MCP for a tool '{tool_name}': {available_tool.get(
|
|
326
|
+
description=f"MCP for a tool '{tool_name}': {available_tool.get('description', '')}",
|
|
275
327
|
args_schema=McpServerTool.create_pydantic_model_from_schema(
|
|
276
328
|
available_tool.get("inputSchema", {})
|
|
277
329
|
),
|
|
@@ -34,28 +34,57 @@ class ArtifactWrapper(NonCodeIndexerToolkit):
|
|
|
34
34
|
return self.artifact.list(bucket_name, return_as_string)
|
|
35
35
|
|
|
36
36
|
def create_file(self, filename: str, filedata: str, bucket_name = None):
|
|
37
|
-
|
|
37
|
+
# Sanitize filename to prevent regex errors during indexing
|
|
38
|
+
sanitized_filename, was_modified = self._sanitize_filename(filename)
|
|
39
|
+
if was_modified:
|
|
40
|
+
logging.warning(f"Filename sanitized: '{filename}' -> '{sanitized_filename}'")
|
|
41
|
+
|
|
42
|
+
if sanitized_filename.endswith(".xlsx"):
|
|
38
43
|
data = json.loads(filedata)
|
|
39
44
|
filedata = self.create_xlsx_filedata(data)
|
|
40
45
|
|
|
41
|
-
result = self.artifact.create(
|
|
46
|
+
result = self.artifact.create(sanitized_filename, filedata, bucket_name)
|
|
42
47
|
|
|
43
48
|
# Dispatch custom event for file creation
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"bucket": bucket_name or self.bucket
|
|
55
|
-
}
|
|
56
|
-
}""")
|
|
49
|
+
dispatch_custom_event("file_modified", {
|
|
50
|
+
"message": f"File '{filename}' created successfully",
|
|
51
|
+
"filename": filename,
|
|
52
|
+
"tool_name": "createFile",
|
|
53
|
+
"toolkit": "artifact",
|
|
54
|
+
"operation_type": "create",
|
|
55
|
+
"meta": {
|
|
56
|
+
"bucket": bucket_name or self.bucket
|
|
57
|
+
}
|
|
58
|
+
})
|
|
57
59
|
|
|
58
60
|
return result
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def _sanitize_filename(filename: str) -> tuple:
|
|
64
|
+
"""Sanitize filename for safe storage and regex pattern matching."""
|
|
65
|
+
from pathlib import Path
|
|
66
|
+
|
|
67
|
+
if not filename or not filename.strip():
|
|
68
|
+
return "unnamed_file", True
|
|
69
|
+
|
|
70
|
+
original = filename
|
|
71
|
+
path_obj = Path(filename)
|
|
72
|
+
name = path_obj.stem
|
|
73
|
+
extension = path_obj.suffix
|
|
74
|
+
|
|
75
|
+
# Whitelist: alphanumeric, underscore, hyphen, space, Unicode letters/digits
|
|
76
|
+
sanitized_name = re.sub(r'[^\w\s-]', '', name, flags=re.UNICODE)
|
|
77
|
+
sanitized_name = re.sub(r'[-\s]+', '-', sanitized_name)
|
|
78
|
+
sanitized_name = sanitized_name.strip('-').strip()
|
|
79
|
+
|
|
80
|
+
if not sanitized_name:
|
|
81
|
+
sanitized_name = "file"
|
|
82
|
+
|
|
83
|
+
if extension:
|
|
84
|
+
extension = re.sub(r'[^\w.-]', '', extension, flags=re.UNICODE)
|
|
85
|
+
|
|
86
|
+
sanitized = sanitized_name + extension
|
|
87
|
+
return sanitized, (sanitized != original)
|
|
59
88
|
|
|
60
89
|
def create_xlsx_filedata(self, data: dict[str, list[list]]) -> bytes:
|
|
61
90
|
try:
|
|
@@ -173,13 +202,13 @@ class ArtifactWrapper(NonCodeIndexerToolkit):
|
|
|
173
202
|
file_name = file['name']
|
|
174
203
|
|
|
175
204
|
# Check if file should be skipped based on skip_extensions
|
|
176
|
-
if any(re.match(pattern.replace('
|
|
205
|
+
if any(re.match(re.escape(pattern).replace(r'\*', '.*') + '$', file_name, re.IGNORECASE)
|
|
177
206
|
for pattern in skip_extensions):
|
|
178
207
|
continue
|
|
179
208
|
|
|
180
209
|
# Check if file should be included based on include_extensions
|
|
181
210
|
# If include_extensions is empty, process all files (that weren't skipped)
|
|
182
|
-
if include_extensions and not (any(re.match(pattern.replace('
|
|
211
|
+
if include_extensions and not (any(re.match(re.escape(pattern).replace(r'\*', '.*') + '$', file_name, re.IGNORECASE)
|
|
183
212
|
for pattern in include_extensions)):
|
|
184
213
|
continue
|
|
185
214
|
|
|
@@ -107,7 +107,8 @@ class FunctionTool(BaseTool):
|
|
|
107
107
|
|
|
108
108
|
# special handler for PyodideSandboxTool
|
|
109
109
|
if self._is_pyodide_tool():
|
|
110
|
-
|
|
110
|
+
# replace new lines in strings in code block
|
|
111
|
+
code = func_args['code'].replace('\\n', '\\\\n')
|
|
111
112
|
func_args['code'] = f"{self._prepare_pyodide_input(state)}\n{code}"
|
|
112
113
|
try:
|
|
113
114
|
tool_result = self.tool.invoke(func_args, config, **kwargs)
|