model-compose 0.0.2__tar.gz → 0.0.4__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.
Files changed (87) hide show
  1. {model_compose-0.0.2 → model_compose-0.0.4}/PKG-INFO +6 -2
  2. {model_compose-0.0.2 → model_compose-0.0.4}/pyproject.toml +5 -3
  3. model_compose-0.0.4/src/mindor/cli/compose.py +180 -0
  4. model_compose-0.0.4/src/mindor/core/component/__init__.py +1 -0
  5. model_compose-0.0.4/src/mindor/core/component/component.py +40 -0
  6. model_compose-0.0.4/src/mindor/core/component/engine/__init__.py +4 -0
  7. model_compose-0.0.4/src/mindor/core/component/engine/base.py +123 -0
  8. model_compose-0.0.4/src/mindor/core/component/engine/context.py +24 -0
  9. model_compose-0.0.4/src/mindor/core/component/engine/http_client.py +87 -0
  10. model_compose-0.0.4/src/mindor/core/component/engine/http_server.py +19 -0
  11. model_compose-0.0.4/src/mindor/core/compose/__init__.py +1 -0
  12. model_compose-0.0.4/src/mindor/core/compose/compose.py +18 -0
  13. model_compose-0.0.4/src/mindor/core/compose/manager.py +28 -0
  14. model_compose-0.0.4/src/mindor/core/controller/__init__.py +2 -0
  15. model_compose-0.0.4/src/mindor/core/controller/controller.py +11 -0
  16. model_compose-0.0.4/src/mindor/core/controller/engine/__init__.py +3 -0
  17. model_compose-0.0.4/src/mindor/core/controller/engine/base.py +152 -0
  18. model_compose-0.0.4/src/mindor/core/controller/engine/http_server.py +76 -0
  19. model_compose-0.0.4/src/mindor/core/controller/engine/mcp_server.py +17 -0
  20. model_compose-0.0.4/src/mindor/core/runtime/env.py +21 -0
  21. model_compose-0.0.4/src/mindor/core/utils/expiring.py +35 -0
  22. model_compose-0.0.4/src/mindor/core/utils/template.py +59 -0
  23. model_compose-0.0.4/src/mindor/core/utils/workqueue.py +56 -0
  24. model_compose-0.0.4/src/mindor/core/workflow/__init__.py +1 -0
  25. model_compose-0.0.4/src/mindor/core/workflow/context.py +27 -0
  26. model_compose-0.0.4/src/mindor/core/workflow/job.py +25 -0
  27. model_compose-0.0.4/src/mindor/core/workflow/workflow.py +156 -0
  28. model_compose-0.0.4/src/mindor/dsl/loader.py +39 -0
  29. model_compose-0.0.4/src/mindor/dsl/schema/action/__init__.py +1 -0
  30. model_compose-0.0.4/src/mindor/dsl/schema/action/action.py +13 -0
  31. model_compose-0.0.4/src/mindor/dsl/schema/action/impl/__init__.py +5 -0
  32. model_compose-0.0.4/src/mindor/dsl/schema/action/impl/common.py +6 -0
  33. model_compose-0.0.4/src/mindor/dsl/schema/action/impl/http_client.py +42 -0
  34. model_compose-0.0.4/src/mindor/dsl/schema/action/impl/http_server.py +8 -0
  35. model_compose-0.0.4/src/mindor/dsl/schema/action/impl/mcp_client.py +7 -0
  36. model_compose-0.0.4/src/mindor/dsl/schema/action/impl/mcp_server.py +8 -0
  37. model_compose-0.0.4/src/mindor/dsl/schema/component/__init__.py +1 -0
  38. model_compose-0.0.4/src/mindor/dsl/schema/component/component.py +13 -0
  39. model_compose-0.0.4/src/mindor/dsl/schema/component/impl/__init__.py +5 -0
  40. model_compose-0.0.4/src/mindor/dsl/schema/component/impl/common.py +9 -0
  41. model_compose-0.0.4/src/mindor/dsl/schema/component/impl/http_client.py +33 -0
  42. model_compose-0.0.4/src/mindor/dsl/schema/component/impl/http_server.py +11 -0
  43. model_compose-0.0.4/src/mindor/dsl/schema/component/impl/mcp_client.py +10 -0
  44. model_compose-0.0.4/src/mindor/dsl/schema/component/impl/mcp_server.py +9 -0
  45. model_compose-0.0.4/src/mindor/dsl/schema/component/impl/types.py +7 -0
  46. model_compose-0.0.4/src/mindor/dsl/schema/compose.py +28 -0
  47. model_compose-0.0.4/src/mindor/dsl/schema/controller/__init__.py +1 -0
  48. model_compose-0.0.4/src/mindor/dsl/schema/controller/controller.py +11 -0
  49. model_compose-0.0.4/src/mindor/dsl/schema/controller/impl/__init__.py +3 -0
  50. model_compose-0.0.4/src/mindor/dsl/schema/controller/impl/common.py +9 -0
  51. model_compose-0.0.4/src/mindor/dsl/schema/controller/impl/http_server.py +10 -0
  52. model_compose-0.0.4/src/mindor/dsl/schema/controller/impl/mcp_server.py +8 -0
  53. model_compose-0.0.4/src/mindor/dsl/schema/controller/impl/types.py +5 -0
  54. model_compose-0.0.4/src/mindor/dsl/schema/runtime/__init__.py +1 -0
  55. model_compose-0.0.4/src/mindor/dsl/schema/runtime/impl/__init__.py +2 -0
  56. model_compose-0.0.4/src/mindor/dsl/schema/runtime/impl/common.py +6 -0
  57. model_compose-0.0.4/src/mindor/dsl/schema/runtime/impl/docker.py +7 -0
  58. model_compose-0.0.4/src/mindor/dsl/schema/runtime/impl/types.py +4 -0
  59. model_compose-0.0.4/src/mindor/dsl/schema/runtime/runtime.py +10 -0
  60. model_compose-0.0.4/src/mindor/dsl/schema/workflow.py +25 -0
  61. {model_compose-0.0.2 → model_compose-0.0.4}/src/model_compose.egg-info/PKG-INFO +6 -2
  62. model_compose-0.0.4/src/model_compose.egg-info/SOURCES.txt +75 -0
  63. model_compose-0.0.4/src/model_compose.egg-info/entry_points.txt +2 -0
  64. model_compose-0.0.4/src/model_compose.egg-info/requires.txt +9 -0
  65. model_compose-0.0.2/src/mindor/cli/commands/down.py +0 -6
  66. model_compose-0.0.2/src/mindor/cli/commands/run.py +0 -9
  67. model_compose-0.0.2/src/mindor/cli/commands/up.py +0 -6
  68. model_compose-0.0.2/src/mindor/cli/main.py +0 -15
  69. model_compose-0.0.2/src/mindor/core/controller.py +0 -5
  70. model_compose-0.0.2/src/mindor/core/workflow.py +0 -4
  71. model_compose-0.0.2/src/mindor/dsl/parser.py +0 -0
  72. model_compose-0.0.2/src/model_compose.egg-info/SOURCES.txt +0 -24
  73. model_compose-0.0.2/src/model_compose.egg-info/entry_points.txt +0 -2
  74. model_compose-0.0.2/src/model_compose.egg-info/requires.txt +0 -5
  75. {model_compose-0.0.2 → model_compose-0.0.4}/LICENSE +0 -0
  76. {model_compose-0.0.2 → model_compose-0.0.4}/README.md +0 -0
  77. {model_compose-0.0.2 → model_compose-0.0.4}/setup.cfg +0 -0
  78. {model_compose-0.0.2 → model_compose-0.0.4}/src/mindor/__init__.py +0 -0
  79. {model_compose-0.0.2 → model_compose-0.0.4}/src/mindor/cli/__init__.py +0 -0
  80. {model_compose-0.0.2/src/mindor/cli/commands → model_compose-0.0.4/src/mindor/core}/__init__.py +0 -0
  81. {model_compose-0.0.2/src/mindor/core → model_compose-0.0.4/src/mindor/core/runtime}/__init__.py +0 -0
  82. {model_compose-0.0.2/src/mindor/dsl → model_compose-0.0.4/src/mindor/core/utils}/__init__.py +0 -0
  83. /model_compose-0.0.2/src/mindor/core/model.py → /model_compose-0.0.4/src/mindor/dsl/__init__.py +0 -0
  84. /model_compose-0.0.2/src/mindor/dsl/loader.py → /model_compose-0.0.4/src/mindor/dsl/schema/__init__.py +0 -0
  85. {model_compose-0.0.2 → model_compose-0.0.4}/src/model_compose.egg-info/dependency_links.txt +0 -0
  86. {model_compose-0.0.2 → model_compose-0.0.4}/src/model_compose.egg-info/top_level.txt +0 -0
  87. {model_compose-0.0.2 → model_compose-0.0.4}/tests/test_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: model-compose
