model-compose 0.1.12__tar.gz → 0.2.0__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 (142) hide show
  1. {model_compose-0.1.12 → model_compose-0.2.0}/PKG-INFO +2 -2
  2. {model_compose-0.1.12 → model_compose-0.2.0}/pyproject.toml +2 -2
  3. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/component/engine/http_client.py +2 -13
  4. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/component/engine/mcp_client.py +0 -3
  5. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/controller/engine/mcp_server.py +2 -2
  6. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/controller/runner/http_client.py +2 -2
  7. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/controller/runner/mcp_client.py +2 -2
  8. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/controller/webui/gradio.py +3 -3
  9. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/utils/http_client.py +18 -4
  10. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/utils/image.py +1 -1
  11. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/utils/renderer.py +2 -2
  12. model_compose-0.2.0/src/mindor/core/utils/time.py +29 -0
  13. model_compose-0.2.0/src/mindor/core/workflow/job/__init__.py +1 -0
  14. model_compose-0.2.0/src/mindor/core/workflow/job/impl/__init__.py +3 -0
  15. model_compose-0.1.12/src/mindor/core/workflow/job.py → model_compose-0.2.0/src/mindor/core/workflow/job/impl/action.py +18 -9
  16. model_compose-0.2.0/src/mindor/core/workflow/job/impl/base.py +17 -0
  17. model_compose-0.2.0/src/mindor/core/workflow/job/impl/wait.py +43 -0
  18. model_compose-0.2.0/src/mindor/core/workflow/job/job.py +7 -0
  19. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/workflow/schema.py +7 -6
  20. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/workflow/workflow.py +5 -14
  21. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/action/impl/http_client.py +6 -6
  22. model_compose-0.2.0/src/mindor/dsl/schema/action/impl/mcp_client.py +9 -0
  23. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/action/impl/shell.py +1 -1
  24. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/component/impl/common.py +4 -4
  25. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/component/impl/http_client.py +3 -3
  26. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/component/impl/http_server.py +3 -3
  27. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/component/impl/mcp_client.py +3 -3
  28. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/component/impl/mcp_server.py +1 -1
  29. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/component/impl/shell.py +1 -1
  30. model_compose-0.2.0/src/mindor/dsl/schema/job/__init__.py +1 -0
  31. model_compose-0.2.0/src/mindor/dsl/schema/job/impl/__init__.py +3 -0
  32. model_compose-0.2.0/src/mindor/dsl/schema/job/impl/action.py +18 -0
  33. model_compose-0.2.0/src/mindor/dsl/schema/job/impl/common.py +10 -0
  34. model_compose-0.2.0/src/mindor/dsl/schema/job/impl/types.py +5 -0
  35. model_compose-0.2.0/src/mindor/dsl/schema/job/impl/wait.py +31 -0
  36. model_compose-0.2.0/src/mindor/dsl/schema/job/job.py +11 -0
  37. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/listener/impl/http_callback.py +4 -4
  38. model_compose-0.2.0/src/mindor/dsl/schema/utils/__init__.py +0 -0
  39. model_compose-0.2.0/src/mindor/dsl/schema/utils/annotation.py +19 -0
  40. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/workflow.py +35 -35
  41. {model_compose-0.1.12 → model_compose-0.2.0}/src/model_compose.egg-info/PKG-INFO +2 -2
  42. {model_compose-0.1.12 → model_compose-0.2.0}/src/model_compose.egg-info/SOURCES.txt +15 -1
  43. {model_compose-0.1.12 → model_compose-0.2.0}/src/model_compose.egg-info/requires.txt +1 -1
  44. model_compose-0.1.12/src/mindor/core/utils/time.py +0 -13
  45. model_compose-0.1.12/src/mindor/dsl/schema/action/impl/mcp_client.py +0 -9
  46. {model_compose-0.1.12 → model_compose-0.2.0}/LICENSE +0 -0
  47. {model_compose-0.1.12 → model_compose-0.2.0}/README.md +0 -0
  48. {model_compose-0.1.12 → model_compose-0.2.0}/setup.cfg +0 -0
  49. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/__init__.py +0 -0
  50. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/cli/__init__.py +0 -0
  51. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/cli/compose.py +0 -0
  52. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/__init__.py +0 -0
  53. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/component/__init__.py +0 -0
  54. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/component/component.py +0 -0
  55. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/component/engine/__init__.py +0 -0
  56. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/component/engine/base.py +0 -0
  57. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/component/engine/context.py +0 -0
  58. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/component/engine/http_server.py +0 -0
  59. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/component/engine/mcp_server.py +0 -0
  60. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/component/engine/shell.py +0 -0
  61. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/compose/__init__.py +0 -0
  62. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/compose/compose.py +0 -0
  63. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/compose/manager.py +0 -0
  64. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/controller/__init__.py +0 -0
  65. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/controller/controller.py +0 -0
  66. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/controller/engine/__init__.py +0 -0
  67. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/controller/engine/base.py +0 -0
  68. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/controller/engine/http_server.py +0 -0
  69. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/controller/runner/__init__.py +0 -0
  70. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/controller/runner/client.py +0 -0
  71. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/controller/runner/runner.py +0 -0
  72. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/controller/webui/__init__.py +0 -0
  73. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/controller/webui/webui.py +0 -0
  74. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/gateway/__init__.py +0 -0
  75. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/gateway/engine/__init__.py +0 -0
  76. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/gateway/engine/base.py +0 -0
  77. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/gateway/engine/http_tunnel.py +0 -0
  78. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/gateway/engine/ssh_tunnel.py +0 -0
  79. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/gateway/gateway.py +0 -0
  80. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/listener/__init__.py +0 -0
  81. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/listener/engine/__init__.py +0 -0
  82. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/listener/engine/base.py +0 -0
  83. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/listener/engine/http_callback.py +0 -0
  84. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/listener/listener.py +0 -0
  85. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/runtime/__init__.py +0 -0
  86. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/runtime/env.py +0 -0
  87. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/services/__init__.py +0 -0
  88. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/services/async_service.py +0 -0
  89. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/utils/__init__.py +0 -0
  90. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/utils/expiring.py +0 -0
  91. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/utils/http_request.py +0 -0
  92. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/utils/http_status.py +0 -0
  93. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/utils/mcp_client.py +0 -0
  94. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/utils/ssh_client.py +0 -0
  95. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/utils/streaming.py +0 -0
  96. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/utils/workqueue.py +0 -0
  97. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/workflow/__init__.py +0 -0
  98. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/core/workflow/context.py +0 -0
  99. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/__init__.py +0 -0
  100. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/loader.py +0 -0
  101. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/__init__.py +0 -0
  102. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/action/__init__.py +0 -0
  103. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/action/action.py +0 -0
  104. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/action/impl/__init__.py +0 -0
  105. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/action/impl/common.py +0 -0
  106. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/action/impl/http_server.py +0 -0
  107. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/action/impl/mcp_server.py +0 -0
  108. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/component/__init__.py +0 -0
  109. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/component/component.py +0 -0
  110. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/component/impl/__init__.py +0 -0
  111. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/component/impl/types.py +0 -0
  112. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/compose.py +0 -0
  113. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/controller/__init__.py +0 -0
  114. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/controller/controller.py +0 -0
  115. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/controller/impl/__init__.py +0 -0
  116. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/controller/impl/common.py +0 -0
  117. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/controller/impl/http_server.py +0 -0
  118. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/controller/impl/mcp_server.py +0 -0
  119. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/controller/impl/types.py +0 -0
  120. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/controller/impl/webui.py +0 -0
  121. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/gateway/__init__.py +0 -0
  122. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/gateway/gateway.py +0 -0
  123. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/gateway/impl/__init__.py +0 -0
  124. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/gateway/impl/common.py +0 -0
  125. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/gateway/impl/http_tunnel.py +0 -0
  126. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/gateway/impl/ssh_tunnel.py +0 -0
  127. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/gateway/impl/types.py +0 -0
  128. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/listener/__init__.py +0 -0
  129. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/listener/impl/__init__.py +0 -0
  130. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/listener/impl/common.py +0 -0
  131. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/listener/impl/types.py +0 -0
  132. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/listener/listener.py +0 -0
  133. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/runtime/__init__.py +0 -0
  134. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/runtime/impl/__init__.py +0 -0
  135. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/runtime/impl/common.py +0 -0
  136. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/runtime/impl/docker.py +0 -0
  137. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/runtime/impl/types.py +0 -0
  138. {model_compose-0.1.12 → model_compose-0.2.0}/src/mindor/dsl/schema/runtime/runtime.py +0 -0
  139. {model_compose-0.1.12 → model_compose-0.2.0}/src/model_compose.egg-info/dependency_links.txt +0 -0
  140. {model_compose-0.1.12 → model_compose-0.2.0}/src/model_compose.egg-info/entry_points.txt +0 -0
  141. {model_compose-0.1.12 → model_compose-0.2.0}/src/model_compose.egg-info/top_level.txt +0 -0
  142. {model_compose-0.1.12 → model_compose-0.2.0}/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.12
