synapse-sdk 1.0.0a23__py3-none-any.whl → 2025.12.3__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.
- synapse_sdk/__init__.py +24 -0
- synapse_sdk/cli/__init__.py +310 -5
- synapse_sdk/cli/alias/__init__.py +22 -0
- synapse_sdk/cli/alias/create.py +36 -0
- synapse_sdk/cli/alias/dataclass.py +31 -0
- synapse_sdk/cli/alias/default.py +16 -0
- synapse_sdk/cli/alias/delete.py +15 -0
- synapse_sdk/cli/alias/list.py +19 -0
- synapse_sdk/cli/alias/read.py +15 -0
- synapse_sdk/cli/alias/update.py +17 -0
- synapse_sdk/cli/alias/utils.py +61 -0
- synapse_sdk/cli/code_server.py +687 -0
- synapse_sdk/cli/config.py +440 -0
- synapse_sdk/cli/devtools.py +90 -0
- synapse_sdk/cli/plugin/__init__.py +33 -0
- synapse_sdk/cli/{create_plugin.py → plugin/create.py} +2 -2
- synapse_sdk/{plugins/cli → cli/plugin}/publish.py +23 -15
- synapse_sdk/clients/agent/__init__.py +9 -3
- synapse_sdk/clients/agent/container.py +143 -0
- synapse_sdk/clients/agent/core.py +19 -0
- synapse_sdk/clients/agent/ray.py +298 -9
- synapse_sdk/clients/backend/__init__.py +30 -12
- synapse_sdk/clients/backend/annotation.py +13 -5
- synapse_sdk/clients/backend/core.py +31 -4
- synapse_sdk/clients/backend/data_collection.py +186 -0
- synapse_sdk/clients/backend/hitl.py +17 -0
- synapse_sdk/clients/backend/integration.py +16 -1
- synapse_sdk/clients/backend/ml.py +5 -1
- synapse_sdk/clients/backend/models.py +78 -0
- synapse_sdk/clients/base.py +384 -41
- synapse_sdk/clients/ray/serve.py +2 -0
- synapse_sdk/clients/validators/collections.py +31 -0
- synapse_sdk/devtools/config.py +94 -0
- synapse_sdk/devtools/server.py +41 -0
- synapse_sdk/devtools/streamlit_app/__init__.py +5 -0
- synapse_sdk/devtools/streamlit_app/app.py +128 -0
- synapse_sdk/devtools/streamlit_app/services/__init__.py +11 -0
- synapse_sdk/devtools/streamlit_app/services/job_service.py +233 -0
- synapse_sdk/devtools/streamlit_app/services/plugin_service.py +236 -0
- synapse_sdk/devtools/streamlit_app/services/serve_service.py +95 -0
- synapse_sdk/devtools/streamlit_app/ui/__init__.py +15 -0
- synapse_sdk/devtools/streamlit_app/ui/config_tab.py +76 -0
- synapse_sdk/devtools/streamlit_app/ui/deployment_tab.py +66 -0
- synapse_sdk/devtools/streamlit_app/ui/http_tab.py +125 -0
- synapse_sdk/devtools/streamlit_app/ui/jobs_tab.py +573 -0
- synapse_sdk/devtools/streamlit_app/ui/serve_tab.py +346 -0
- synapse_sdk/devtools/streamlit_app/ui/status_bar.py +118 -0
- synapse_sdk/devtools/streamlit_app/utils/__init__.py +40 -0
- synapse_sdk/devtools/streamlit_app/utils/json_viewer.py +197 -0
- synapse_sdk/devtools/streamlit_app/utils/log_formatter.py +38 -0
- synapse_sdk/devtools/streamlit_app/utils/styles.py +241 -0
- synapse_sdk/devtools/streamlit_app/utils/ui_components.py +289 -0
- synapse_sdk/devtools/streamlit_app.py +10 -0
- synapse_sdk/loggers.py +120 -9
- synapse_sdk/plugins/README.md +1340 -0
- synapse_sdk/plugins/__init__.py +0 -13
- synapse_sdk/plugins/categories/base.py +117 -11
- synapse_sdk/plugins/categories/data_validation/actions/validation.py +72 -0
- synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +33 -5
- synapse_sdk/plugins/categories/export/actions/__init__.py +3 -0
- synapse_sdk/plugins/categories/export/actions/export/__init__.py +28 -0
- synapse_sdk/plugins/categories/export/actions/export/action.py +165 -0
- synapse_sdk/plugins/categories/export/actions/export/enums.py +113 -0
- synapse_sdk/plugins/categories/export/actions/export/exceptions.py +53 -0
- synapse_sdk/plugins/categories/export/actions/export/models.py +74 -0
- synapse_sdk/plugins/categories/export/actions/export/run.py +195 -0
- synapse_sdk/plugins/categories/export/actions/export/utils.py +187 -0
- synapse_sdk/plugins/categories/export/templates/config.yaml +21 -0
- synapse_sdk/plugins/categories/export/templates/plugin/__init__.py +390 -0
- synapse_sdk/plugins/categories/export/templates/plugin/export.py +160 -0
- synapse_sdk/plugins/categories/neural_net/actions/deployment.py +13 -12
- synapse_sdk/plugins/categories/neural_net/actions/train.py +1134 -31
- synapse_sdk/plugins/categories/neural_net/actions/tune.py +534 -0
- synapse_sdk/plugins/categories/neural_net/base/inference.py +1 -1
- synapse_sdk/plugins/categories/neural_net/templates/config.yaml +32 -4
- synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +26 -10
- synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +4 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/__init__.py +3 -0
- synapse_sdk/plugins/categories/{export/actions/export.py → pre_annotation/actions/pre_annotation/action.py} +4 -4
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/__init__.py +28 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/action.py +148 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/enums.py +269 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/exceptions.py +14 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/factory.py +76 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/models.py +100 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/orchestrator.py +248 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/run.py +64 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/__init__.py +17 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/annotation.py +265 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/base.py +170 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/extraction.py +83 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/metrics.py +92 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/preprocessor.py +243 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/validation.py +143 -0
- synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +19 -0
- synapse_sdk/plugins/categories/pre_annotation/templates/plugin/to_task.py +40 -0
- synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +2 -0
- synapse_sdk/plugins/categories/upload/__init__.py +0 -0
- synapse_sdk/plugins/categories/upload/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +19 -0
- synapse_sdk/plugins/categories/upload/actions/upload/action.py +236 -0
- synapse_sdk/plugins/categories/upload/actions/upload/context.py +185 -0
- synapse_sdk/plugins/categories/upload/actions/upload/enums.py +493 -0
- synapse_sdk/plugins/categories/upload/actions/upload/exceptions.py +36 -0
- synapse_sdk/plugins/categories/upload/actions/upload/factory.py +138 -0
- synapse_sdk/plugins/categories/upload/actions/upload/models.py +214 -0
- synapse_sdk/plugins/categories/upload/actions/upload/orchestrator.py +183 -0
- synapse_sdk/plugins/categories/upload/actions/upload/registry.py +113 -0
- synapse_sdk/plugins/categories/upload/actions/upload/run.py +179 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/base.py +107 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +62 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/collection.py +63 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/generate.py +91 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/initialize.py +82 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +235 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +201 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/upload.py +104 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/validate.py +71 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/base.py +82 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/batch.py +39 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/single.py +29 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/flat.py +300 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/recursive.py +287 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/excel.py +174 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/none.py +16 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/sync.py +84 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/default.py +60 -0
- synapse_sdk/plugins/categories/upload/actions/upload/utils.py +250 -0
- synapse_sdk/plugins/categories/upload/templates/README.md +470 -0
- synapse_sdk/plugins/categories/upload/templates/config.yaml +33 -0
- synapse_sdk/plugins/categories/upload/templates/plugin/__init__.py +310 -0
- synapse_sdk/plugins/categories/upload/templates/plugin/upload.py +102 -0
- synapse_sdk/plugins/enums.py +3 -1
- synapse_sdk/plugins/models.py +148 -11
- synapse_sdk/plugins/templates/plugin-config-schema.json +406 -0
- synapse_sdk/plugins/templates/schema.json +491 -0
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +1 -0
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +1 -1
- synapse_sdk/plugins/utils/__init__.py +46 -0
- synapse_sdk/plugins/utils/actions.py +119 -0
- synapse_sdk/plugins/utils/config.py +203 -0
- synapse_sdk/plugins/{utils.py → utils/legacy.py} +26 -46
- synapse_sdk/plugins/utils/ray_gcs.py +66 -0
- synapse_sdk/plugins/utils/registry.py +58 -0
- synapse_sdk/shared/__init__.py +25 -0
- synapse_sdk/shared/enums.py +93 -0
- synapse_sdk/types.py +19 -0
- synapse_sdk/utils/converters/__init__.py +240 -0
- synapse_sdk/utils/converters/coco/__init__.py +0 -0
- synapse_sdk/utils/converters/coco/from_dm.py +322 -0
- synapse_sdk/utils/converters/coco/to_dm.py +215 -0
- synapse_sdk/utils/converters/dm/__init__.py +57 -0
- synapse_sdk/utils/converters/dm/base.py +137 -0
- synapse_sdk/utils/converters/dm/from_v1.py +273 -0
- synapse_sdk/utils/converters/dm/to_v1.py +321 -0
- synapse_sdk/utils/converters/dm/tools/__init__.py +214 -0
- synapse_sdk/utils/converters/dm/tools/answer.py +95 -0
- synapse_sdk/utils/converters/dm/tools/bounding_box.py +132 -0
- synapse_sdk/utils/converters/dm/tools/bounding_box_3d.py +121 -0
- synapse_sdk/utils/converters/dm/tools/classification.py +75 -0
- synapse_sdk/utils/converters/dm/tools/keypoint.py +117 -0
- synapse_sdk/utils/converters/dm/tools/named_entity.py +111 -0
- synapse_sdk/utils/converters/dm/tools/polygon.py +122 -0
- synapse_sdk/utils/converters/dm/tools/polyline.py +124 -0
- synapse_sdk/utils/converters/dm/tools/prompt.py +94 -0
- synapse_sdk/utils/converters/dm/tools/relation.py +86 -0
- synapse_sdk/utils/converters/dm/tools/segmentation.py +141 -0
- synapse_sdk/utils/converters/dm/tools/segmentation_3d.py +83 -0
- synapse_sdk/utils/converters/dm/types.py +168 -0
- synapse_sdk/utils/converters/dm/utils.py +162 -0
- synapse_sdk/utils/converters/dm_legacy/__init__.py +56 -0
- synapse_sdk/utils/converters/dm_legacy/from_v1.py +627 -0
- synapse_sdk/utils/converters/dm_legacy/to_v1.py +367 -0
- synapse_sdk/utils/converters/pascal/__init__.py +0 -0
- synapse_sdk/utils/converters/pascal/from_dm.py +244 -0
- synapse_sdk/utils/converters/pascal/to_dm.py +214 -0
- synapse_sdk/utils/converters/yolo/__init__.py +0 -0
- synapse_sdk/utils/converters/yolo/from_dm.py +384 -0
- synapse_sdk/utils/converters/yolo/to_dm.py +267 -0
- synapse_sdk/utils/dataset.py +46 -0
- synapse_sdk/utils/encryption.py +158 -0
- synapse_sdk/utils/file/__init__.py +58 -0
- synapse_sdk/utils/file/archive.py +32 -0
- synapse_sdk/utils/file/checksum.py +56 -0
- synapse_sdk/utils/file/chunking.py +31 -0
- synapse_sdk/utils/file/download.py +385 -0
- synapse_sdk/utils/file/encoding.py +40 -0
- synapse_sdk/utils/file/io.py +22 -0
- synapse_sdk/utils/file/upload.py +165 -0
- synapse_sdk/utils/file/video/__init__.py +29 -0
- synapse_sdk/utils/file/video/transcode.py +307 -0
- synapse_sdk/utils/file.py.backup +301 -0
- synapse_sdk/utils/http.py +138 -0
- synapse_sdk/utils/network.py +309 -0
- synapse_sdk/utils/storage/__init__.py +72 -0
- synapse_sdk/utils/storage/providers/__init__.py +183 -0
- synapse_sdk/utils/storage/providers/file_system.py +134 -0
- synapse_sdk/utils/storage/providers/gcp.py +13 -0
- synapse_sdk/utils/storage/providers/http.py +190 -0
- synapse_sdk/utils/storage/providers/s3.py +91 -0
- synapse_sdk/utils/storage/providers/sftp.py +47 -0
- synapse_sdk/utils/storage/registry.py +17 -0
- synapse_sdk-2025.12.3.dist-info/METADATA +123 -0
- synapse_sdk-2025.12.3.dist-info/RECORD +279 -0
- {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/WHEEL +1 -1
- synapse_sdk/clients/backend/dataset.py +0 -51
- synapse_sdk/plugins/categories/import/actions/import.py +0 -10
- synapse_sdk/plugins/cli/__init__.py +0 -21
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
- synapse_sdk/utils/file.py +0 -168
- synapse_sdk/utils/storage.py +0 -91
- synapse_sdk-1.0.0a23.dist-info/METADATA +0 -44
- synapse_sdk-1.0.0a23.dist-info/RECORD +0 -114
- /synapse_sdk/{plugins/cli → cli/plugin}/run.py +0 -0
- /synapse_sdk/{plugins/categories/import → clients/validators}/__init__.py +0 -0
- /synapse_sdk/{plugins/categories/import/actions → devtools}/__init__.py +0 -0
- {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/entry_points.txt +0 -0
- {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info/licenses}/LICENSE +0 -0
- {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Main Streamlit DevTools Application."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Dict, Optional
|
|
5
|
+
|
|
6
|
+
import streamlit as st
|
|
7
|
+
|
|
8
|
+
from synapse_sdk.clients.backend import BackendClient
|
|
9
|
+
from synapse_sdk.devtools.config import get_backend_config, load_devtools_config
|
|
10
|
+
|
|
11
|
+
from .services import JobService, PluginService, ServeService
|
|
12
|
+
from .ui import ConfigTab, DeploymentTab, HttpTab, JobsTab, ServeTab
|
|
13
|
+
from .ui.status_bar import StatusBar
|
|
14
|
+
from .utils import CUSTOM_CSS
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DevToolsApp:
|
|
18
|
+
"""Main DevTools application class."""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self.plugin_directory = Path.cwd()
|
|
22
|
+
|
|
23
|
+
# Initialize backend client and agent info
|
|
24
|
+
self.backend_client = self._init_backend_client()
|
|
25
|
+
self.agent_id = self._get_agent_id()
|
|
26
|
+
self.agent_info = self._get_agent_info()
|
|
27
|
+
|
|
28
|
+
# Initialize services
|
|
29
|
+
self.plugin_service = PluginService(self.plugin_directory, self.backend_client)
|
|
30
|
+
self.job_service = JobService(self.backend_client)
|
|
31
|
+
self.serve_service = ServeService(self.backend_client)
|
|
32
|
+
|
|
33
|
+
# Initialize UI components
|
|
34
|
+
self.status_bar = StatusBar(
|
|
35
|
+
backend_client=self.backend_client, agent_id=self.agent_id, agent_info=self.agent_info
|
|
36
|
+
)
|
|
37
|
+
self.config_tab = ConfigTab(self.plugin_service)
|
|
38
|
+
self.http_tab = HttpTab(self.plugin_service, self.agent_id)
|
|
39
|
+
self.deployment_tab = DeploymentTab(self.plugin_service)
|
|
40
|
+
self.jobs_tab = JobsTab(self.job_service, self.agent_id, self.agent_info)
|
|
41
|
+
self.serve_tab = ServeTab(self.serve_service, self.agent_id, self.agent_info)
|
|
42
|
+
|
|
43
|
+
def _init_backend_client(self) -> Optional[BackendClient]:
|
|
44
|
+
"""Initialize backend client from configuration."""
|
|
45
|
+
config = get_backend_config()
|
|
46
|
+
if config:
|
|
47
|
+
return BackendClient(config['host'], access_token=config['token'])
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
def _get_agent_id(self) -> Optional[int]:
|
|
51
|
+
"""Get agent ID from devtools configuration."""
|
|
52
|
+
devtools_config = load_devtools_config()
|
|
53
|
+
agent_config = devtools_config.get('agent', {})
|
|
54
|
+
if agent_config and 'id' in agent_config:
|
|
55
|
+
return agent_config['id']
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
def _get_agent_info(self) -> Optional[Dict]:
|
|
59
|
+
"""Get complete agent information from configuration or backend."""
|
|
60
|
+
devtools_config = load_devtools_config()
|
|
61
|
+
agent_config = devtools_config.get('agent', {})
|
|
62
|
+
|
|
63
|
+
# Return the full agent config which should include name, ip, etc.
|
|
64
|
+
if agent_config:
|
|
65
|
+
return agent_config
|
|
66
|
+
|
|
67
|
+
# If we have an agent_id but no full config, try to fetch from backend
|
|
68
|
+
if self.agent_id and self.backend_client:
|
|
69
|
+
try:
|
|
70
|
+
# Try to get agent details from backend
|
|
71
|
+
# This would require an API endpoint - for now return basic info
|
|
72
|
+
return {
|
|
73
|
+
'id': self.agent_id,
|
|
74
|
+
'name': agent_config.get('name', f'Agent-{self.agent_id}'),
|
|
75
|
+
'url': agent_config.get('url', agent_config.get('ip', 'localhost')),
|
|
76
|
+
}
|
|
77
|
+
except Exception:
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
def run(self):
|
|
83
|
+
"""Main application entry point."""
|
|
84
|
+
# Page configuration
|
|
85
|
+
st.set_page_config(
|
|
86
|
+
page_title='Synapse DevTools',
|
|
87
|
+
page_icon=None,
|
|
88
|
+
layout='wide',
|
|
89
|
+
initial_sidebar_state='collapsed',
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Apply custom CSS
|
|
93
|
+
st.markdown(CUSTOM_CSS, unsafe_allow_html=True)
|
|
94
|
+
|
|
95
|
+
# Display status bar
|
|
96
|
+
self.status_bar.render()
|
|
97
|
+
|
|
98
|
+
# Initialize session state
|
|
99
|
+
if 'config' not in st.session_state:
|
|
100
|
+
st.session_state['config'] = self.plugin_service.load_config()
|
|
101
|
+
|
|
102
|
+
# Create tabs
|
|
103
|
+
tabs = st.tabs(['Configuration', 'HTTP Request', 'Deployment', 'Jobs', 'Serve Apps'])
|
|
104
|
+
|
|
105
|
+
with tabs[0]:
|
|
106
|
+
self.config_tab.render()
|
|
107
|
+
|
|
108
|
+
with tabs[1]:
|
|
109
|
+
self.http_tab.render()
|
|
110
|
+
|
|
111
|
+
with tabs[2]:
|
|
112
|
+
self.deployment_tab.render()
|
|
113
|
+
|
|
114
|
+
with tabs[3]:
|
|
115
|
+
self.jobs_tab.render()
|
|
116
|
+
|
|
117
|
+
with tabs[4]:
|
|
118
|
+
self.serve_tab.render()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def main():
|
|
122
|
+
"""Main entry point for Streamlit app."""
|
|
123
|
+
app = DevToolsApp()
|
|
124
|
+
app.run()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
if __name__ == '__main__':
|
|
128
|
+
main()
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""Job service for managing job operations."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
from synapse_sdk.clients.backend import BackendClient
|
|
7
|
+
from synapse_sdk.clients.exceptions import ClientError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class JobService:
|
|
11
|
+
"""Service for job-related operations."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, backend_client: Optional[BackendClient] = None):
|
|
14
|
+
self.backend_client = backend_client
|
|
15
|
+
|
|
16
|
+
def list_jobs(self, agent_id: Optional[int] = None, agent_info: Optional[Dict] = None) -> List[Dict]:
|
|
17
|
+
"""List jobs from the backend."""
|
|
18
|
+
if not self.backend_client:
|
|
19
|
+
return []
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
params = {}
|
|
23
|
+
if agent_id:
|
|
24
|
+
params['agent'] = agent_id
|
|
25
|
+
|
|
26
|
+
jobs_response = self.backend_client.list_jobs(params=params)
|
|
27
|
+
|
|
28
|
+
# Handle paginated response - extract results
|
|
29
|
+
if jobs_response is None:
|
|
30
|
+
return []
|
|
31
|
+
elif isinstance(jobs_response, dict) and 'results' in jobs_response:
|
|
32
|
+
jobs = jobs_response['results']
|
|
33
|
+
else:
|
|
34
|
+
jobs = jobs_response if isinstance(jobs_response, list) else []
|
|
35
|
+
|
|
36
|
+
# Remove None jobs and sort by created time (most recent first)
|
|
37
|
+
valid_jobs = [job for job in jobs if job is not None]
|
|
38
|
+
valid_jobs.sort(key=lambda job: job.get('created', ''), reverse=True)
|
|
39
|
+
|
|
40
|
+
# Try to enrich jobs with plugin names
|
|
41
|
+
enriched_jobs = []
|
|
42
|
+
for job in valid_jobs:
|
|
43
|
+
enriched_job = job.copy()
|
|
44
|
+
|
|
45
|
+
# Try to get plugin info
|
|
46
|
+
if 'plugin_release' in job:
|
|
47
|
+
try:
|
|
48
|
+
# Try to fetch plugin release details
|
|
49
|
+
plugin_release_response = self.backend_client.get(f'/plugin_releases/{job["plugin_release"]}/')
|
|
50
|
+
if plugin_release_response and isinstance(plugin_release_response, dict):
|
|
51
|
+
# Get version from plugin release
|
|
52
|
+
enriched_job['plugin_version'] = plugin_release_response.get('version')
|
|
53
|
+
|
|
54
|
+
# Try to get plugin details from the plugin ID
|
|
55
|
+
plugin_id = plugin_release_response.get('plugin')
|
|
56
|
+
if plugin_id:
|
|
57
|
+
try:
|
|
58
|
+
plugin_response = self.backend_client.get(f'/plugins/{plugin_id}/')
|
|
59
|
+
if plugin_response and isinstance(plugin_response, dict):
|
|
60
|
+
enriched_job['plugin_name'] = plugin_response.get('name')
|
|
61
|
+
enriched_job['plugin_code'] = plugin_response.get('code')
|
|
62
|
+
except Exception:
|
|
63
|
+
# Fallback to config if plugin fetch fails
|
|
64
|
+
config = plugin_release_response.get('config', {})
|
|
65
|
+
enriched_job['plugin_name'] = config.get('name') or config.get('code')
|
|
66
|
+
enriched_job['plugin_code'] = config.get('code')
|
|
67
|
+
else:
|
|
68
|
+
# Fallback to config if no plugin ID
|
|
69
|
+
config = plugin_release_response.get('config', {})
|
|
70
|
+
enriched_job['plugin_name'] = config.get('name') or config.get('code')
|
|
71
|
+
enriched_job['plugin_code'] = config.get('code')
|
|
72
|
+
except Exception:
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
# Try to get agent info
|
|
76
|
+
if 'agent' in job:
|
|
77
|
+
# First check if we have local agent info
|
|
78
|
+
if agent_info and job.get('agent') == agent_id:
|
|
79
|
+
enriched_job['agent_name'] = agent_info.get('name')
|
|
80
|
+
enriched_job['agent_url'] = agent_info.get('url')
|
|
81
|
+
else:
|
|
82
|
+
# Try to fetch agent details from API
|
|
83
|
+
try:
|
|
84
|
+
agent_response = self.backend_client.get(f'/agents/{job["agent"]}/')
|
|
85
|
+
if agent_response and isinstance(agent_response, dict):
|
|
86
|
+
enriched_job['agent_name'] = agent_response.get('name')
|
|
87
|
+
enriched_job['agent_url'] = agent_response.get('url')
|
|
88
|
+
except Exception:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
enriched_jobs.append(enriched_job)
|
|
92
|
+
|
|
93
|
+
return enriched_jobs
|
|
94
|
+
except ClientError:
|
|
95
|
+
raise
|
|
96
|
+
except Exception as e:
|
|
97
|
+
raise Exception(f'Failed to list jobs: {e}')
|
|
98
|
+
|
|
99
|
+
def get_job_logs(self, job_id: str):
|
|
100
|
+
"""Get all logs for a job at once."""
|
|
101
|
+
if not self.backend_client:
|
|
102
|
+
raise Exception('Backend client not configured')
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
# Get logs from console_logs endpoint (returns list of strings)
|
|
106
|
+
logs_response = self.backend_client.list_job_console_logs(job_id)
|
|
107
|
+
|
|
108
|
+
# API returns a list of log strings
|
|
109
|
+
if isinstance(logs_response, list):
|
|
110
|
+
if not logs_response:
|
|
111
|
+
# Empty list means no logs yet
|
|
112
|
+
yield 'No logs available for this job yet.\n'
|
|
113
|
+
else:
|
|
114
|
+
for log_entry in logs_response:
|
|
115
|
+
# Each entry is already a formatted string with timestamp
|
|
116
|
+
yield str(log_entry) + '\n' if not str(log_entry).endswith('\n') else str(log_entry)
|
|
117
|
+
elif logs_response:
|
|
118
|
+
# Fallback for unexpected format
|
|
119
|
+
yield str(logs_response) + '\n'
|
|
120
|
+
else:
|
|
121
|
+
yield 'No logs available for this job yet.\n'
|
|
122
|
+
|
|
123
|
+
except Exception as e:
|
|
124
|
+
from synapse_sdk.clients.exceptions import ClientError
|
|
125
|
+
|
|
126
|
+
# Check if it's a ClientError with specific status
|
|
127
|
+
if isinstance(e, ClientError):
|
|
128
|
+
if e.status == 404:
|
|
129
|
+
yield 'Job not found or logs not available.\n'
|
|
130
|
+
elif e.status == 401:
|
|
131
|
+
yield 'Unauthorized to access job logs.\n'
|
|
132
|
+
else:
|
|
133
|
+
# Check for Korean error message in the reason
|
|
134
|
+
error_str = str(e.reason) if hasattr(e, 'reason') else str(e)
|
|
135
|
+
if '찾을 수 없습니다' in error_str:
|
|
136
|
+
yield 'Job logs not found. The job may not have generated any logs yet.\n'
|
|
137
|
+
else:
|
|
138
|
+
yield f'Failed to fetch logs: {error_str}\n'
|
|
139
|
+
else:
|
|
140
|
+
error_str = str(e)
|
|
141
|
+
if '찾을 수 없습니다' in error_str or '404' in error_str or 'Not found' in error_str:
|
|
142
|
+
yield 'Job logs not found. The job may not have generated any logs yet.\n'
|
|
143
|
+
elif '401' in error_str or 'Unauthorized' in error_str:
|
|
144
|
+
yield 'Unauthorized to access job logs.\n'
|
|
145
|
+
else:
|
|
146
|
+
yield f'Failed to fetch logs: {error_str}\n'
|
|
147
|
+
|
|
148
|
+
def stream_job_logs(self, job_id: str):
|
|
149
|
+
"""Stream logs for a job (simulated streaming since tail endpoint doesn't exist)."""
|
|
150
|
+
if not self.backend_client:
|
|
151
|
+
raise Exception('Backend client not configured')
|
|
152
|
+
|
|
153
|
+
# Since tail_console_logs endpoint returns 404, we'll simulate streaming
|
|
154
|
+
# by fetching all logs and yielding them one by one
|
|
155
|
+
try:
|
|
156
|
+
from synapse_sdk.clients.exceptions import ClientError
|
|
157
|
+
|
|
158
|
+
# Get logs from console_logs endpoint (returns list of strings)
|
|
159
|
+
logs_response = self.backend_client.list_job_console_logs(job_id)
|
|
160
|
+
|
|
161
|
+
# API returns a list of log strings
|
|
162
|
+
if isinstance(logs_response, list):
|
|
163
|
+
if not logs_response:
|
|
164
|
+
# Empty list means no logs yet
|
|
165
|
+
yield 'No logs available for this job yet.\n'
|
|
166
|
+
else:
|
|
167
|
+
# Yield logs one by one to simulate streaming
|
|
168
|
+
for log_entry in logs_response:
|
|
169
|
+
# Each entry is already a formatted string with timestamp
|
|
170
|
+
yield str(log_entry) + '\n' if not str(log_entry).endswith('\n') else str(log_entry)
|
|
171
|
+
elif logs_response:
|
|
172
|
+
# Fallback for unexpected format
|
|
173
|
+
yield str(logs_response) + '\n'
|
|
174
|
+
else:
|
|
175
|
+
yield 'No logs available for this job yet.\n'
|
|
176
|
+
|
|
177
|
+
except Exception as e:
|
|
178
|
+
# Check if it's a ClientError with specific status
|
|
179
|
+
if isinstance(e, ClientError):
|
|
180
|
+
if e.status == 404:
|
|
181
|
+
yield 'Job not found or logs not available.\n'
|
|
182
|
+
elif e.status == 401:
|
|
183
|
+
yield 'Unauthorized to access job logs.\n'
|
|
184
|
+
else:
|
|
185
|
+
# Check for Korean error message in the reason
|
|
186
|
+
error_str = str(e.reason) if hasattr(e, 'reason') else str(e)
|
|
187
|
+
if '찾을 수 없습니다' in error_str:
|
|
188
|
+
yield 'Job logs not found. The job may not have generated any logs yet.\n'
|
|
189
|
+
else:
|
|
190
|
+
yield f'Failed to stream logs: {error_str}\n'
|
|
191
|
+
else:
|
|
192
|
+
error_str = str(e)
|
|
193
|
+
if '찾을 수 없습니다' in error_str or '404' in error_str or 'Not found' in error_str:
|
|
194
|
+
yield 'Job logs not found. The job may not have generated any logs yet.\n'
|
|
195
|
+
elif '401' in error_str or 'Unauthorized' in error_str:
|
|
196
|
+
yield 'Unauthorized to access job logs.\n'
|
|
197
|
+
else:
|
|
198
|
+
yield f'Failed to stream logs: {error_str}\n'
|
|
199
|
+
|
|
200
|
+
@staticmethod
|
|
201
|
+
def format_duration(created: str, completed: Optional[str] = None) -> str:
|
|
202
|
+
"""Format duration between created and completed timestamps."""
|
|
203
|
+
if not created:
|
|
204
|
+
return '-'
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
created_dt = datetime.fromisoformat(created.replace('+09:00', '+09:00'))
|
|
208
|
+
if completed:
|
|
209
|
+
completed_dt = datetime.fromisoformat(completed.replace('+09:00', '+09:00'))
|
|
210
|
+
duration = completed_dt - created_dt
|
|
211
|
+
total_seconds = int(duration.total_seconds())
|
|
212
|
+
if total_seconds < 60:
|
|
213
|
+
return f'{total_seconds}s'
|
|
214
|
+
else:
|
|
215
|
+
minutes = total_seconds // 60
|
|
216
|
+
seconds = total_seconds % 60
|
|
217
|
+
return f'{minutes}m {seconds}s'
|
|
218
|
+
else:
|
|
219
|
+
return '...'
|
|
220
|
+
except Exception:
|
|
221
|
+
return '-'
|
|
222
|
+
|
|
223
|
+
@staticmethod
|
|
224
|
+
def format_timestamp(timestamp: str) -> str:
|
|
225
|
+
"""Format timestamp for display."""
|
|
226
|
+
if not timestamp:
|
|
227
|
+
return '-'
|
|
228
|
+
|
|
229
|
+
try:
|
|
230
|
+
dt = datetime.fromisoformat(timestamp.replace('+09:00', '+09:00'))
|
|
231
|
+
return dt.strftime('%m/%d %H:%M')
|
|
232
|
+
except Exception:
|
|
233
|
+
return timestamp[:16] if len(timestamp) > 16 else timestamp
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""Plugin service for managing plugin operations."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Dict, Optional
|
|
8
|
+
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
from synapse_sdk.cli.plugin.publish import _publish
|
|
12
|
+
from synapse_sdk.clients.backend import BackendClient
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PluginService:
|
|
16
|
+
"""Service for plugin-related operations."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, plugin_directory: Path, backend_client: Optional[BackendClient] = None):
|
|
19
|
+
self.plugin_directory = plugin_directory
|
|
20
|
+
self.config_path = plugin_directory / 'config.yaml'
|
|
21
|
+
self.test_http_path = plugin_directory / 'test.http'
|
|
22
|
+
self.backend_client = backend_client
|
|
23
|
+
|
|
24
|
+
def load_config(self) -> Dict:
|
|
25
|
+
"""Load plugin configuration from config.yaml."""
|
|
26
|
+
if not self.config_path.exists():
|
|
27
|
+
return {}
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
with open(self.config_path, 'r') as f:
|
|
31
|
+
return yaml.safe_load(f)
|
|
32
|
+
except Exception:
|
|
33
|
+
return {}
|
|
34
|
+
|
|
35
|
+
def save_config(self, config: Dict) -> bool:
|
|
36
|
+
"""Save plugin configuration to config.yaml."""
|
|
37
|
+
try:
|
|
38
|
+
# Backup existing config
|
|
39
|
+
if self.config_path.exists():
|
|
40
|
+
backup_path = self.config_path.with_suffix('.yaml.bak')
|
|
41
|
+
self.config_path.rename(backup_path)
|
|
42
|
+
|
|
43
|
+
# Write new config
|
|
44
|
+
with open(self.config_path, 'w') as f:
|
|
45
|
+
yaml.dump(config, f, default_flow_style=False, allow_unicode=True, indent=2)
|
|
46
|
+
|
|
47
|
+
return True
|
|
48
|
+
except Exception:
|
|
49
|
+
# Restore backup if write failed
|
|
50
|
+
backup_path = self.config_path.with_suffix('.yaml.bak')
|
|
51
|
+
if backup_path.exists():
|
|
52
|
+
backup_path.rename(self.config_path)
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
def parse_test_http(self) -> Dict[str, Dict]:
|
|
56
|
+
"""Parse test.http file and extract action parameters."""
|
|
57
|
+
if not self.test_http_path.exists():
|
|
58
|
+
return {}
|
|
59
|
+
|
|
60
|
+
requests = {}
|
|
61
|
+
current_request_name = None
|
|
62
|
+
current_body_lines = []
|
|
63
|
+
in_body = False
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
with open(self.test_http_path, 'r') as f:
|
|
67
|
+
for line in f:
|
|
68
|
+
line = line.strip()
|
|
69
|
+
if line.startswith('###'):
|
|
70
|
+
# Process previous request
|
|
71
|
+
if current_request_name and current_body_lines:
|
|
72
|
+
try:
|
|
73
|
+
full_request = json.loads(''.join(current_body_lines))
|
|
74
|
+
if 'params' in full_request:
|
|
75
|
+
requests[current_request_name] = full_request['params']
|
|
76
|
+
except json.JSONDecodeError:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
# Start new request
|
|
80
|
+
current_request_name = line.replace('###', '').strip()
|
|
81
|
+
current_body_lines = []
|
|
82
|
+
in_body = False
|
|
83
|
+
elif current_request_name and line.startswith('{'):
|
|
84
|
+
in_body = True
|
|
85
|
+
current_body_lines.append(line)
|
|
86
|
+
elif in_body and current_request_name:
|
|
87
|
+
current_body_lines.append(line)
|
|
88
|
+
|
|
89
|
+
# Process last request
|
|
90
|
+
if current_request_name and current_body_lines:
|
|
91
|
+
try:
|
|
92
|
+
full_request = json.loads(''.join(current_body_lines))
|
|
93
|
+
if 'params' in full_request:
|
|
94
|
+
requests[current_request_name] = full_request['params']
|
|
95
|
+
except json.JSONDecodeError:
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
return requests
|
|
99
|
+
except Exception:
|
|
100
|
+
return {}
|
|
101
|
+
|
|
102
|
+
def update_test_http_params(self, action: str, new_params: Dict) -> bool:
|
|
103
|
+
"""Update parameters for a specific action in test.http."""
|
|
104
|
+
if not self.test_http_path.exists():
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
with open(self.test_http_path, 'r') as f:
|
|
109
|
+
lines = f.readlines()
|
|
110
|
+
|
|
111
|
+
current_action = None
|
|
112
|
+
in_json = False
|
|
113
|
+
json_start_line = -1
|
|
114
|
+
json_lines = []
|
|
115
|
+
updated = False
|
|
116
|
+
|
|
117
|
+
for i, line in enumerate(lines):
|
|
118
|
+
if line.strip().startswith('###'):
|
|
119
|
+
current_action = line.replace('###', '').strip()
|
|
120
|
+
in_json = False
|
|
121
|
+
json_lines = []
|
|
122
|
+
elif current_action and line.strip().startswith('{'):
|
|
123
|
+
in_json = True
|
|
124
|
+
json_start_line = i
|
|
125
|
+
json_lines = [line]
|
|
126
|
+
elif in_json:
|
|
127
|
+
json_lines.append(line)
|
|
128
|
+
if line.strip() == '}':
|
|
129
|
+
if current_action == action:
|
|
130
|
+
try:
|
|
131
|
+
full_json = json.loads(''.join(json_lines))
|
|
132
|
+
full_json['params'] = new_params
|
|
133
|
+
new_json = json.dumps(full_json, indent=2)
|
|
134
|
+
lines[json_start_line : i + 1] = [new_json + '\n']
|
|
135
|
+
updated = True
|
|
136
|
+
break
|
|
137
|
+
except json.JSONDecodeError:
|
|
138
|
+
pass
|
|
139
|
+
in_json = False
|
|
140
|
+
json_lines = []
|
|
141
|
+
|
|
142
|
+
if updated:
|
|
143
|
+
with open(self.test_http_path, 'w') as f:
|
|
144
|
+
f.writelines(lines)
|
|
145
|
+
return True
|
|
146
|
+
|
|
147
|
+
return False
|
|
148
|
+
except Exception:
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
def execute_plugin_action(
|
|
152
|
+
self, action: str, params: Dict, plugin_code: str, agent_id: Optional[int] = None, debug: bool = True
|
|
153
|
+
) -> Dict:
|
|
154
|
+
"""Execute a plugin action via HTTP request."""
|
|
155
|
+
if not self.backend_client:
|
|
156
|
+
return {'success': False, 'error': 'Backend client not configured'}
|
|
157
|
+
|
|
158
|
+
if not plugin_code:
|
|
159
|
+
return {'success': False, 'error': 'Plugin code not found in configuration'}
|
|
160
|
+
|
|
161
|
+
plugin_url = f'{self.backend_client.base_url}/plugins/{plugin_code}/run/'
|
|
162
|
+
|
|
163
|
+
headers = {
|
|
164
|
+
'Accept': 'application/json; indent=4',
|
|
165
|
+
'Content-Type': 'application/json',
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
# Get auth headers from backend client
|
|
169
|
+
auth_headers = self.backend_client._get_headers()
|
|
170
|
+
headers.update(auth_headers)
|
|
171
|
+
|
|
172
|
+
payload = {
|
|
173
|
+
'agent': agent_id or 2,
|
|
174
|
+
'action': action,
|
|
175
|
+
'params': params,
|
|
176
|
+
'debug': debug,
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
start_time = time.time()
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
import requests
|
|
183
|
+
|
|
184
|
+
response = requests.post(plugin_url, json=payload, headers=headers, timeout=30)
|
|
185
|
+
execution_time = int((time.time() - start_time) * 1000)
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
response_data = response.json()
|
|
189
|
+
except Exception:
|
|
190
|
+
response_data = response.text
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
'success': response.status_code < 400,
|
|
194
|
+
'status_code': response.status_code,
|
|
195
|
+
'response_data': response_data,
|
|
196
|
+
'execution_time': execution_time,
|
|
197
|
+
'url': plugin_url,
|
|
198
|
+
'method': 'POST',
|
|
199
|
+
'headers': headers,
|
|
200
|
+
'payload': payload,
|
|
201
|
+
}
|
|
202
|
+
except Exception as e:
|
|
203
|
+
execution_time = int((time.time() - start_time) * 1000)
|
|
204
|
+
return {
|
|
205
|
+
'success': False,
|
|
206
|
+
'status_code': 500,
|
|
207
|
+
'error': str(e),
|
|
208
|
+
'execution_time': execution_time,
|
|
209
|
+
'url': plugin_url,
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
def publish_plugin(self, host: str, access_token: str, debug: bool = True) -> Dict:
|
|
213
|
+
"""Publish plugin to Synapse platform."""
|
|
214
|
+
original_cwd = os.getcwd()
|
|
215
|
+
try:
|
|
216
|
+
os.chdir(str(self.plugin_directory))
|
|
217
|
+
|
|
218
|
+
debug_modules = os.getenv('SYNAPSE_DEBUG_MODULES', '')
|
|
219
|
+
plugin_release = _publish(host, access_token, debug, debug_modules)
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
'success': True,
|
|
223
|
+
'message': (
|
|
224
|
+
f'Successfully published "{plugin_release.name}" ({plugin_release.code}) to synapse backend!'
|
|
225
|
+
),
|
|
226
|
+
'plugin_code': plugin_release.code,
|
|
227
|
+
'version': plugin_release.version,
|
|
228
|
+
'name': plugin_release.name,
|
|
229
|
+
}
|
|
230
|
+
except Exception as e:
|
|
231
|
+
return {
|
|
232
|
+
'success': False,
|
|
233
|
+
'error': str(e),
|
|
234
|
+
}
|
|
235
|
+
finally:
|
|
236
|
+
os.chdir(original_cwd)
|