3
- Version: 0.0.2
3
+ Version: 0.0.4
4
4
  Summary: model-compose: Declarative AI Model and Workflow Orchestrator (from Mindor)
5
5
  Author-email: Hanyeol Cho <hanyeol.cho@gmail.com>
6
6
  License-Expression: MIT
@@ -11,7 +11,11 @@ Requires-Dist: click
11
11
  Requires-Dist: pyyaml
12
12
  Requires-Dist: pydantic
13
13
  Requires-Dist: python-dotenv
14
- Requires-Dist: jinja2
14
+ Requires-Dist: aiohttp
15
+ Requires-Dist: requests
16
+ Requires-Dist: fastapi
17
+ Requires-Dist: uvicorn
18
+ Requires-Dist: ulid
15
19
  Dynamic: license-file
16
20
 
17
21
  # model-compose: Declarative AI Model and Workflow Orchestrator (from Mindor)
@@ -1,16 +1,18 @@
1
1
  [project]
2
2
  name = "model-compose"
3
- version = "0.0.2"
3
+ version = "0.0.4"
4
4
  description = "model-compose: Declarative AI Model and Workflow Orchestrator (from Mindor)"
5
5
  authors = [
6
6
  { name = "Hanyeol Cho", email = "hanyeol.cho@gmail.com" }
7
7
  ]