3
+ Version: 0.2.0
4
4
  Summary: model-compose: Declarative AI Model and Workflow Orchestrator
5
5
  Author-email: Hanyeol Cho <hanyeol.cho@gmail.com>
6
6
  License-Expression: MIT
@@ -15,7 +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
+ Requires-Dist: mcp>=1.10.1
19
19
  Requires-Dist: pyngrok
20
20
  Requires-Dist: ulid
21
21
  Requires-Dist: gradio
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "model-compose"
3
- version = "0.1.12"
3
+ version = "0.2.0"
4
4
  description = "model-compose: Declarative AI Model and Workflow Orchestrator"
5
5
  authors = [
6
6
  { name = "Hanyeol Cho", email = "hanyeol.cho@gmail.com" }
@@ -16,7 +16,7 @@ dependencies = [
16
16
  "requests",
17
17
  "fastapi",
18
18
  "uvicorn",
19
- "mcp",
19
+ "mcp>=1.10.1",
20
20
  "pyngrok",
21
21
  "ulid",
22
22
  "gradio",
@@ -19,10 +19,6 @@ class HttpClientCompletion(ABC):
19
19
  async def run(self, context: ComponentActionContext, client: HttpClient) -> Any:
20
20
  pass
21
21
 
22
- @abstractmethod
23
- async def close(self) -> None:
24
- pass
25
-
26
22
  class HttpClientPollingCompletion(HttpClientCompletion):
27
23
  async def run(self, context: ComponentActionContext, client: HttpClient) -> Any:
28
24
  url_or_path = await self._resolve_url_or_path(context)
@@ -31,8 +27,8 @@ class HttpClientPollingCompletion(HttpClientCompletion):
31
27
  body = await context.render_variable(self.config.body)
32
28
  headers = await context.render_variable(self.config.headers)
33
29
 
34
- interval = parse_duration(self.config.interval) if self.config.interval else 5.0
35
- timeout = parse_duration(self.config.timeout) if self.config.timeout else 300
30
+ interval = parse_duration((await context.render_variable(self.config.interval)) or 5.0)
31
+ timeout = parse_duration((await context.render_variable(self.config.timeout)) or 300.0)
36
32
  deadline = datetime.now(timezone.utc) + timeout
37
33
 
38
34
  await asyncio.sleep(interval.total_seconds())
@@ -73,9 +69,6 @@ class HttpClientCallbackCompletion(HttpClientCompletion):
73
69
  HttpCallbackListener.register_pending_future(callback_id, future)
74
70
 
75
71
  return await future
76
-
77
- async def close(self) -> None:
78
- pass
79
72
 
80
73
  class HttpClientAction:
81
74
  def __init__(self, config: HttpClientActionConfig):
@@ -112,10 +105,6 @@ class HttpClientAction:
112
105
 
113
106
  return (await context.render_variable(self.config.output, ignore_files=True)) if self.config.output else (result or response)
114
107
 
115
- async def close(self) -> None:
116
- if self.completion:
117
- await self.completion.close()
118
-
119
108
  async def _resolve_url_or_path(self, context: ComponentActionContext) -> str:
120
109
  if self.config.path:
121
110
  return await context.render_variable(self.config.path)
@@ -17,9 +17,6 @@ class McpClientAction():
17
17
  context.register_source("response", response)
18
18
 
19
19
  return (await context.render_variable(self.config.output, ignore_files=True)) if self.config.output else response
20
-
21
- async def close(self) -> None:
22
- pass
23
20
 
24
21
  async def _convert_output_value(self, content: ContentBlock) -> Any:
25
22
  if isinstance(content, TextContent):
@@ -7,7 +7,7 @@ from mindor.dsl.schema.workflow import WorkflowConfig, WorkflowVariableConfig
7
7
  from mindor.core.workflow.schema import WorkflowSchema, create_workflow_schema
8
8
  from mindor.core.utils.streaming import StreamResource, Base64StreamResource
9
9
  from mindor.core.utils.streaming import save_stream_to_temporary_file
10
- from mindor.core.utils.http_client import HttpClient
10
+ from mindor.core.utils.http_client import create_stream_with_url
11
11
  from .base import ControllerEngine, ControllerType, ControllerEngineMap, TaskState
12
12
  from mcp.server.fastmcp.server import FastMCP
13
13
  from mcp.types import TextContent, ImageContent, AudioContent
@@ -83,7 +83,7 @@ class WorkflowToolGenerator():
83
83
  return await save_stream_to_temporary_file(Base64StreamResource(value), subtype)
84
84
 
85
85
  if format == "url" and isinstance(value, str):
86
- return await save_stream_to_temporary_file(await HttpClient.request_once(value), subtype)
86
+ return await save_stream_to_temporary_file(await create_stream_with_url(value), subtype)
87
87
 
88
88
  if isinstance(value, StreamResource):
89
89
  return await save_stream_to_temporary_file(value, subtype)
@@ -36,10 +36,10 @@ class HttpControllerClient(ControllerClient):
36
36
  def _resolve_controller_url(self) -> str:
37
37
  return f"http://localhost:{self.config.port}{self.config.base_path or ''}"
38
38
 
39
- def _flatten_for_multipart(self, body: Dict[str, Any], key: str = "") -> List[Tuple[str, Any]]:
39
+ def _flatten_for_multipart(self, data: Dict[str, Any], key: str = "") -> List[Tuple[str, Any]]:
40
40
  flattened = []
41
41
 
42
- for subkey, value in body.items():
42
+ for subkey, value in data.items():
43
43
  full_key = f"{key}[{subkey}]" if key else subkey
44
44
 
45
45
  if isinstance(value, dict):
@@ -4,7 +4,7 @@ from mindor.core.workflow.schema import WorkflowSchema
4
4
  from mindor.core.utils.mcp_client import McpClient, ContentBlock, TextContent, ImageContent, AudioContent
5
5
  from mindor.core.utils.streaming import StreamResource, FileStreamResource, Base64StreamResource
6
6
  from mindor.core.utils.streaming import encode_stream_to_base64
7
- from mindor.core.utils.http_client import HttpClient
7
+ from mindor.core.utils.http_client import create_stream_with_url
8
8
  from mindor.core.utils.image import load_image_from_stream
9
9
  from .runner import ControllerClient
10
10
  import json, os
@@ -59,7 +59,7 @@ class McpControllerClient(ControllerClient):
59
59
  return await load_image_from_stream(Base64StreamResource(value), subtype)
60
60
 
61
61
  if format == "url" and isinstance(value, str):
62
- return await load_image_from_stream(await HttpClient.request_once(value), subtype)
62
+ return await load_image_from_stream(await create_stream_with_url(value), subtype)
63
63
 
64
64
  if isinstance(value, StreamResource):
65
65
  return await load_image_from_stream(value, subtype)
@@ -4,7 +4,7 @@ from mindor.core.workflow.schema import WorkflowSchema
4
4
  from mindor.core.utils.streaming import StreamResource, Base64StreamResource
5
5
  from mindor.core.utils.streaming import save_stream_to_temporary_file
6
6
  from mindor.core.utils.http_request import create_upload_file
7
- from mindor.core.utils.http_client import HttpClient
7
+ from mindor.core.utils.http_client import create_stream_with_url
8
8
  from mindor.core.utils.image import load_image_from_stream
9
9
  import gradio as gr
10
10
  import json
@@ -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.request_once(value), subtype)
187
+ return await load_image_from_stream(await create_stream_with_url(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.request_once(value), subtype)
199
+ return await save_stream_to_temporary_file(await create_stream_with_url(value), subtype)
200
200
 
201
201
  if isinstance(value, StreamResource):
202
202
  return await save_stream_to_temporary_file(value, subtype)
@@ -32,14 +32,16 @@ class HttpStreamResource(StreamResource):
32
32
  yield chunk
33
33
 
34
34
  class HttpClient:
35
+ shared_instance: Optional["HttpClient"] = None
36
+
35
37
  def __init__(self, base_url: Optional[str] = None, headers: Optional[Dict[str, str]] = None):
36
38
  self.base_url: Optional[str] = base_url
37
39
  self.headers: Optional[Dict[str, str]] = headers
38
- self.session = aiohttp.ClientSession(self.base_url.rstrip("/") + "/" if self.base_url else None, headers=self.headers)
40
+ self.session: aiohttp.ClientSession = self._create_session(self.base_url)
39
41
 
40
42
  async def __aenter__(self):
41
43
  if not self.session:
42
- self.session = aiohttp.ClientSession(self.base_url.rstrip("/") + "/" if self.base_url else None, headers=self.headers)
44
+ self.session = self._create_session(self.base_url)
43
45
  return self
44
46
 
45
47
  async def __aexit__(self, exc_type, exc_val, exc_tb):
@@ -56,7 +58,7 @@ class HttpClient:
56
58
  ) -> Union[Any, Tuple[Any, int]]:
57
59
  response: aiohttp.ClientResponse = None
58
60
  try:
59
- response = await self._request_with_session(self.session, url_or_path, method, params, body, headers)
61
+ response = await self._request_with_session(self.session, url_or_path, method, params, body, { **(self.headers or {}), **(headers or {})})
60
62
  content, _ = await self._parse_response_content(response)
61
63
 
62
64
  if raise_on_error and response.status >= 400:
@@ -76,6 +78,12 @@ class HttpClient:
76
78
  await self.session.close()
77
79
  self.session = None
78
80
 
81
+ @classmethod
82
+ def get_shared_instance(cls) -> "HttpClient":
83
+ if not cls.shared_instance:
84
+ cls.shared_instance = HttpClient()
85
+ return cls.shared_instance
86
+
79
87
  @classmethod
80
88
  async def request_once(cls, *args, **kwargs):
81
89
  instance = cls()
@@ -83,7 +91,10 @@ class HttpClient:
83
91
  return await instance.request(*args, **kwargs)
84
92
  finally:
85
93
  await instance.close()
86
-
94
+
95
+ def _create_session(self, base_url: Optional[str]) -> aiohttp.ClientSession:
96
+ return aiohttp.ClientSession(base_url.rstrip("/") + "/" if base_url else None)
97
+
87
98
  async def _request_with_session(
88
99
  self,
89
100
  session: aiohttp.ClientSession,
@@ -122,3 +133,6 @@ class HttpClient:
122
133
  filename = disposition.get("filename")
123
134
 
124
135
  return (HttpStreamResource(response, content_type, filename), content_type)
136
+
137
+ async def create_stream_with_url(url: str):
138
+ return await HttpClient.get_shared_instance().request(url)
@@ -10,5 +10,5 @@ async def load_image_from_stream(stream: StreamResource, extension: Optional[str
10
10
  async for chunk in stream:
11
11
  data.extend(chunk)
12
12
  return PILImage.open(io.BytesIO(data))
13
- except Exception:
13
+ except Exception as e:
14
14
  return None
@@ -2,7 +2,7 @@ from typing import Callable, Dict, List, Optional, Awaitable, Any
2
2
  from .streaming import StreamResource, Base64StreamResource
3
3
  from .streaming import encode_stream_to_base64, save_stream_to_temporary_file
4
4
  from .http_request import create_upload_file
5
- from .http_client import HttpClient
5
+ from .http_client import create_stream_with_url
6
6
  from starlette.datastructures import UploadFile
7
7
  import re, json, base64
8
8
 
@@ -128,7 +128,7 @@ class VariableRenderer:
128
128
  return await save_stream_to_temporary_file(Base64StreamResource(value), subtype)
129
129
 
130
130
  if format == "url" and isinstance(value, str):
131
- return await save_stream_to_temporary_file(await HttpClient.request_once(value), subtype)
131
+ return await save_stream_to_temporary_file(await create_stream_with_url(value), subtype)
132
132
 
133
133
  if isinstance(value, StreamResource):
134
134
  return await save_stream_to_temporary_file(value, subtype)
@@ -0,0 +1,29 @@
1
+ from typing import Union, Optional
2
+ from datetime import timedelta, datetime
3
+ import zoneinfo
4
+
5
+ def parse_duration(value: Union[str, float, int]) -> timedelta:
6
+ if isinstance(value, (float, int)):
7
+ return timedelta(seconds=value)
8
+
9
+ if value.endswith("s"):
10
+ return timedelta(seconds=float(value[:-1]))
11
+
12
+ if value.endswith("m"):
13
+ return timedelta(minutes=float(value[:-1]))
14
+
15
+ if value.endswith("h"):
16
+ return timedelta(hours=float(value[:-1]))
17
+
18
+ if value.endswith("d"):
19
+ return timedelta(days=float(value[:-1]))
20
+
21
+ raise ValueError(f"Unsupported duration format: {value}")
22
+
23
+ def parse_datetime(value: Union[str, datetime], timezone: Optional[str]) -> datetime:
24
+ time = datetime.fromisoformat(value) if isinstance(value, str) else value
25
+
26
+ if timezone and time.tzinfo is None:
27
+ time = time.replace(tzinfo=zoneinfo.ZoneInfo(timezone))
28
+
29
+ return time
@@ -0,0 +1 @@
1
+ from .job import *
@@ -0,0 +1,3 @@
1
+ from .base import *
2
+ from .action import *
3
+ from .wait import *
@@ -1,18 +1,16 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Callable, Any
2
- from mindor.dsl.schema.workflow import JobConfig
2
+ from mindor.dsl.schema.job import ActionJobConfig, JobType
3
3
  from mindor.dsl.schema.component import ComponentConfig
4
- from mindor.core.component import ComponentEngine
5
- from .context import WorkflowContext
4
+ from mindor.core.component import ComponentEngine, ComponentResolver, create_component
5
+ from .base import Job, JobMap, WorkflowContext
6
6
  import asyncio, ulid
7
7
 
8
- class Job:
9
- def __init__(self, id: str, config: JobConfig, component_provider: Callable[[str, Union[ComponentConfig, str]], ComponentEngine]):
10
- self.id: str = id
11
- self.config: JobConfig = config
12
- self.component_provider: Callable[[str, Union[ComponentConfig, str]], ComponentEngine] = component_provider
8
+ class ActionJob(Job):
9
+ def __init__(self, id: str, config: ActionJobConfig, components: Dict[str, ComponentConfig]):
10
+ super().__init__(id, config, components)
13
11
 
14
12
  async def run(self, context: WorkflowContext) -> Any:
15
- component: ComponentEngine = self.component_provider(self.id, await context.render_variable(self.config.component))
13
+ component: ComponentEngine = self._create_component(self.id, await context.render_variable(self.config.component))
16
14
 
17
15
  if not component.started:
18
16
  await component.start()
@@ -35,3 +33,14 @@ class Job:
35
33
  context.register_source("output", output)
36
34
 
37
35
  return output
36
+
37
+ def _create_component(self, id: str, component: Union[ComponentConfig, str]) -> ComponentEngine:
38
+ return create_component(*self._resolve_component(id, component), daemon=False)
39
+
40
+ def _resolve_component(self, id: str, component: Union[ComponentConfig, str]) -> Tuple[str, ComponentConfig]:
41
+ if isinstance(component, str):
42
+ return ComponentResolver(self.components).resolve(component)
43
+
44
+ return id, component
45
+
46
+ JobMap[JobType.ACTION] = ActionJob
@@ -0,0 +1,17 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Callable, Any
2
+ from abc import ABC, abstractmethod
3
+ from mindor.dsl.schema.job import JobConfig, JobType
4
+ from mindor.dsl.schema.component import ComponentConfig
5
+ from mindor.core.workflow.context import WorkflowContext
6
+
7
+ class Job(ABC):
8
+ def __init__(self, id: str, config: JobConfig, components: Dict[str, ComponentConfig]):
9
+ self.id: str = id
10
+ self.config: JobConfig = config
11
+ self.components: Dict[str, ComponentConfig] = components
12
+
13
+ @abstractmethod
14
+ async def run(self, context: WorkflowContext) -> Any:
15
+ pass
16
+
17
+ JobMap: Dict[JobType, Type[Job]] = {}
@@ -0,0 +1,43 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Callable, Any
2
+ from mindor.dsl.schema.job import WaitJobConfig, WaitJobType
3
+ from mindor.dsl.schema.component import ComponentConfig
4
+ from mindor.core.utils.time import parse_duration, parse_datetime
5
+ from .base import Job, JobType, JobMap, WorkflowContext
6
+ from datetime import datetime
7
+ import asyncio
8
+
9
+ class WaitJob(Job):
10
+ def __init__(self, id: str, config: WaitJobConfig, components: Dict[str, ComponentConfig]):
11
+ super().__init__(id, config, components)
12
+
13
+ async def run(self, context: WorkflowContext) -> Any:
14
+ if self.config.mode == WaitJobType.TIME_INTERVAL:
15
+ return await self._wait_until_time_interval(context)
16
+
17
+ if self.config.mode == WaitJobType.SPECIFIC_TIME:
18
+ return await self._wait_until_specific_time(context)
19
+
20
+ return None
21
+
22
+ async def _wait_until_time_interval(self, context: WorkflowContext) -> Any:
23
+ duration = parse_duration((await context.render_variable(self.config.duration)) or 0.0)
24
+ duration = duration.total_seconds()
25
+
26
+ if duration > 0.0:
27
+ await asyncio.sleep(duration)
28
+
29
+ return None
30
+
31
+ async def _wait_until_specific_time(self, context: WorkflowContext) -> Any:
32
+ timezone = await context.render_variable(self.config.timezone)
33
+ time = parse_datetime((await context.render_variable(self.config.time)) or datetime(2000, 1, 1, 0, 0, 0), timezone)
34
+
35
+ now = datetime.now(tz=time.tzinfo)
36
+ duration = (time - now).total_seconds()
37
+
38
+ if duration > 0.0:
39
+ await asyncio.sleep(duration)
40
+
41
+ return None
42
+
43
+ JobMap[JobType.WAIT] = WaitJob
@@ -0,0 +1,7 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Callable, Any
2
+ from mindor.dsl.schema.workflow import JobConfig
3
+ from mindor.dsl.schema.component import ComponentConfig
4
+ from .impl import Job, JobMap
5
+
6
+ def create_job(id: str, config: JobConfig, components: Dict[str, ComponentConfig]) -> Job:
7
+ return JobMap[config.type](id, config, components)
@@ -2,6 +2,7 @@ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annot
2
2
  from dataclasses import dataclass, asdict
3
3
  from pydantic import BaseModel
4
4
  from mindor.dsl.schema.workflow import WorkflowConfig, WorkflowVariableConfig, WorkflowVariableGroupConfig
5
+ from mindor.dsl.schema.job import ActionJobConfig
5
6
  from mindor.dsl.schema.component import ComponentConfig
6
7
  import re, json
7
8
 
@@ -154,6 +155,9 @@ class WorkflowVariableResolver:
154
155
 
155
156
  if variable.type in [ "image", "audio", "video", "file", "select" ] and variable.subtype:
156
157
  config_dict["options"] = variable.subtype.split(",")
158
+
159
+ if variable.annotations is None:
160
+ config_dict["annotations"] = []
157
161
 
158
162
  return WorkflowVariableConfig(**config_dict)
159
163
 
@@ -192,7 +196,7 @@ class WorkflowInputVariableResolver(WorkflowVariableResolver):
192
196
  variables: List[WorkflowVariable] = []
193
197
 
194
198
  for job in workflow.jobs.values():
195
- if not job.input or job.input == "${input}":
199
+ if isinstance(job, ActionJobConfig) and (not job.input or job.input == "${input}"):
196
200
  action_id = job.action or "__default__"
197
201
  if isinstance(job.component, str):
198
202
  component: Optional[ComponentConfig] = components[job.component] if job.component in components else None
@@ -205,10 +209,7 @@ class WorkflowInputVariableResolver(WorkflowVariableResolver):
205
209
  if action:
206
210
  variables.extend(self._enumerate_input_variables(action, "input", internal=True))
207
211
  else:
208
- variables.extend(self._enumerate_input_variables(job.input, "input"))
209
-
210
- for value in [ job.component, job.action, job.repeat_count ]:
211
- variables.extend(self._enumerate_input_variables(value, "input"))
212
+ variables.extend(self._enumerate_input_variables(job, "input"))
212
213
 
213
214
  return self._to_variable_config_list(variables)
214
215
 
@@ -226,7 +227,7 @@ class WorkflowOutputVariableResolver(WorkflowVariableResolver):
226
227
  if repeat_count != 1:
227
228
  variables.append(WorkflowVariableGroup(variables=(job_variables := []), repeat_count=repeat_count))
228
229
 
229
- if not job.output or job.output == "${output}":
230
+ if isinstance(job, ActionJobConfig) and (not job.output or job.output == "${output}"):
230
231
  action_id = job.action or "__default__"
231
232
  if isinstance(job.component, str):
232
233
  component: Optional[ComponentConfig] = components[job.component] if job.component in components else None
@@ -3,7 +3,7 @@ from mindor.dsl.schema.workflow import WorkflowConfig, JobConfig
3
3
  from mindor.dsl.schema.component import ComponentConfig
4
4
  from mindor.core.component import ComponentEngine, ComponentResolver, create_component
5
5
  from .context import WorkflowContext
6
- from .job import Job
6
+ from .job import Job, create_job
7
7
  import asyncio
8
8
 
9
9
  class JobGraphValidator:
@@ -74,12 +74,12 @@ class WorkflowResolver:
74
74
  return default_ids[0] if default_ids else "__default__"
75
75
 
76
76
  class WorkflowRunner:
77
- def __init__(self, jobs: Dict[str, JobConfig], component_provider: Callable[[str, Union[ComponentConfig, str]], ComponentEngine]):
77
+ def __init__(self, jobs: Dict[str, JobConfig], components: Dict[str, ComponentConfig]):
78
78
  self.jobs: Dict[str, JobConfig] = jobs
79
- self.component_provider: Callable[[str, Union[ComponentConfig, str]], ComponentEngine] = component_provider
79
+ self.components: Dict[str, ComponentConfig] = components
80
80
 
81
81
  async def run(self, context: WorkflowContext) -> Any:
82
- pending_jobs: Dict[str, Job] = { job_id: Job(job_id, job, self.component_provider) for job_id, job in self.jobs.items() }
82
+ pending_jobs: Dict[str, Job] = { job_id: create_job(job_id, job, self.components) for job_id, job in self.jobs.items() }
83
83
  running_job_ids: Set[str] = set()
84
84
  completed_job_ids: Set[str] = set()
85
85
  scheduled_job_tasks: Dict[str, asyncio.Task] = {}
@@ -131,21 +131,12 @@ class Workflow:
131
131
 
132
132
  async def run(self, task_id: str, input: Dict[str, Any]) -> Any:
133
133
  context = WorkflowContext(task_id, input)
134
- runner = WorkflowRunner(self.config.jobs, self._create_component)
134
+ runner = WorkflowRunner(self.config.jobs, self.components)
135
135
 
136
136
  return await runner.run(context)
137
137
 
138
138
  def validate(self) -> None:
139
139
  JobGraphValidator(self.config.jobs).validate()
140
140
 
141
- def _create_component(self, id: str, component: Union[ComponentConfig, str]) -> ComponentEngine:
142
- return create_component(*self._resolve_component(id, component), daemon=False)
143
-
144
- def _resolve_component(self, id: str, component: Union[ComponentConfig, str]) -> Tuple[str, ComponentConfig]:
145
- if isinstance(component, str):
146
- return ComponentResolver(self.components).resolve(component)
147
-
148
- return id, component
149
-
150
141
  def create_workflow(id: str, config: WorkflowConfig, components: Dict[str, ComponentConfig]) -> Workflow:
151
142
  return Workflow(id, config, components)
@@ -11,9 +11,9 @@ class PollingCompletionConfig(CommonCompletionConfig):
11
11
  endpoint: Optional[str] = None
12
12
  path: Optional[str] = None
13
13
  method: Literal[ "GET", "POST", "PUT", "DELETE", "PATCH" ] = "GET"
14
- headers: Optional[Dict[str, str]] = Field(default_factory=dict)
15
- body: Optional[Dict[str, Any]] = Field(default_factory=dict)
16
- params: Optional[Dict[str, str]] = Field(default_factory=dict)
14
+ headers: Dict[str, str] = Field(default_factory=dict)
15
+ body: Dict[str, Any] = Field(default_factory=dict)
16
+ params: Dict[str, str] = Field(default_factory=dict)
17
17
  status: Optional[str] = None
18
18
  success_when: Optional[List[Union[int, str]]] = None
19
19
  fail_when: Optional[List[Union[int, str]]] = None
@@ -49,9 +49,9 @@ class HttpClientActionConfig(CommonActionConfig):
49
49
  endpoint: Optional[str] = None
50
50
  path: Optional[str] = None
51
51
  method: Literal[ "GET", "POST", "PUT", "DELETE", "PATCH" ] = "POST"
52
- headers: Optional[Dict[str, str]] = Field(default_factory=dict)
53
- body: Optional[Dict[str, Any]] = Field(default_factory=dict)
54
- params: Optional[Dict[str, str]] = Field(default_factory=dict)
52
+ headers: Dict[str, str] = Field(default_factory=dict)
53
+ body: Dict[str, Any] = Field(default_factory=dict)
54
+ params: Dict[str, str] = Field(default_factory=dict)
55
55
  completion: Optional[HttpClientCompletionConfig] = None
56
56
 
57
57
  @model_validator(mode="before")
@@ -0,0 +1,9 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from pydantic import BaseModel, Field
3
+ from pydantic import model_validator
4
+ from .common import CommonActionConfig
5
+
6
+ class McpClientActionConfig(CommonActionConfig):
7
+ tool: str = Field(default="__default__", description="Name of the tool to invoke.")
8
+ arguments: Dict[str, Any] = Field(default_factory=dict, description="Arguments to pass to the tool.")
9
+ headers: Dict[str, str] = Field(default_factory=dict, description="Optional HTTP headers to include in the tool call.")
@@ -6,5 +6,5 @@ from .common import CommonActionConfig
6
6
  class ShellActionConfig(CommonActionConfig):
7
7
  command: List[str] = Field(..., description="The shell command to execute, as a list of arguments.")
8
8
  working_dir: Optional[str] = Field(default=None, description="Working directory for the command.")
9
- env: Optional[Dict[str, str]] = Field(default_factory=dict, description="Environment variables to set when executing the command.")
9
+ env: Dict[str, str] = Field(default_factory=dict, description="Environment variables to set when executing the command.")
10
10
  timeout: Optional[float] = Field(default=None, description="Maximum time allowed for the command to run, in seconds.")
@@ -5,7 +5,7 @@ from .types import ComponentType
5
5
 
6
6
  class CommonComponentConfig(BaseModel):
7
7
  type: ComponentType
8
- runtime: Literal[ "docker", "native" ] = "native"
9
- max_concurrent_count: int = 1
10
- default: bool = False
11
- actions: Optional[Dict[str, CommonActionConfig]] = Field(default_factory=dict)
8
+ runtime: Literal[ "docker", "native" ] = Field(default="native")
9
+ max_concurrent_count: int = Field(default=1)
10
+ default: bool = Field(default=False)
11
+ actions: Dict[str, CommonActionConfig] = Field(default_factory=dict)
@@ -6,9 +6,9 @@ from .common import ComponentType, CommonComponentConfig
6
6
 
7
7
  class HttpClientComponentConfig(CommonComponentConfig):
8
8
  type: Literal[ComponentType.HTTP_CLIENT]
9
- base_url: Optional[str] = None
10
- headers: Optional[Dict[str, Any]] = Field(default_factory=dict)
11
- actions: Optional[Dict[str, HttpClientActionConfig]] = Field(default_factory=dict)
9
+ base_url: Optional[str] = Field(default=None)
10
+ headers: Dict[str, Any] = Field(default_factory=dict)
11
+ actions: Dict[str, HttpClientActionConfig] = Field(default_factory=dict)
12
12
 
13
13
  @model_validator(mode="before")
14
14
  def inflate_single_action(cls, values: Dict[str, Any]):
@@ -6,6 +6,6 @@ from .common import ComponentType, CommonComponentConfig
6
6
 
7
7
  class HttpServerComponentConfig(CommonComponentConfig):
8
8
  type: Literal[ComponentType.HTTP_SERVER]
9
- port: Optional[int] = Field(None, ge=1, le=65535)
10
- base_path: Optional[str] = None
11
- actions: Optional[Dict[str, HttpServerActionConfig]] = Field(default_factory=dict)
9
+ port: Optional[int] = Field(default=None, ge=1, le=65535)
10
+ base_path: Optional[str] = Field(default=None)
11
+ actions: Dict[str, HttpServerActionConfig] = Field(default_factory=dict)
@@ -6,9 +6,9 @@ from .common import ComponentType, CommonComponentConfig
6
6
 
7
7
  class McpClientComponentConfig(CommonComponentConfig):
8
8
  type: Literal[ComponentType.MCP_CLIENT]
9
- url: str = Field(..., description="URL of the MCP server to invoke tools")
10
- headers: Optional[Dict[str, Any]] = Field(default_factory=dict)
11
- actions: Optional[Dict[str, McpClientActionConfig]] = Field(default_factory=dict)
9
+ url: str = Field(..., description="URL of the MCP server to invoke tools.")
10
+ headers: Dict[str, Any] = Field(default_factory=dict)
11
+ actions: Dict[str, McpClientActionConfig] = Field(default_factory=dict)
12
12
 
13
13
  @model_validator(mode="before")
14
14
  def inflate_single_action(cls, values: Dict[str, Any]):
@@ -6,4 +6,4 @@ from .common import ComponentType, CommonComponentConfig
6
6
 
7
7
  class McpServerComponentConfig(CommonComponentConfig):
8
8
  type: Literal[ComponentType.MCP_SERVER]
9
- actions: Optional[Dict[str, McpServerActionConfig]] = Field(default_factory=dict)
9
+ actions: Dict[str, McpServerActionConfig] = Field(default_factory=dict)
@@ -9,7 +9,7 @@ class ShellComponentConfig(CommonComponentConfig):
9
9
  base_dir: Optional[str] = Field(default=None, description="Base working directory for all actions in this component.")
10
10
  env: Optional[Dict[str, str]] = Field(default_factory=dict, description="Environment variables to set for all actions in this component.")
11
11
  actions: Optional[Dict[str, ShellActionConfig]] = Field(default_factory=dict, description="Shell actions mapped by an identifier.")
12
-
12
+
13
13
  @model_validator(mode="before")
14
14
  def inflate_single_action(cls, values: Dict[str, Any]):
15
15
  if "actions" not in values:
@@ -0,0 +1 @@
1
+ from .job import *
@@ -0,0 +1,3 @@
1
+ from .common import *
2
+ from .action import *
3
+ from .wait import *