model-compose 0.1.6__tar.gz → 0.1.8__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.6/src/model_compose.egg-info → model_compose-0.1.8}/PKG-INFO +2 -1
- {model_compose-0.1.6 → model_compose-0.1.8}/pyproject.toml +2 -1
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/component/engine/__init__.py +3 -0
- model_compose-0.1.8/src/mindor/core/component/engine/mcp_client.py +19 -0
- model_compose-0.1.8/src/mindor/core/component/engine/mcp_server.py +19 -0
- model_compose-0.1.8/src/mindor/core/component/engine/shell.py +80 -0
- model_compose-0.1.8/src/mindor/core/controller/engine/mcp_server.py +158 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/controller/runner/mcp_client.py +3 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/controller/webui/gradio.py +21 -19
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/controller/webui/webui.py +4 -19
- model_compose-0.1.8/src/mindor/core/gateway/engine/ssh_tunnel.py +25 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/utils/http_client.py +1 -1
- model_compose-0.1.8/src/mindor/core/utils/mcp_client.py +4 -0
- model_compose-0.1.8/src/mindor/core/utils/ssh_client.py +4 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/utils/streaming.py +1 -1
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/utils/template.py +26 -13
- {model_compose-0.1.6/src/mindor/core/controller/webui → model_compose-0.1.8/src/mindor/core/workflow}/schema.py +105 -45
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/action/impl/__init__.py +1 -0
- model_compose-0.1.8/src/mindor/dsl/schema/action/impl/shell.py +10 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/component/component.py +2 -1
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/component/impl/__init__.py +1 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/component/impl/http_server.py +1 -1
- model_compose-0.1.8/src/mindor/dsl/schema/component/impl/shell.py +19 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/component/impl/types.py +1 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/controller/impl/common.py +1 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/controller/impl/mcp_server.py +2 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/gateway/gateway.py +2 -1
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/gateway/impl/__init__.py +1 -0
- model_compose-0.1.8/src/mindor/dsl/schema/gateway/impl/ssh_tunnel.py +7 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/gateway/impl/types.py +1 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/workflow.py +38 -27
- {model_compose-0.1.6 → model_compose-0.1.8/src/model_compose.egg-info}/PKG-INFO +2 -1
- {model_compose-0.1.6 → model_compose-0.1.8}/src/model_compose.egg-info/SOURCES.txt +10 -1
- {model_compose-0.1.6 → model_compose-0.1.8}/src/model_compose.egg-info/requires.txt +1 -0
- model_compose-0.1.6/src/mindor/core/controller/engine/mcp_server.py +0 -28
- {model_compose-0.1.6 → model_compose-0.1.8}/LICENSE +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/README.md +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/setup.cfg +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/cli/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/cli/compose.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/component/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/component/component.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/component/engine/base.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/component/engine/context.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/component/engine/http_client.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/component/engine/http_server.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/compose/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/compose/compose.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/compose/manager.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/controller/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/controller/controller.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/controller/engine/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/controller/engine/base.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/controller/engine/http_server.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/controller/runner/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/controller/runner/client.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/controller/runner/http_client.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/controller/runner/runner.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/controller/webui/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/gateway/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/gateway/engine/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/gateway/engine/base.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/gateway/engine/http_tunnel.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/gateway/gateway.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/listener/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/listener/engine/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/listener/engine/base.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/listener/engine/http_callback.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/listener/listener.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/runtime/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/runtime/env.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/services/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/services/async_service.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/utils/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/utils/expiring.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/utils/http_request.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/utils/image.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/utils/time.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/utils/workqueue.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/workflow/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/workflow/context.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/workflow/job.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/core/workflow/workflow.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/loader.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/action/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/action/action.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/action/impl/common.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/action/impl/http_client.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/action/impl/http_server.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/action/impl/mcp_client.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/action/impl/mcp_server.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/component/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/component/impl/common.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/component/impl/http_client.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/component/impl/mcp_client.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/component/impl/mcp_server.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/compose.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/controller/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/controller/controller.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/controller/impl/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/controller/impl/http_server.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/controller/impl/types.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/controller/impl/webui.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/gateway/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/gateway/impl/common.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/gateway/impl/http_tunnel.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/listener/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/listener/impl/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/listener/impl/common.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/listener/impl/http_callback.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/listener/impl/types.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/listener/listener.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/runtime/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/runtime/impl/__init__.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/runtime/impl/common.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/runtime/impl/docker.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/runtime/impl/types.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/mindor/dsl/schema/runtime/runtime.py +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/model_compose.egg-info/dependency_links.txt +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/model_compose.egg-info/entry_points.txt +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/src/model_compose.egg-info/top_level.txt +0 -0
- {model_compose-0.1.6 → model_compose-0.1.8}/tests/test_cli.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: model-compose
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8
|
|
4
4
|
Summary: model-compose: Declarative AI Model and Workflow Orchestrator (part of the Mindor project)
|
|
5
5
|
Author-email: Hanyeol Cho <hanyeol.cho@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -15,6 +15,7 @@ Requires-Dist: aiohttp
|
|
|
15
15
|
Requires-Dist: requests
|
|
16
16
|
Requires-Dist: fastapi
|
|
17
17
|
Requires-Dist: uvicorn
|
|
18
|
+
Requires-Dist: mcp
|
|
18
19
|
Requires-Dist: pyngrok
|
|
19
20
|
Requires-Dist: ulid
|
|
20
21
|
Requires-Dist: gradio
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "model-compose"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.8"
|
|
4
4
|
description = "model-compose: Declarative AI Model and Workflow Orchestrator (part of the Mindor project)"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Hanyeol Cho", email = "hanyeol.cho@gmail.com" }
|
|
@@ -16,6 +16,7 @@ dependencies = [
|
|
|
16
16
|
"requests",
|
|
17
17
|
"fastapi",
|
|
18
18
|
"uvicorn",
|
|
19
|
+
"mcp",
|
|
19
20
|
"pyngrok",
|
|
20
21
|
"ulid",
|
|
21
22
|
"gradio",
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
|
|
2
|
+
from mindor.dsl.schema.component import McpClientComponentConfig
|
|
3
|
+
from .base import ComponentEngine, ComponentType, ComponentEngineMap, ActionConfig
|
|
4
|
+
from .context import ComponentContext
|
|
5
|
+
|
|
6
|
+
class McpClientComponent(ComponentEngine):
|
|
7
|
+
def __init__(self, id: str, config: McpClientComponentConfig, env: Dict[str, str], daemon: bool):
|
|
8
|
+
super().__init__(id, config, env, daemon)
|
|
9
|
+
|
|
10
|
+
async def _serve(self) -> None:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
async def _shutdown(self) -> None:
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
async def _run(self, action: ActionConfig, context: ComponentContext) -> Any:
|
|
17
|
+
return {}
|
|
18
|
+
|
|
19
|
+
ComponentEngineMap[ComponentType.MCP_CLIENT] = McpClientComponent
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
|
|
2
|
+
from mindor.dsl.schema.component import McpServerComponentConfig
|
|
3
|
+
from .base import ComponentEngine, ComponentType, ComponentEngineMap, ActionConfig
|
|
4
|
+
from .context import ComponentContext
|
|
5
|
+
|
|
6
|
+
class McpServerComponent(ComponentEngine):
|
|
7
|
+
def __init__(self, id: str, config: McpServerComponentConfig, env: Dict[str, str], daemon: bool):
|
|
8
|
+
super().__init__(id, config, env, daemon)
|
|
9
|
+
|
|
10
|
+
async def _serve(self) -> None:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
async def _shutdown(self) -> None:
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
async def _run(self, action: ActionConfig, context: ComponentContext) -> Any:
|
|
17
|
+
return {}
|
|
18
|
+
|
|
19
|
+
ComponentEngineMap[ComponentType.MCP_SERVER] = McpServerComponent
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
|
|
2
|
+
from mindor.dsl.schema.component import ShellComponentConfig
|
|
3
|
+
from mindor.dsl.schema.action import ActionConfig, ShellActionConfig
|
|
4
|
+
from .base import ComponentEngine, ComponentType, ComponentEngineMap, ActionConfig
|
|
5
|
+
from .context import ComponentContext
|
|
6
|
+
from asyncio.subprocess import Process
|
|
7
|
+
import asyncio, os
|
|
8
|
+
|
|
9
|
+
class ShellAction:
|
|
10
|
+
def __init__(self, base_dir: Optional[str], env: Optional[Dict[str, str]], config: ShellActionConfig):
|
|
11
|
+
self.base_dir: Optional[str] = base_dir
|
|
12
|
+
self.env: Optional[Dict[str, str]] = env
|
|
13
|
+
self.config: ShellActionConfig = config
|
|
14
|
+
|
|
15
|
+
async def run(self, context: ComponentContext) -> Any:
|
|
16
|
+
working_dir = self._resolve_working_directory()
|
|
17
|
+
env = { **(self.env or {}), **(self.config.env or {}) }
|
|
18
|
+
|
|
19
|
+
result = await self._run_command(self.config.command, working_dir, env, self.config.timeout)
|
|
20
|
+
context.register_source("result", result)
|
|
21
|
+
|
|
22
|
+
return (await context.render_template(self.config.output, ignore_files=True)) if self.config.output else result
|
|
23
|
+
|
|
24
|
+
async def _run_command(self, command: List[str], working_dir: str, env: Dict[str, str], timeout: Optional[float]) -> Dict[str, Any]:
|
|
25
|
+
process = await asyncio.create_subprocess_exec(
|
|
26
|
+
*command,
|
|
27
|
+
cwd=working_dir,
|
|
28
|
+
env={**os.environ, **env},
|
|
29
|
+
stdout=asyncio.subprocess.PIPE,
|
|
30
|
+
stderr=asyncio.subprocess.PIPE
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=timeout)
|
|
35
|
+
except asyncio.TimeoutError:
|
|
36
|
+
if await self._kill_process(process):
|
|
37
|
+
raise RuntimeError(f"Command timed out: {' '.join(command)}")
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
"stdout": stdout.decode().strip(),
|
|
41
|
+
"stderr": stderr.decode().strip(),
|
|
42
|
+
"exit_code": process.returncode
|
|
43
|
+
}
|
|
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
|
+
|
|
56
|
+
def _resolve_working_directory(self) -> str:
|
|
57
|
+
working_dir = self.config.working_dir
|
|
58
|
+
if working_dir:
|
|
59
|
+
if self.base_dir:
|
|
60
|
+
working_dir = os.path.abspath(os.path.join(self.base_dir, working_dir))
|
|
61
|
+
else:
|
|
62
|
+
working_dir = os.path.abspath(working_dir)
|
|
63
|
+
else:
|
|
64
|
+
working_dir = self.base_dir or os.getcwd()
|
|
65
|
+
return working_dir
|
|
66
|
+
|
|
67
|
+
class ShellComponent(ComponentEngine):
|
|
68
|
+
def __init__(self, id: str, config: ShellComponentConfig, env: Dict[str, str], daemon: bool):
|
|
69
|
+
super().__init__(id, config, env, daemon)
|
|
70
|
+
|
|
71
|
+
async def _serve(self) -> None:
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
async def _shutdown(self) -> None:
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
async def _run(self, action: ActionConfig, context: ComponentContext) -> Any:
|
|
78
|
+
return await ShellAction(self.config.base_dir, self.config.env, action).run(context)
|
|
79
|
+
|
|
80
|
+
ComponentEngineMap[ComponentType.SHELL] = ShellComponent
|
|
@@ -0,0 +1,158 @@
|
|
|
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
|
+
|
|
154
|
+
async def _shutdown(self) -> None:
|
|
155
|
+
if self.server:
|
|
156
|
+
self.server.should_exit = True
|
|
157
|
+
|
|
158
|
+
ControllerEngineMap[ControllerType.MCP_SERVER] = McpServerController
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
|
|
2
2
|
from mindor.dsl.schema.controller import ControllerConfig
|
|
3
|
+
from mindor.core.utils.mcp_client import McpClient
|
|
3
4
|
from .runner import ControllerClient
|
|
4
5
|
|
|
5
6
|
class McpControllerClient(ControllerClient):
|
|
6
7
|
def __init__(self, config: ControllerConfig):
|
|
7
8
|
super().__init__(config)
|
|
8
9
|
|
|
10
|
+
self.client: McpClient = McpClient()
|
|
11
|
+
|
|
9
12
|
async def run_workflow(self, workflow_id: Optional[str], input: Any) -> Any:
|
|
10
13
|
pass
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Callable, Awaitable, Any
|
|
2
2
|
from mindor.dsl.schema.workflow import WorkflowVariableConfig, WorkflowVariableGroupConfig
|
|
3
|
+
from mindor.core.workflow.schema import WorkflowSchema
|
|
3
4
|
from mindor.core.utils.streaming import StreamResource, Base64StreamResource
|
|
4
5
|
from mindor.core.utils.streaming import save_stream_to_temporary_file
|
|
5
6
|
from mindor.core.utils.http_request import create_upload_file
|
|
6
7
|
from mindor.core.utils.http_client import HttpClient
|
|
7
8
|
from mindor.core.utils.image import load_image_from_stream
|
|
8
|
-
from .schema import WorkflowSchema
|
|
9
|
-
from starlette.datastructures import UploadFile
|
|
10
9
|
import gradio as gr
|
|
11
|
-
import json
|
|
10
|
+
import json
|
|
12
11
|
|
|
13
12
|
class ComponentGroup:
|
|
14
13
|
def __init__(self, group: gr.Component, components: List[gr.Component]):
|
|
@@ -48,9 +47,9 @@ class GradioWebUIBuilder:
|
|
|
48
47
|
output_components = gr.Textbox(label="", lines=8, interactive=False, show_copy_button=True)
|
|
49
48
|
|
|
50
49
|
async def _run_workflow(*args):
|
|
51
|
-
input = await self.
|
|
50
|
+
input = await self._build_input_value(args, workflow.input)
|
|
52
51
|
output = await runner(input)
|
|
53
|
-
output = await self.
|
|
52
|
+
output = await self._flatten_output_value(output, workflow.output)
|
|
54
53
|
return output[0] if len(output) == 1 else output
|
|
55
54
|
|
|
56
55
|
run_button.click(
|
|
@@ -63,18 +62,15 @@ class GradioWebUIBuilder:
|
|
|
63
62
|
|
|
64
63
|
def _build_input_component(self, variable: WorkflowVariableConfig) -> gr.Component:
|
|
65
64
|
label = (variable.name or "") + (" *" if variable.required else "") + (f" (default: {variable.default})" if variable.default else "")
|
|
66
|
-
info = variable.description or ""
|
|
65
|
+
info = variable.get_annotation_value("description") or ""
|
|
67
66
|
default = variable.default
|
|
68
67
|
|
|
69
68
|
if variable.type == "string" or variable.format in [ "base64", "url" ]:
|
|
70
69
|
return gr.Textbox(label=label, value="", info=info)
|
|
71
70
|
|
|
72
|
-
if variable.type
|
|
73
|
-
return gr.Number(label=label, value="",
|
|
74
|
-
|
|
75
|
-
if variable.type == "integer":
|
|
76
|
-
return gr.Number(label=label, value="", precision=0, info=info)
|
|
77
|
-
|
|
71
|
+
if variable.type in [ "integer", "number" ]:
|
|
72
|
+
return gr.Number(label=label, value="", info=info)
|
|
73
|
+
|
|
78
74
|
if variable.type == "boolean":
|
|
79
75
|
return gr.Checkbox(label=label, value=default or False, info=info)
|
|
80
76
|
|
|
@@ -95,16 +91,16 @@ class GradioWebUIBuilder:
|
|
|
95
91
|
|
|
96
92
|
return gr.Textbox(label=label, value=default, info=f"Unsupported type: {variable.type}")
|
|
97
93
|
|
|
98
|
-
async def
|
|
94
|
+
async def _build_input_value(self, arguments: List[Any], variables: List[WorkflowVariableConfig]) -> Any:
|
|
99
95
|
if len(variables) == 1 and not variables[0].name:
|
|
100
|
-
|
|
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)
|
|
101
99
|
|
|
102
100
|
input: Dict[str, Any] = {}
|
|
103
|
-
|
|
104
101
|
for value, variable in zip(arguments, variables):
|
|
105
102
|
type, subtype, format = variable.type.value, variable.subtype, variable.format.value if variable.format else None
|
|
106
103
|
input[variable.name] = await self._convert_input_value(value, type, subtype, format, variable.internal)
|
|
107
|
-
|
|
108
104
|
return input
|
|
109
105
|
|
|
110
106
|
async def _convert_input_value(self, value: Any, type: str, subtype: Optional[str], format: Optional[str], internal: bool) -> Any:
|
|
@@ -126,11 +122,17 @@ class GradioWebUIBuilder:
|
|
|
126
122
|
return groups
|
|
127
123
|
|
|
128
124
|
label = variable.name or ""
|
|
129
|
-
info = variable.description or ""
|
|
125
|
+
info = variable.get_annotation_value("description") or ""
|
|
130
126
|
|
|
131
127
|
if variable.type in [ "string", "base64" ]:
|
|
132
128
|
return gr.Textbox(label=label, interactive=False, show_copy_button=True, info=info)
|
|
133
129
|
|
|
130
|
+
if variable.type == "markdown":
|
|
131
|
+
return gr.Markdown(label=label)
|
|
132
|
+
|
|
133
|
+
if variable.type in [ "json", "object[]" ]:
|
|
134
|
+
return gr.JSON(label=label)
|
|
135
|
+
|
|
134
136
|
if variable.type == "image":
|
|
135
137
|
return gr.Image(label=label, interactive=False)
|
|
136
138
|
|
|
@@ -152,13 +154,13 @@ class GradioWebUIBuilder:
|
|
|
152
154
|
flattened.append(item)
|
|
153
155
|
return flattened
|
|
154
156
|
|
|
155
|
-
async def
|
|
157
|
+
async def _flatten_output_value(self, output: Any, variables: List[Union[WorkflowVariableConfig, WorkflowVariableGroupConfig]]) -> Any:
|
|
156
158
|
flattened = []
|
|
157
159
|
for variable in variables:
|
|
158
160
|
if isinstance(variable, WorkflowVariableGroupConfig):
|
|
159
161
|
group = output[variable.name] if variable.name else output
|
|
160
162
|
for value in group:
|
|
161
|
-
flattened.extend(await self.
|
|
163
|
+
flattened.extend(await self._flatten_output_value(value, variable.variables))
|
|
162
164
|
else:
|
|
163
165
|
value = output[variable.name] if variable.name else output
|
|
164
166
|
type, subtype, format = variable.type.value, variable.subtype, variable.format.value if variable.format else None
|
|
@@ -3,14 +3,13 @@ from mindor.dsl.schema.controller import ControllerWebUIConfig, ControllerConfig
|
|
|
3
3
|
from mindor.dsl.schema.component import ComponentConfig
|
|
4
4
|
from mindor.dsl.schema.workflow import WorkflowConfig
|
|
5
5
|
from mindor.core.controller.runner import ControllerRunner
|
|
6
|
+
from mindor.core.workflow.schema import create_workflow_schema
|
|
6
7
|
from mindor.core.services import AsyncService
|
|
7
|
-
from .schema import WorkflowSchema, WorkflowInputVariableResolver, WorkflowOutputVariableResolver
|
|
8
8
|
from .gradio import GradioWebUIBuilder
|
|
9
9
|
|
|
10
|
-
from fastapi import FastAPI
|
|
11
|
-
import asyncio, uvicorn
|
|
10
|
+
from fastapi import FastAPI
|
|
12
11
|
from gradio import mount_gradio_app
|
|
13
|
-
import gradio
|
|
12
|
+
import uvicorn, gradio
|
|
14
13
|
|
|
15
14
|
class ControllerWebUI(AsyncService):
|
|
16
15
|
def __init__(
|
|
@@ -38,7 +37,7 @@ class ControllerWebUI(AsyncService):
|
|
|
38
37
|
def _configure_driver(self) -> None:
|
|
39
38
|
if self.config.driver == "gradio":
|
|
40
39
|
blocks: gradio.Blocks = GradioWebUIBuilder().build(
|
|
41
|
-
schema=self.
|
|
40
|
+
schema=create_workflow_schema(self.workflows, self.components),
|
|
42
41
|
runner=self._run_workflow
|
|
43
42
|
)
|
|
44
43
|
self.app = mount_gradio_app(self.app, blocks, path="")
|
|
@@ -59,17 +58,3 @@ class ControllerWebUI(AsyncService):
|
|
|
59
58
|
|
|
60
59
|
async def _run_workflow(self, workflow_id: Optional[str], input: Any) -> Any:
|
|
61
60
|
return await self.runner.run_workflow(workflow_id, input)
|
|
62
|
-
|
|
63
|
-
def _resolve_workflow_schema(self) -> Dict[str, WorkflowSchema]:
|
|
64
|
-
schema: Dict[str, WorkflowSchema] = {}
|
|
65
|
-
|
|
66
|
-
for workflow_id, workflow in self.workflows.items():
|
|
67
|
-
schema[workflow_id] = WorkflowSchema(
|
|
68
|
-
name=workflow.name,
|
|
69
|
-
title=workflow.title,
|
|
70
|
-
description=workflow.description,
|
|
71
|
-
input=WorkflowInputVariableResolver().resolve(workflow, self.components),
|
|
72
|
-
output=WorkflowOutputVariableResolver().resolve(workflow, self.components)
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
return schema
|
|
@@ -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
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Optional, Dict, Tuple, AsyncIterator, Any
|
|
2
2
|
from .http_request import build_request_body, parse_options_header
|
|
3
3
|
from .streaming import StreamResource
|
|
4
4
|
from requests.structures import CaseInsensitiveDict
|
|
@@ -2,7 +2,7 @@ from typing import Optional, AsyncIterator
|
|
|
2
2
|
from abc import ABC, abstractmethod
|
|
3
3
|
from tempfile import NamedTemporaryFile
|
|
4
4
|
from aiofiles.threadpool.binary import AsyncBufferedReader
|
|
5
|
-
import
|
|
5
|
+
import io, base64
|
|
6
6
|
|
|
7
7
|
class StreamResource(ABC):
|
|
8
8
|
def __init__(self, content_type: Optional[str], filename: Optional[str]):
|
|
@@ -11,12 +11,13 @@ class TemplateRenderer:
|
|
|
11
11
|
self.source_resolver: Callable[[str], Awaitable[Any]] = source_resolver
|
|
12
12
|
self.patterns: Dict[str, re.Pattern] = {
|
|
13
13
|
"variable": re.compile(
|
|
14
|
-
r"""\$\{
|
|
15
|
-
([a-zA-Z_][^.\s]*)
|
|
16
|
-
(?:\.([^\s
|
|
17
|
-
(?:\s*as\s*([^\s
|
|
18
|
-
(?:\s*\|\s*([
|
|
19
|
-
|
|
14
|
+
r"""\$\{ # ${
|
|
15
|
+
(?:\s*([a-zA-Z_][^.\s]*)) # key: input, env, etc.
|
|
16
|
+
(?:\.([^\s|}]+))? # path: key, key.path[0], etc.
|
|
17
|
+
(?:\s*as\s*([^\s/;}]+)(?:/([^\s;}]+))?(?:;([^\s}]+))?)? # type/subtype;format
|
|
18
|
+
(?:\s*\|\s*((?:\$\{[^}]+\}|\\[$@{}]|(?!\s*(?:@\(|\$\{)).)+))? # default value after `|`
|
|
19
|
+
(?:\s*(@\(\s*[\w]+\s+(?:\\[$@{}]|(?!\s*\$\{).)+\)))? # annotations
|
|
20
|
+
\s*\}""", # }
|
|
20
21
|
re.VERBOSE,
|
|
21
22
|
),
|
|
22
23
|
"keypath": re.compile(r"[-_\w]+|\[\d+\]"),
|
|
@@ -39,18 +40,19 @@ class TemplateRenderer:
|
|
|
39
40
|
|
|
40
41
|
async def _render_text(self, text: str, ignore_files: bool) -> Any:
|
|
41
42
|
while True:
|
|
42
|
-
|
|
43
|
+
m = self.patterns["variable"].search(text)
|
|
43
44
|
|
|
44
|
-
if not
|
|
45
|
+
if not m:
|
|
45
46
|
break
|
|
46
47
|
|
|
47
|
-
key, path, type, subtype, format, default, variable = (*
|
|
48
|
+
key, path, type, subtype, format, default, variable = (*m.group(1, 2, 3, 4, 5, 6), m.group(0))
|
|
48
49
|
try:
|
|
49
50
|
value = self._resolve_by_path(await self.source_resolver(key), path)
|
|
50
51
|
except Exception:
|
|
51
52
|
value = None
|
|
52
53
|
|
|
53
|
-
value
|
|
54
|
+
if value is None and default is not None:
|
|
55
|
+
value = await self._render_text(default, ignore_files)
|
|
54
56
|
|
|
55
57
|
if type and value is not None:
|
|
56
58
|
value = await self._convert_value_to_type(value, type, subtype, format, ignore_files)
|
|
@@ -89,13 +91,24 @@ class TemplateRenderer:
|
|
|
89
91
|
|
|
90
92
|
if type == "integer":
|
|
91
93
|
return int(value)
|
|
92
|
-
|
|
93
|
-
if type == "json":
|
|
94
|
-
return json.loads(value)
|
|
95
94
|
|
|
96
95
|
if type == "boolean":
|
|
97
96
|
return str(value).lower() in [ "true", "1" ]
|
|
97
|
+
|
|
98
|
+
if type == "json":
|
|
99
|
+
if isinstance(value, str):
|
|
100
|
+
return json.loads(value)
|
|
101
|
+
return value
|
|
98
102
|
|
|
103
|
+
if type == "object[]":
|
|
104
|
+
if isinstance(value, list):
|
|
105
|
+
objects = [ v for v in value if isinstance(v, dict) ]
|
|
106
|
+
if subtype:
|
|
107
|
+
paths = [ (path, path.split(".")[-1]) for path in subtype.split(",") ]
|
|
108
|
+
return [ { key: self._resolve_by_path(obj, path) for path, key in paths } for obj in objects ]
|
|
109
|
+
return objects
|
|
110
|
+
return []
|
|
111
|
+
|
|
99
112
|
if type == "base64":
|
|
100
113
|
if isinstance(value, StreamResource):
|
|
101
114
|
return await encode_stream_to_base64(value)
|