8
8
  readme = "README.md"
9
9
  license = "MIT"
10
- dependencies = ["click", "pyyaml", "pydantic", "python-dotenv", "jinja2"]
10
+ dependencies = [
11
+ "click", "pyyaml", "pydantic", "python-dotenv", "aiohttp", "requests", "fastapi", "uvicorn", "ulid"
12
+ ]
11
13
 
12
14
  [project.scripts]
13
- model-compose = "mindor.cli.main:app"
15
+ model-compose = "mindor.cli.compose:compose_command"
14
16
 
15
17
  [project.urls]
16
18
  "Homepage" = "https://github.com/hanyeol/model-compose"
@@ -0,0 +1,180 @@
1
+ import click
2
+ import json
3
+ from pathlib import Path
4
+ import asyncio
5
+
6
+ from mindor.dsl.loader import load_compose_config
7
+ from mindor.core.runtime.env import load_env_files
8
+ from mindor.core.compose import *
9
+
10
+ @click.group()
11
+ @click.option(
12
+ "--file", "-f", "config_files", multiple=True,
13
+ type=click.Path(exists=True, dir_okay=False, path_type=Path),
14
+ help="Compose configuration files",
15
+ )
16
+ @click.pass_context
17
+ def compose_command(ctx: click.Context, config_files: List[Path]) -> None:
18
+ """model-compose (from Mindor)"""
19
+ ctx.ensure_object(dict)
20
+ ctx.obj["config_files"] = list(config_files)
21
+
22
+ @click.command(name="up")
23
+ @click.option("-d", "--detach", is_flag=True, help="Run in detached mode.")
24
+ @click.option(
25
+ "--env-file", "env_files", multiple=True,
26
+ type=click.Path(exists=True, dir_okay=False, path_type=Path),
27
+ required=False,
28
+ help="Path to a .env file containing environment variables.",
29
+ )
30
+ @click.option(
31
+ "--env", "-e", "env_data", multiple=True,
32
+ help="Environment variable in the form KEY=VALUE. Repeatable.",
33
+ )
34
+ @click.pass_context
35
+ def up_command(
36
+ ctx: click.Context,
37
+ detach: bool,
38
+ env_files: List[Path],
39
+ env_data: List[str]
40
+ ) -> None:
41
+ config_files = ctx.obj.get("config_files", [])
42
+ async def _async_command():
43
+ try:
44
+ config = load_compose_config(".", config_files)
45
+ env = load_env_files(".", env_files or [])
46
+ await launch_services(config, detach, env)
47
+ except Exception as e:
48
+ click.echo(f"❌ {e}", err=True)
49
+ asyncio.run(_async_command())
50
+
51
+ @click.command(name="down")
52
+ @click.option(
53
+ "--env-file", "env_files", multiple=True,
54
+ type=click.Path(exists=True, dir_okay=False, path_type=Path),
55
+ required=False,
56
+ help="Path to a .env file containing environment variables.",
57
+ )
58
+ @click.option(
59
+ "--env", "-e", "env_data", multiple=True,
60
+ help="Environment variable in the form KEY=VALUE. Repeatable.",
61
+ )
62
+ @click.pass_context
63
+ def down_command(
64
+ ctx: click.Context,
65
+ env_files: List[Path],
66
+ env_data: List[str],
67
+ ) -> None:
68
+ config_files = ctx.obj.get("config_files", [])
69
+ async def _async_command():
70
+ try:
71
+ config = load_compose_config(".", config_files)
72
+ env = load_env_files(".", env_files or [])
73
+ await shutdown_services(config, env)
74
+ except Exception as e:
75
+ click.echo(f"❌ {e}", err=True)
76
+ asyncio.run(_async_command())
77
+
78
+ @click.command(name="start")
79
+ @click.option(
80
+ "--env-file", "env_files", multiple=True,
81
+ type=click.Path(exists=True, dir_okay=False, path_type=Path),
82
+ required=False,
83
+ help="Path to a .env file containing environment variables.",
84
+ )
85
+ @click.option(
86
+ "--env", "-e", "env_data", multiple=True,
87
+ help="Environment variable in the form KEY=VALUE. Repeatable.",
88
+ )
89
+ @click.pass_context
90
+ def start_command(
91
+ ctx: click.Context,
92
+ env_files: List[Path],
93
+ env_data: List[str],
94
+ ) -> None:
95
+ config_files = ctx.obj.get("config_files", [])
96
+ async def _async_command():
97
+ try:
98
+ config = load_compose_config(".", config_files)
99
+ env = load_env_files(".", env_files or [])
100
+ await start_services(config, env)
101
+ except Exception as e:
102
+ click.echo(f"❌ {e}", err=True)
103
+ asyncio.run(_async_command())
104
+
105
+ @click.command(name="stop")
106
+ @click.option(
107
+ "--env-file", "env_files", multiple=True,
108
+ type=click.Path(exists=True, dir_okay=False, path_type=Path),
109
+ required=False,
110
+ help="Path to a .env file containing environment variables.",
111
+ )
112
+ @click.option(
113
+ "--env", "-e", "env_data", multiple=True,
114
+ help="Environment variable in the form KEY=VALUE. Repeatable.",
115
+ )
116
+ @click.pass_context
117
+ def stop_command(
118
+ ctx: click.Context,
119
+ env_files: List[Path],
120
+ env_data: List[str],
121
+ ) -> None:
122
+ config_files = ctx.obj.get("config_files", [])
123
+ async def _async_command():
124
+ try:
125
+ config = load_compose_config(".", config_files)
126
+ env = load_env_files(".", env_files or [])
127
+ await stop_services(config, env)
128
+ except Exception as e:
129
+ click.echo(f"❌ {e}", err=True)
130
+ asyncio.run(_async_command())
131
+
132
+ @click.command(name="run")
133
+ @click.argument("workflow", required=False)
134
+ @click.option(
135
+ "--input", "-i", "input_json",
136
+ type=str,
137
+ required=False,
138
+ help="JSON input string for the workflow",
139
+ )
140
+ @click.option(
141
+ "--env-file", "env_files", multiple=True,
142
+ type=click.Path(exists=True, dir_okay=False, path_type=Path),
143
+ required=False,
144
+ help="Path to a .env file containing environment variables.",
145
+ )
146
+ @click.option(
147
+ "--env", "-e", "env_data", multiple=True,
148
+ help="Environment variable in the form KEY=VALUE. Repeatable.",
149
+ )
150
+ @click.pass_context
151
+ def run_command(
152
+ ctx: click.Context,
153
+ workflow: Optional[str],
154
+ input_json: Optional[str],
155
+ env_files: List[Path],
156
+ env_data: List[str],
157
+ ) -> None:
158
+ config_files = ctx.obj.get("config_files", [])
159
+ async def _async_command():
160
+ try:
161
+ config = load_compose_config(".", config_files)
162
+ env = load_env_files(".", env_files or [])
163
+ input = json.loads(input_json) if input_json else {}
164
+ state = await run_workflow(config, workflow, input, env)
165
+ click.echo(json.dumps(
166
+ state.error if state.error else state.output,
167
+ indent=2,
168
+ ensure_ascii=False
169
+ ))
170
+ except json.JSONDecodeError:
171
+ click.echo("❌ Invalid JSON provided for --input", err=True)
172
+ except Exception as e:
173
+ click.echo(f"❌ {e}", err=True)
174
+ asyncio.run(_async_command())
175
+
176
+ compose_command.add_command(up_command)
177
+ compose_command.add_command(down_command)
178
+ compose_command.add_command(start_command)
179
+ compose_command.add_command(stop_command)
180
+ compose_command.add_command(run_command)
@@ -0,0 +1 @@
1
+ from .component import *
@@ -0,0 +1,40 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from mindor.dsl.schema.component import ComponentConfig
3
+ from .engine import BaseComponent, ComponentEngineMap
4
+
5
+ ComponentInstances: Dict[str, BaseComponent] = {}
6
+
7
+ class ComponentResolver:
8
+ def __init__(self, components: Dict[str, ComponentConfig]):
9
+ self.components: Dict[str, ComponentConfig] = components
10
+
11
+ def resolve(self, component_id: Optional[str]) -> Tuple[str, ComponentConfig]:
12
+ component_id = component_id or self._find_default_id(self.components)
13
+
14
+ if not component_id in self.components:
15
+ raise ValueError(f"Component not found: {component_id}")
16
+
17
+ return component_id, self.components[component_id]
18
+
19
+ def _find_default_id(self, components: Dict[str, ComponentConfig]) -> str:
20
+ default_ids = [ component_id for component_id, component in components.items() if component.default ]
21
+
22
+ if len(default_ids) > 1:
23
+ raise ValueError("Multiple components have default: true")
24
+
25
+ if not default_ids and "__default__" not in components:
26
+ raise ValueError("No default component defined.")
27
+
28
+ return default_ids[0] if default_ids else "__default__"
29
+
30
+ def create_component(id: str, config: ComponentConfig, env: Dict[str, str], daemon: bool) -> BaseComponent:
31
+ try:
32
+ component = ComponentInstances[id] if id in ComponentInstances else None
33
+
34
+ if not component:
35
+ component = ComponentEngineMap[config.type](id, config, env, daemon)
36
+ ComponentInstances[id] = component
37
+
38
+ return component
39
+ except KeyError:
40
+ raise ValueError(f"Unsupported component type: {config.type}")
@@ -0,0 +1,4 @@
1
+ from .base import *
2
+ from .http_server import *
3
+ from .http_client import *
4
+
@@ -0,0 +1,123 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Callable, Any
3
+
4
+ from mindor.dsl.schema.component import ComponentConfig, ComponentType
5
+ from mindor.dsl.schema.action import ActionConfig
6
+ from mindor.core.utils.workqueue import WorkQueue
7
+ from .context import ComponentContext
8
+
9
+ from threading import Thread
10
+ import asyncio
11
+
12
+ class ActionResolver:
13
+ def __init__(self, actions: Dict[str, ActionConfig]):
14
+ self.actions = actions
15
+
16
+ def resolve(self, action_id: Optional[str]) -> Tuple[str, ActionConfig]:
17
+ action_id = action_id or self._find_default_id(self.actions)
18
+
19
+ if not action_id in self.actions:
20
+ raise ValueError(f"Action not found: {action_id}")
21
+
22
+ return action_id, self.actions[action_id]
23
+
24
+ def _find_default_id(self, actions: Dict[str, ActionConfig]) -> str:
25
+ default_ids = [ action_id for action_id, action in actions.items() if action.default ]
26
+
27
+ if len(default_ids) > 1:
28
+ raise ValueError("Multiple actions have default: true")
29
+
30
+ if not default_ids and "__default__" not in actions:
31
+ raise ValueError("No default action defined.")
32
+
33
+ return default_ids[0] if default_ids else "__default__"
34
+
35
+ class BaseComponent(ABC):
36
+ def __init__(self, id: str, config: ComponentConfig, env: Dict[str, str], daemon: bool):
37
+ self.id: str = id
38
+ self.config: ComponentConfig = config
39
+ self.env: Dict[str, str] = env
40
+ self.daemon: bool = daemon
41
+
42
+ self.queue: Optional[WorkQueue] = None
43
+ self.thread: Optional[Thread] = None
44
+ self.thread_loop: Optional[asyncio.AbstractEventLoop] = None
45
+ self.daemon_task: Optional[asyncio.Task] = None
46
+ self.started: bool = False
47
+
48
+ if self.config.max_concurrent_count > 0:
49
+ self.queue = WorkQueue(self.config.max_concurrent_count, self._run)
50
+
51
+ async def start(self, background: bool = False) -> None:
52
+ if background:
53
+ def _start_in_thread():
54
+ self.thread_loop = asyncio.new_event_loop()
55
+ asyncio.set_event_loop(self.thread_loop)
56
+ self.thread_loop.run_until_complete(self._start())
57
+
58
+ self.thread = Thread(target=_start_in_thread)
59
+ self.thread.start()
60
+ else:
61
+ await self._start()
62
+
63
+ async def stop(self) -> None:
64
+ if self.thread:
65
+ future = asyncio.run_coroutine_threadsafe(self._stop(), self.thread_loop)
66
+ future.result()
67
+ self.thread_loop.close()
68
+ self.thread_loop = None
69
+ self.thread.join()
70
+ self.thread = None
71
+ else:
72
+ await self._stop()
73
+
74
+ async def wait_until_stopped(self) -> None:
75
+ if self.thread:
76
+ self.thread.join()
77
+
78
+ if self.daemon_task:
79
+ await self.daemon_task
80
+
81
+ async def run(self, action_id: Union[str, None], input: Dict[str, Any]) -> Dict[str, Any]:
82
+ _, action = ActionResolver(self.config.actions).resolve(action_id)
83
+ context = ComponentContext(input, self.env)
84
+
85
+ if self.queue:
86
+ return await (await self.queue.schedule(action, context))
87
+
88
+ return await self._run(action, context)
89
+
90
+ async def _start(self) -> None:
91
+ if self.queue:
92
+ await self.queue.start()
93
+
94
+ self.started = True
95
+
96
+ if self.daemon:
97
+ if not self.thread:
98
+ self.daemon_task = asyncio.create_task(self._serve())
99
+ else:
100
+ await self._serve()
101
+
102
+ async def _stop(self) -> None:
103
+ if self.queue:
104
+ await self.queue.stop()
105
+
106
+ if self.daemon:
107
+ await self._shutdown()
108
+
109
+ self.started = False
110
+
111
+ @abstractmethod
112
+ async def _serve(self) -> None:
113
+ pass
114
+
115
+ @abstractmethod
116
+ async def _shutdown(self) -> None:
117
+ pass
118
+
119
+ @abstractmethod
120
+ async def _run(self, action: ActionConfig, context: ComponentContext) -> Dict[str, Any]:
121
+ pass
122
+
123
+ ComponentEngineMap: Dict[ComponentType, Type[BaseComponent]] = {}
@@ -0,0 +1,24 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from mindor.core.utils.template import TemplateRenderer
3
+
4
+ class ComponentContext:
5
+ def __init__(self, input: Dict[str, Any], env: Dict[str, str]):
6
+ self.input: Dict[str, Any] = input
7
+ self.env: Dict[str, str] = env
8
+ self.sources: Dict[str, Any] = {}
9
+ self.renderer: TemplateRenderer = TemplateRenderer(self._resolve_source)
10
+
11
+ def register_source(self, key: str, source: Any) -> None:
12
+ self.sources[key] = source
13
+
14
+ def render_template(self, data: Dict[str, Any]) -> Any:
15
+ return self.renderer.render(data)
16
+
17
+ def _resolve_source(self, key: str) -> Any:
18
+ if key in self.sources:
19
+ return self.sources[key]
20
+ if key == "input":
21
+ return self.input
22
+ if key == "env":
23
+ return self.env
24
+ raise KeyError(f"Unknown source: {key}")
@@ -0,0 +1,87 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from mindor.dsl.schema.component import HttpClientComponentConfig, HttpClientActionConfig, HttpUrl
3
+ from .base import BaseComponent, ComponentType, ComponentEngineMap, ActionConfig
4
+ from .context import ComponentContext
5
+
6
+ from requests.structures import CaseInsensitiveDict
7
+ from urllib.parse import urlencode, urljoin
8
+ import aiohttp, json
9
+
10
+ class HttpClientAction:
11
+ def __init__(self, base_url: Union[str, None], config: HttpClientActionConfig):
12
+ self.base_url: Union[HttpUrl, None] = base_url
13
+ self.config: HttpClientActionConfig = config
14
+
15
+ async def run(self, context: ComponentContext) -> Dict[str, Any]:
16
+ url = self._resolve_request_url(context)
17
+ method = context.render_template(self.config.method)
18
+ params = context.render_template(self.config.params)
19
+ body = context.render_template(self.config.body)
20
+ headers = context.render_template(self.config.headers)
21
+
22
+ response = await self._request(url, method, params, body, headers)
23
+
24
+ if not self.config.output:
25
+ return response or {}
26
+
27
+ if response:
28
+ context.register_source("response", response)
29
+
30
+ return context.render_template(self.config.output)
31
+
32
+ async def _request(self, url: str, method: str, params: Optional[Dict[str, Any]], body: Optional[Dict[str, Any]], headers: Optional[Dict[str, str]]) -> Union[dict, str, bytes]:
33
+ async with aiohttp.ClientSession() as session:
34
+ async with session.request(
35
+ method,
36
+ url,
37
+ params=params,
38
+ data=self._serialize_request_body(body, headers) if body is not None else None,
39
+ headers=headers
40
+ ) as response:
41
+ if response.status >= 400:
42
+ raise ValueError(f"Request failed with status {response.status}")
43
+
44
+ return await self._deserialize_response_body(response)
45
+
46
+ def _resolve_request_url(self, context: ComponentContext) -> str:
47
+ if self.base_url and self.config.path:
48
+ return context.render_template(str(self.base_url)) + context.render_template(self.config.path)
49
+
50
+ return context.render_template(str(self.config.endpoint))
51
+
52
+ def _serialize_request_body(self, body: Dict[str, Any], headers: Optional[Dict[str, str]]) -> str:
53
+ content_type = CaseInsensitiveDict(headers or {}).get("Content-Type", "").lower()
54
+
55
+ if content_type == "application/json":
56
+ return json.dumps(body)
57
+
58
+ if content_type == "application/x-www-form-urlencoded":
59
+ return urlencode(body)
60
+
61
+ return str(body)
62
+
63
+ async def _deserialize_response_body(self, response: aiohttp.ClientResponse) -> Union[dict, str, bytes]:
64
+ content_type = response.headers.get("Content-Type", "").lower()
65
+
66
+ if content_type == "application/json":
67
+ return await response.json()
68
+
69
+ if content_type.startswith("text/"):
70
+ return await response.text()
71
+
72
+ return await response.read()
73
+
74
+ class HttpClientComponent(BaseComponent):
75
+ def __init__(self, id: str, config: HttpClientComponentConfig, env: Dict[str, str], daemon: bool):
76
+ super().__init__(id, config, env, daemon)
77
+
78
+ async def _serve(self) -> None:
79
+ pass
80
+
81
+ async def _shutdown(self) -> None:
82
+ pass
83
+
84
+ async def _run(self, action: ActionConfig, context: ComponentContext) -> Dict[str, Any]:
85
+ return await HttpClientAction(self.config.base_url, action).run(context)
86
+
87
+ ComponentEngineMap[ComponentType.HTTP_CLIENT] = HttpClientComponent
@@ -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 HttpServerComponentConfig
3
+ from .base import BaseComponent, ComponentType, ComponentEngineMap, ActionConfig
4
+ from .context import ComponentContext
5
+
6
+ class HttpServerComponent(BaseComponent):
7
+ def __init__(self, id: str, config: HttpServerComponentConfig, 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) -> Dict[str, Any]:
17
+ return {}
18
+
19
+ ComponentEngineMap[ComponentType.HTTP_SERVER] = HttpServerComponent
@@ -0,0 +1 @@
1
+ from .compose import *
@@ -0,0 +1,18 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from mindor.dsl.schema.compose import ComposeConfig
3
+ from .manager import ComposeManager, TaskState
4
+
5
+ async def launch_services(config: ComposeConfig, detach: bool, env: Dict[str, str]):
6
+ await ComposeManager(config, env, daemon=True).launch_services(detach=detach)
7
+
8
+ async def shutdown_services(config: ComposeConfig, env: Dict[str, str]):
9
+ await ComposeManager(config, env, daemon=False).shutdown_services()
10
+
11
+ async def start_services(config: ComposeConfig, detach: bool, env: Dict[str, str]):
12
+ await ComposeManager(config, env, daemon=False).start_services()
13
+
14
+ async def stop_services(config: ComposeConfig, env: Dict[str, str]):
15
+ await ComposeManager(config, env, daemon=False).stop_services()
16
+
17
+ async def run_workflow(config: ComposeConfig, workflow_id: Optional[str], input: Dict[str, Any], env: Dict[str, str]) -> TaskState:
18
+ return await ComposeManager(config, env, daemon=False).run_workflow(workflow_id, input)
@@ -0,0 +1,28 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from mindor.dsl.schema.compose import ComposeConfig
3
+ from mindor.core.controller import BaseController, TaskState, create_controller
4
+
5
+ class ComposeManager:
6
+ def __init__(self, config: ComposeConfig, env: Dict[str, str], daemon: bool = True):
7
+ self.config: ComposeConfig = config
8
+ self.controller: BaseController = create_controller(self.config.controller, self.config.components, self.config.workflows, env, daemon)
9
+
10
+ async def launch_services(self, detach: bool, background: bool = False):
11
+ await self.controller.start(background=background)
12
+ await self.controller.wait_until_stopped()
13
+
14
+ async def shutdown_services(self):
15
+ await self.controller.stop()
16
+
17
+ async def start_services(self, background: bool = False):
18
+ await self.controller.start(background=background)
19
+ await self.controller.wait_until_stopped()
20
+
21
+ async def stop_services(self):
22
+ await self.controller.stop()
23
+
24
+ async def run_workflow(self, workflow_id: Optional[str], input: Dict[str, Any]) -> TaskState:
25
+ if not self.controller.started:
26
+ await self.controller.start()
27
+
28
+ return await self.controller.run_workflow(workflow_id, input)
@@ -0,0 +1,2 @@
1
+ from .controller import *
2
+ from .engine import TaskState
@@ -0,0 +1,11 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from mindor.dsl.schema.controller import ControllerConfig
3
+ from mindor.dsl.schema.component import ComponentConfig
4
+ from mindor.dsl.schema.workflow import WorkflowConfig
5
+ from .engine import BaseController, ControllerEngineMap
6
+
7
+ def create_controller(config: ControllerConfig, components: Dict[str, ComponentConfig], workflows: Dict[str, WorkflowConfig], env: Dict[str, str], daemon: bool) -> BaseController:
8
+ try:
9
+ return ControllerEngineMap[config.type](config, components, workflows, env, daemon)
10
+ except KeyError:
11
+ raise ValueError(f"Unsupported controller type: {config.type}")
@@ -0,0 +1,3 @@
1
+ from .base import *
2
+ from .http_server import *
3
+ from .mcp_server import *