alita-sdk 0.3.257__py3-none-any.whl → 0.3.562__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/__init__.py +10 -0
- alita_sdk/cli/__main__.py +17 -0
- alita_sdk/cli/agent/__init__.py +5 -0
- alita_sdk/cli/agent/default.py +258 -0
- alita_sdk/cli/agent_executor.py +155 -0
- alita_sdk/cli/agent_loader.py +215 -0
- alita_sdk/cli/agent_ui.py +228 -0
- alita_sdk/cli/agents.py +3601 -0
- alita_sdk/cli/callbacks.py +647 -0
- alita_sdk/cli/cli.py +168 -0
- alita_sdk/cli/config.py +306 -0
- 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/formatting.py +182 -0
- alita_sdk/cli/input_handler.py +419 -0
- alita_sdk/cli/inventory.py +1073 -0
- alita_sdk/cli/mcp_loader.py +315 -0
- alita_sdk/cli/toolkit.py +327 -0
- alita_sdk/cli/toolkit_loader.py +85 -0
- alita_sdk/cli/tools/__init__.py +43 -0
- alita_sdk/cli/tools/approval.py +224 -0
- alita_sdk/cli/tools/filesystem.py +1751 -0
- 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 +11 -0
- alita_sdk/configurations/ado.py +148 -2
- alita_sdk/configurations/azure_search.py +1 -1
- alita_sdk/configurations/bigquery.py +1 -1
- alita_sdk/configurations/bitbucket.py +94 -2
- alita_sdk/configurations/browser.py +18 -0
- alita_sdk/configurations/carrier.py +19 -0
- alita_sdk/configurations/confluence.py +130 -1
- alita_sdk/configurations/delta_lake.py +1 -1
- alita_sdk/configurations/figma.py +76 -5
- alita_sdk/configurations/github.py +65 -1
- alita_sdk/configurations/gitlab.py +81 -0
- alita_sdk/configurations/google_places.py +17 -0
- alita_sdk/configurations/jira.py +103 -0
- alita_sdk/configurations/openapi.py +111 -0
- alita_sdk/configurations/postman.py +1 -1
- alita_sdk/configurations/qtest.py +72 -3
- alita_sdk/configurations/report_portal.py +115 -0
- alita_sdk/configurations/salesforce.py +19 -0
- alita_sdk/configurations/service_now.py +1 -12
- alita_sdk/configurations/sharepoint.py +167 -0
- alita_sdk/configurations/sonar.py +18 -0
- alita_sdk/configurations/sql.py +20 -0
- alita_sdk/configurations/testio.py +101 -0
- alita_sdk/configurations/testrail.py +88 -0
- alita_sdk/configurations/xray.py +94 -1
- alita_sdk/configurations/zephyr_enterprise.py +94 -1
- alita_sdk/configurations/zephyr_essential.py +95 -0
- alita_sdk/runtime/clients/artifact.py +21 -4
- alita_sdk/runtime/clients/client.py +458 -67
- alita_sdk/runtime/clients/mcp_discovery.py +342 -0
- alita_sdk/runtime/clients/mcp_manager.py +262 -0
- alita_sdk/runtime/clients/sandbox_client.py +352 -0
- alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
- alita_sdk/runtime/langchain/assistant.py +183 -43
- alita_sdk/runtime/langchain/constants.py +647 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +209 -31
- alita_sdk/runtime/langchain/document_loaders/AlitaImageLoader.py +1 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +10 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaMarkdownLoader.py +66 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaPDFLoader.py +79 -10
- alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +52 -15
- alita_sdk/runtime/langchain/document_loaders/AlitaPythonLoader.py +9 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaTableLoader.py +1 -4
- alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +15 -2
- alita_sdk/runtime/langchain/document_loaders/ImageParser.py +30 -0
- alita_sdk/runtime/langchain/document_loaders/constants.py +189 -41
- alita_sdk/runtime/langchain/interfaces/llm_processor.py +4 -2
- alita_sdk/runtime/langchain/langraph_agent.py +407 -92
- alita_sdk/runtime/langchain/utils.py +102 -8
- alita_sdk/runtime/llms/preloaded.py +2 -6
- alita_sdk/runtime/models/mcp_models.py +61 -0
- 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 +28 -0
- alita_sdk/runtime/toolkits/application.py +14 -4
- alita_sdk/runtime/toolkits/artifact.py +24 -9
- alita_sdk/runtime/toolkits/datasource.py +13 -6
- alita_sdk/runtime/toolkits/mcp.py +780 -0
- alita_sdk/runtime/toolkits/planning.py +178 -0
- alita_sdk/runtime/toolkits/skill_router.py +238 -0
- alita_sdk/runtime/toolkits/subgraph.py +11 -6
- alita_sdk/runtime/toolkits/tools.py +314 -70
- alita_sdk/runtime/toolkits/vectorstore.py +11 -5
- alita_sdk/runtime/tools/__init__.py +24 -0
- alita_sdk/runtime/tools/application.py +16 -4
- alita_sdk/runtime/tools/artifact.py +367 -33
- alita_sdk/runtime/tools/data_analysis.py +183 -0
- alita_sdk/runtime/tools/function.py +100 -4
- alita_sdk/runtime/tools/graph.py +81 -0
- alita_sdk/runtime/tools/image_generation.py +218 -0
- alita_sdk/runtime/tools/llm.py +1013 -177
- alita_sdk/runtime/tools/loop.py +3 -1
- alita_sdk/runtime/tools/loop_output.py +3 -1
- alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
- alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
- alita_sdk/runtime/tools/mcp_server_tool.py +3 -1
- 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 -1
- alita_sdk/runtime/tools/sandbox.py +375 -0
- alita_sdk/runtime/tools/skill_router.py +776 -0
- alita_sdk/runtime/tools/tool.py +3 -1
- alita_sdk/runtime/tools/vectorstore.py +69 -65
- alita_sdk/runtime/tools/vectorstore_base.py +163 -90
- alita_sdk/runtime/utils/AlitaCallback.py +137 -21
- alita_sdk/runtime/utils/mcp_client.py +492 -0
- alita_sdk/runtime/utils/mcp_oauth.py +361 -0
- alita_sdk/runtime/utils/mcp_sse_client.py +434 -0
- alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
- alita_sdk/runtime/utils/streamlit.py +41 -14
- alita_sdk/runtime/utils/toolkit_utils.py +28 -9
- alita_sdk/runtime/utils/utils.py +48 -0
- alita_sdk/tools/__init__.py +135 -37
- alita_sdk/tools/ado/__init__.py +2 -2
- alita_sdk/tools/ado/repos/__init__.py +15 -19
- alita_sdk/tools/ado/repos/repos_wrapper.py +12 -20
- alita_sdk/tools/ado/test_plan/__init__.py +26 -8
- alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +56 -28
- alita_sdk/tools/ado/wiki/__init__.py +27 -12
- alita_sdk/tools/ado/wiki/ado_wrapper.py +114 -40
- alita_sdk/tools/ado/work_item/__init__.py +27 -12
- alita_sdk/tools/ado/work_item/ado_wrapper.py +95 -11
- alita_sdk/tools/advanced_jira_mining/__init__.py +12 -8
- alita_sdk/tools/aws/delta_lake/__init__.py +14 -11
- alita_sdk/tools/aws/delta_lake/tool.py +5 -1
- alita_sdk/tools/azure_ai/search/__init__.py +13 -8
- alita_sdk/tools/base/tool.py +5 -1
- alita_sdk/tools/base_indexer_toolkit.py +454 -110
- alita_sdk/tools/bitbucket/__init__.py +27 -19
- alita_sdk/tools/bitbucket/api_wrapper.py +285 -27
- alita_sdk/tools/bitbucket/cloud_api_wrapper.py +5 -5
- alita_sdk/tools/browser/__init__.py +41 -16
- alita_sdk/tools/browser/crawler.py +3 -1
- alita_sdk/tools/browser/utils.py +15 -6
- alita_sdk/tools/carrier/__init__.py +18 -17
- alita_sdk/tools/carrier/backend_reports_tool.py +8 -4
- alita_sdk/tools/carrier/excel_reporter.py +8 -4
- alita_sdk/tools/chunkers/__init__.py +3 -1
- alita_sdk/tools/chunkers/code/codeparser.py +1 -1
- alita_sdk/tools/chunkers/sematic/json_chunker.py +2 -1
- alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
- alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
- alita_sdk/tools/chunkers/universal_chunker.py +270 -0
- alita_sdk/tools/cloud/aws/__init__.py +11 -7
- alita_sdk/tools/cloud/azure/__init__.py +11 -7
- alita_sdk/tools/cloud/gcp/__init__.py +11 -7
- alita_sdk/tools/cloud/k8s/__init__.py +11 -7
- alita_sdk/tools/code/linter/__init__.py +9 -8
- alita_sdk/tools/code/loaders/codesearcher.py +3 -2
- alita_sdk/tools/code/sonar/__init__.py +20 -13
- alita_sdk/tools/code_indexer_toolkit.py +199 -0
- alita_sdk/tools/confluence/__init__.py +21 -14
- alita_sdk/tools/confluence/api_wrapper.py +197 -58
- alita_sdk/tools/confluence/loader.py +14 -2
- alita_sdk/tools/custom_open_api/__init__.py +11 -5
- alita_sdk/tools/elastic/__init__.py +10 -8
- alita_sdk/tools/elitea_base.py +546 -64
- alita_sdk/tools/figma/__init__.py +11 -8
- alita_sdk/tools/figma/api_wrapper.py +352 -153
- alita_sdk/tools/github/__init__.py +17 -17
- alita_sdk/tools/github/api_wrapper.py +9 -26
- alita_sdk/tools/github/github_client.py +81 -12
- alita_sdk/tools/github/schemas.py +2 -1
- alita_sdk/tools/github/tool.py +5 -1
- alita_sdk/tools/gitlab/__init__.py +18 -13
- alita_sdk/tools/gitlab/api_wrapper.py +224 -80
- alita_sdk/tools/gitlab_org/__init__.py +13 -10
- alita_sdk/tools/google/bigquery/__init__.py +13 -13
- alita_sdk/tools/google/bigquery/tool.py +5 -1
- alita_sdk/tools/google_places/__init__.py +20 -11
- alita_sdk/tools/jira/__init__.py +21 -11
- alita_sdk/tools/jira/api_wrapper.py +315 -168
- alita_sdk/tools/keycloak/__init__.py +10 -8
- alita_sdk/tools/localgit/__init__.py +8 -3
- alita_sdk/tools/localgit/local_git.py +62 -54
- alita_sdk/tools/localgit/tool.py +5 -1
- alita_sdk/tools/memory/__init__.py +38 -14
- alita_sdk/tools/non_code_indexer_toolkit.py +7 -2
- alita_sdk/tools/ocr/__init__.py +10 -8
- alita_sdk/tools/openapi/__init__.py +281 -108
- alita_sdk/tools/openapi/api_wrapper.py +883 -0
- alita_sdk/tools/openapi/tool.py +20 -0
- alita_sdk/tools/pandas/__init__.py +18 -11
- alita_sdk/tools/pandas/api_wrapper.py +40 -45
- alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
- alita_sdk/tools/postman/__init__.py +10 -11
- alita_sdk/tools/postman/api_wrapper.py +19 -8
- alita_sdk/tools/postman/postman_analysis.py +8 -1
- alita_sdk/tools/pptx/__init__.py +10 -10
- alita_sdk/tools/qtest/__init__.py +21 -14
- alita_sdk/tools/qtest/api_wrapper.py +1784 -88
- alita_sdk/tools/rally/__init__.py +12 -10
- alita_sdk/tools/report_portal/__init__.py +22 -16
- alita_sdk/tools/salesforce/__init__.py +21 -16
- alita_sdk/tools/servicenow/__init__.py +20 -16
- alita_sdk/tools/servicenow/api_wrapper.py +1 -1
- alita_sdk/tools/sharepoint/__init__.py +16 -14
- alita_sdk/tools/sharepoint/api_wrapper.py +179 -39
- alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
- alita_sdk/tools/sharepoint/utils.py +8 -2
- alita_sdk/tools/slack/__init__.py +11 -7
- alita_sdk/tools/sql/__init__.py +21 -19
- alita_sdk/tools/sql/api_wrapper.py +71 -23
- alita_sdk/tools/testio/__init__.py +20 -13
- alita_sdk/tools/testrail/__init__.py +12 -11
- alita_sdk/tools/testrail/api_wrapper.py +214 -46
- alita_sdk/tools/utils/__init__.py +28 -4
- alita_sdk/tools/utils/content_parser.py +182 -62
- alita_sdk/tools/utils/text_operations.py +254 -0
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +83 -27
- alita_sdk/tools/xray/__init__.py +17 -14
- alita_sdk/tools/xray/api_wrapper.py +58 -113
- alita_sdk/tools/yagmail/__init__.py +8 -3
- alita_sdk/tools/zephyr/__init__.py +11 -7
- alita_sdk/tools/zephyr_enterprise/__init__.py +15 -9
- alita_sdk/tools/zephyr_enterprise/api_wrapper.py +30 -15
- alita_sdk/tools/zephyr_essential/__init__.py +15 -10
- alita_sdk/tools/zephyr_essential/api_wrapper.py +297 -54
- alita_sdk/tools/zephyr_essential/client.py +6 -4
- alita_sdk/tools/zephyr_scale/__init__.py +12 -8
- alita_sdk/tools/zephyr_scale/api_wrapper.py +39 -31
- alita_sdk/tools/zephyr_squad/__init__.py +11 -7
- {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/METADATA +184 -37
- alita_sdk-0.3.562.dist-info/RECORD +450 -0
- alita_sdk-0.3.562.dist-info/entry_points.txt +2 -0
- alita_sdk/tools/bitbucket/tools.py +0 -304
- alita_sdk-0.3.257.dist-info/RECORD +0 -343
- {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
|
|
2
|
+
import re
|
|
3
|
+
from typing import Optional, Generator, List
|
|
3
4
|
|
|
4
5
|
from langchain_core.documents import Document
|
|
5
6
|
from langchain_core.tools import ToolException
|
|
@@ -7,8 +8,10 @@ from office365.runtime.auth.client_credential import ClientCredential
|
|
|
7
8
|
from office365.sharepoint.client_context import ClientContext
|
|
8
9
|
from pydantic import Field, PrivateAttr, create_model, model_validator, SecretStr
|
|
9
10
|
|
|
11
|
+
from .utils import decode_sharepoint_string
|
|
10
12
|
from ..non_code_indexer_toolkit import NonCodeIndexerToolkit
|
|
11
13
|
from ..utils.content_parser import parse_file_content
|
|
14
|
+
from ...runtime.utils.utils import IndexerKeywords
|
|
12
15
|
|
|
13
16
|
NoInput = create_model(
|
|
14
17
|
"NoInput"
|
|
@@ -23,7 +26,11 @@ ReadList = create_model(
|
|
|
23
26
|
GetFiles = create_model(
|
|
24
27
|
"GetFiles",
|
|
25
28
|
folder_name=(Optional[str], Field(description="Folder name to get list of the files.", default=None)),
|
|
26
|
-
limit_files=(Optional[int], Field(description="Limit (maximum number) of files to be returned.
|
|
29
|
+
limit_files=(Optional[int], Field(description="Limit (maximum number) of files to be returned."
|
|
30
|
+
"Can be called with synonyms, such as First, Top, etc., "
|
|
31
|
+
"or can be reflected just by a number for example 'Top 10 files'. "
|
|
32
|
+
"Use default value if not specified in a query WITH NO EXTRA "
|
|
33
|
+
"CONFIRMATION FROM A USER", default=100)),
|
|
27
34
|
)
|
|
28
35
|
|
|
29
36
|
ReadDocument = create_model(
|
|
@@ -85,43 +92,100 @@ class SharepointApiWrapper(NonCodeIndexerToolkit):
|
|
|
85
92
|
target_list = self._client.web.lists.get_by_title(list_title)
|
|
86
93
|
self._client.load(target_list)
|
|
87
94
|
self._client.execute_query()
|
|
88
|
-
items = target_list.items.
|
|
89
|
-
logging.info("{0} items from sharepoint loaded successfully.".format(len(items)))
|
|
95
|
+
items = target_list.items.top(limit).get().execute_query()
|
|
96
|
+
logging.info("{0} items from sharepoint loaded successfully via SharePoint REST API.".format(len(items)))
|
|
90
97
|
result = []
|
|
91
98
|
for item in items:
|
|
92
99
|
result.append(item.properties)
|
|
93
100
|
return result
|
|
94
|
-
except Exception as
|
|
95
|
-
logging.
|
|
96
|
-
|
|
101
|
+
except Exception as base_e:
|
|
102
|
+
logging.warning(f"Primary SharePoint REST list read failed: {base_e}. Attempting Graph API fallback.")
|
|
103
|
+
# Attempt Graph API fallback
|
|
104
|
+
try:
|
|
105
|
+
from .authorization_helper import SharepointAuthorizationHelper
|
|
106
|
+
auth_helper = SharepointAuthorizationHelper(
|
|
107
|
+
client_id=self.client_id,
|
|
108
|
+
client_secret=self.client_secret.get_secret_value() if self.client_secret else None,
|
|
109
|
+
tenant="", # optional for graph api (derived inside helper)
|
|
110
|
+
scope="", # optional for graph api
|
|
111
|
+
token_json="", # not needed for client credentials flow here
|
|
112
|
+
)
|
|
113
|
+
graph_items = auth_helper.get_list_items(self.site_url, list_title, limit)
|
|
114
|
+
if graph_items:
|
|
115
|
+
logging.info(f"{len(graph_items)} items from sharepoint loaded successfully via Graph API fallback.")
|
|
116
|
+
return graph_items
|
|
117
|
+
else:
|
|
118
|
+
return ToolException("List appears empty or inaccessible via both REST and Graph APIs.")
|
|
119
|
+
except Exception as graph_e:
|
|
120
|
+
logging.error(f"Graph API fallback failed: {graph_e}")
|
|
121
|
+
return ToolException(f"Cannot read list '{list_title}'. Check list name and permissions: {base_e} | {graph_e}")
|
|
97
122
|
|
|
98
123
|
|
|
99
124
|
def get_files_list(self, folder_name: str = None, limit_files: int = 100):
|
|
100
125
|
""" If folder name is specified, lists all files in this folder under Shared Documents path. If folder name is empty, lists all files under root catalog (Shared Documents). Number of files is limited by limit_files (default is 100)."""
|
|
101
126
|
try:
|
|
127
|
+
# exclude default system libraries like 'Form Templates', 'Site Assets', 'Style Library'
|
|
128
|
+
all_libraries = self._client.web.lists.filter("BaseTemplate eq 101 and Title ne 'Form Templates' and Title ne 'Site Assets' and Title ne 'Style Library'").get().execute_query()
|
|
102
129
|
result = []
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
for
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
130
|
+
if not limit_files:
|
|
131
|
+
limit_files = 100
|
|
132
|
+
#
|
|
133
|
+
site_segments = [seg for seg in self.site_url.strip('/').split('/') if seg][-2:]
|
|
134
|
+
full_path_prefix = '/'.join(site_segments)
|
|
135
|
+
#
|
|
136
|
+
for lib in all_libraries:
|
|
137
|
+
library_type = decode_sharepoint_string(lib.properties["EntityTypeName"])
|
|
138
|
+
target_folder_url = library_type
|
|
139
|
+
if folder_name:
|
|
140
|
+
folder_path = folder_name.strip('/')
|
|
141
|
+
expected_prefix = f'{full_path_prefix}/{library_type}'
|
|
142
|
+
if folder_path.startswith(full_path_prefix):
|
|
143
|
+
if folder_path.startswith(expected_prefix):
|
|
144
|
+
target_folder_url = folder_path.removeprefix(f'{full_path_prefix}/')
|
|
145
|
+
else:
|
|
146
|
+
# ignore full path folder which is not targeted to current library
|
|
147
|
+
continue
|
|
148
|
+
else:
|
|
149
|
+
target_folder_url = f"{library_type}/{folder_name}"
|
|
150
|
+
#
|
|
151
|
+
files = (self._client.web.get_folder_by_server_relative_path(target_folder_url)
|
|
152
|
+
.get_files(True)
|
|
153
|
+
.execute_query())
|
|
154
|
+
#
|
|
155
|
+
for file in files:
|
|
156
|
+
if f"{library_type}/Forms" in file.properties['ServerRelativeUrl']:
|
|
157
|
+
# skip files from system folder "Forms"
|
|
158
|
+
continue
|
|
159
|
+
if len(result) >= limit_files:
|
|
160
|
+
break
|
|
161
|
+
temp_props = {
|
|
162
|
+
'Name': file.properties['Name'],
|
|
163
|
+
'Path': file.properties['ServerRelativeUrl'],
|
|
164
|
+
'Created': file.properties['TimeCreated'],
|
|
165
|
+
'Modified': file.properties['TimeLastModified'],
|
|
166
|
+
'Link': file.properties['LinkingUrl'],
|
|
167
|
+
'id': file.properties['UniqueId']
|
|
168
|
+
}
|
|
169
|
+
result.append(temp_props)
|
|
121
170
|
return result if result else ToolException("Can not get files or folder is empty. Please, double check folder name and read permissions.")
|
|
122
171
|
except Exception as e:
|
|
123
|
-
|
|
124
|
-
|
|
172
|
+
# attempt to get via graph api
|
|
173
|
+
try:
|
|
174
|
+
# attempt to get files via graph api
|
|
175
|
+
from .authorization_helper import SharepointAuthorizationHelper
|
|
176
|
+
auth_helper = SharepointAuthorizationHelper(
|
|
177
|
+
client_id=self.client_id,
|
|
178
|
+
client_secret=self.client_secret.get_secret_value(),
|
|
179
|
+
tenant="", # optional for graph api
|
|
180
|
+
scope="", # optional for graph api
|
|
181
|
+
token_json="", # optional for graph api
|
|
182
|
+
)
|
|
183
|
+
files = auth_helper.get_files_list(self.site_url, folder_name, limit_files)
|
|
184
|
+
return files
|
|
185
|
+
except Exception as graph_e:
|
|
186
|
+
logging.error(f"Failed to load files from sharepoint via base api: {e}")
|
|
187
|
+
logging.error(f"Failed to load files from sharepoint via graph api: {graph_e}")
|
|
188
|
+
return ToolException(f"Can not get files. Please, double check folder name and read permissions: {e} and {graph_e}")
|
|
125
189
|
|
|
126
190
|
def read_file(self, path,
|
|
127
191
|
is_capture_image: bool = False,
|
|
@@ -134,11 +198,28 @@ class SharepointApiWrapper(NonCodeIndexerToolkit):
|
|
|
134
198
|
self._client.load(file).execute_query()
|
|
135
199
|
|
|
136
200
|
file_content = file.read()
|
|
201
|
+
file_name = file.name
|
|
137
202
|
self._client.execute_query()
|
|
138
203
|
except Exception as e:
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
204
|
+
# attempt to get via graph api
|
|
205
|
+
try:
|
|
206
|
+
# attempt to get files via graph api
|
|
207
|
+
from .authorization_helper import SharepointAuthorizationHelper
|
|
208
|
+
auth_helper = SharepointAuthorizationHelper(
|
|
209
|
+
client_id=self.client_id,
|
|
210
|
+
client_secret=self.client_secret.get_secret_value(),
|
|
211
|
+
tenant="", # optional for graph api
|
|
212
|
+
scope="", # optional for graph api
|
|
213
|
+
token_json="", # optional for graph api
|
|
214
|
+
)
|
|
215
|
+
file_content = auth_helper.get_file_content(self.site_url, path)
|
|
216
|
+
file_name = path.split('/')[-1]
|
|
217
|
+
except Exception as graph_e:
|
|
218
|
+
logging.error(f"Failed to load file from SharePoint via base api: {e}. Path: {path}. Please, double check file name and path.")
|
|
219
|
+
logging.error(f"Failed to load file from SharePoint via graph api: {graph_e}. Path: {path}. Please, double check file name and path.")
|
|
220
|
+
return ToolException(f"File not found. Please, check file name and path: {e} and {graph_e}")
|
|
221
|
+
#
|
|
222
|
+
return parse_file_content(file_name=file_name,
|
|
142
223
|
file_content=file_content,
|
|
143
224
|
is_capture_image=is_capture_image,
|
|
144
225
|
page_number=page_number,
|
|
@@ -146,13 +227,60 @@ class SharepointApiWrapper(NonCodeIndexerToolkit):
|
|
|
146
227
|
excel_by_sheets=excel_by_sheets,
|
|
147
228
|
llm=self.llm)
|
|
148
229
|
|
|
230
|
+
def _index_tool_params(self):
|
|
231
|
+
return {
|
|
232
|
+
'limit_files': (Optional[int], Field(
|
|
233
|
+
description="Limit (maximum number) of files to be returned. Can be called with synonyms, "
|
|
234
|
+
"such as First, Top, etc., or can be reflected just by a number for example 'Top 10 files'. "
|
|
235
|
+
"Use default value if not specified in a query WITH NO EXTRA CONFIRMATION FROM A USER",
|
|
236
|
+
default=1000, ge=0)),
|
|
237
|
+
'include_extensions': (Optional[List[str]], Field(
|
|
238
|
+
description="List of file extensions to include when processing: i.e. ['*.png', '*.jpg']. "
|
|
239
|
+
"If empty, all files will be processed (except skip_extensions).",
|
|
240
|
+
default=[])),
|
|
241
|
+
'skip_extensions': (Optional[List[str]], Field(
|
|
242
|
+
description="List of file extensions to skip when processing: i.e. ['*.png', '*.jpg']",
|
|
243
|
+
default=[])),
|
|
244
|
+
'path': (Optional[str], Field(
|
|
245
|
+
description="Folder path. "
|
|
246
|
+
"Accepts either a full server-relative path (e.g., '/sites/SiteName/...') or a relative path. "
|
|
247
|
+
"If a relative path is provided, the search will be performed recursively under 'Shared Documents' and other private libraries.",
|
|
248
|
+
default=None)),
|
|
249
|
+
}
|
|
250
|
+
|
|
149
251
|
def _base_loader(self, **kwargs) -> Generator[Document, None, None]:
|
|
252
|
+
|
|
253
|
+
self._log_tool_event(message="Starting SharePoint files extraction", tool_name="loader")
|
|
150
254
|
try:
|
|
151
|
-
all_files = self.get_files_list()
|
|
255
|
+
all_files = self.get_files_list(kwargs.get('path'), kwargs.get('limit_files', 10000))
|
|
256
|
+
self._log_tool_event(message="List of the files has been extracted", tool_name="loader")
|
|
152
257
|
except Exception as e:
|
|
153
258
|
raise ToolException(f"Unable to extract files: {e}")
|
|
154
259
|
|
|
260
|
+
include_extensions = kwargs.get('include_extensions', [])
|
|
261
|
+
skip_extensions = kwargs.get('skip_extensions', [])
|
|
262
|
+
self._log_tool_event(message=f"Files filtering started. Include extensions: {include_extensions}. "
|
|
263
|
+
f"Skip extensions: {skip_extensions}", tool_name="loader")
|
|
264
|
+
# show the progress of filtering
|
|
265
|
+
total_files = len(all_files) if isinstance(all_files, list) else 0
|
|
266
|
+
filtered_files_count = 0
|
|
155
267
|
for file in all_files:
|
|
268
|
+
filtered_files_count += 1
|
|
269
|
+
if filtered_files_count % 10 == 0 or filtered_files_count == total_files:
|
|
270
|
+
self._log_tool_event(message=f"Files filtering progress: {filtered_files_count}/{total_files}", tool_name="loader")
|
|
271
|
+
file_name = file.get('Name', '')
|
|
272
|
+
|
|
273
|
+
# Check if file should be skipped based on skip_extensions
|
|
274
|
+
if any(re.match(re.escape(pattern).replace(r'\*', '.*') + '$', file_name, re.IGNORECASE)
|
|
275
|
+
for pattern in skip_extensions):
|
|
276
|
+
continue
|
|
277
|
+
|
|
278
|
+
# Check if file should be included based on include_extensions
|
|
279
|
+
# If include_extensions is empty, process all files (that weren't skipped)
|
|
280
|
+
if include_extensions and not (any(re.match(re.escape(pattern).replace(r'\*', '.*') + '$', file_name, re.IGNORECASE)
|
|
281
|
+
for pattern in include_extensions)):
|
|
282
|
+
continue
|
|
283
|
+
|
|
156
284
|
metadata = {
|
|
157
285
|
("updated_on" if k == "Modified" else k): str(v)
|
|
158
286
|
for k, v in file.items()
|
|
@@ -162,20 +290,32 @@ class SharepointApiWrapper(NonCodeIndexerToolkit):
|
|
|
162
290
|
def _extend_data(self, documents: Generator[Document, None, None]):
|
|
163
291
|
for document in documents:
|
|
164
292
|
try:
|
|
165
|
-
document.metadata[
|
|
166
|
-
document.metadata[
|
|
293
|
+
document.metadata[IndexerKeywords.CONTENT_IN_BYTES.value] = self._load_file_content_in_bytes(document.metadata['Path'])
|
|
294
|
+
document.metadata[IndexerKeywords.CONTENT_FILE_NAME.value] = document.metadata['Name']
|
|
167
295
|
yield document
|
|
168
296
|
except Exception as e:
|
|
169
297
|
logging.error(f"Failed while parsing the file '{document.metadata['Path']}': {e}")
|
|
170
298
|
yield document
|
|
171
299
|
|
|
172
300
|
def _load_file_content_in_bytes(self, path):
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
301
|
+
try:
|
|
302
|
+
file = self._client.web.get_file_by_server_relative_path(path)
|
|
303
|
+
self._client.load(file).execute_query()
|
|
304
|
+
file_content = file.read()
|
|
305
|
+
self._client.execute_query()
|
|
306
|
+
#
|
|
307
|
+
return file_content
|
|
308
|
+
except Exception as e:
|
|
309
|
+
# attempt to get via graph api
|
|
310
|
+
from .authorization_helper import SharepointAuthorizationHelper
|
|
311
|
+
auth_helper = SharepointAuthorizationHelper(
|
|
312
|
+
client_id=self.client_id,
|
|
313
|
+
client_secret=self.client_secret.get_secret_value(),
|
|
314
|
+
tenant="", # optional for graph api
|
|
315
|
+
scope="", # optional for graph api
|
|
316
|
+
token_json="", # optional for graph api
|
|
317
|
+
)
|
|
318
|
+
return auth_helper.get_file_content(self.site_url, path)
|
|
179
319
|
|
|
180
320
|
def get_available_tools(self):
|
|
181
321
|
return super().get_available_tools() + [
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
from datetime import datetime, timezone
|
|
2
|
+
from urllib.parse import unquote, urlparse, quote
|
|
2
3
|
|
|
3
4
|
import jwt
|
|
4
5
|
import requests
|
|
6
|
+
from botocore.response import get_response
|
|
7
|
+
|
|
5
8
|
|
|
6
9
|
class SharepointAuthorizationHelper:
|
|
7
10
|
|
|
@@ -54,4 +57,191 @@ class SharepointAuthorizationHelper:
|
|
|
54
57
|
except jwt.ExpiredSignatureError:
|
|
55
58
|
return False
|
|
56
59
|
except jwt.InvalidTokenError:
|
|
57
|
-
return False
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
def _validate_response(self, response, required_field, error_prefix=None):
|
|
63
|
+
if response.status_code != 200:
|
|
64
|
+
raise RuntimeError(f"{error_prefix or 'Request'} failed: {response.status_code} {response.text}")
|
|
65
|
+
json_data = response.json()
|
|
66
|
+
if required_field not in json_data:
|
|
67
|
+
raise KeyError(f"'{required_field}' missing in response")
|
|
68
|
+
return json_data[required_field]
|
|
69
|
+
|
|
70
|
+
def generate_token_and_site_id(self, site_url: str) -> tuple[str, str]:
|
|
71
|
+
try:
|
|
72
|
+
parsed = urlparse(site_url)
|
|
73
|
+
domain = parsed.hostname
|
|
74
|
+
site_path = parsed.path.strip('/')
|
|
75
|
+
if not domain or not site_path:
|
|
76
|
+
raise ValueError(f"site_url missing domain or site path: {site_url}")
|
|
77
|
+
app_name = domain.split('.')[0]
|
|
78
|
+
openid_config_url = f"https://login.microsoftonline.com/{app_name}.onmicrosoft.com/v2.0/.well-known/openid-configuration"
|
|
79
|
+
response = requests.get(openid_config_url)
|
|
80
|
+
token_url = self._validate_response(response, required_field="token_endpoint", error_prefix="OpenID config")
|
|
81
|
+
token_data = {
|
|
82
|
+
"grant_type": "client_credentials",
|
|
83
|
+
"client_id": self.client_id,
|
|
84
|
+
"client_secret": self.client_secret,
|
|
85
|
+
"scope": "https://graph.microsoft.com/.default"
|
|
86
|
+
}
|
|
87
|
+
token_response = requests.post(token_url, data=token_data)
|
|
88
|
+
access_token = self._validate_response(token_response, required_field="access_token", error_prefix="Token request")
|
|
89
|
+
graph_site_url = f"https://graph.microsoft.com/v1.0/sites/{domain}:/{site_path}"
|
|
90
|
+
headers = {"Authorization": f"Bearer {access_token}"}
|
|
91
|
+
site_response = requests.get(graph_site_url, headers=headers)
|
|
92
|
+
site_id = self._validate_response(site_response, required_field="id", error_prefix="Site info")
|
|
93
|
+
return access_token, site_id
|
|
94
|
+
except Exception as e:
|
|
95
|
+
raise RuntimeError(f"Error while obtaining access_token and site_id: {e}")
|
|
96
|
+
|
|
97
|
+
def get_files_list(self, site_url: str, folder_name: str = None, limit_files: int = 100):
|
|
98
|
+
if not site_url or not site_url.startswith("https://"):
|
|
99
|
+
raise ValueError(f"Invalid site_url format: {site_url}")
|
|
100
|
+
if limit_files is not None and (not isinstance(limit_files, int) or limit_files <= 0):
|
|
101
|
+
raise ValueError(f"limit_files must be a positive integer, got: {limit_files}")
|
|
102
|
+
try:
|
|
103
|
+
access_token, site_id = self.generate_token_and_site_id(site_url)
|
|
104
|
+
headers = {"Authorization": f"Bearer {access_token}"}
|
|
105
|
+
drives_url = f"https://graph.microsoft.com/v1.0/sites/{site_id}/drives"
|
|
106
|
+
drives_response = requests.get(drives_url, headers=headers)
|
|
107
|
+
drives = self._validate_response(drives_response, required_field="value", error_prefix="Drives request")
|
|
108
|
+
result = []
|
|
109
|
+
def _recurse_drive(drive_id, drive_path, parent_folder, limit_files):
|
|
110
|
+
# Escape folder_name for URL safety if present
|
|
111
|
+
if parent_folder:
|
|
112
|
+
safe_folder_name = quote(parent_folder.strip('/'), safe="/")
|
|
113
|
+
url = f"https://graph.microsoft.com/v1.0/sites/{site_id}/drives/{drive_id}/root:/{safe_folder_name}:/children?$top={limit_files}"
|
|
114
|
+
else:
|
|
115
|
+
url = f"https://graph.microsoft.com/v1.0/sites/{site_id}/drives/{drive_id}/root/children?$top={limit_files}"
|
|
116
|
+
response = requests.get(url, headers=headers)
|
|
117
|
+
if response.status_code != 200:
|
|
118
|
+
return []
|
|
119
|
+
files_json = response.json()
|
|
120
|
+
if "value" not in files_json:
|
|
121
|
+
return []
|
|
122
|
+
files = []
|
|
123
|
+
for file in files_json["value"]:
|
|
124
|
+
file_name = file.get('name', '')
|
|
125
|
+
# Build full path reflecting nested folders
|
|
126
|
+
if parent_folder:
|
|
127
|
+
full_path = '/' + '/'.join([drive_path.strip('/'), parent_folder.strip('/'), file_name.strip('/')])
|
|
128
|
+
else:
|
|
129
|
+
full_path = '/' + '/'.join([drive_path.strip('/'), file_name.strip('/')])
|
|
130
|
+
temp_props = {
|
|
131
|
+
'Name': file_name,
|
|
132
|
+
'Path': full_path,
|
|
133
|
+
'Created': file.get('createdDateTime'),
|
|
134
|
+
'Modified': file.get('lastModifiedDateTime'),
|
|
135
|
+
'Link': file.get('webUrl'),
|
|
136
|
+
'id': file.get('id')
|
|
137
|
+
}
|
|
138
|
+
if not all([temp_props['Name'], temp_props['Path'], temp_props['id']]):
|
|
139
|
+
continue # skip files with missing required fields
|
|
140
|
+
if 'folder' in file:
|
|
141
|
+
# Recursively extract files from this folder
|
|
142
|
+
inner_folder = parent_folder + '/' + file_name if parent_folder else file_name
|
|
143
|
+
inner_files = _recurse_drive(drive_id, drive_path, inner_folder, limit_files)
|
|
144
|
+
files.extend(inner_files)
|
|
145
|
+
else:
|
|
146
|
+
files.append(temp_props)
|
|
147
|
+
if limit_files is not None and len(result) + len(files) >= limit_files:
|
|
148
|
+
return files[:limit_files - len(result)]
|
|
149
|
+
return files
|
|
150
|
+
#
|
|
151
|
+
site_segments = [seg for seg in site_url.strip('/').split('/') if seg][-2:]
|
|
152
|
+
full_path_prefix = '/'.join(site_segments)
|
|
153
|
+
#
|
|
154
|
+
for drive in drives:
|
|
155
|
+
drive_id = drive.get("id")
|
|
156
|
+
drive_path = unquote(urlparse(drive.get("webUrl")).path) if drive.get("webUrl") else ""
|
|
157
|
+
if not drive_id:
|
|
158
|
+
continue # skip drives without id
|
|
159
|
+
#
|
|
160
|
+
sub_folder = folder_name
|
|
161
|
+
if folder_name:
|
|
162
|
+
folder_path = folder_name.strip('/')
|
|
163
|
+
expected_prefix = drive_path.strip('/')#f'{full_path_prefix}/{library_type}'
|
|
164
|
+
if folder_path.startswith(full_path_prefix):
|
|
165
|
+
if folder_path.startswith(expected_prefix):
|
|
166
|
+
sub_folder = folder_path.removeprefix(f'{expected_prefix}').strip('/')#target_folder_url = folder_path.removeprefix(f'{full_path_prefix}/')
|
|
167
|
+
else:
|
|
168
|
+
# ignore full path folder which is not targeted to current drive
|
|
169
|
+
continue
|
|
170
|
+
#
|
|
171
|
+
files = _recurse_drive(drive_id, drive_path, sub_folder, limit_files)
|
|
172
|
+
result.extend(files)
|
|
173
|
+
if limit_files is not None and len(result) >= limit_files:
|
|
174
|
+
return result[:limit_files]
|
|
175
|
+
return result
|
|
176
|
+
except Exception as e:
|
|
177
|
+
raise RuntimeError(f"Error in get_files_list: {e}")
|
|
178
|
+
|
|
179
|
+
def get_file_content(self, site_url: str, path: str):
|
|
180
|
+
try:
|
|
181
|
+
access_token, site_id = self.generate_token_and_site_id(site_url)
|
|
182
|
+
headers = {"Authorization": f"Bearer {access_token}"}
|
|
183
|
+
drives_url = f"https://graph.microsoft.com/v1.0/sites/{site_id}/drives"
|
|
184
|
+
drives_response = requests.get(drives_url, headers=headers)
|
|
185
|
+
drives = self._validate_response(drives_response, required_field="value", error_prefix="Drives request")
|
|
186
|
+
path = path.strip('/')
|
|
187
|
+
#
|
|
188
|
+
for drive in drives:
|
|
189
|
+
drive_path = unquote(urlparse(drive.get("webUrl")).path).strip('/')
|
|
190
|
+
if not drive_path or not path.startswith(drive_path):
|
|
191
|
+
continue
|
|
192
|
+
drive_id = drive.get("id")
|
|
193
|
+
if not drive_id:
|
|
194
|
+
continue
|
|
195
|
+
path = path.replace(drive_path, '').strip('/')
|
|
196
|
+
safe_path = quote(path, safe="")
|
|
197
|
+
url = f"https://graph.microsoft.com/v1.0/drives/{drive_id}/root:/{safe_path}:/content"
|
|
198
|
+
response = requests.get(url, headers=headers)
|
|
199
|
+
if response.status_code == 200:
|
|
200
|
+
return response.content
|
|
201
|
+
raise RuntimeError(f"File '{path}' not found in any private or shared documents.")
|
|
202
|
+
except Exception as e:
|
|
203
|
+
raise RuntimeError(f"Error in get_file_content: {e}")
|
|
204
|
+
|
|
205
|
+
def get_list_items(self, site_url: str, list_title: str, limit: int = 1000):
|
|
206
|
+
"""Fallback Graph API method to read SharePoint list items by list title.
|
|
207
|
+
|
|
208
|
+
Returns a list of dictionaries representing list item fields.
|
|
209
|
+
"""
|
|
210
|
+
if not site_url or not site_url.startswith("https://"):
|
|
211
|
+
raise ValueError(f"Invalid site_url format: {site_url}")
|
|
212
|
+
try:
|
|
213
|
+
access_token, site_id = self.generate_token_and_site_id(site_url)
|
|
214
|
+
headers = {"Authorization": f"Bearer {access_token}"}
|
|
215
|
+
lists_url = f"https://graph.microsoft.com/v1.0/sites/{site_id}/lists"
|
|
216
|
+
response = requests.get(lists_url, headers=headers)
|
|
217
|
+
if response.status_code != 200:
|
|
218
|
+
raise RuntimeError(f"Lists request failed: {response.status_code} {response.text}")
|
|
219
|
+
lists_json = response.json()
|
|
220
|
+
lists = lists_json.get("value", [])
|
|
221
|
+
target_list = None
|
|
222
|
+
normalized_title = list_title.strip().lower()
|
|
223
|
+
for lst in lists:
|
|
224
|
+
# displayName is the user-visible title. name can differ (internal name)
|
|
225
|
+
display_name = (lst.get("displayName") or lst.get("name") or '').strip().lower()
|
|
226
|
+
if display_name == normalized_title:
|
|
227
|
+
target_list = lst
|
|
228
|
+
break
|
|
229
|
+
if not target_list:
|
|
230
|
+
raise RuntimeError(f"List '{list_title}' not found via Graph API.")
|
|
231
|
+
list_id = target_list.get('id')
|
|
232
|
+
if not list_id:
|
|
233
|
+
raise RuntimeError(f"List '{list_title}' missing id field.")
|
|
234
|
+
items_url = f"https://graph.microsoft.com/v1.0/sites/{site_id}/lists/{list_id}/items?expand=fields&$top={limit}"
|
|
235
|
+
items_response = requests.get(items_url, headers=headers)
|
|
236
|
+
if items_response.status_code != 200:
|
|
237
|
+
raise RuntimeError(f"List items request failed: {items_response.status_code} {items_response.text}")
|
|
238
|
+
items_json = items_response.json()
|
|
239
|
+
values = items_json.get('value', [])
|
|
240
|
+
result = []
|
|
241
|
+
for item in values:
|
|
242
|
+
fields = item.get('fields', {})
|
|
243
|
+
if fields:
|
|
244
|
+
result.append(fields)
|
|
245
|
+
return result
|
|
246
|
+
except Exception as e:
|
|
247
|
+
raise RuntimeError(f"Error in get_list_items: {e}")
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import re
|
|
2
2
|
from io import BytesIO
|
|
3
|
+
from docx import Document
|
|
4
|
+
|
|
3
5
|
|
|
4
6
|
def read_docx_from_bytes(file_content):
|
|
5
7
|
"""Read and return content from a .docx file using a byte stream."""
|
|
@@ -11,4 +13,8 @@ def read_docx_from_bytes(file_content):
|
|
|
11
13
|
return '\n'.join(text)
|
|
12
14
|
except Exception as e:
|
|
13
15
|
print(f"Error reading .docx from bytes: {e}")
|
|
14
|
-
return ""
|
|
16
|
+
return ""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def decode_sharepoint_string(s):
|
|
20
|
+
return re.sub(r'_x([0-9A-Fa-f]{4})_', lambda m: chr(int(m.group(1), 16)), s)
|
|
@@ -2,6 +2,7 @@ from typing import List, Optional, Literal
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
|
|
5
|
+
from ..elitea_base import filter_missconfigured_index_tools
|
|
5
6
|
from ...configurations.slack import SlackConfiguration
|
|
6
7
|
|
|
7
8
|
logger = logging.getLogger(__name__)
|
|
@@ -11,7 +12,7 @@ from pydantic import create_model, BaseModel, Field
|
|
|
11
12
|
from ..base.tool import BaseAction
|
|
12
13
|
|
|
13
14
|
from .api_wrapper import SlackApiWrapper
|
|
14
|
-
from ..utils import
|
|
15
|
+
from ..utils import clean_string, get_max_toolkit_length, check_connection_response
|
|
15
16
|
from slack_sdk.errors import SlackApiError
|
|
16
17
|
from slack_sdk import WebClient
|
|
17
18
|
|
|
@@ -27,12 +28,10 @@ def get_tools(tool):
|
|
|
27
28
|
|
|
28
29
|
class SlackToolkit(BaseToolkit):
|
|
29
30
|
tools: List[BaseTool] = []
|
|
30
|
-
toolkit_max_length: int = 0
|
|
31
31
|
|
|
32
32
|
@staticmethod
|
|
33
33
|
def toolkit_config_schema() -> BaseModel:
|
|
34
34
|
selected_tools = {x['name']: x['args_schema'].schema() for x in SlackApiWrapper.model_construct().get_available_tools()}
|
|
35
|
-
SlackToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
|
|
36
35
|
|
|
37
36
|
@check_connection_response
|
|
38
37
|
def check_connection(self):
|
|
@@ -50,7 +49,7 @@ class SlackToolkit(BaseToolkit):
|
|
|
50
49
|
|
|
51
50
|
model = create_model(
|
|
52
51
|
name,
|
|
53
|
-
slack_configuration=(
|
|
52
|
+
slack_configuration=(SlackConfiguration, Field(default=None, description="Slack configuration",
|
|
54
53
|
json_schema_extra={'configuration_types': ['slack']})),
|
|
55
54
|
selected_tools=(List[Literal[tuple(selected_tools)]],
|
|
56
55
|
Field(default=[], json_schema_extra={'args_schemas': selected_tools})),
|
|
@@ -67,6 +66,7 @@ class SlackToolkit(BaseToolkit):
|
|
|
67
66
|
return model
|
|
68
67
|
|
|
69
68
|
@classmethod
|
|
69
|
+
@filter_missconfigured_index_tools
|
|
70
70
|
def get_toolkit(cls, selected_tools: Optional[List[str]] = None, toolkit_name: Optional[str] = None, **kwargs):
|
|
71
71
|
if selected_tools is None:
|
|
72
72
|
selected_tools = []
|
|
@@ -76,17 +76,21 @@ class SlackToolkit(BaseToolkit):
|
|
|
76
76
|
**kwargs['slack_configuration'],
|
|
77
77
|
}
|
|
78
78
|
slack_api_wrapper = SlackApiWrapper(**wrapper_payload)
|
|
79
|
-
prefix = clean_string(toolkit_name, cls.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
|
|
80
79
|
available_tools = slack_api_wrapper.get_available_tools()
|
|
81
80
|
tools = []
|
|
82
81
|
for tool in available_tools:
|
|
83
82
|
if selected_tools and tool["name"] not in selected_tools:
|
|
84
83
|
continue
|
|
84
|
+
description = f"Slack Tool: {tool['description']}"
|
|
85
|
+
if toolkit_name:
|
|
86
|
+
description = f"{description}\nToolkit: {toolkit_name}"
|
|
87
|
+
description = description[:1000]
|
|
85
88
|
tools.append(BaseAction(
|
|
86
89
|
api_wrapper=slack_api_wrapper,
|
|
87
|
-
name=
|
|
88
|
-
description=
|
|
90
|
+
name=tool["name"],
|
|
91
|
+
description=description,
|
|
89
92
|
args_schema=tool["args_schema"],
|
|
93
|
+
metadata={"toolkit_name": toolkit_name} if toolkit_name else {}
|
|
90
94
|
))
|
|
91
95
|
return cls(tools=tools)
|
|
92
96
|
|