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,10 +1,10 @@
|
|
|
1
|
-
import base64
|
|
2
1
|
import functools
|
|
3
2
|
import json
|
|
4
3
|
import logging
|
|
5
4
|
import re
|
|
6
5
|
from enum import Enum
|
|
7
6
|
from typing import Dict, List, Generator, Optional, Union
|
|
7
|
+
from urllib.parse import urlparse, parse_qs
|
|
8
8
|
|
|
9
9
|
import requests
|
|
10
10
|
from FigmaPy import FigmaPy
|
|
@@ -12,10 +12,43 @@ from langchain_core.documents import Document
|
|
|
12
12
|
from langchain_core.tools import ToolException
|
|
13
13
|
from pydantic import Field, PrivateAttr, create_model, model_validator, SecretStr
|
|
14
14
|
|
|
15
|
-
from ..
|
|
15
|
+
from ..non_code_indexer_toolkit import NonCodeIndexerToolkit
|
|
16
|
+
from ..utils.available_tools_decorator import extend_with_parent_available_tools
|
|
16
17
|
from ..utils.content_parser import load_content_from_bytes
|
|
17
18
|
|
|
18
19
|
GLOBAL_LIMIT = 10000
|
|
20
|
+
GLOBAL_RETAIN = ['id', 'name', 'type', 'document', 'children']
|
|
21
|
+
GLOBAL_REMOVE = []
|
|
22
|
+
GLOBAL_DEPTH_START = 4
|
|
23
|
+
GLOBAL_DEPTH_END = 6
|
|
24
|
+
EXTRA_PARAMS = (
|
|
25
|
+
Optional[Dict[str, Union[str, int, List, None]]],
|
|
26
|
+
Field(
|
|
27
|
+
description=(
|
|
28
|
+
"Additional parameters for customizing response processing:\n"
|
|
29
|
+
"- `limit`: Maximum size of the output in characters.\n"
|
|
30
|
+
"- `regexp`: Regex pattern to filter or clean the output.\n"
|
|
31
|
+
"- `fields_retain`: List of field names to always keep in the output, on levels starting from `depth_start`.\n"
|
|
32
|
+
"- `fields_remove`: List of field names to exclude from the output, unless also present in `fields_retain`.\n"
|
|
33
|
+
"- `depth_start`: The depth in the object hierarchy at which field filtering begins (fields are retained or removed).\n"
|
|
34
|
+
"- `depth_end`: The depth at which all fields are ignored and recursion stops.\n"
|
|
35
|
+
"Use these parameters to control the granularity and size of the returned data, especially for large or deeply nested objects."
|
|
36
|
+
),
|
|
37
|
+
default={
|
|
38
|
+
"limit": GLOBAL_LIMIT, "regexp": None,
|
|
39
|
+
"fields_retain": GLOBAL_RETAIN, "fields_remove": GLOBAL_REMOVE,
|
|
40
|
+
"depth_start": GLOBAL_DEPTH_START, "depth_end": GLOBAL_DEPTH_END,
|
|
41
|
+
},
|
|
42
|
+
examples=[
|
|
43
|
+
{
|
|
44
|
+
"limit": "1000",
|
|
45
|
+
"regexp": r'("strokes"|"fills")\s*:\s*("[^"]*"|[^\s,}\[]+)\s*(?=,|\}|\n)',
|
|
46
|
+
"fields_retain": GLOBAL_RETAIN, "fields_remove": GLOBAL_REMOVE,
|
|
47
|
+
"depth_start": GLOBAL_DEPTH_START, "depth_end": GLOBAL_DEPTH_END,
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
),
|
|
51
|
+
)
|
|
19
52
|
|
|
20
53
|
|
|
21
54
|
class ArgsSchema(Enum):
|
|
@@ -35,19 +68,7 @@ class ArgsSchema(Enum):
|
|
|
35
68
|
examples=["8:6,1:7"],
|
|
36
69
|
),
|
|
37
70
|
),
|
|
38
|
-
extra_params=
|
|
39
|
-
Optional[Dict[str, Union[str, int, None]]],
|
|
40
|
-
Field(
|
|
41
|
-
description="Additional parameters including limit and regex pattern to be removed from response",
|
|
42
|
-
default={"limit": GLOBAL_LIMIT, "regexp": None},
|
|
43
|
-
examples=[
|
|
44
|
-
{
|
|
45
|
-
"limit": "1000",
|
|
46
|
-
"regexp": r'("strokes"|"fills")\s*:\s*("[^"]*"|[^\s,}\[]+)\s*(?=,|\}|\n)',
|
|
47
|
-
}
|
|
48
|
-
],
|
|
49
|
-
),
|
|
50
|
-
),
|
|
71
|
+
extra_params=EXTRA_PARAMS,
|
|
51
72
|
)
|
|
52
73
|
File = create_model(
|
|
53
74
|
"FileNodes",
|
|
@@ -60,25 +81,13 @@ class ArgsSchema(Enum):
|
|
|
60
81
|
),
|
|
61
82
|
geometry=(
|
|
62
83
|
Optional[str],
|
|
63
|
-
Field(description="Sets to 'paths' to export vector data"),
|
|
84
|
+
Field(description="Sets to 'paths' to export vector data", default=None),
|
|
64
85
|
),
|
|
65
86
|
version=(
|
|
66
87
|
Optional[str],
|
|
67
|
-
Field(description="Sets version of file"),
|
|
68
|
-
),
|
|
69
|
-
extra_params=(
|
|
70
|
-
Optional[Dict[str, Union[str, int, None]]],
|
|
71
|
-
Field(
|
|
72
|
-
description="Additional parameters including limit and regex pattern to be removed from response",
|
|
73
|
-
default={"limit": GLOBAL_LIMIT, "regexp": None},
|
|
74
|
-
examples=[
|
|
75
|
-
{
|
|
76
|
-
"limit": "1000",
|
|
77
|
-
"regexp": r'("strokes"|"fills")\s*:\s*("[^"]*"|[^\s,}\[]+)\s*(?=,|\}|\n)',
|
|
78
|
-
}
|
|
79
|
-
],
|
|
80
|
-
),
|
|
88
|
+
Field(description="Sets version of file", default=None),
|
|
81
89
|
),
|
|
90
|
+
extra_params=EXTRA_PARAMS,
|
|
82
91
|
)
|
|
83
92
|
FileKey = create_model(
|
|
84
93
|
"FileKey",
|
|
@@ -89,19 +98,7 @@ class ArgsSchema(Enum):
|
|
|
89
98
|
examples=["Fp24FuzPwH0L74ODSrCnQo"],
|
|
90
99
|
),
|
|
91
100
|
),
|
|
92
|
-
extra_params=
|
|
93
|
-
Optional[Dict[str, Union[str, int, None]]],
|
|
94
|
-
Field(
|
|
95
|
-
description="Additional parameters including limit and regex pattern to be removed from response",
|
|
96
|
-
default={"limit": GLOBAL_LIMIT, "regexp": None},
|
|
97
|
-
examples=[
|
|
98
|
-
{
|
|
99
|
-
"limit": "1000",
|
|
100
|
-
"regexp": r'("strokes"|"fills")\s*:\s*("[^"]*"|[^\s,}\[]+)\s*(?=,|\}|\n)',
|
|
101
|
-
}
|
|
102
|
-
],
|
|
103
|
-
),
|
|
104
|
-
),
|
|
101
|
+
extra_params=EXTRA_PARAMS,
|
|
105
102
|
)
|
|
106
103
|
FileComment = create_model(
|
|
107
104
|
"FileComment",
|
|
@@ -119,22 +116,11 @@ class ArgsSchema(Enum):
|
|
|
119
116
|
client_meta=(
|
|
120
117
|
Optional[dict],
|
|
121
118
|
Field(
|
|
122
|
-
description="Positioning information of the comment (Vector, FrameOffset, Region, FrameOffsetRegion)"
|
|
123
|
-
|
|
124
|
-
),
|
|
125
|
-
extra_params=(
|
|
126
|
-
Optional[Dict[str, Union[str, int, None]]],
|
|
127
|
-
Field(
|
|
128
|
-
description="Additional parameters including limit and regex pattern to be removed from response",
|
|
129
|
-
default={"limit": GLOBAL_LIMIT, "regexp": None},
|
|
130
|
-
examples=[
|
|
131
|
-
{
|
|
132
|
-
"limit": "1000",
|
|
133
|
-
"regexp": r'("strokes"|"fills")\s*:\s*("[^"]*"|[^\s,}\[]+)\s*(?=,|\}|\n)',
|
|
134
|
-
}
|
|
135
|
-
],
|
|
119
|
+
description="Positioning information of the comment (Vector, FrameOffset, Region, FrameOffsetRegion)",
|
|
120
|
+
default=None,
|
|
136
121
|
),
|
|
137
122
|
),
|
|
123
|
+
extra_params=EXTRA_PARAMS,
|
|
138
124
|
)
|
|
139
125
|
FileImages = create_model(
|
|
140
126
|
"FileImages",
|
|
@@ -146,40 +132,30 @@ class ArgsSchema(Enum):
|
|
|
146
132
|
),
|
|
147
133
|
),
|
|
148
134
|
ids=(
|
|
149
|
-
str,
|
|
135
|
+
Optional[str],
|
|
150
136
|
Field(
|
|
151
137
|
description="Specifies id of file images separated by comma",
|
|
152
138
|
examples=["8:6,1:7"],
|
|
139
|
+
default="0:0",
|
|
153
140
|
),
|
|
154
141
|
),
|
|
155
142
|
scale=(
|
|
156
143
|
Optional[str],
|
|
157
|
-
Field(description="A number between 0.01 and 4, the image scaling factor"),
|
|
144
|
+
Field(description="A number between 0.01 and 4, the image scaling factor", default=None),
|
|
158
145
|
),
|
|
159
146
|
format=(
|
|
160
147
|
Optional[str],
|
|
161
148
|
Field(
|
|
162
149
|
description="A string enum for the image output format",
|
|
163
150
|
examples=["jpg", "png", "svg", "pdf"],
|
|
151
|
+
default=None,
|
|
164
152
|
),
|
|
165
153
|
),
|
|
166
154
|
version=(
|
|
167
155
|
Optional[str],
|
|
168
|
-
Field(description="A specific version ID to use"),
|
|
169
|
-
),
|
|
170
|
-
extra_params=(
|
|
171
|
-
Optional[Dict[str, Union[str, int, None]]],
|
|
172
|
-
Field(
|
|
173
|
-
description="Additional parameters including limit and regex pattern to be removed from response",
|
|
174
|
-
default={"limit": GLOBAL_LIMIT, "regexp": None},
|
|
175
|
-
examples=[
|
|
176
|
-
{
|
|
177
|
-
"limit": "1000",
|
|
178
|
-
"regexp": r'("strokes"|"fills")\s*:\s*("[^"]*"|[^\s,}\[]+)\s*(?=,|\}|\n)',
|
|
179
|
-
}
|
|
180
|
-
],
|
|
181
|
-
),
|
|
156
|
+
Field(description="A specific version ID to use", default=None),
|
|
182
157
|
),
|
|
158
|
+
extra_params=EXTRA_PARAMS,
|
|
183
159
|
)
|
|
184
160
|
TeamProjects = create_model(
|
|
185
161
|
"TeamProjects",
|
|
@@ -190,19 +166,7 @@ class ArgsSchema(Enum):
|
|
|
190
166
|
examples=["1101853299713989222"],
|
|
191
167
|
),
|
|
192
168
|
),
|
|
193
|
-
extra_params=
|
|
194
|
-
Optional[Dict[str, Union[str, int, None]]],
|
|
195
|
-
Field(
|
|
196
|
-
description="Additional parameters including limit and regex pattern to be removed from response",
|
|
197
|
-
default={"limit": GLOBAL_LIMIT, "regexp": None},
|
|
198
|
-
examples=[
|
|
199
|
-
{
|
|
200
|
-
"limit": "1000",
|
|
201
|
-
"regexp": r'("strokes"|"fills")\s*:\s*("[^"]*"|[^\s,}\[]+)\s*(?=,|\}|\n)',
|
|
202
|
-
}
|
|
203
|
-
],
|
|
204
|
-
),
|
|
205
|
-
),
|
|
169
|
+
extra_params=EXTRA_PARAMS,
|
|
206
170
|
)
|
|
207
171
|
ProjectFiles = create_model(
|
|
208
172
|
"ProjectFiles",
|
|
@@ -213,91 +177,253 @@ class ArgsSchema(Enum):
|
|
|
213
177
|
examples=["55391681"],
|
|
214
178
|
),
|
|
215
179
|
),
|
|
216
|
-
extra_params=
|
|
217
|
-
Optional[Dict[str, Union[str, int, None]]],
|
|
218
|
-
Field(
|
|
219
|
-
description="Additional parameters including limit and regex pattern to be removed from response",
|
|
220
|
-
default={"limit": GLOBAL_LIMIT, "regexp": None},
|
|
221
|
-
examples=[
|
|
222
|
-
{
|
|
223
|
-
"limit": "1000",
|
|
224
|
-
"regexp": r'("strokes"|"fills")\s*:\s*("[^"]*"|[^\s,}\[]+)\s*(?=,|\}|\n)',
|
|
225
|
-
}
|
|
226
|
-
],
|
|
227
|
-
),
|
|
228
|
-
),
|
|
180
|
+
extra_params=EXTRA_PARAMS,
|
|
229
181
|
)
|
|
230
182
|
|
|
231
183
|
|
|
232
|
-
class FigmaApiWrapper(
|
|
184
|
+
class FigmaApiWrapper(NonCodeIndexerToolkit):
|
|
233
185
|
token: Optional[SecretStr] = Field(default=None)
|
|
234
186
|
oauth2: Optional[SecretStr] = Field(default=None)
|
|
235
187
|
global_limit: Optional[int] = Field(default=GLOBAL_LIMIT)
|
|
236
188
|
global_regexp: Optional[str] = Field(default=None)
|
|
189
|
+
global_fields_retain: Optional[List[str]] = GLOBAL_RETAIN
|
|
190
|
+
global_fields_remove: Optional[List[str]] = GLOBAL_REMOVE
|
|
191
|
+
global_depth_start: Optional[int] = GLOBAL_DEPTH_START
|
|
192
|
+
global_depth_end: Optional[int] = GLOBAL_DEPTH_END
|
|
237
193
|
_client: Optional[FigmaPy] = PrivateAttr()
|
|
238
194
|
|
|
239
|
-
def _base_loader(
|
|
240
|
-
|
|
241
|
-
|
|
195
|
+
def _base_loader(
|
|
196
|
+
self,
|
|
197
|
+
file_or_page_url: Optional[str] = None,
|
|
198
|
+
project_id: Optional[str] = None,
|
|
199
|
+
file_keys_include: Optional[List[str]] = None,
|
|
200
|
+
file_keys_exclude: Optional[List[str]] = None,
|
|
201
|
+
node_ids_include: Optional[List[str]] = None,
|
|
202
|
+
node_ids_exclude: Optional[List[str]] = None,
|
|
203
|
+
node_types_include: Optional[List[str]] = None,
|
|
204
|
+
node_types_exclude: Optional[List[str]] = None,
|
|
205
|
+
**kwargs
|
|
206
|
+
) -> Generator[Document, None, None]:
|
|
207
|
+
if file_or_page_url:
|
|
208
|
+
# If URL is provided and valid, extract and override file_keys_include and node_ids_include
|
|
209
|
+
try:
|
|
210
|
+
parsed = urlparse(file_or_page_url)
|
|
211
|
+
path_parts = parsed.path.strip('/').split('/')
|
|
212
|
+
|
|
213
|
+
# Check if the path matches the expected format
|
|
214
|
+
if len(path_parts) >= 2 and path_parts[0] == 'design':
|
|
215
|
+
file_keys_include = [path_parts[1]]
|
|
216
|
+
if len(path_parts) == 3:
|
|
217
|
+
# To ensure url structure matches Figma's format with 3 path segments
|
|
218
|
+
query_params = parse_qs(parsed.query)
|
|
219
|
+
if "node-id" in query_params:
|
|
220
|
+
node_ids_include = query_params.get('node-id', [])
|
|
221
|
+
except Exception as e:
|
|
222
|
+
raise ToolException(
|
|
223
|
+
f"Unexpected error while processing Figma url {file_or_page_url}: {e}")
|
|
224
|
+
|
|
225
|
+
# If both include and exclude are provided, use only include
|
|
226
|
+
if file_keys_include:
|
|
227
|
+
self._log_tool_event(f"Loading files: {file_keys_include}")
|
|
228
|
+
for file_key in file_keys_include:
|
|
229
|
+
self._log_tool_event(f"Loading file `{file_key}`")
|
|
230
|
+
file = self._client.get_file(file_key, geometry='depth=1') # fetch only top-level structure (only pages without inner components)
|
|
231
|
+
if not file:
|
|
232
|
+
raise ToolException(f"Unexpected error while retrieving file {file_key}. Please try specifying the node-id of an inner page.")
|
|
233
|
+
metadata = {
|
|
234
|
+
'id': file_key,
|
|
235
|
+
'file_key': file_key,
|
|
236
|
+
'name': file.name,
|
|
237
|
+
'updated_on': file.last_modified,
|
|
238
|
+
'figma_pages_include': node_ids_include or [],
|
|
239
|
+
'figma_pages_exclude': node_ids_exclude or [],
|
|
240
|
+
'figma_nodes_include': node_types_include or [],
|
|
241
|
+
'figma_nodes_exclude': node_types_exclude or []
|
|
242
|
+
}
|
|
243
|
+
yield Document(page_content=json.dumps(metadata), metadata=metadata)
|
|
244
|
+
elif project_id:
|
|
245
|
+
self._log_tool_event(f"Loading project files from project `{project_id}`")
|
|
242
246
|
files = json.loads(self.get_project_files(project_id)).get('files', [])
|
|
243
247
|
for file in files:
|
|
248
|
+
if file_keys_exclude and file.get('key', '') in file_keys_exclude:
|
|
249
|
+
continue
|
|
244
250
|
yield Document(page_content=json.dumps(file), metadata={
|
|
245
251
|
'id': file.get('key', ''),
|
|
246
252
|
'file_key': file.get('key', ''),
|
|
247
253
|
'name': file.get('name', ''),
|
|
248
|
-
'updated_on': file.get('last_modified', '')
|
|
254
|
+
'updated_on': file.get('last_modified', ''),
|
|
255
|
+
'figma_pages_include': node_ids_include or [],
|
|
256
|
+
'figma_pages_exclude': node_ids_exclude or [],
|
|
257
|
+
'figma_nodes_include': node_types_include or [],
|
|
258
|
+
'figma_nodes_exclude': node_types_exclude or []
|
|
249
259
|
})
|
|
250
|
-
elif
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
+
elif file_keys_exclude or node_ids_exclude:
|
|
261
|
+
raise ValueError("Excludes without parent (project_id or file_keys_include) do not make sense.")
|
|
262
|
+
else:
|
|
263
|
+
raise ValueError("You must provide at least project_id or file_keys_include.")
|
|
264
|
+
|
|
265
|
+
def has_image_representation(self, node):
|
|
266
|
+
node_type = node.get('type', '').lower()
|
|
267
|
+
default_images_types = [
|
|
268
|
+
'image', 'canvas', 'frame', 'vector', 'table', 'slice', 'sticky', 'shape_with_text', 'connector'
|
|
269
|
+
]
|
|
270
|
+
# filter nodes of type which has image representation
|
|
271
|
+
# or rectangles with image as background
|
|
272
|
+
if (node_type in default_images_types
|
|
273
|
+
or (node_type == 'rectangle' and 'fills' in node and any(
|
|
274
|
+
fill.get('type') == 'IMAGE' for fill in node['fills'] if isinstance(fill, dict)))):
|
|
275
|
+
return True
|
|
276
|
+
return False
|
|
277
|
+
|
|
278
|
+
def get_texts_recursive(self, node):
|
|
279
|
+
texts = []
|
|
280
|
+
node_type = node.get('type', '').lower()
|
|
281
|
+
if node_type == 'text':
|
|
282
|
+
texts.append(node.get('characters', ''))
|
|
283
|
+
if 'children' in node:
|
|
284
|
+
for child in node['children']:
|
|
285
|
+
texts.extend(self.get_texts_recursive(child))
|
|
286
|
+
return texts
|
|
287
|
+
|
|
288
|
+
def _load_pages(self, document: Document):
|
|
289
|
+
file_key = document.metadata.get('id', '')
|
|
290
|
+
node_ids_include = document.metadata.pop('figma_pages_include', [])
|
|
291
|
+
node_ids_exclude = document.metadata.pop('figma_pages_exclude', [])
|
|
292
|
+
self._log_tool_event(f"Included pages: {node_ids_include}. Excluded pages: {node_ids_exclude}.")
|
|
293
|
+
if node_ids_include:
|
|
294
|
+
# try to fetch only specified pages/nodes in one request
|
|
295
|
+
file = self._get_file_nodes(file_key,','.join(node_ids_include)) # attempt to fetch only specified pages/nodes in one request
|
|
296
|
+
if file:
|
|
297
|
+
return [node['document'] for node in file.get('nodes', {}).values() if 'document' in node]
|
|
298
|
+
else:
|
|
299
|
+
#
|
|
300
|
+
file = self._client.get_file(file_key)
|
|
301
|
+
if file:
|
|
302
|
+
figma_pages = file.document.get('children', [])
|
|
303
|
+
return [node for node in figma_pages if ('id' in node and node['id'].replace(':', '-') not in node_ids_exclude)]
|
|
304
|
+
# fallback to loading all pages and filtering them one by one
|
|
305
|
+
file = self._client.get_file(file_key, geometry='depth=1')
|
|
306
|
+
if not file:
|
|
307
|
+
raise ToolException(
|
|
308
|
+
f"Unexpected error while retrieving file {file_key}. Please try specifying the node-id of an inner page.")
|
|
309
|
+
figma_pages_raw = file.document.get('children', [])
|
|
310
|
+
# extract pages one by one
|
|
311
|
+
if node_ids_include:
|
|
312
|
+
return [self._get_file_nodes(file_key, node_id) for node_id in node_ids_include]
|
|
313
|
+
else:
|
|
314
|
+
# return [self._get_file_nodes(file_key, page["id"]) for page in figma_pages_raw if ('id' in page and page['id'].replace(':', '-') not in node_ids_exclude)]
|
|
315
|
+
result = []
|
|
316
|
+
for page in figma_pages_raw:
|
|
317
|
+
if 'id' in page and page['id'].replace(':', '-') not in node_ids_exclude:
|
|
318
|
+
page_res = self._get_file_nodes(file_key, page["id"]).get('nodes', {}).get(page["id"], {}).get("document", {})
|
|
319
|
+
result.append(page_res)
|
|
320
|
+
return result
|
|
260
321
|
|
|
261
322
|
def _process_document(self, document: Document) -> Generator[Document, None, None]:
|
|
262
323
|
file_key = document.metadata.get('id', '')
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
324
|
+
self._log_tool_event(f"Loading details (images) for `{file_key}`")
|
|
325
|
+
figma_pages = self._load_pages(document)
|
|
326
|
+
node_types_include = [t.strip().lower() for t in document.metadata.pop('figma_nodes_include', [])]
|
|
327
|
+
node_types_exclude = [t.strip().lower() for t in document.metadata.pop('figma_nodes_exclude', [])]
|
|
328
|
+
|
|
329
|
+
image_nodes = []
|
|
330
|
+
text_nodes = {}
|
|
331
|
+
for page in figma_pages:
|
|
332
|
+
for node in page.get('children', []):
|
|
333
|
+
# filter by node_type if specified any include or exclude
|
|
334
|
+
node_type = node.get('type', '').lower()
|
|
335
|
+
include = node_types_include and node_type in node_types_include
|
|
336
|
+
exclude = node_types_exclude and node_type not in node_types_exclude
|
|
337
|
+
no_filter = not node_types_include and not node_types_exclude
|
|
338
|
+
|
|
339
|
+
if include or exclude or no_filter:
|
|
340
|
+
node_id = node.get('id')
|
|
341
|
+
if node_id:
|
|
342
|
+
if self.has_image_representation(node):
|
|
343
|
+
image_nodes.append(node['id'])
|
|
344
|
+
else:
|
|
345
|
+
text_nodes[node['id']] = self.get_texts_recursive(node)
|
|
346
|
+
# process image nodes
|
|
347
|
+
if image_nodes:
|
|
348
|
+
file_images = self._client.get_file_images(file_key, image_nodes)
|
|
349
|
+
images = self._client.get_file_images(file_key, image_nodes).images or {} if file_images else {}
|
|
350
|
+
total_images = len(images)
|
|
351
|
+
if total_images == 0:
|
|
352
|
+
logging.info(f"No images found for file {file_key}.")
|
|
353
|
+
return
|
|
354
|
+
progress_step = max(1, total_images // 10)
|
|
355
|
+
for idx, (node_id, image_url) in enumerate(images.items(), 1):
|
|
356
|
+
if not image_url:
|
|
357
|
+
logging.warning(f"Image URL not found for node_id {node_id} in file {file_key}. Skipping.")
|
|
358
|
+
continue
|
|
359
|
+
response = requests.get(image_url)
|
|
360
|
+
if response.status_code == 200:
|
|
361
|
+
content_type = response.headers.get('Content-Type', '')
|
|
362
|
+
if 'text/html' not in content_type.lower():
|
|
363
|
+
extension = f".{content_type.split('/')[-1]}" if content_type.startswith('image') else '.txt'
|
|
364
|
+
page_content = load_content_from_bytes(
|
|
365
|
+
file_content=response.content,
|
|
366
|
+
extension=extension, llm=self.llm)
|
|
367
|
+
yield Document(
|
|
368
|
+
page_content=page_content,
|
|
369
|
+
metadata={
|
|
370
|
+
'id': node_id,
|
|
371
|
+
'updated_on': document.metadata.get('updated_on', ''),
|
|
372
|
+
'file_key': file_key,
|
|
373
|
+
'node_id': node_id,
|
|
374
|
+
'image_url': image_url,
|
|
375
|
+
'type': 'image'
|
|
376
|
+
}
|
|
377
|
+
)
|
|
378
|
+
if idx % progress_step == 0 or idx == total_images:
|
|
379
|
+
percent = int((idx / total_images) * 100)
|
|
380
|
+
msg = f"Processed {idx}/{total_images} images ({percent}%) for file {file_key}."
|
|
381
|
+
logging.info(msg)
|
|
382
|
+
self._log_tool_event(msg)
|
|
383
|
+
# process text nodes
|
|
384
|
+
if text_nodes:
|
|
385
|
+
for node_id, texts in text_nodes.items():
|
|
386
|
+
if texts:
|
|
284
387
|
yield Document(
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
388
|
+
page_content="\n".join(texts),
|
|
389
|
+
metadata={
|
|
390
|
+
'id': node_id,
|
|
391
|
+
'updated_on': document.metadata.get('updated_on', ''),
|
|
392
|
+
'file_key': file_key,
|
|
393
|
+
'node_id': node_id,
|
|
394
|
+
'type': 'text'
|
|
395
|
+
}
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
def _remove_metadata_keys(self):
|
|
399
|
+
return super()._remove_metadata_keys() + ['figma_pages_include', 'figma_pages_exclude', 'figma_nodes_include', 'figma_nodes_exclude']
|
|
292
400
|
|
|
293
401
|
def _index_tool_params(self):
|
|
294
402
|
"""Return the parameters for indexing data."""
|
|
295
403
|
return {
|
|
404
|
+
"file_or_page_url": (Optional[str], Field(
|
|
405
|
+
description="Url to file or page to index: i.e. https://www.figma.com/design/[YOUR_FILE_KEY]/Login-page-designs?node-id=[YOUR_PAGE_ID]",
|
|
406
|
+
default=None)),
|
|
296
407
|
"project_id": (Optional[str], Field(
|
|
297
|
-
description="ID of the project to list files from: i.e.
|
|
408
|
+
description="ID of the project to list files from: i.e. 55391681",
|
|
409
|
+
default=None)),
|
|
410
|
+
'file_keys_include': (Optional[List[str]], Field(
|
|
411
|
+
description="List of file keys to include in index if project_id is not provided: i.e. ['Fp24FuzPwH0L74ODSrCnQo', 'jmhAr6q78dJoMRqt48zisY']",
|
|
412
|
+
default=None)),
|
|
413
|
+
'file_keys_exclude': (Optional[List[str]], Field(
|
|
414
|
+
description="List of file keys to exclude from index. It is applied only if project_id is provided and file_keys_include is not provided: i.e. ['Fp24FuzPwH0L74ODSrCnQo', 'jmhAr6q78dJoMRqt48zisY']",
|
|
415
|
+
default=None)),
|
|
416
|
+
'node_ids_include': (Optional[List[str]], Field(
|
|
417
|
+
description="List of top-level nodes (pages) in file to include in index. It is node-id from figma url: i.e. ['123-56', '7651-9230'].",
|
|
298
418
|
default=None)),
|
|
299
|
-
'
|
|
300
|
-
description="List of file
|
|
419
|
+
'node_ids_exclude': (Optional[List[str]], Field(
|
|
420
|
+
description="List of top-level nodes (pages) in file to exclude from index. It is applied only if node_ids_include is not provided. It is node-id from figma url: i.e. ['Fp24FuzPwH0L74ODSrCnQo', 'jmhAr6q78dJoMRqt48zisY']",
|
|
421
|
+
default=None)),
|
|
422
|
+
'node_types_include': (Optional[List[str]], Field(
|
|
423
|
+
description="List type of nodes to include in index: i.e. ['FRAME', 'COMPONENT', 'RECTANGLE', 'COMPONENT_SET', 'INSTANCE', 'VECTOR', ...].",
|
|
424
|
+
default=None)),
|
|
425
|
+
'node_types_exclude': (Optional[List[str]], Field(
|
|
426
|
+
description="List type of nodes to exclude from index. It is applied only if node_types_include is not provided: i.e. ['FRAME', 'COMPONENT', 'RECTANGLE', 'COMPONENT_SET', 'INSTANCE', 'VECTOR', ...]",
|
|
301
427
|
default=None))
|
|
302
428
|
}
|
|
303
429
|
|
|
@@ -328,6 +454,11 @@ class FigmaApiWrapper(BaseVectorStoreToolApiWrapper):
|
|
|
328
454
|
logging.error(msg)
|
|
329
455
|
raise ToolException(msg)
|
|
330
456
|
|
|
457
|
+
@model_validator(mode='before')
|
|
458
|
+
@classmethod
|
|
459
|
+
def check_before(cls, values):
|
|
460
|
+
return super().validate_toolkit(values)
|
|
461
|
+
|
|
331
462
|
@model_validator(mode="after")
|
|
332
463
|
@classmethod
|
|
333
464
|
def validate_toolkit(cls, values):
|
|
@@ -395,6 +526,53 @@ class FigmaApiWrapper(BaseVectorStoreToolApiWrapper):
|
|
|
395
526
|
}
|
|
396
527
|
return obj
|
|
397
528
|
|
|
529
|
+
def process_fields(obj, fields_retain=None, fields_remove=None, depth_start=1, depth_end=2, depth=1):
|
|
530
|
+
"""
|
|
531
|
+
Reduces a nested dictionary or list by retaining or removing specified fields at certain depths.
|
|
532
|
+
|
|
533
|
+
- At each level, starting from `depth_start`, only fields in `fields_retain` are kept; fields in `fields_remove` are excluded unless also retained.
|
|
534
|
+
- Recursion stops at `depth_end`, ignoring all fields at or beyond this depth.
|
|
535
|
+
- Tracks which fields were retained and removed during processing.
|
|
536
|
+
- Returns a JSON string of the reduced object, plus lists of retained and removed fields.
|
|
537
|
+
"""
|
|
538
|
+
fields_retain = set(fields_retain or [])
|
|
539
|
+
fields_remove = set(fields_remove or []) - fields_retain # fields in remove have lower priority than in retain
|
|
540
|
+
|
|
541
|
+
retained = set()
|
|
542
|
+
removed = set()
|
|
543
|
+
|
|
544
|
+
def _process(o, d):
|
|
545
|
+
if depth_end is not None and d >= depth_end:
|
|
546
|
+
return None # Ignore keys at or beyond cut_depth
|
|
547
|
+
if isinstance(o, dict):
|
|
548
|
+
result = {}
|
|
549
|
+
for k, v in o.items():
|
|
550
|
+
if k in fields_remove:
|
|
551
|
+
removed.add(k)
|
|
552
|
+
continue
|
|
553
|
+
if d >= depth_start:
|
|
554
|
+
if k in fields_retain:
|
|
555
|
+
retained.add(k)
|
|
556
|
+
result[k] = _process(v, d + 1) # process recursively
|
|
557
|
+
else:
|
|
558
|
+
# else: skip keys not in retain/default/to_process
|
|
559
|
+
removed.add(k) # remember skipped keys
|
|
560
|
+
else:
|
|
561
|
+
# retained.add(k) # remember retained keys
|
|
562
|
+
result[k] = _process(v, d + 1)
|
|
563
|
+
return result
|
|
564
|
+
elif isinstance(o, list):
|
|
565
|
+
return [_process(item, d + 1) for item in o]
|
|
566
|
+
else:
|
|
567
|
+
return o
|
|
568
|
+
|
|
569
|
+
new_obj = _process(obj, depth)
|
|
570
|
+
return {
|
|
571
|
+
"result": json.dumps(new_obj),
|
|
572
|
+
"retained_fields": list(retained),
|
|
573
|
+
"removed_fields": list(removed)
|
|
574
|
+
}
|
|
575
|
+
|
|
398
576
|
def fix_trailing_commas(json_string):
|
|
399
577
|
json_string = re.sub(r",\s*,+", ",", json_string)
|
|
400
578
|
json_string = re.sub(r",\s*([\]}])", r"\1", json_string)
|
|
@@ -404,10 +582,12 @@ class FigmaApiWrapper(BaseVectorStoreToolApiWrapper):
|
|
|
404
582
|
@functools.wraps(func)
|
|
405
583
|
def wrapper(self, *args, **kwargs):
|
|
406
584
|
extra_params = kwargs.pop("extra_params", {})
|
|
407
|
-
|
|
408
585
|
limit = extra_params.get("limit", self.global_limit)
|
|
409
586
|
regexp = extra_params.get("regexp", self.global_regexp)
|
|
410
|
-
|
|
587
|
+
fields_retain = extra_params.get("fields_retain", self.global_fields_retain)
|
|
588
|
+
fields_remove = extra_params.get("fields_remove", self.global_fields_remove)
|
|
589
|
+
depth_start = extra_params.get("depth_start", self.global_depth_start)
|
|
590
|
+
depth_end = extra_params.get("depth_end", self.global_depth_end)
|
|
411
591
|
try:
|
|
412
592
|
limit = int(limit)
|
|
413
593
|
result = func(self, *args, **kwargs)
|
|
@@ -417,13 +597,26 @@ class FigmaApiWrapper(BaseVectorStoreToolApiWrapper):
|
|
|
417
597
|
return ToolException(
|
|
418
598
|
"Response result is empty. Check your input parameters or credentials"
|
|
419
599
|
)
|
|
420
|
-
|
|
421
600
|
if isinstance(result, (dict, list)):
|
|
422
|
-
|
|
423
|
-
|
|
601
|
+
raw_result = result
|
|
602
|
+
processed_result = simplified_dict(raw_result)
|
|
603
|
+
raw_str_result = json.dumps(processed_result)
|
|
604
|
+
str_result = raw_str_result
|
|
605
|
+
if regexp:
|
|
606
|
+
regexp = re.compile(regexp)
|
|
607
|
+
str_result = re.sub(regexp, "", raw_str_result)
|
|
608
|
+
str_result = fix_trailing_commas(str_result)
|
|
609
|
+
if len(str_result) > limit:
|
|
610
|
+
reduced = process_fields(raw_result, fields_retain=fields_retain, fields_remove=fields_remove, depth_start=depth_start, depth_end=depth_end)
|
|
611
|
+
note = (f"Size of the output exceeds limit {limit}. Data reducing has been applied. "
|
|
612
|
+
f"Starting from the depth_start = {depth_start} the following object fields were removed: {reduced['removed_fields']}. "
|
|
613
|
+
f"The following fields were retained: {reduced['retained_fields']}. "
|
|
614
|
+
f"Starting from depth_end = {depth_end} all fields were ignored. "
|
|
615
|
+
f"You can adjust fields_retain, fields_remove, depth_start, depth_end, limit and regexp parameters to get more precise output")
|
|
616
|
+
return f"## NOTE:\n{note}.\n## Result: {reduced['result']}"[:limit]
|
|
617
|
+
return str_result
|
|
424
618
|
else:
|
|
425
619
|
result = json.dumps(result)
|
|
426
|
-
|
|
427
620
|
if regexp:
|
|
428
621
|
regexp = re.compile(regexp)
|
|
429
622
|
result = re.sub(regexp, "", result)
|
|
@@ -444,6 +637,12 @@ class FigmaApiWrapper(BaseVectorStoreToolApiWrapper):
|
|
|
444
637
|
f"files/{file_key}/nodes?ids={str(ids)}", method="get"
|
|
445
638
|
)
|
|
446
639
|
|
|
640
|
+
def _get_file_nodes(self, file_key: str, ids: str, **kwargs):
|
|
641
|
+
"""Reads a specified file nodes by field key from Figma."""
|
|
642
|
+
return self._client.api_request(
|
|
643
|
+
f"files/{file_key}/nodes?ids={str(ids)}", method="get"
|
|
644
|
+
)
|
|
645
|
+
|
|
447
646
|
@process_output
|
|
448
647
|
def get_file(
|
|
449
648
|
self,
|
|
@@ -488,7 +687,7 @@ class FigmaApiWrapper(BaseVectorStoreToolApiWrapper):
|
|
|
488
687
|
def get_file_images(
|
|
489
688
|
self,
|
|
490
689
|
file_key: str,
|
|
491
|
-
ids: str = "0:0",
|
|
690
|
+
ids: Optional[str] = "0:0",
|
|
492
691
|
scale: Optional[str] = None,
|
|
493
692
|
format: Optional[str] = None,
|
|
494
693
|
version: Optional[str] = None,
|
|
@@ -510,7 +709,7 @@ class FigmaApiWrapper(BaseVectorStoreToolApiWrapper):
|
|
|
510
709
|
"""Retrieves all files for a specified project ID from Figma."""
|
|
511
710
|
return self._client.get_project_files(project_id)
|
|
512
711
|
|
|
513
|
-
@
|
|
712
|
+
@extend_with_parent_available_tools
|
|
514
713
|
def get_available_tools(self):
|
|
515
714
|
return [
|
|
516
715
|
{
|