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.

Files changed (103) hide show
  1. alita_sdk/cli/agent/__init__.py +5 -0
  2. alita_sdk/cli/agent/default.py +83 -1
  3. alita_sdk/cli/agent_loader.py +22 -4
  4. alita_sdk/cli/agent_ui.py +13 -3
  5. alita_sdk/cli/agents.py +1876 -186
  6. alita_sdk/cli/callbacks.py +96 -25
  7. alita_sdk/cli/cli.py +10 -1
  8. alita_sdk/cli/config.py +151 -9
  9. alita_sdk/cli/context/__init__.py +30 -0
  10. alita_sdk/cli/context/cleanup.py +198 -0
  11. alita_sdk/cli/context/manager.py +731 -0
  12. alita_sdk/cli/context/message.py +285 -0
  13. alita_sdk/cli/context/strategies.py +289 -0
  14. alita_sdk/cli/context/token_estimation.py +127 -0
  15. alita_sdk/cli/input_handler.py +167 -4
  16. alita_sdk/cli/inventory.py +1256 -0
  17. alita_sdk/cli/toolkit.py +14 -17
  18. alita_sdk/cli/toolkit_loader.py +35 -5
  19. alita_sdk/cli/tools/__init__.py +8 -1
  20. alita_sdk/cli/tools/filesystem.py +910 -64
  21. alita_sdk/cli/tools/planning.py +143 -157
  22. alita_sdk/cli/tools/terminal.py +154 -20
  23. alita_sdk/community/__init__.py +64 -8
  24. alita_sdk/community/inventory/__init__.py +224 -0
  25. alita_sdk/community/inventory/config.py +257 -0
  26. alita_sdk/community/inventory/enrichment.py +2137 -0
  27. alita_sdk/community/inventory/extractors.py +1469 -0
  28. alita_sdk/community/inventory/ingestion.py +3172 -0
  29. alita_sdk/community/inventory/knowledge_graph.py +1457 -0
  30. alita_sdk/community/inventory/parsers/__init__.py +218 -0
  31. alita_sdk/community/inventory/parsers/base.py +295 -0
  32. alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
  33. alita_sdk/community/inventory/parsers/go_parser.py +851 -0
  34. alita_sdk/community/inventory/parsers/html_parser.py +389 -0
  35. alita_sdk/community/inventory/parsers/java_parser.py +593 -0
  36. alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
  37. alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
  38. alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
  39. alita_sdk/community/inventory/parsers/python_parser.py +604 -0
  40. alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
  41. alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
  42. alita_sdk/community/inventory/parsers/text_parser.py +322 -0
  43. alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
  44. alita_sdk/community/inventory/patterns/__init__.py +61 -0
  45. alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
  46. alita_sdk/community/inventory/patterns/loader.py +348 -0
  47. alita_sdk/community/inventory/patterns/registry.py +198 -0
  48. alita_sdk/community/inventory/presets.py +535 -0
  49. alita_sdk/community/inventory/retrieval.py +1403 -0
  50. alita_sdk/community/inventory/toolkit.py +169 -0
  51. alita_sdk/community/inventory/visualize.py +1370 -0
  52. alita_sdk/configurations/bitbucket.py +0 -3
  53. alita_sdk/runtime/clients/client.py +108 -31
  54. alita_sdk/runtime/langchain/assistant.py +4 -2
  55. alita_sdk/runtime/langchain/constants.py +3 -1
  56. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
  57. alita_sdk/runtime/langchain/document_loaders/constants.py +10 -6
  58. alita_sdk/runtime/langchain/langraph_agent.py +123 -31
  59. alita_sdk/runtime/llms/preloaded.py +2 -6
  60. alita_sdk/runtime/toolkits/__init__.py +2 -0
  61. alita_sdk/runtime/toolkits/application.py +1 -1
  62. alita_sdk/runtime/toolkits/mcp.py +107 -91
  63. alita_sdk/runtime/toolkits/planning.py +173 -0
  64. alita_sdk/runtime/toolkits/tools.py +59 -7
  65. alita_sdk/runtime/tools/artifact.py +46 -17
  66. alita_sdk/runtime/tools/function.py +2 -1
  67. alita_sdk/runtime/tools/llm.py +320 -32
  68. alita_sdk/runtime/tools/mcp_remote_tool.py +23 -7
  69. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  70. alita_sdk/runtime/tools/planning/models.py +246 -0
  71. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  72. alita_sdk/runtime/tools/vectorstore_base.py +44 -9
  73. alita_sdk/runtime/utils/AlitaCallback.py +106 -20
  74. alita_sdk/runtime/utils/mcp_client.py +465 -0
  75. alita_sdk/runtime/utils/mcp_oauth.py +80 -0
  76. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  77. alita_sdk/runtime/utils/streamlit.py +6 -10
  78. alita_sdk/runtime/utils/toolkit_utils.py +14 -5
  79. alita_sdk/tools/__init__.py +54 -27
  80. alita_sdk/tools/ado/repos/repos_wrapper.py +1 -2
  81. alita_sdk/tools/base_indexer_toolkit.py +99 -20
  82. alita_sdk/tools/bitbucket/__init__.py +2 -2
  83. alita_sdk/tools/chunkers/__init__.py +3 -1
  84. alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
  85. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  86. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  87. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  88. alita_sdk/tools/code_indexer_toolkit.py +55 -22
  89. alita_sdk/tools/confluence/api_wrapper.py +63 -14
  90. alita_sdk/tools/elitea_base.py +86 -21
  91. alita_sdk/tools/jira/__init__.py +1 -1
  92. alita_sdk/tools/jira/api_wrapper.py +91 -40
  93. alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
  94. alita_sdk/tools/qtest/__init__.py +1 -1
  95. alita_sdk/tools/sharepoint/api_wrapper.py +2 -2
  96. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +17 -13
  97. alita_sdk/tools/zephyr_essential/api_wrapper.py +12 -13
  98. {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.497.dist-info}/METADATA +2 -1
  99. {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.497.dist-info}/RECORD +103 -61
  100. {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.497.dist-info}/WHEEL +0 -0
  101. {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.497.dist-info}/entry_points.txt +0 -0
  102. {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.497.dist-info}/licenses/LICENSE +0 -0
  103. {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
- tools.extend(ArtifactToolkit.get_toolkit(
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 = str(tool['id'])
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("description", "")}",
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
- if filename.endswith(".xlsx"):
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(filename, filedata, bucket_name)
46
+ result = self.artifact.create(sanitized_filename, filedata, bucket_name)
42
47
 
43
48
  # Dispatch custom event for file creation
44
- self._log_tool_event(
45
- tool_name="file_modified",
46
- message="""
47
- {
48
- "message": f"File '{filename}' created successfully",
49
- "filename": filename,
50
- "tool_name": "createFile",
51
- "toolkit": "artifact",
52
- "operation_type": "create",
53
- "meta": {
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('*', '.*') + '$', file_name, re.IGNORECASE)
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('*', '.*') + '$', file_name, re.IGNORECASE)
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
- code = func_args['code']
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)