model-compose 0.1.7__tar.gz → 0.1.9__tar.gz
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.
- {model_compose-0.1.7/src/model_compose.egg-info → model_compose-0.1.9}/PKG-INFO +1 -1
- {model_compose-0.1.7 → model_compose-0.1.9}/pyproject.toml +1 -1
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/component/engine/http_client.py +63 -32
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/component/engine/shell.py +16 -5
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/controller/engine/http_server.py +3 -2
- model_compose-0.1.9/src/mindor/core/controller/engine/mcp_server.py +159 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/controller/runner/http_client.py +3 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/controller/runner/mcp_client.py +3 -2
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/controller/runner/runner.py +10 -1
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/controller/webui/gradio.py +13 -13
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/controller/webui/webui.py +9 -3
- model_compose-0.1.9/src/mindor/core/gateway/engine/ssh_tunnel.py +25 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/listener/engine/http_callback.py +8 -8
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/utils/http_client.py +50 -23
- model_compose-0.1.9/src/mindor/core/utils/http_status.py +20 -0
- model_compose-0.1.9/src/mindor/core/utils/mcp_client.py +122 -0
- model_compose-0.1.7/src/mindor/core/utils/mcp_client.py → model_compose-0.1.9/src/mindor/core/utils/ssh_client.py +1 -1
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/utils/streaming.py +2 -2
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/utils/template.py +27 -14
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/workflow/schema.py +83 -44
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/action/impl/http_client.py +4 -4
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/component/impl/http_client.py +1 -1
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/gateway/gateway.py +2 -1
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/gateway/impl/__init__.py +1 -0
- model_compose-0.1.9/src/mindor/dsl/schema/gateway/impl/ssh_tunnel.py +7 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/gateway/impl/types.py +1 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/workflow.py +11 -1
- {model_compose-0.1.7 → model_compose-0.1.9/src/model_compose.egg-info}/PKG-INFO +1 -1
- {model_compose-0.1.7 → model_compose-0.1.9}/src/model_compose.egg-info/SOURCES.txt +4 -0
- model_compose-0.1.7/src/mindor/core/controller/engine/mcp_server.py +0 -93
- {model_compose-0.1.7 → model_compose-0.1.9}/LICENSE +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/README.md +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/setup.cfg +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/cli/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/cli/compose.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/component/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/component/component.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/component/engine/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/component/engine/base.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/component/engine/context.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/component/engine/http_server.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/component/engine/mcp_client.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/component/engine/mcp_server.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/compose/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/compose/compose.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/compose/manager.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/controller/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/controller/controller.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/controller/engine/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/controller/engine/base.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/controller/runner/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/controller/runner/client.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/controller/webui/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/gateway/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/gateway/engine/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/gateway/engine/base.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/gateway/engine/http_tunnel.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/gateway/gateway.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/listener/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/listener/engine/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/listener/engine/base.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/listener/listener.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/runtime/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/runtime/env.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/services/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/services/async_service.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/utils/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/utils/expiring.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/utils/http_request.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/utils/image.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/utils/time.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/utils/workqueue.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/workflow/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/workflow/context.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/workflow/job.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/workflow/workflow.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/loader.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/action/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/action/action.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/action/impl/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/action/impl/common.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/action/impl/http_server.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/action/impl/mcp_client.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/action/impl/mcp_server.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/action/impl/shell.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/component/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/component/component.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/component/impl/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/component/impl/common.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/component/impl/http_server.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/component/impl/mcp_client.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/component/impl/mcp_server.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/component/impl/shell.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/component/impl/types.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/compose.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/controller/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/controller/controller.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/controller/impl/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/controller/impl/common.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/controller/impl/http_server.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/controller/impl/mcp_server.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/controller/impl/types.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/controller/impl/webui.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/gateway/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/gateway/impl/common.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/gateway/impl/http_tunnel.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/listener/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/listener/impl/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/listener/impl/common.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/listener/impl/http_callback.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/listener/impl/types.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/listener/listener.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/runtime/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/runtime/impl/__init__.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/runtime/impl/common.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/runtime/impl/docker.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/runtime/impl/types.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/dsl/schema/runtime/runtime.py +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/model_compose.egg-info/dependency_links.txt +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/model_compose.egg-info/entry_points.txt +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/model_compose.egg-info/requires.txt +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/src/model_compose.egg-info/top_level.txt +0 -0
- {model_compose-0.1.7 → model_compose-0.1.9}/tests/test_cli.py +0 -0
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, AsyncIterator, Any
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
2
3
|
from mindor.dsl.schema.component import HttpClientComponentConfig
|
|
3
4
|
from mindor.dsl.schema.action import ActionConfig, HttpClientActionConfig, HttpClientCompletionConfig
|
|
4
5
|
from mindor.core.listener import HttpCallbackListener
|
|
5
6
|
from mindor.core.utils.http_client import HttpClient
|
|
7
|
+
from mindor.core.utils.http_status import is_status_code_matched
|
|
6
8
|
from mindor.core.utils.time import parse_duration
|
|
7
9
|
from .base import ComponentEngine, ComponentType, ComponentEngineMap
|
|
8
10
|
from .context import ComponentContext
|
|
9
11
|
from datetime import datetime, timezone
|
|
10
12
|
import asyncio
|
|
11
13
|
|
|
12
|
-
class
|
|
13
|
-
|
|
14
|
+
class HttpClientCompletion(ABC):
|
|
15
|
+
@abstractmethod
|
|
16
|
+
async def run(self, context: ComponentContext) -> Any:
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
async def close(self) -> None:
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
class HttpClientPollingCompletion(HttpClientCompletion):
|
|
24
|
+
def __init__(self, config: HttpClientCompletionConfig, base_url: Optional[str], headers: Optional[Dict[str, str]]):
|
|
25
|
+
self.config: HttpClientCompletionConfig = config
|
|
14
26
|
self.base_url: Optional[str] = base_url
|
|
15
27
|
self.headers: Optional[Dict[str, str]] = headers
|
|
16
|
-
self.config: HttpClientCompletionConfig = config
|
|
17
28
|
self.client: HttpClient = HttpClient()
|
|
18
29
|
|
|
19
30
|
async def run(self, context: ComponentContext) -> Any:
|
|
@@ -30,33 +41,38 @@ class HttpClientPollingCompletion:
|
|
|
30
41
|
await asyncio.sleep(interval.total_seconds())
|
|
31
42
|
|
|
32
43
|
while datetime.now(timezone.utc) < deadline:
|
|
33
|
-
response = await self.client.request(url, method, params, body, headers)
|
|
44
|
+
response, status_code = await self.client.request(url, method, params, body, headers, raise_on_error=False)
|
|
34
45
|
context.register_source("result", response)
|
|
35
46
|
|
|
36
|
-
status = (await context.render_template(self.config.status)) if self.config.status else
|
|
37
|
-
|
|
38
|
-
if not status:
|
|
47
|
+
status = (await context.render_template(self.config.status)) if self.config.status else None
|
|
48
|
+
if self.config.status and not status:
|
|
39
49
|
raise RuntimeError(f"Polling failed: no status found in response.")
|
|
40
50
|
|
|
41
|
-
|
|
42
|
-
if status
|
|
51
|
+
if status:
|
|
52
|
+
if status in self.config.success_when or []:
|
|
43
53
|
return response
|
|
44
|
-
|
|
45
|
-
for fail_when in self.config.fail_when if self.config.fail_when else [ "failed" ]:
|
|
46
|
-
if status == await context.render_template(fail_when):
|
|
54
|
+
if status in self.config.fail_when or []:
|
|
47
55
|
raise RuntimeError(f"Polling failed: status '{status}' matched a failure condition.")
|
|
56
|
+
else: # use status code
|
|
57
|
+
if is_status_code_matched(status_code, self.config.success_when or []):
|
|
58
|
+
return response
|
|
59
|
+
if is_status_code_matched(status_code, self.config.fail_when or []):
|
|
60
|
+
raise RuntimeError(f"Polling failed: status code '{status_code}' matched a failure condition.")
|
|
48
61
|
|
|
49
62
|
await asyncio.sleep(interval.total_seconds())
|
|
50
63
|
|
|
51
64
|
raise TimeoutError(f"Polling timed out after {timeout}.")
|
|
52
65
|
|
|
66
|
+
async def close(self) -> None:
|
|
67
|
+
await self.client.close()
|
|
68
|
+
|
|
53
69
|
async def _resolve_request_url(self, context: ComponentContext) -> str:
|
|
54
70
|
if self.base_url and self.config.path:
|
|
55
71
|
return await context.render_template(self.base_url) + await context.render_template(self.config.path)
|
|
56
72
|
|
|
57
73
|
return await context.render_template(self.config.endpoint)
|
|
58
74
|
|
|
59
|
-
class HttpClientCallbackCompletion:
|
|
75
|
+
class HttpClientCallbackCompletion(HttpClientCompletion):
|
|
60
76
|
def __init__(self, config: HttpClientCompletionConfig):
|
|
61
77
|
self.config: HttpClientCompletionConfig = config
|
|
62
78
|
|
|
@@ -66,56 +82,71 @@ class HttpClientCallbackCompletion:
|
|
|
66
82
|
HttpCallbackListener.register_pending_future(callback_id, future)
|
|
67
83
|
|
|
68
84
|
return await future
|
|
85
|
+
|
|
86
|
+
async def close(self) -> None:
|
|
87
|
+
pass
|
|
69
88
|
|
|
70
89
|
class HttpClientAction:
|
|
71
|
-
def __init__(self, base_url: Optional[str], headers: Optional[Dict[str, str]]
|
|
90
|
+
def __init__(self, config: HttpClientActionConfig, base_url: Optional[str], headers: Optional[Dict[str, str]]):
|
|
91
|
+
self.config: HttpClientActionConfig = config
|
|
72
92
|
self.base_url: Optional[str] = base_url
|
|
73
93
|
self.headers: Optional[Dict[str, str]] = headers
|
|
74
|
-
self.
|
|
75
|
-
self.client: HttpClient = HttpClient()
|
|
94
|
+
self.completion: HttpClientCompletion = None
|
|
76
95
|
|
|
77
|
-
|
|
96
|
+
if self.config.completion:
|
|
97
|
+
self._configure_completion()
|
|
98
|
+
|
|
99
|
+
def _configure_completion(self) -> None:
|
|
100
|
+
if self.config.completion.type == "polling":
|
|
101
|
+
self.completion = HttpClientPollingCompletion(self.config.completion, self.base_url, self.headers)
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
if self.config.completion.type == "callback":
|
|
105
|
+
self.completion = HttpClientCallbackCompletion(self.config.completion)
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
raise ValueError(f"Unsupported http completion type: {self.config.completion.type}")
|
|
109
|
+
|
|
110
|
+
async def run(self, context: ComponentContext, client: HttpClient) -> Any:
|
|
78
111
|
url = await self._resolve_request_url(context)
|
|
79
112
|
method = await context.render_template(self.config.method)
|
|
80
113
|
params = await context.render_template(self.config.params)
|
|
81
114
|
body = await context.render_template(self.config.body)
|
|
82
115
|
headers = await context.render_template({ **self.headers, **self.config.headers })
|
|
83
116
|
|
|
84
|
-
response, result = await
|
|
117
|
+
response, result = await client.request(url, method, params, body, headers), None
|
|
85
118
|
context.register_source("response", response)
|
|
86
119
|
|
|
87
|
-
if self.
|
|
88
|
-
result = await self.
|
|
120
|
+
if self.completion:
|
|
121
|
+
result = await self.completion.run(context)
|
|
89
122
|
context.register_source("result", result)
|
|
90
123
|
|
|
91
124
|
return (await context.render_template(self.config.output, ignore_files=True)) if self.config.output else (result or response)
|
|
92
125
|
|
|
126
|
+
async def close(self) -> None:
|
|
127
|
+
if self.completion:
|
|
128
|
+
await self.completion.close()
|
|
129
|
+
|
|
93
130
|
async def _resolve_request_url(self, context: ComponentContext) -> str:
|
|
94
131
|
if self.base_url and self.config.path:
|
|
95
132
|
return await context.render_template(self.base_url) + await context.render_template(self.config.path)
|
|
96
133
|
|
|
97
134
|
return await context.render_template(self.config.endpoint)
|
|
98
135
|
|
|
99
|
-
async def _handle_completion(self, completion: HttpClientCompletionConfig, context: ComponentContext) -> Any:
|
|
100
|
-
if completion.type == "polling":
|
|
101
|
-
return await HttpClientPollingCompletion(self.base_url, self.headers, completion).run(context)
|
|
102
|
-
|
|
103
|
-
if completion.type == "callback":
|
|
104
|
-
return await HttpClientCallbackCompletion(completion).run(context)
|
|
105
|
-
|
|
106
|
-
return None
|
|
107
|
-
|
|
108
136
|
class HttpClientComponent(ComponentEngine):
|
|
109
137
|
def __init__(self, id: str, config: HttpClientComponentConfig, env: Dict[str, str], daemon: bool):
|
|
110
138
|
super().__init__(id, config, env, daemon)
|
|
111
139
|
|
|
140
|
+
self.client: HttpClient = None
|
|
141
|
+
|
|
112
142
|
async def _serve(self) -> None:
|
|
113
|
-
|
|
143
|
+
self.client = HttpClient()
|
|
114
144
|
|
|
115
145
|
async def _shutdown(self) -> None:
|
|
116
|
-
|
|
146
|
+
await self.client.close()
|
|
147
|
+
self.client = None
|
|
117
148
|
|
|
118
149
|
async def _run(self, action: ActionConfig, context: ComponentContext) -> Any:
|
|
119
|
-
return await HttpClientAction(self.config.base_url, self.config.headers
|
|
150
|
+
return await HttpClientAction(action, self.config.base_url, self.config.headers).run(context, self.client)
|
|
120
151
|
|
|
121
152
|
ComponentEngineMap[ComponentType.HTTP_CLIENT] = HttpClientComponent
|
|
@@ -3,6 +3,7 @@ from mindor.dsl.schema.component import ShellComponentConfig
|
|
|
3
3
|
from mindor.dsl.schema.action import ActionConfig, ShellActionConfig
|
|
4
4
|
from .base import ComponentEngine, ComponentType, ComponentEngineMap, ActionConfig
|
|
5
5
|
from .context import ComponentContext
|
|
6
|
+
from asyncio.subprocess import Process
|
|
6
7
|
import asyncio, os
|
|
7
8
|
|
|
8
9
|
class ShellAction:
|
|
@@ -32,16 +33,26 @@ class ShellAction:
|
|
|
32
33
|
try:
|
|
33
34
|
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=timeout)
|
|
34
35
|
except asyncio.TimeoutError:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
raise RuntimeError(f"Command timed out: {' '.join(command)}")
|
|
36
|
+
if await self._kill_process(process):
|
|
37
|
+
raise RuntimeError(f"Command timed out: {' '.join(command)}")
|
|
38
38
|
|
|
39
39
|
return {
|
|
40
|
-
"exit_code": process.returncode,
|
|
41
40
|
"stdout": stdout.decode().strip(),
|
|
42
|
-
"stderr": stderr.decode().strip()
|
|
41
|
+
"stderr": stderr.decode().strip(),
|
|
42
|
+
"exit_code": process.returncode
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
async def _kill_process(self, process: Process) -> bool:
|
|
46
|
+
if process.returncode is None:
|
|
47
|
+
process.kill()
|
|
48
|
+
try:
|
|
49
|
+
await process.wait()
|
|
50
|
+
except Exception as e:
|
|
51
|
+
pass
|
|
52
|
+
return True
|
|
53
|
+
else:
|
|
54
|
+
return False
|
|
55
|
+
|
|
45
56
|
def _resolve_working_directory(self) -> str:
|
|
46
57
|
working_dir = self.config.working_dir
|
|
47
58
|
if working_dir:
|
{model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/controller/engine/http_server.py
RENAMED
|
@@ -59,7 +59,7 @@ class HttpServerController(ControllerEngine):
|
|
|
59
59
|
self._configure_routes()
|
|
60
60
|
self.app.include_router(self.router, prefix=self.config.base_path)
|
|
61
61
|
|
|
62
|
-
def _configure_server(self):
|
|
62
|
+
def _configure_server(self) -> None:
|
|
63
63
|
self.app.add_middleware(
|
|
64
64
|
CORSMiddleware,
|
|
65
65
|
allow_origins=[self.config.origins],
|
|
@@ -68,7 +68,7 @@ class HttpServerController(ControllerEngine):
|
|
|
68
68
|
allow_headers=["*"],
|
|
69
69
|
)
|
|
70
70
|
|
|
71
|
-
def _configure_routes(self):
|
|
71
|
+
def _configure_routes(self) -> None:
|
|
72
72
|
@self.router.post("/workflows")
|
|
73
73
|
async def run_workflow(
|
|
74
74
|
request: Request
|
|
@@ -107,6 +107,7 @@ class HttpServerController(ControllerEngine):
|
|
|
107
107
|
log_level="info"
|
|
108
108
|
))
|
|
109
109
|
await self.server.serve()
|
|
110
|
+
self.server = None
|
|
110
111
|
|
|
111
112
|
async def _shutdown(self) -> None:
|
|
112
113
|
if self.server:
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Callable, Awaitable, Any
|
|
2
|
+
from mindor.dsl.schema.controller import McpServerControllerConfig
|
|
3
|
+
from mindor.dsl.schema.component import ComponentConfig
|
|
4
|
+
from mindor.dsl.schema.listener import ListenerConfig
|
|
5
|
+
from mindor.dsl.schema.gateway import GatewayConfig
|
|
6
|
+
from mindor.dsl.schema.workflow import WorkflowConfig, WorkflowVariableConfig
|
|
7
|
+
from mindor.core.workflow.schema import WorkflowSchema, create_workflow_schema
|
|
8
|
+
from mindor.core.utils.streaming import StreamResource
|
|
9
|
+
from .base import ControllerEngine, ControllerType, ControllerEngineMap, TaskState
|
|
10
|
+
from mcp.server.fastmcp.server import FastMCP
|
|
11
|
+
from mcp.types import TextContent, ImageContent, AudioContent
|
|
12
|
+
import uvicorn, re, json
|
|
13
|
+
|
|
14
|
+
_invalid_function_chars_regex = re.compile(r"[^a-zA-Z0-9_]")
|
|
15
|
+
|
|
16
|
+
class WorkflowToolGenerator():
|
|
17
|
+
def generate(self, workflow_id: str, workflow: WorkflowSchema, runner: Callable[[Optional[str], Any], Awaitable[Any]]) -> Tuple[Callable[[Any], Awaitable[Any]], str]:
|
|
18
|
+
async def _run_workflow(input: Any, workflow_id=workflow_id, variables=workflow.output) -> Any:
|
|
19
|
+
return await self._build_output_value(await runner(workflow_id, input), variables)
|
|
20
|
+
|
|
21
|
+
async def _build_input_value(arguments, variables=workflow.input) -> Any:
|
|
22
|
+
return await self._build_input_value(arguments, variables)
|
|
23
|
+
|
|
24
|
+
safe_workflow_id = re.sub(_invalid_function_chars_regex, "_", workflow_id)
|
|
25
|
+
arguments = ",".join([ variable.name or "input" for variable in workflow.input ])
|
|
26
|
+
code = f"async def _run_workflow_{safe_workflow_id}({arguments}): return await _run_workflow(await _build_input_value([{arguments}]))"
|
|
27
|
+
context = { "_run_workflow": _run_workflow, "_build_input_value": _build_input_value }
|
|
28
|
+
exec(compile(code, f"<string>", "exec"), context)
|
|
29
|
+
|
|
30
|
+
return (context[f"_run_workflow_{safe_workflow_id}"], self._generate_description(workflow))
|
|
31
|
+
|
|
32
|
+
async def _build_input_value(self, arguments: List[Any], variables: List[WorkflowVariableConfig]) -> Any:
|
|
33
|
+
input: Dict[str, Any] = {}
|
|
34
|
+
|
|
35
|
+
for value, variable in zip(arguments, variables):
|
|
36
|
+
type, subtype, format = variable.type.value, variable.subtype, variable.format.value if variable.format else None
|
|
37
|
+
input[variable.name or "input"] = await self._convert_input_value(value, type, subtype, format, variable.default)
|
|
38
|
+
|
|
39
|
+
return input
|
|
40
|
+
|
|
41
|
+
async def _convert_input_value(self, value: Any, type: str, subtype: Optional[str], format: Optional[str], default: Optional[Any]) -> Any:
|
|
42
|
+
if type in [ "image", "audio", "video", "file" ]:
|
|
43
|
+
if format and format != "path":
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
return value if value != "" else None
|
|
47
|
+
|
|
48
|
+
async def _build_output_value(self, state: TaskState, variables: List[WorkflowVariableConfig]) -> List[Union[TextContent, ImageContent, AudioContent]]:
|
|
49
|
+
output: List[Union[TextContent, ImageContent]] = []
|
|
50
|
+
|
|
51
|
+
if state.output:
|
|
52
|
+
for variable in variables:
|
|
53
|
+
type, subtype, format = variable.type.value, variable.subtype, variable.format.value if variable.format else None
|
|
54
|
+
output.append(await self._convert_output_value(state.output, type, subtype, format))
|
|
55
|
+
|
|
56
|
+
return output
|
|
57
|
+
|
|
58
|
+
async def _convert_output_value(self, value: Any, type: str, subtype: Optional[str], format: Optional[str]) -> Union[TextContent, ImageContent, AudioContent]:
|
|
59
|
+
if type in [ "image", "audio", "video", "file" ]:
|
|
60
|
+
if format and format != "path":
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
if isinstance(value, (dict, list)):
|
|
64
|
+
return TextContent(type="text", text=json.dumps(value))
|
|
65
|
+
|
|
66
|
+
return TextContent(type="text", text=str(value))
|
|
67
|
+
|
|
68
|
+
def _generate_description(self, workflow: WorkflowSchema) -> str:
|
|
69
|
+
lines = []
|
|
70
|
+
|
|
71
|
+
lines.append(workflow.description or workflow.title or "")
|
|
72
|
+
lines.append("")
|
|
73
|
+
lines.append("Args:")
|
|
74
|
+
|
|
75
|
+
for variable in workflow.input:
|
|
76
|
+
name, type = variable.name or "input", self._get_docstring_type(variable)
|
|
77
|
+
description = variable.get_annotation_value("description") or ""
|
|
78
|
+
lines.append(f" {name} ({type}): {description}")
|
|
79
|
+
|
|
80
|
+
lines.append("Returns:")
|
|
81
|
+
|
|
82
|
+
if len(workflow.output) == 1 and not workflow.output[0].name:
|
|
83
|
+
variable = workflow.output[0]
|
|
84
|
+
name, type = variable.name or "", self._get_docstring_type(variable)
|
|
85
|
+
lines.append(f" {type}: {description}")
|
|
86
|
+
else:
|
|
87
|
+
lines.append(f" dict: results containing:")
|
|
88
|
+
for variable in workflow.output:
|
|
89
|
+
name, type = variable.name or "", self._get_docstring_type(variable)
|
|
90
|
+
description = variable.get_annotation_value("description") or ""
|
|
91
|
+
lines.append(f" - {name} ({type}): {description}")
|
|
92
|
+
|
|
93
|
+
return "\n".join(lines)
|
|
94
|
+
|
|
95
|
+
def _get_docstring_type(self, variable: WorkflowVariableConfig) -> str:
|
|
96
|
+
type, subtype, format = variable.type.value, variable.subtype, variable.format.value if variable.format else None
|
|
97
|
+
|
|
98
|
+
if type == "object[]":
|
|
99
|
+
return "list[dict]"
|
|
100
|
+
|
|
101
|
+
if type == "number":
|
|
102
|
+
return "float"
|
|
103
|
+
|
|
104
|
+
if type == "integer":
|
|
105
|
+
return "int"
|
|
106
|
+
|
|
107
|
+
return "str"
|
|
108
|
+
|
|
109
|
+
class McpServerController(ControllerEngine):
|
|
110
|
+
def __init__(
|
|
111
|
+
self,
|
|
112
|
+
config: McpServerControllerConfig,
|
|
113
|
+
components: Dict[str, ComponentConfig],
|
|
114
|
+
listeners: List[ListenerConfig],
|
|
115
|
+
gateways: List[GatewayConfig],
|
|
116
|
+
workflows: Dict[str, WorkflowConfig],
|
|
117
|
+
env: Dict[str, str],
|
|
118
|
+
daemon: bool
|
|
119
|
+
):
|
|
120
|
+
super().__init__(config, components, listeners, gateways, workflows, env, daemon)
|
|
121
|
+
|
|
122
|
+
self.server: Optional[uvicorn.Server] = None
|
|
123
|
+
self.app: FastMCP = FastMCP(self.config.name, settings={
|
|
124
|
+
"streamable_http_path": self.config.base_path
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
self._configure_tools()
|
|
128
|
+
|
|
129
|
+
def _configure_tools(self) -> None:
|
|
130
|
+
schema = create_workflow_schema(self.workflows, self.components)
|
|
131
|
+
|
|
132
|
+
for workflow_id, workflow in schema.items():
|
|
133
|
+
fn, description = WorkflowToolGenerator().generate(workflow_id, workflow, self._run_workflow_as_tool)
|
|
134
|
+
self.app.add_tool(
|
|
135
|
+
fn=fn,
|
|
136
|
+
name=workflow.name or workflow_id,
|
|
137
|
+
title=workflow.title,
|
|
138
|
+
description=description,
|
|
139
|
+
annotations=None
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
async def _run_workflow_as_tool(self, workflow_id: Optional[str], input: Any) -> TaskState:
|
|
143
|
+
return await self.run_workflow(workflow_id, input, wait_for_completion=True)
|
|
144
|
+
|
|
145
|
+
async def _serve(self) -> None:
|
|
146
|
+
self.server = uvicorn.Server(uvicorn.Config(
|
|
147
|
+
self.app.streamable_http_app(),
|
|
148
|
+
host=self.config.host,
|
|
149
|
+
port=self.config.port,
|
|
150
|
+
log_level="info"
|
|
151
|
+
))
|
|
152
|
+
await self.server.serve()
|
|
153
|
+
self.server = None
|
|
154
|
+
|
|
155
|
+
async def _shutdown(self) -> None:
|
|
156
|
+
if self.server:
|
|
157
|
+
self.server.should_exit = True
|
|
158
|
+
|
|
159
|
+
ControllerEngineMap[ControllerType.MCP_SERVER] = McpServerController
|
{model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/controller/runner/http_client.py
RENAMED
|
@@ -32,6 +32,9 @@ class HttpControllerClient(ControllerClient):
|
|
|
32
32
|
|
|
33
33
|
return await self.client.request(url, method, None, body, headers)
|
|
34
34
|
|
|
35
|
+
async def close(self) -> None:
|
|
36
|
+
await self.client.close()
|
|
37
|
+
|
|
35
38
|
def _flatten_for_multipart(self, body: Dict[str, Any], key: str = "") -> List[Tuple[str, Any]]:
|
|
36
39
|
flattened = []
|
|
37
40
|
|
|
@@ -7,7 +7,8 @@ class McpControllerClient(ControllerClient):
|
|
|
7
7
|
def __init__(self, config: ControllerConfig):
|
|
8
8
|
super().__init__(config)
|
|
9
9
|
|
|
10
|
-
self.client: McpClient = McpClient()
|
|
11
|
-
|
|
12
10
|
async def run_workflow(self, workflow_id: Optional[str], input: Any) -> Any:
|
|
13
11
|
pass
|
|
12
|
+
|
|
13
|
+
async def close(self) -> None:
|
|
14
|
+
pass
|
|
@@ -13,6 +13,10 @@ class ControllerClient(ABC):
|
|
|
13
13
|
async def run_workflow(self, workflow_id: Optional[str], input: Any) -> Any:
|
|
14
14
|
pass
|
|
15
15
|
|
|
16
|
+
@abstractmethod
|
|
17
|
+
async def close(self) -> None:
|
|
18
|
+
pass
|
|
19
|
+
|
|
16
20
|
class ControllerRunner:
|
|
17
21
|
def __init__(self, config: ControllerConfig):
|
|
18
22
|
self.config: ControllerConfig = config
|
|
@@ -24,10 +28,15 @@ class ControllerRunner:
|
|
|
24
28
|
if self.config.type == "http-server":
|
|
25
29
|
self.client = HttpControllerClient(self.config)
|
|
26
30
|
return
|
|
27
|
-
|
|
31
|
+
|
|
28
32
|
if self.config.type == "mcp-server":
|
|
29
33
|
self.client = McpControllerClient(self.config)
|
|
30
34
|
return
|
|
35
|
+
|
|
36
|
+
raise ValueError(f"Unsupported controller type: {self.config.type}")
|
|
31
37
|
|
|
32
38
|
async def run_workflow(self, workflow_id: Optional[str], input: Any) -> Any:
|
|
33
39
|
return await self.client.run_workflow(workflow_id, input)
|
|
40
|
+
|
|
41
|
+
async def close(self) -> None:
|
|
42
|
+
await self.client.close()
|
|
@@ -62,18 +62,15 @@ class GradioWebUIBuilder:
|
|
|
62
62
|
|
|
63
63
|
def _build_input_component(self, variable: WorkflowVariableConfig) -> gr.Component:
|
|
64
64
|
label = (variable.name or "") + (" *" if variable.required else "") + (f" (default: {variable.default})" if variable.default else "")
|
|
65
|
-
info = variable.description or ""
|
|
65
|
+
info = variable.get_annotation_value("description") or ""
|
|
66
66
|
default = variable.default
|
|
67
67
|
|
|
68
68
|
if variable.type == "string" or variable.format in [ "base64", "url" ]:
|
|
69
69
|
return gr.Textbox(label=label, value="", info=info)
|
|
70
70
|
|
|
71
|
-
if variable.type
|
|
72
|
-
return gr.Number(label=label, value="",
|
|
73
|
-
|
|
74
|
-
if variable.type == "integer":
|
|
75
|
-
return gr.Number(label=label, value="", precision=0, info=info)
|
|
76
|
-
|
|
71
|
+
if variable.type in [ "integer", "number" ]:
|
|
72
|
+
return gr.Number(label=label, value="", info=info)
|
|
73
|
+
|
|
77
74
|
if variable.type == "boolean":
|
|
78
75
|
return gr.Checkbox(label=label, value=default or False, info=info)
|
|
79
76
|
|
|
@@ -96,14 +93,14 @@ class GradioWebUIBuilder:
|
|
|
96
93
|
|
|
97
94
|
async def _build_input_value(self, arguments: List[Any], variables: List[WorkflowVariableConfig]) -> Any:
|
|
98
95
|
if len(variables) == 1 and not variables[0].name:
|
|
99
|
-
|
|
96
|
+
value, variable = arguments[0], variables[0]
|
|
97
|
+
type, subtype, format = variable.type.value, variable.subtype, variable.format.value if variable.format else None
|
|
98
|
+
return await self._convert_input_value(value, type, subtype, format, variable.internal)
|
|
100
99
|
|
|
101
100
|
input: Dict[str, Any] = {}
|
|
102
|
-
|
|
103
101
|
for value, variable in zip(arguments, variables):
|
|
104
102
|
type, subtype, format = variable.type.value, variable.subtype, variable.format.value if variable.format else None
|
|
105
103
|
input[variable.name] = await self._convert_input_value(value, type, subtype, format, variable.internal)
|
|
106
|
-
|
|
107
104
|
return input
|
|
108
105
|
|
|
109
106
|
async def _convert_input_value(self, value: Any, type: str, subtype: Optional[str], format: Optional[str], internal: bool) -> Any:
|
|
@@ -125,13 +122,16 @@ class GradioWebUIBuilder:
|
|
|
125
122
|
return groups
|
|
126
123
|
|
|
127
124
|
label = variable.name or ""
|
|
128
|
-
info = variable.description or ""
|
|
125
|
+
info = variable.get_annotation_value("description") or ""
|
|
129
126
|
|
|
130
127
|
if variable.type in [ "string", "base64" ]:
|
|
131
128
|
return gr.Textbox(label=label, interactive=False, show_copy_button=True, info=info)
|
|
132
129
|
|
|
133
130
|
if variable.type == "markdown":
|
|
134
131
|
return gr.Markdown(label=label)
|
|
132
|
+
|
|
133
|
+
if variable.type in [ "json", "object[]" ]:
|
|
134
|
+
return gr.JSON(label=label)
|
|
135
135
|
|
|
136
136
|
if variable.type == "image":
|
|
137
137
|
return gr.Image(label=label, interactive=False)
|
|
@@ -184,7 +184,7 @@ class GradioWebUIBuilder:
|
|
|
184
184
|
return await load_image_from_stream(Base64StreamResource(value), subtype)
|
|
185
185
|
|
|
186
186
|
if format == "url" and isinstance(value, str):
|
|
187
|
-
return await load_image_from_stream(await HttpClient
|
|
187
|
+
return await load_image_from_stream(await HttpClient.request_once(value), subtype)
|
|
188
188
|
|
|
189
189
|
if isinstance(value, StreamResource):
|
|
190
190
|
return await load_image_from_stream(value, subtype)
|
|
@@ -196,7 +196,7 @@ class GradioWebUIBuilder:
|
|
|
196
196
|
return await save_stream_to_temporary_file(Base64StreamResource(value), subtype)
|
|
197
197
|
|
|
198
198
|
if format == "url" and isinstance(value, str):
|
|
199
|
-
return await save_stream_to_temporary_file(await HttpClient
|
|
199
|
+
return await save_stream_to_temporary_file(await HttpClient.request_once(value), subtype)
|
|
200
200
|
|
|
201
201
|
if isinstance(value, StreamResource):
|
|
202
202
|
return await save_stream_to_temporary_file(value, subtype)
|
|
@@ -30,7 +30,7 @@ class ControllerWebUI(AsyncService):
|
|
|
30
30
|
self.env: Dict[str, str] = env
|
|
31
31
|
self.server: Optional[uvicorn.Server] = None
|
|
32
32
|
self.app: FastAPI = FastAPI(openapi_url=None, docs_url=None, redoc_url=None)
|
|
33
|
-
self.runner: ControllerRunner =
|
|
33
|
+
self.runner: ControllerRunner = None
|
|
34
34
|
|
|
35
35
|
self._configure_driver()
|
|
36
36
|
|
|
@@ -44,6 +44,7 @@ class ControllerWebUI(AsyncService):
|
|
|
44
44
|
return
|
|
45
45
|
|
|
46
46
|
async def _serve(self) -> None:
|
|
47
|
+
self.runner = ControllerRunner(self.controller)
|
|
47
48
|
self.server = uvicorn.Server(uvicorn.Config(
|
|
48
49
|
self.app,
|
|
49
50
|
host=self.config.host,
|
|
@@ -51,10 +52,15 @@ class ControllerWebUI(AsyncService):
|
|
|
51
52
|
log_level="info"
|
|
52
53
|
))
|
|
53
54
|
await self.server.serve()
|
|
55
|
+
await self.runner.close()
|
|
56
|
+
self.server = None
|
|
57
|
+
self.runner = None
|
|
54
58
|
|
|
55
59
|
async def _shutdown(self) -> None:
|
|
56
60
|
if self.server:
|
|
57
61
|
self.server.should_exit = True
|
|
58
|
-
|
|
62
|
+
|
|
59
63
|
async def _run_workflow(self, workflow_id: Optional[str], input: Any) -> Any:
|
|
60
|
-
|
|
64
|
+
if self.runner:
|
|
65
|
+
return await self.runner.run_workflow(workflow_id, input)
|
|
66
|
+
return None
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Callable, Iterator, Any
|
|
2
|
+
from mindor.dsl.schema.gateway import SshTunnelGatewayConfig
|
|
3
|
+
from .base import GatewayEngine, GatewayType, GatewayEngineMap
|
|
4
|
+
from pyngrok import ngrok
|
|
5
|
+
|
|
6
|
+
class SshTunnelGateway(GatewayEngine):
|
|
7
|
+
def __init__(self, id: str, config: SshTunnelGatewayConfig, env: Dict[str, str], daemon: bool):
|
|
8
|
+
super().__init__(id, config, env, daemon)
|
|
9
|
+
|
|
10
|
+
self.tunnel: Optional[Any] = None
|
|
11
|
+
self.public_url: Optional[str] = None
|
|
12
|
+
|
|
13
|
+
def get_context(self) -> Dict[str, Any]:
|
|
14
|
+
return {
|
|
15
|
+
"public_url": self.public_url,
|
|
16
|
+
"port": self.config.port
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async def _serve(self) -> None:
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
async def _shutdown(self) -> None:
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
GatewayEngineMap[GatewayType.SSH_TUNNEL] = SshTunnelGateway
|
{model_compose-0.1.7 → model_compose-0.1.9}/src/mindor/core/listener/engine/http_callback.py
RENAMED
|
@@ -22,7 +22,7 @@ class HttpCallbackContext:
|
|
|
22
22
|
self.renderer: TemplateRenderer = TemplateRenderer(self._resolve_source)
|
|
23
23
|
|
|
24
24
|
async def items(self) -> AsyncIterator["HttpCallbackContext"]:
|
|
25
|
-
|
|
25
|
+
for item in await self._items():
|
|
26
26
|
yield HttpCallbackContext(item, self.query, False, None)
|
|
27
27
|
|
|
28
28
|
async def render_template(self, template: str, ignore_files: bool = True) -> Any:
|
|
@@ -56,7 +56,7 @@ class HttpCallbackListener(ListenerEngine):
|
|
|
56
56
|
self._configure_routes()
|
|
57
57
|
self.app.include_router(self.router, prefix=self.config.base_path)
|
|
58
58
|
|
|
59
|
-
def _configure_routes(self):
|
|
59
|
+
def _configure_routes(self) -> None:
|
|
60
60
|
for callback in self.config.callbacks:
|
|
61
61
|
self.router.add_api_route(
|
|
62
62
|
path=callback.path,
|
|
@@ -69,8 +69,8 @@ class HttpCallbackListener(ListenerEngine):
|
|
|
69
69
|
async def _handler(request: Request) -> Response:
|
|
70
70
|
content_type, _ = parse_options_header(request.headers, "Content-Type")
|
|
71
71
|
body, query = await parse_request_body(request, content_type), request.query_params
|
|
72
|
-
context
|
|
73
|
-
succeeded
|
|
72
|
+
context = HttpCallbackContext(body, query, callback.bulk, callback.item)
|
|
73
|
+
succeeded = await self._is_callback_succeeded(callback, context)
|
|
74
74
|
|
|
75
75
|
async for item in context.items():
|
|
76
76
|
callback_id = await item.render_template(callback.identify_by)
|
|
@@ -91,13 +91,12 @@ class HttpCallbackListener(ListenerEngine):
|
|
|
91
91
|
status = (await context.render_template(callback.status)) if callback.status else None
|
|
92
92
|
|
|
93
93
|
if status:
|
|
94
|
-
if
|
|
94
|
+
if status in callback.success_when or []:
|
|
95
95
|
return True
|
|
96
|
-
|
|
97
|
-
if callback.fail_when and status == await context.render_template(callback.fail_when):
|
|
96
|
+
if status in callback.fail_when or []:
|
|
98
97
|
return False
|
|
99
98
|
|
|
100
|
-
return True
|
|
99
|
+
return True
|
|
101
100
|
|
|
102
101
|
async def _serve(self) -> None:
|
|
103
102
|
self.server = uvicorn.Server(uvicorn.Config(
|
|
@@ -107,6 +106,7 @@ class HttpCallbackListener(ListenerEngine):
|
|
|
107
106
|
log_level="info"
|
|
108
107
|
))
|
|
109
108
|
await self.server.serve()
|
|
109
|
+
self.server = None
|
|
110
110
|
|
|
111
111
|
async def _shutdown(self) -> None:
|
|
112
112
|
if self.server:
|