alita-sdk 0.3.462__py3-none-any.whl → 0.3.627__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- alita_sdk/cli/agent/__init__.py +5 -0
- alita_sdk/cli/agent/default.py +258 -0
- alita_sdk/cli/agent_executor.py +15 -3
- alita_sdk/cli/agent_loader.py +56 -8
- alita_sdk/cli/agent_ui.py +93 -31
- alita_sdk/cli/agents.py +2274 -230
- alita_sdk/cli/callbacks.py +96 -25
- alita_sdk/cli/cli.py +10 -1
- alita_sdk/cli/config.py +162 -9
- alita_sdk/cli/context/__init__.py +30 -0
- alita_sdk/cli/context/cleanup.py +198 -0
- alita_sdk/cli/context/manager.py +731 -0
- alita_sdk/cli/context/message.py +285 -0
- alita_sdk/cli/context/strategies.py +289 -0
- alita_sdk/cli/context/token_estimation.py +127 -0
- alita_sdk/cli/input_handler.py +419 -0
- alita_sdk/cli/inventory.py +1073 -0
- alita_sdk/cli/testcases/__init__.py +94 -0
- alita_sdk/cli/testcases/data_generation.py +119 -0
- alita_sdk/cli/testcases/discovery.py +96 -0
- alita_sdk/cli/testcases/executor.py +84 -0
- alita_sdk/cli/testcases/logger.py +85 -0
- alita_sdk/cli/testcases/parser.py +172 -0
- alita_sdk/cli/testcases/prompts.py +91 -0
- alita_sdk/cli/testcases/reporting.py +125 -0
- alita_sdk/cli/testcases/setup.py +108 -0
- alita_sdk/cli/testcases/test_runner.py +282 -0
- alita_sdk/cli/testcases/utils.py +39 -0
- alita_sdk/cli/testcases/validation.py +90 -0
- alita_sdk/cli/testcases/workflow.py +196 -0
- alita_sdk/cli/toolkit.py +14 -17
- alita_sdk/cli/toolkit_loader.py +35 -5
- alita_sdk/cli/tools/__init__.py +36 -2
- alita_sdk/cli/tools/approval.py +224 -0
- alita_sdk/cli/tools/filesystem.py +910 -64
- alita_sdk/cli/tools/planning.py +389 -0
- alita_sdk/cli/tools/terminal.py +414 -0
- alita_sdk/community/__init__.py +72 -12
- alita_sdk/community/inventory/__init__.py +236 -0
- alita_sdk/community/inventory/config.py +257 -0
- alita_sdk/community/inventory/enrichment.py +2137 -0
- alita_sdk/community/inventory/extractors.py +1469 -0
- alita_sdk/community/inventory/ingestion.py +3172 -0
- alita_sdk/community/inventory/knowledge_graph.py +1457 -0
- alita_sdk/community/inventory/parsers/__init__.py +218 -0
- alita_sdk/community/inventory/parsers/base.py +295 -0
- alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
- alita_sdk/community/inventory/parsers/go_parser.py +851 -0
- alita_sdk/community/inventory/parsers/html_parser.py +389 -0
- alita_sdk/community/inventory/parsers/java_parser.py +593 -0
- alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
- alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
- alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
- alita_sdk/community/inventory/parsers/python_parser.py +604 -0
- alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
- alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
- alita_sdk/community/inventory/parsers/text_parser.py +322 -0
- alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
- alita_sdk/community/inventory/patterns/__init__.py +61 -0
- alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
- alita_sdk/community/inventory/patterns/loader.py +348 -0
- alita_sdk/community/inventory/patterns/registry.py +198 -0
- alita_sdk/community/inventory/presets.py +535 -0
- alita_sdk/community/inventory/retrieval.py +1403 -0
- alita_sdk/community/inventory/toolkit.py +173 -0
- alita_sdk/community/inventory/toolkit_utils.py +176 -0
- alita_sdk/community/inventory/visualize.py +1370 -0
- alita_sdk/configurations/__init__.py +1 -1
- alita_sdk/configurations/ado.py +141 -20
- alita_sdk/configurations/bitbucket.py +0 -3
- alita_sdk/configurations/confluence.py +76 -42
- alita_sdk/configurations/figma.py +76 -0
- alita_sdk/configurations/gitlab.py +17 -5
- alita_sdk/configurations/openapi.py +329 -0
- alita_sdk/configurations/qtest.py +72 -1
- alita_sdk/configurations/report_portal.py +96 -0
- alita_sdk/configurations/sharepoint.py +148 -0
- alita_sdk/configurations/testio.py +83 -0
- alita_sdk/runtime/clients/artifact.py +3 -3
- alita_sdk/runtime/clients/client.py +353 -48
- alita_sdk/runtime/clients/sandbox_client.py +0 -21
- alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
- alita_sdk/runtime/langchain/assistant.py +123 -26
- alita_sdk/runtime/langchain/constants.py +642 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +6 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
- alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
- alita_sdk/runtime/langchain/document_loaders/constants.py +12 -7
- alita_sdk/runtime/langchain/langraph_agent.py +279 -73
- alita_sdk/runtime/langchain/utils.py +82 -15
- alita_sdk/runtime/llms/preloaded.py +2 -6
- alita_sdk/runtime/skills/__init__.py +91 -0
- alita_sdk/runtime/skills/callbacks.py +498 -0
- alita_sdk/runtime/skills/discovery.py +540 -0
- alita_sdk/runtime/skills/executor.py +610 -0
- alita_sdk/runtime/skills/input_builder.py +371 -0
- alita_sdk/runtime/skills/models.py +330 -0
- alita_sdk/runtime/skills/registry.py +355 -0
- alita_sdk/runtime/skills/skill_runner.py +330 -0
- alita_sdk/runtime/toolkits/__init__.py +7 -0
- alita_sdk/runtime/toolkits/application.py +21 -9
- alita_sdk/runtime/toolkits/artifact.py +15 -5
- alita_sdk/runtime/toolkits/datasource.py +13 -6
- alita_sdk/runtime/toolkits/mcp.py +139 -251
- alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
- alita_sdk/runtime/toolkits/planning.py +178 -0
- alita_sdk/runtime/toolkits/skill_router.py +238 -0
- alita_sdk/runtime/toolkits/subgraph.py +251 -6
- alita_sdk/runtime/toolkits/tools.py +238 -32
- alita_sdk/runtime/toolkits/vectorstore.py +11 -5
- alita_sdk/runtime/tools/__init__.py +3 -1
- alita_sdk/runtime/tools/application.py +20 -6
- alita_sdk/runtime/tools/artifact.py +511 -28
- alita_sdk/runtime/tools/data_analysis.py +183 -0
- alita_sdk/runtime/tools/function.py +43 -15
- alita_sdk/runtime/tools/image_generation.py +50 -44
- alita_sdk/runtime/tools/llm.py +852 -67
- alita_sdk/runtime/tools/loop.py +3 -1
- alita_sdk/runtime/tools/loop_output.py +3 -1
- alita_sdk/runtime/tools/mcp_remote_tool.py +25 -10
- alita_sdk/runtime/tools/mcp_server_tool.py +7 -6
- alita_sdk/runtime/tools/planning/__init__.py +36 -0
- alita_sdk/runtime/tools/planning/models.py +246 -0
- alita_sdk/runtime/tools/planning/wrapper.py +607 -0
- alita_sdk/runtime/tools/router.py +2 -4
- alita_sdk/runtime/tools/sandbox.py +9 -6
- alita_sdk/runtime/tools/skill_router.py +776 -0
- alita_sdk/runtime/tools/tool.py +3 -1
- alita_sdk/runtime/tools/vectorstore.py +7 -2
- alita_sdk/runtime/tools/vectorstore_base.py +51 -11
- alita_sdk/runtime/utils/AlitaCallback.py +137 -21
- alita_sdk/runtime/utils/constants.py +5 -1
- alita_sdk/runtime/utils/mcp_client.py +492 -0
- alita_sdk/runtime/utils/mcp_oauth.py +202 -5
- alita_sdk/runtime/utils/mcp_sse_client.py +36 -7
- alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
- alita_sdk/runtime/utils/serialization.py +155 -0
- alita_sdk/runtime/utils/streamlit.py +6 -10
- alita_sdk/runtime/utils/toolkit_utils.py +16 -5
- alita_sdk/runtime/utils/utils.py +36 -0
- alita_sdk/tools/__init__.py +113 -29
- alita_sdk/tools/ado/repos/__init__.py +51 -33
- alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
- alita_sdk/tools/ado/test_plan/__init__.py +25 -9
- alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
- alita_sdk/tools/ado/utils.py +1 -18
- alita_sdk/tools/ado/wiki/__init__.py +25 -8
- alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
- alita_sdk/tools/ado/work_item/__init__.py +26 -9
- alita_sdk/tools/ado/work_item/ado_wrapper.py +56 -3
- alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
- alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
- alita_sdk/tools/aws/delta_lake/tool.py +5 -1
- alita_sdk/tools/azure_ai/search/__init__.py +11 -8
- alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
- alita_sdk/tools/base/tool.py +5 -1
- alita_sdk/tools/base_indexer_toolkit.py +170 -45
- alita_sdk/tools/bitbucket/__init__.py +17 -12
- alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
- alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
- alita_sdk/tools/browser/__init__.py +5 -4
- alita_sdk/tools/carrier/__init__.py +5 -6
- alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
- alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
- alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
- alita_sdk/tools/chunkers/__init__.py +3 -1
- alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
- alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
- alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
- alita_sdk/tools/chunkers/universal_chunker.py +270 -0
- alita_sdk/tools/cloud/aws/__init__.py +10 -7
- alita_sdk/tools/cloud/azure/__init__.py +10 -7
- alita_sdk/tools/cloud/gcp/__init__.py +10 -7
- alita_sdk/tools/cloud/k8s/__init__.py +10 -7
- alita_sdk/tools/code/linter/__init__.py +10 -8
- alita_sdk/tools/code/loaders/codesearcher.py +3 -2
- alita_sdk/tools/code/sonar/__init__.py +10 -7
- alita_sdk/tools/code_indexer_toolkit.py +73 -23
- alita_sdk/tools/confluence/__init__.py +21 -15
- alita_sdk/tools/confluence/api_wrapper.py +78 -23
- alita_sdk/tools/confluence/loader.py +4 -2
- alita_sdk/tools/custom_open_api/__init__.py +12 -5
- alita_sdk/tools/elastic/__init__.py +11 -8
- alita_sdk/tools/elitea_base.py +493 -30
- alita_sdk/tools/figma/__init__.py +58 -11
- alita_sdk/tools/figma/api_wrapper.py +1235 -143
- alita_sdk/tools/figma/figma_client.py +73 -0
- alita_sdk/tools/figma/toon_tools.py +2748 -0
- alita_sdk/tools/github/__init__.py +13 -14
- alita_sdk/tools/github/github_client.py +224 -100
- alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
- alita_sdk/tools/github/schemas.py +14 -5
- alita_sdk/tools/github/tool.py +5 -1
- alita_sdk/tools/github/tool_prompts.py +9 -22
- alita_sdk/tools/gitlab/__init__.py +15 -11
- alita_sdk/tools/gitlab/api_wrapper.py +207 -41
- alita_sdk/tools/gitlab_org/__init__.py +10 -8
- alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
- alita_sdk/tools/google/bigquery/__init__.py +13 -12
- alita_sdk/tools/google/bigquery/tool.py +5 -1
- alita_sdk/tools/google_places/__init__.py +10 -8
- alita_sdk/tools/google_places/api_wrapper.py +1 -1
- alita_sdk/tools/jira/__init__.py +17 -11
- alita_sdk/tools/jira/api_wrapper.py +91 -40
- alita_sdk/tools/keycloak/__init__.py +11 -8
- alita_sdk/tools/localgit/__init__.py +9 -3
- alita_sdk/tools/localgit/local_git.py +62 -54
- alita_sdk/tools/localgit/tool.py +5 -1
- alita_sdk/tools/memory/__init__.py +11 -3
- alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
- alita_sdk/tools/ocr/__init__.py +11 -8
- alita_sdk/tools/openapi/__init__.py +490 -114
- alita_sdk/tools/openapi/api_wrapper.py +1368 -0
- alita_sdk/tools/openapi/tool.py +20 -0
- alita_sdk/tools/pandas/__init__.py +20 -12
- alita_sdk/tools/pandas/api_wrapper.py +38 -25
- alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
- alita_sdk/tools/postman/__init__.py +11 -11
- alita_sdk/tools/pptx/__init__.py +10 -9
- alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
- alita_sdk/tools/qtest/__init__.py +30 -10
- alita_sdk/tools/qtest/api_wrapper.py +430 -13
- alita_sdk/tools/rally/__init__.py +10 -8
- alita_sdk/tools/rally/api_wrapper.py +1 -1
- alita_sdk/tools/report_portal/__init__.py +12 -9
- alita_sdk/tools/salesforce/__init__.py +10 -9
- alita_sdk/tools/servicenow/__init__.py +17 -14
- alita_sdk/tools/servicenow/api_wrapper.py +1 -1
- alita_sdk/tools/sharepoint/__init__.py +10 -8
- alita_sdk/tools/sharepoint/api_wrapper.py +4 -4
- alita_sdk/tools/slack/__init__.py +10 -8
- alita_sdk/tools/slack/api_wrapper.py +2 -2
- alita_sdk/tools/sql/__init__.py +11 -9
- alita_sdk/tools/testio/__init__.py +10 -8
- alita_sdk/tools/testrail/__init__.py +11 -8
- alita_sdk/tools/testrail/api_wrapper.py +1 -1
- alita_sdk/tools/utils/__init__.py +9 -4
- alita_sdk/tools/utils/content_parser.py +77 -3
- alita_sdk/tools/utils/text_operations.py +410 -0
- alita_sdk/tools/utils/tool_prompts.py +79 -0
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +17 -13
- alita_sdk/tools/xray/__init__.py +12 -9
- alita_sdk/tools/yagmail/__init__.py +9 -3
- alita_sdk/tools/zephyr/__init__.py +9 -7
- alita_sdk/tools/zephyr_enterprise/__init__.py +11 -8
- alita_sdk/tools/zephyr_essential/__init__.py +10 -8
- alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
- alita_sdk/tools/zephyr_essential/client.py +2 -2
- alita_sdk/tools/zephyr_scale/__init__.py +11 -9
- alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
- alita_sdk/tools/zephyr_squad/__init__.py +10 -8
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +147 -7
- alita_sdk-0.3.627.dist-info/RECORD +468 -0
- alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
- alita_sdk-0.3.462.dist-info/RECORD +0 -384
- alita_sdk-0.3.462.dist-info/entry_points.txt +0 -2
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/top_level.txt +0 -0
|
@@ -25,7 +25,6 @@ def _safe_import_configuration(
|
|
|
25
25
|
_safe_import_configuration('github', 'github', 'GithubConfiguration')
|
|
26
26
|
_safe_import_configuration('pgvector', 'pgvector', 'PgVectorConfiguration')
|
|
27
27
|
_safe_import_configuration('ado', 'ado', 'AdoConfiguration')
|
|
28
|
-
_safe_import_configuration('ado_repos', 'ado', 'AdoReposConfiguration')
|
|
29
28
|
_safe_import_configuration('gitlab', 'gitlab', 'GitlabConfiguration')
|
|
30
29
|
_safe_import_configuration('qtest', 'qtest', 'QtestConfiguration')
|
|
31
30
|
_safe_import_configuration('bitbucket', 'bitbucket', 'BitbucketConfiguration')
|
|
@@ -53,6 +52,7 @@ _safe_import_configuration('sharepoint', 'sharepoint', 'SharepointConfiguration'
|
|
|
53
52
|
_safe_import_configuration('carrier', 'carrier', 'CarrierConfiguration')
|
|
54
53
|
_safe_import_configuration('report_portal', 'report_portal', 'ReportPortalConfiguration')
|
|
55
54
|
_safe_import_configuration('testio', 'testio', 'TestIOConfiguration')
|
|
55
|
+
_safe_import_configuration('openapi', 'openapi', 'OpenApiConfiguration')
|
|
56
56
|
|
|
57
57
|
# Log import summary
|
|
58
58
|
available_count = len(AVAILABLE_CONFIGURATIONS)
|
alita_sdk/configurations/ado.py
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import re
|
|
1
2
|
from typing import Optional
|
|
3
|
+
from urllib.parse import quote
|
|
2
4
|
|
|
5
|
+
import requests
|
|
3
6
|
from pydantic import BaseModel, ConfigDict, Field, SecretStr
|
|
4
7
|
|
|
5
8
|
|
|
@@ -19,25 +22,143 @@ class AdoConfiguration(BaseModel):
|
|
|
19
22
|
project: str = Field(description="ADO project")
|
|
20
23
|
token: Optional[SecretStr] = Field(description="ADO Token")
|
|
21
24
|
|
|
25
|
+
@staticmethod
|
|
26
|
+
def check_connection(settings: dict) -> str | None:
|
|
27
|
+
"""
|
|
28
|
+
Test the connection to Azure DevOps API.
|
|
22
29
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
json_schema_extra={
|
|
26
|
-
"metadata": {
|
|
27
|
-
"label": "ADO repos",
|
|
28
|
-
"icon_url": "ado-repos-icon.svg",
|
|
29
|
-
"section": "credentials",
|
|
30
|
-
"type": "ado_repos",
|
|
31
|
-
"categories": ["code repositories"],
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
)
|
|
35
|
-
repository_id: str = Field(description="ADO repository ID")
|
|
30
|
+
Args:
|
|
31
|
+
settings: Dictionary containing 'organization_url', 'project', and optionally 'token'
|
|
36
32
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
33
|
+
Returns:
|
|
34
|
+
None if connection is successful, error message string otherwise
|
|
35
|
+
"""
|
|
36
|
+
organization_url = settings.get("organization_url")
|
|
37
|
+
if organization_url is None or organization_url == "":
|
|
38
|
+
if organization_url == "":
|
|
39
|
+
return "Organization URL cannot be empty"
|
|
40
|
+
return "Organization URL is required"
|
|
41
|
+
|
|
42
|
+
# Validate organization URL format
|
|
43
|
+
if not isinstance(organization_url, str):
|
|
44
|
+
return "Organization URL must be a string"
|
|
45
|
+
|
|
46
|
+
organization_url = organization_url.strip()
|
|
47
|
+
if not organization_url:
|
|
48
|
+
return "Organization URL cannot be empty"
|
|
49
|
+
|
|
50
|
+
if not organization_url.startswith(("http://", "https://")):
|
|
51
|
+
return "Organization URL must start with http:// or https://"
|
|
52
|
+
|
|
53
|
+
# Remove trailing slash for consistency
|
|
54
|
+
organization_url = organization_url.rstrip("/")
|
|
55
|
+
|
|
56
|
+
project = settings.get("project")
|
|
57
|
+
if project is None or project == "":
|
|
58
|
+
if project == "":
|
|
59
|
+
return "Project cannot be empty"
|
|
60
|
+
return "Project is required"
|
|
61
|
+
|
|
62
|
+
# Validate project format
|
|
63
|
+
if not isinstance(project, str):
|
|
64
|
+
return "Project must be a string"
|
|
65
|
+
|
|
66
|
+
project = project.strip()
|
|
67
|
+
if not project:
|
|
68
|
+
return "Project cannot be empty"
|
|
69
|
+
|
|
70
|
+
token = settings.get("token")
|
|
71
|
+
|
|
72
|
+
# Extract secret value if it's a SecretStr
|
|
73
|
+
if token is not None and hasattr(token, "get_secret_value"):
|
|
74
|
+
token = token.get_secret_value()
|
|
75
|
+
|
|
76
|
+
# Validate token if provided
|
|
77
|
+
if token is not None and (not token or not token.strip()):
|
|
78
|
+
return "Token cannot be empty if provided"
|
|
79
|
+
|
|
80
|
+
# NOTE on verification strategy:
|
|
81
|
+
# - Project endpoints can work anonymously for public projects.
|
|
82
|
+
# That makes them a weak signal for detecting a bad/expired token.
|
|
83
|
+
# - If a token is provided, first validate it against a profile endpoint
|
|
84
|
+
# that requires authentication, then check project access.
|
|
85
|
+
|
|
86
|
+
# Strictly require a canonical organization URL so we can build reliable API URLs.
|
|
87
|
+
# Supported formats:
|
|
88
|
+
# - https://dev.azure.com/<org>
|
|
89
|
+
# - https://<org>.visualstudio.com
|
|
90
|
+
org_name: str | None = None
|
|
91
|
+
org_url_kind: str | None = None # 'dev.azure.com' | '*.visualstudio.com'
|
|
92
|
+
m = re.match(r"^https?://dev\.azure\.com/(?P<org>[^/]+)$", organization_url, flags=re.IGNORECASE)
|
|
93
|
+
if m:
|
|
94
|
+
org_name = m.group('org')
|
|
95
|
+
org_url_kind = 'dev.azure.com'
|
|
96
|
+
else:
|
|
97
|
+
m = re.match(r"^https?://(?P<org>[^/.]+)\.visualstudio\.com$", organization_url, flags=re.IGNORECASE)
|
|
98
|
+
if m:
|
|
99
|
+
org_name = m.group('org')
|
|
100
|
+
org_url_kind = '*.visualstudio.com'
|
|
101
|
+
|
|
102
|
+
if org_name is None:
|
|
103
|
+
return (
|
|
104
|
+
"Organization URL format is invalid. Use 'https://dev.azure.com/<org>' "
|
|
105
|
+
"(recommended) or 'https://<org>.visualstudio.com'."
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
project_encoded = quote(project, safe="")
|
|
109
|
+
project_url = f"{organization_url}/_apis/projects/{project_encoded}?api-version=7.0"
|
|
110
|
+
# Auth-required endpoint to validate PAT (works regardless of project visibility)
|
|
111
|
+
if org_url_kind == 'dev.azure.com':
|
|
112
|
+
profile_url = f"https://vssps.dev.azure.com/{org_name}/_apis/profile/profiles/me?api-version=7.1-preview.3"
|
|
113
|
+
else:
|
|
114
|
+
# For legacy org URLs, use the matching vssps host
|
|
115
|
+
profile_url = f"https://{org_name}.vssps.visualstudio.com/_apis/profile/profiles/me?api-version=7.1-preview.3"
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
headers = {}
|
|
119
|
+
if token:
|
|
120
|
+
# Use Basic Auth with PAT token (username can be empty)
|
|
121
|
+
from requests.auth import HTTPBasicAuth
|
|
122
|
+
auth = HTTPBasicAuth("", token)
|
|
123
|
+
|
|
124
|
+
# 1) Validate token first (strong signal)
|
|
125
|
+
profile_resp = requests.get(profile_url, auth=auth, timeout=10)
|
|
126
|
+
if profile_resp.status_code == 200:
|
|
127
|
+
pass
|
|
128
|
+
elif profile_resp.status_code == 401:
|
|
129
|
+
return "Invalid or expired token (PAT). Please generate a new token and try again."
|
|
130
|
+
elif profile_resp.status_code == 403:
|
|
131
|
+
return "Token is valid but lacks permission to access profile. Check PAT scopes/permissions."
|
|
132
|
+
elif profile_resp.status_code == 404:
|
|
133
|
+
return "Organization not found. Verify the Organization URL."
|
|
134
|
+
else:
|
|
135
|
+
return f"Token validation failed (HTTP {profile_resp.status_code})."
|
|
136
|
+
|
|
137
|
+
# 2) Validate project access
|
|
138
|
+
response = requests.get(project_url, auth=auth, timeout=10)
|
|
139
|
+
else:
|
|
140
|
+
# Try without authentication (works for public projects)
|
|
141
|
+
response = requests.get(project_url, headers=headers, timeout=10)
|
|
142
|
+
|
|
143
|
+
if response.status_code == 200:
|
|
144
|
+
return None # Connection successful
|
|
145
|
+
elif response.status_code == 401:
|
|
146
|
+
if token:
|
|
147
|
+
return "Not authorized. Token may be invalid for this organization or expired."
|
|
148
|
+
else:
|
|
149
|
+
return "Authentication required - project may be private"
|
|
150
|
+
elif response.status_code == 403:
|
|
151
|
+
return "Access forbidden - token may lack required permissions for this project"
|
|
152
|
+
elif response.status_code == 404:
|
|
153
|
+
return f"Project '{project}' not found or not accessible. Check project name and organization URL."
|
|
154
|
+
else:
|
|
155
|
+
return f"Connection failed (HTTP {response.status_code})."
|
|
156
|
+
|
|
157
|
+
except requests.exceptions.Timeout:
|
|
158
|
+
return "Connection timeout - Azure DevOps did not respond within 10 seconds"
|
|
159
|
+
except requests.exceptions.ConnectionError:
|
|
160
|
+
return "Connection error - unable to reach Azure DevOps. Check the Organization URL and your network."
|
|
161
|
+
except requests.exceptions.RequestException as e:
|
|
162
|
+
return f"Request failed: {str(e)}"
|
|
163
|
+
except Exception:
|
|
164
|
+
return "Unexpected error during Azure DevOps connection check"
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
|
+
from urllib.parse import urlparse, urlunparse
|
|
2
3
|
|
|
4
|
+
from atlassian import Confluence
|
|
3
5
|
from pydantic import BaseModel, ConfigDict, Field, SecretStr
|
|
4
6
|
|
|
5
7
|
|
|
@@ -55,25 +57,53 @@ class ConfluenceConfiguration(BaseModel):
|
|
|
55
57
|
from requests.auth import HTTPBasicAuth
|
|
56
58
|
|
|
57
59
|
# Validate base_url
|
|
58
|
-
|
|
60
|
+
base_url_input = settings.get("base_url", "")
|
|
61
|
+
base_url = base_url_input.strip() if isinstance(base_url_input, str) else ""
|
|
59
62
|
if not base_url:
|
|
60
63
|
return "Confluence URL is required"
|
|
61
64
|
|
|
62
|
-
# Normalize URL - remove trailing slashes
|
|
63
|
-
base_url = base_url.rstrip("/")
|
|
64
|
-
|
|
65
65
|
# Basic URL validation
|
|
66
66
|
if not base_url.startswith(("http://", "https://")):
|
|
67
67
|
return "Confluence URL must start with http:// or https://"
|
|
68
|
+
|
|
69
|
+
# Normalize URL - remove trailing slashes
|
|
70
|
+
base_url = base_url.rstrip("/")
|
|
71
|
+
|
|
72
|
+
# Build candidate base URLs.
|
|
73
|
+
# Confluence Cloud REST API is typically under /wiki. Users often paste
|
|
74
|
+
# https://<site>.atlassian.net and shouldn't be forced to know about /wiki.
|
|
75
|
+
parsed = urlparse(base_url)
|
|
76
|
+
host = (parsed.hostname or "").lower()
|
|
77
|
+
path = parsed.path or ""
|
|
78
|
+
|
|
79
|
+
def with_wiki_path(url: str) -> str:
|
|
80
|
+
p = urlparse(url)
|
|
81
|
+
# Keep existing path if it already starts with /wiki
|
|
82
|
+
if (p.path or "").startswith("/wiki"):
|
|
83
|
+
return url
|
|
84
|
+
# Append /wiki, preserving any existing path (rare but safe)
|
|
85
|
+
new_path = (p.path or "") + "/wiki"
|
|
86
|
+
return urlunparse(p._replace(path=new_path.rstrip("/")))
|
|
87
|
+
|
|
88
|
+
candidate_base_urls: list[str] = []
|
|
89
|
+
if host.endswith(".atlassian.net"):
|
|
90
|
+
# For Atlassian Cloud, prefer the /wiki variant first
|
|
91
|
+
candidate_base_urls.append(with_wiki_path(base_url))
|
|
92
|
+
candidate_base_urls.append(base_url)
|
|
93
|
+
# De-duplicate while preserving order
|
|
94
|
+
candidate_base_urls = list(dict.fromkeys(candidate_base_urls))
|
|
68
95
|
|
|
69
96
|
# Check authentication credentials
|
|
70
97
|
username = settings.get("username")
|
|
71
98
|
api_key = settings.get("api_key")
|
|
72
99
|
token = settings.get("token")
|
|
73
100
|
|
|
101
|
+
api_key_value = api_key.get_secret_value() if hasattr(api_key, 'get_secret_value') else api_key
|
|
102
|
+
token_value = token.get_secret_value() if hasattr(token, 'get_secret_value') else token
|
|
103
|
+
|
|
74
104
|
# Validate authentication - at least one method must be provided
|
|
75
|
-
has_basic_auth = bool(username and
|
|
76
|
-
has_token = bool(
|
|
105
|
+
has_basic_auth = bool(username and api_key_value and str(api_key_value).strip())
|
|
106
|
+
has_token = bool(token_value and str(token_value).strip())
|
|
77
107
|
|
|
78
108
|
# Determine authentication method
|
|
79
109
|
auth_headers = {}
|
|
@@ -81,52 +111,56 @@ class ConfluenceConfiguration(BaseModel):
|
|
|
81
111
|
|
|
82
112
|
if has_token:
|
|
83
113
|
# Bearer token authentication
|
|
84
|
-
token_value = token.get_secret_value() if hasattr(token, 'get_secret_value') else token
|
|
85
114
|
auth_headers["Authorization"] = f"Bearer {token_value}"
|
|
86
115
|
elif has_basic_auth:
|
|
87
116
|
# Basic authentication
|
|
88
|
-
api_key_value = api_key.get_secret_value() if hasattr(api_key, 'get_secret_value') else api_key
|
|
89
117
|
auth = HTTPBasicAuth(username, api_key_value)
|
|
90
118
|
else:
|
|
91
119
|
return "Authentication required: provide either token or both username and api_key"
|
|
92
120
|
|
|
93
|
-
# Test connection using /rest/api/user/current endpoint
|
|
94
|
-
# This endpoint returns current user info and validates authentication
|
|
95
|
-
test_url = f"{base_url}/rest/api/user/current"
|
|
96
|
-
|
|
97
121
|
try:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if
|
|
112
|
-
return
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
+
# Test connection using /rest/api/user/current endpoint
|
|
123
|
+
# This endpoint returns current user info and validates authentication
|
|
124
|
+
last_status = None
|
|
125
|
+
for candidate_base in candidate_base_urls:
|
|
126
|
+
test_url = f"{candidate_base}/rest/api/user/current"
|
|
127
|
+
response = requests.get(
|
|
128
|
+
test_url,
|
|
129
|
+
auth=auth,
|
|
130
|
+
headers=auth_headers,
|
|
131
|
+
timeout=10
|
|
132
|
+
)
|
|
133
|
+
last_status = response.status_code
|
|
134
|
+
|
|
135
|
+
if response.status_code == 200:
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
# If we get 404 on the first candidate, try the next one
|
|
139
|
+
if response.status_code == 404:
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
if response.status_code == 401:
|
|
143
|
+
return "Invalid credentials (401) - check token or username/api_key"
|
|
144
|
+
if response.status_code == 403:
|
|
145
|
+
return "Access forbidden (403) - credentials lack Confluence permissions"
|
|
146
|
+
if response.status_code == 429:
|
|
147
|
+
return "Rate limited (429) - please try again later"
|
|
148
|
+
if 500 <= response.status_code <= 599:
|
|
149
|
+
return f"Confluence service error (HTTP {response.status_code})"
|
|
150
|
+
return f"Confluence request failed (HTTP {response.status_code})"
|
|
151
|
+
|
|
152
|
+
# All candidates returned 404
|
|
153
|
+
return "Confluence API endpoint not found (404) - verify the Confluence URL"
|
|
122
154
|
|
|
123
155
|
except requests.exceptions.SSLError as e:
|
|
124
|
-
|
|
156
|
+
if 'Hostname mismatch' in str(e):
|
|
157
|
+
return "SSL error - hostname mismatch. Verify the Confluence URL"
|
|
158
|
+
return "SSL error - certificate verification failed"
|
|
125
159
|
except requests.exceptions.ConnectionError:
|
|
126
|
-
return
|
|
160
|
+
return "Connection error - unable to reach Confluence. Check URL and network."
|
|
127
161
|
except requests.exceptions.Timeout:
|
|
128
|
-
return
|
|
162
|
+
return "Connection timeout - Confluence did not respond within 10 seconds. Check URL and network."
|
|
129
163
|
except requests.exceptions.RequestException as e:
|
|
130
|
-
return f"
|
|
131
|
-
except Exception
|
|
132
|
-
return
|
|
164
|
+
return f"Request failed: {str(e)}"
|
|
165
|
+
except Exception:
|
|
166
|
+
return "Unexpected error during Confluence connection check"
|
|
@@ -1,8 +1,30 @@
|
|
|
1
|
+
from json import JSONDecodeError
|
|
1
2
|
from typing import Optional
|
|
2
3
|
|
|
4
|
+
import requests
|
|
3
5
|
from pydantic import BaseModel, ConfigDict, Field, SecretStr
|
|
4
6
|
|
|
5
7
|
|
|
8
|
+
def _parse_error_response(response: requests.Response) -> Optional[str]:
|
|
9
|
+
"""
|
|
10
|
+
Parse error response from Figma API to extract detailed error message.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
response: Response object from requests
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
Detailed error message if found, None otherwise
|
|
17
|
+
"""
|
|
18
|
+
try:
|
|
19
|
+
json_response = response.json()
|
|
20
|
+
error = json_response.get("err") or json_response.get("error")
|
|
21
|
+
if error and 'Invalid token' in str(error):
|
|
22
|
+
return "Invalid token. Please verify the Figma token and try again."
|
|
23
|
+
except (JSONDecodeError, KeyError, AttributeError):
|
|
24
|
+
pass
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
|
|
6
28
|
class FigmaConfiguration(BaseModel):
|
|
7
29
|
model_config = ConfigDict(
|
|
8
30
|
json_schema_extra={
|
|
@@ -28,3 +50,57 @@ class FigmaConfiguration(BaseModel):
|
|
|
28
50
|
}
|
|
29
51
|
)
|
|
30
52
|
token: Optional[SecretStr] = Field(description="Figma Token", json_schema_extra={"secret": True}, default=None)
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def check_connection(settings: dict) -> str | None:
|
|
56
|
+
"""
|
|
57
|
+
Test the connection to Figma API.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
settings: Dictionary containing 'token' (required)
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
None if connection is successful, error message string otherwise
|
|
64
|
+
"""
|
|
65
|
+
token = settings.get("token")
|
|
66
|
+
if token is None:
|
|
67
|
+
return "Token is required"
|
|
68
|
+
|
|
69
|
+
# Extract secret value if it's a SecretStr
|
|
70
|
+
if hasattr(token, "get_secret_value"):
|
|
71
|
+
token = token.get_secret_value()
|
|
72
|
+
|
|
73
|
+
# Validate token is not empty
|
|
74
|
+
if not token or not token.strip():
|
|
75
|
+
return "Token cannot be empty"
|
|
76
|
+
|
|
77
|
+
# Figma API endpoint
|
|
78
|
+
base_url = "https://api.figma.com"
|
|
79
|
+
endpoint = f"{base_url}/v1/me"
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
response = requests.get(
|
|
83
|
+
endpoint,
|
|
84
|
+
headers={"X-Figma-Token": token},
|
|
85
|
+
timeout=10,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if response.status_code == 200:
|
|
89
|
+
return None # Connection successful
|
|
90
|
+
elif response.status_code == 401:
|
|
91
|
+
detailed_error = _parse_error_response(response)
|
|
92
|
+
return detailed_error if detailed_error else "Invalid token"
|
|
93
|
+
elif response.status_code == 403:
|
|
94
|
+
detailed_error = _parse_error_response(response)
|
|
95
|
+
return detailed_error if detailed_error else "Access forbidden - token may lack required permissions"
|
|
96
|
+
else:
|
|
97
|
+
return f"Connection failed with status {response.status_code}"
|
|
98
|
+
|
|
99
|
+
except requests.exceptions.Timeout:
|
|
100
|
+
return "Connection timeout - Figma API is not responding"
|
|
101
|
+
except requests.exceptions.ConnectionError:
|
|
102
|
+
return "Connection error - unable to reach Figma API"
|
|
103
|
+
except requests.exceptions.RequestException as e:
|
|
104
|
+
return f"Request failed: {str(e)}"
|
|
105
|
+
except Exception as e:
|
|
106
|
+
return f"Unexpected error: {str(e)}"
|
|
@@ -44,18 +44,28 @@ class GitlabConfiguration(BaseModel):
|
|
|
44
44
|
None if connection successful, error message string if failed
|
|
45
45
|
"""
|
|
46
46
|
import requests
|
|
47
|
+
from urllib.parse import urlparse
|
|
47
48
|
|
|
48
49
|
# Validate url
|
|
49
50
|
url = settings.get("url", "").strip()
|
|
50
51
|
if not url:
|
|
51
52
|
return "GitLab URL is required"
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
# Basic URL validation
|
|
57
|
-
if not url.startswith(("http://", "https://")):
|
|
54
|
+
parsed_url = urlparse(url)
|
|
55
|
+
if parsed_url.scheme not in {"http", "https"}:
|
|
58
56
|
return "GitLab URL must start with http:// or https://"
|
|
57
|
+
|
|
58
|
+
if not parsed_url.netloc:
|
|
59
|
+
return "GitLab URL is invalid"
|
|
60
|
+
|
|
61
|
+
# Reject non-base URLs (project/file links), and require a clean base URL.
|
|
62
|
+
# Expected format: <http/https>://<domain>[/]
|
|
63
|
+
if parsed_url.query or parsed_url.fragment or (parsed_url.path not in {"", "/"}):
|
|
64
|
+
base_suggestion = f"{parsed_url.scheme}://{parsed_url.netloc}/"
|
|
65
|
+
return f"GitLab URL must be a base URL like {base_suggestion}"
|
|
66
|
+
|
|
67
|
+
# Normalize URL - ensure no trailing slash
|
|
68
|
+
url = f"{parsed_url.scheme}://{parsed_url.netloc}"
|
|
59
69
|
|
|
60
70
|
# Validate private_token
|
|
61
71
|
private_token = settings.get("private_token")
|
|
@@ -99,6 +109,8 @@ class GitlabConfiguration(BaseModel):
|
|
|
99
109
|
return f"GitLab API returned status code {response.status_code}"
|
|
100
110
|
|
|
101
111
|
except requests.exceptions.SSLError as e:
|
|
112
|
+
if 'Hostname mismatch' in str(e):
|
|
113
|
+
return "GitLab API endpoint not found: verify the GitLab URL"
|
|
102
114
|
return f"SSL certificate verification failed: {str(e)}"
|
|
103
115
|
except requests.exceptions.ConnectionError:
|
|
104
116
|
return f"Cannot connect to GitLab at {url}: connection refused"
|