model-compose 0.0.3__tar.gz → 0.0.5__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 (89) hide show
  1. {model_compose-0.0.3/src/model_compose.egg-info → model_compose-0.0.5}/PKG-INFO +1 -1
  2. {model_compose-0.0.3 → model_compose-0.0.5}/pyproject.toml +1 -1
  3. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/component/engine/base.py +3 -3
  4. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/component/engine/context.py +5 -1
  5. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/component/engine/http_client.py +20 -14
  6. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/controller/engine/base.py +2 -2
  7. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/controller/engine/http_server.py +21 -8
  8. model_compose-0.0.5/src/mindor/core/listener/__init__.py +1 -0
  9. model_compose-0.0.5/src/mindor/core/listener/engine/__init__.py +2 -0
  10. model_compose-0.0.5/src/mindor/core/listener/engine/base.py +13 -0
  11. model_compose-0.0.5/src/mindor/core/listener/engine/http_callback.py +37 -0
  12. model_compose-0.0.5/src/mindor/core/listener/listener.py +9 -0
  13. model_compose-0.0.5/src/mindor/core/utils/http_headers.py +24 -0
  14. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/workflow/context.py +5 -1
  15. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/workflow/job.py +3 -1
  16. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/workflow/workflow.py +2 -2
  17. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/action/impl/http_client.py +19 -3
  18. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/component/impl/http_client.py +4 -4
  19. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/controller/impl/http_server.py +2 -1
  20. model_compose-0.0.5/src/mindor/dsl/schema/listener/__init__.py +1 -0
  21. model_compose-0.0.5/src/mindor/dsl/schema/listener/impl/__init__.py +2 -0
  22. model_compose-0.0.5/src/mindor/dsl/schema/listener/impl/common.py +7 -0
  23. model_compose-0.0.5/src/mindor/dsl/schema/listener/impl/http_callback.py +25 -0
  24. model_compose-0.0.5/src/mindor/dsl/schema/listener/impl/types.py +4 -0
  25. model_compose-0.0.5/src/mindor/dsl/schema/listener/listener.py +10 -0
  26. {model_compose-0.0.3 → model_compose-0.0.5/src/model_compose.egg-info}/PKG-INFO +1 -1
  27. {model_compose-0.0.3 → model_compose-0.0.5}/src/model_compose.egg-info/SOURCES.txt +12 -0
  28. {model_compose-0.0.3 → model_compose-0.0.5}/LICENSE +0 -0
  29. {model_compose-0.0.3 → model_compose-0.0.5}/README.md +0 -0
  30. {model_compose-0.0.3 → model_compose-0.0.5}/setup.cfg +0 -0
  31. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/__init__.py +0 -0
  32. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/cli/__init__.py +0 -0
  33. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/cli/compose.py +0 -0
  34. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/__init__.py +0 -0
  35. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/component/__init__.py +0 -0
  36. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/component/component.py +0 -0
  37. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/component/engine/__init__.py +0 -0
  38. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/component/engine/http_server.py +0 -0
  39. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/compose/__init__.py +0 -0
  40. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/compose/compose.py +0 -0
  41. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/compose/manager.py +0 -0
  42. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/controller/__init__.py +0 -0
  43. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/controller/controller.py +0 -0
  44. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/controller/engine/__init__.py +0 -0
  45. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/controller/engine/mcp_server.py +0 -0
  46. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/runtime/__init__.py +0 -0
  47. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/runtime/env.py +0 -0
  48. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/utils/__init__.py +0 -0
  49. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/utils/expiring.py +0 -0
  50. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/utils/template.py +0 -0
  51. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/utils/workqueue.py +0 -0
  52. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/core/workflow/__init__.py +0 -0
  53. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/__init__.py +0 -0
  54. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/loader.py +1 -1
  55. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/__init__.py +0 -0
  56. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/action/__init__.py +0 -0
  57. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/action/action.py +1 -1
  58. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/action/impl/__init__.py +0 -0
  59. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/action/impl/common.py +1 -1
  60. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/action/impl/http_server.py +1 -1
  61. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/action/impl/mcp_client.py +1 -1
  62. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/action/impl/mcp_server.py +1 -1
  63. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/component/__init__.py +0 -0
  64. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/component/component.py +0 -0
  65. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/component/impl/__init__.py +0 -0
  66. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/component/impl/common.py +1 -1
  67. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/component/impl/http_server.py +1 -1
  68. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/component/impl/mcp_client.py +1 -1
  69. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/component/impl/mcp_server.py +1 -1
  70. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/component/impl/types.py +0 -0
  71. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/compose.py +1 -1
  72. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/controller/__init__.py +0 -0
  73. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/controller/controller.py +1 -1
  74. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/controller/impl/__init__.py +0 -0
  75. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/controller/impl/common.py +1 -1
  76. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/controller/impl/mcp_server.py +1 -1
  77. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/controller/impl/types.py +0 -0
  78. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/runtime/__init__.py +0 -0
  79. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/runtime/impl/__init__.py +0 -0
  80. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/runtime/impl/common.py +1 -1
  81. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/runtime/impl/docker.py +1 -1
  82. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/runtime/impl/types.py +0 -0
  83. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/runtime/runtime.py +1 -1
  84. {model_compose-0.0.3 → model_compose-0.0.5}/src/mindor/dsl/schema/workflow.py +1 -1
  85. {model_compose-0.0.3 → model_compose-0.0.5}/src/model_compose.egg-info/dependency_links.txt +0 -0
  86. {model_compose-0.0.3 → model_compose-0.0.5}/src/model_compose.egg-info/entry_points.txt +0 -0
  87. {model_compose-0.0.3 → model_compose-0.0.5}/src/model_compose.egg-info/requires.txt +0 -0
  88. {model_compose-0.0.3 → model_compose-0.0.5}/src/model_compose.egg-info/top_level.txt +0 -0
  89. {model_compose-0.0.3 → model_compose-0.0.5}/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.3
3
+ Version: 0.0.5
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "model-compose"
3
- version = "0.0.3"
3
+ version = "0.0.5"
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" }
@@ -1,5 +1,5 @@
1
- from abc import ABC, abstractmethod
2
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Callable, Any
2
+ from abc import ABC, abstractmethod
3
3
 
4
4
  from mindor.dsl.schema.component import ComponentConfig, ComponentType
5
5
  from mindor.dsl.schema.action import ActionConfig
@@ -78,9 +78,9 @@ class BaseComponent(ABC):
78
78
  if self.daemon_task:
79
79
  await self.daemon_task
80
80
 
81
- async def run(self, action_id: Union[str, None], input: Dict[str, Any]) -> Dict[str, Any]:
81
+ async def run(self, action_id: Union[str, None], call_id: str, input: Dict[str, Any]) -> Dict[str, Any]:
82
82
  _, action = ActionResolver(self.config.actions).resolve(action_id)
83
- context = ComponentContext(input, self.env)
83
+ context = ComponentContext(call_id, input, self.env)
84
84
 
85
85
  if self.queue:
86
86
  return await (await self.queue.schedule(action, context))
@@ -2,9 +2,11 @@ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annot
2
2
  from mindor.core.utils.template import TemplateRenderer
3
3
 
4
4
  class ComponentContext:
5
- def __init__(self, input: Dict[str, Any], env: Dict[str, str]):
5
+ def __init__(self, call_id: str, input: Dict[str, Any], env: Dict[str, str]):
6
+ self.call_id: str = call_id
6
7
  self.input: Dict[str, Any] = input
7
8
  self.env: Dict[str, str] = env
9
+ self.context: Dict[str, Any] = { "call_id": call_id }
8
10
  self.sources: Dict[str, Any] = {}
9
11
  self.renderer: TemplateRenderer = TemplateRenderer(self._resolve_source)
10
12
 
@@ -21,4 +23,6 @@ class ComponentContext:
21
23
  return self.input
22
24
  if key == "env":
23
25
  return self.env
26
+ if key == "context":
27
+ return self.context
24
28
  raise KeyError(f"Unknown source: {key}")
@@ -1,10 +1,10 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
2
  from mindor.dsl.schema.component import HttpClientComponentConfig, HttpClientActionConfig, HttpUrl
3
+ from mindor.core.utils.http_headers import parse_options_header, get_header_value
3
4
  from .base import BaseComponent, ComponentType, ComponentEngineMap, ActionConfig
4
5
  from .context import ComponentContext
5
6
 
6
- from requests.structures import CaseInsensitiveDict
7
- from urllib.parse import urlencode, urljoin
7
+ from urllib.parse import urlencode
8
8
  import aiohttp, json
9
9
 
10
10
  class HttpClientAction:
@@ -22,14 +22,14 @@ class HttpClientAction:
22
22
  response = await self._request(url, method, params, body, headers)
23
23
 
24
24
  if not self.config.output:
25
- return response or {}
25
+ return response
26
26
 
27
27
  if response:
28
28
  context.register_source("response", response)
29
29
 
30
30
  return context.render_template(self.config.output)
31
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]:
32
+ async def _request(self, url: str, method: str, params: Optional[Dict[str, Any]], body: Optional[Any], headers: Optional[Dict[str, str]]) -> Any:
33
33
  async with aiohttp.ClientSession() as session:
34
34
  async with session.request(
35
35
  method,
@@ -49,19 +49,19 @@ class HttpClientAction:
49
49
 
50
50
  return context.render_template(str(self.config.endpoint))
51
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)
52
+ def _serialize_request_body(self, body: Any, headers: Optional[Dict[str, str]]) -> Any:
53
+ content_type, _ = parse_options_header(get_header_value(headers, "Content-Type", ""))
57
54
 
58
55
  if content_type == "application/x-www-form-urlencoded":
59
56
  return urlencode(body)
60
-
61
- return str(body)
57
+
58
+ if isinstance(body, (str, bytes)):
59
+ return body
60
+
61
+ return json.dumps(body)
62
62
 
63
- async def _deserialize_response_body(self, response: aiohttp.ClientResponse) -> Union[dict, str, bytes]:
64
- content_type = response.headers.get("Content-Type", "").lower()
63
+ async def _deserialize_response_body(self, response: aiohttp.ClientResponse) -> Any:
64
+ content_type, _ = parse_options_header(response.headers.get("Content-Type", ""))
65
65
 
66
66
  if content_type == "application/json":
67
67
  return await response.json()
@@ -69,7 +69,13 @@ class HttpClientAction:
69
69
  if content_type.startswith("text/"):
70
70
  return await response.text()
71
71
 
72
- return await response.read()
72
+ _, disposition = parse_options_header(response.headers.get("Content-Disposition"))
73
+
74
+ return {
75
+ "filename": disposition.get("filename"),
76
+ "content_type": content_type,
77
+ "stream": response.content
78
+ }
73
79
 
74
80
  class HttpClientComponent(BaseComponent):
75
81
  def __init__(self, id: str, config: HttpClientComponentConfig, env: Dict[str, str], daemon: bool):
@@ -1,6 +1,6 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
1
2
  from abc import ABC, abstractmethod
2
3
  from dataclasses import dataclass
3
- from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
4
4
 
5
5
  from mindor.dsl.schema.controller import ControllerConfig, ControllerType
6
6
  from mindor.dsl.schema.component import ComponentConfig
@@ -125,7 +125,7 @@ class BaseController(ABC):
125
125
 
126
126
  try:
127
127
  workflow = create_workflow(*WorkflowResolver(self.workflows).resolve(workflow_id), self.components, self.env)
128
- output = await workflow.run(input)
128
+ output = await workflow.run(task_id, input)
129
129
  state = TaskState(task_id=task_id, status="completed", output=output)
130
130
  except Exception as e:
131
131
  state = TaskState(task_id=task_id, status="failed", error=str(e))
@@ -1,13 +1,13 @@
1
- from pydantic import BaseModel
2
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
3
2
  from typing_extensions import Self
3
+ from pydantic import BaseModel
4
4
 
5
5
  from mindor.dsl.schema.controller import HttpServerControllerConfig
6
6
  from mindor.dsl.schema.component import ComponentConfig
7
7
  from mindor.dsl.schema.workflow import WorkflowConfig
8
8
  from .base import BaseController, ControllerType, ControllerEngineMap, TaskState
9
9
 
10
- from fastapi import FastAPI, Body, HTTPException
10
+ from fastapi import FastAPI, APIRouter, Body, HTTPException
11
11
  import uvicorn
12
12
 
13
13
  class WorkflowTaskRequestBody(BaseModel):
@@ -36,11 +36,13 @@ class HttpServerController(BaseController):
36
36
 
37
37
  self.server: Optional[uvicorn.Server] = None
38
38
  self.app: FastAPI = FastAPI()
39
-
39
+ self.router: APIRouter = APIRouter()
40
+
40
41
  self._configure_routes()
42
+ self.app.include_router(self.router, prefix=self.config.base_path)
41
43
 
42
44
  def _configure_routes(self):
43
- @self.app.post("/workflows", response_model=TaskResult, response_model_exclude_none=True)
45
+ @self.router.post("/workflows", response_model=TaskResult, response_model_exclude_none=True)
44
46
  async def run_workflow(
45
47
  body: WorkflowTaskRequestBody = Body(...)
46
48
  ):
@@ -48,7 +50,7 @@ class HttpServerController(BaseController):
48
50
 
49
51
  return TaskResult.from_instance(state)
50
52
 
51
- @self.app.get("/tasks/{task_id}", response_model=TaskResult, response_model_exclude_none=True)
53
+ @self.router.get("/tasks/{task_id}", response_model=TaskResult, response_model_exclude_none=True)
52
54
  async def get_task_state(
53
55
  task_id: str
54
56
  ):
@@ -59,11 +61,22 @@ class HttpServerController(BaseController):
59
61
 
60
62
  return TaskResult.from_instance(state)
61
63
 
64
+ @self.router.get("/tasks/{task_id}/output")
65
+ async def get_task_output(
66
+ task_id: str
67
+ ):
68
+ state = self.get_task_state(task_id)
69
+
70
+ if not state:
71
+ raise HTTPException(status_code=404, detail="Task not found")
72
+
73
+ return state.output
74
+
62
75
  async def _serve(self) -> None:
63
76
  self.server = uvicorn.Server(uvicorn.Config(
64
- self.app,
65
- host=self.config.host,
66
- port=self.config.port,
77
+ self.app,
78
+ host=self.config.host,
79
+ port=self.config.port,
67
80
  log_level="info"
68
81
  ))
69
82
  await self.server.serve()
@@ -0,0 +1 @@
1
+ from .listener import *
@@ -0,0 +1,2 @@
1
+ from .base import *
2
+ from .http_callback import *
@@ -0,0 +1,13 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from abc import ABC, abstractmethod
3
+ from dataclasses import dataclass
4
+
5
+ from mindor.dsl.schema.listener import ListenerConfig, ListenerType
6
+ from mindor.core.listener import BaseListener, create_listener
7
+
8
+ class BaseListener(ABC):
9
+ def __init__(self, config: ListenerConfig, env: Dict[str, str]):
10
+ self.config: ListenerConfig = config
11
+ self.env: Dict[str, str] = env
12
+
13
+ ListenerEngineMap: Dict[ListenerType, Type[BaseListener]] = {}
@@ -0,0 +1,37 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from typing_extensions import Self
3
+ from pydantic import BaseModel
4
+
5
+ from mindor.dsl.schema.listener import HttpCallbackListenerConfig
6
+ from .base import BaseListener, ListenerType, ListenerEngineMap
7
+
8
+ from fastapi import FastAPI, APIRouter, Body, HTTPException
9
+ import uvicorn
10
+
11
+ class HttpCallbackListener(BaseListener):
12
+ def __init__(self, config: HttpCallbackListenerConfig, env: Dict[str, str]):
13
+ super().__init__(config, env)
14
+
15
+ self.server: Optional[uvicorn.Server] = None
16
+ self.app: FastAPI = FastAPI()
17
+ self.router: APIRouter = APIRouter()
18
+
19
+ self._configure_routes()
20
+ self.app.include_router(self.router, prefix=self.config.base_path)
21
+
22
+ def _configure_routes(self):
23
+ pass
24
+
25
+ async def _serve(self) -> None:
26
+ self.server = uvicorn.Server(uvicorn.Config(
27
+ self.app,
28
+ host=self.config.host,
29
+ port=self.config.port,
30
+ log_level="info"
31
+ ))
32
+ await self.server.serve()
33
+
34
+ async def _shutdown(self) -> None:
35
+ self.server.should_exit = True
36
+
37
+ ListenerEngineMap[ListenerType.HTTP_CALLBACK] = HttpCallbackListener
@@ -0,0 +1,9 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from mindor.dsl.schema.listener import ListenerConfig
3
+ from .engine import BaseListener, ListenerEngineMap
4
+
5
+ def create_listener(config: ControllerConfig, env: Dict[str, str]) -> BaseListener:
6
+ try:
7
+ return ListenerEngineMap[config.type](config, env)
8
+ except KeyError:
9
+ raise ValueError(f"Unsupported listener type: {config.type}")
@@ -0,0 +1,24 @@
1
+ from typing import Dict, Tuple, Optional
2
+ from requests.structures import CaseInsensitiveDict
3
+ import re
4
+
5
+ _token_pattern = r"([\w!#$%&'*+\-.^_`|~]+)"
6
+ _quoted_pattern = r'"([^"]*)"'
7
+ _option_regex = re.compile(fr";\s*{_token_pattern}=(?:{_token_pattern}|{_quoted_pattern})", re.ASCII)
8
+ _firefox_escape_fix = re.compile(r'\\"(?!; |\s*$)')
9
+
10
+ def parse_options_header(value: str) -> Tuple[str, Dict[str, str]]:
11
+ value = _firefox_escape_fix.sub("%22", value)
12
+ pos = value.find(";")
13
+ if pos >= 0:
14
+ options = {
15
+ m.group(1).lower(): m.group(2) or m.group(3).replace("%22", "") for m in _option_regex.finditer(value[pos:])
16
+ }
17
+ value = value[:pos]
18
+ else:
19
+ options = {}
20
+
21
+ return value.strip().lower(), options
22
+
23
+ def get_header_value(headers: Dict[str, str], header: str, default_value: Optional[str] = None) -> Optional[str]:
24
+ return CaseInsensitiveDict(headers or {}).get(header, default_value)
@@ -2,9 +2,11 @@ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annot
2
2
  from mindor.core.utils.template import TemplateRenderer
3
3
 
4
4
  class WorkflowContext:
5
- def __init__(self, input: Dict[str, Any], env: Dict[str, str]):
5
+ def __init__(self, task_id: str, input: Dict[str, Any], env: Dict[str, str]):
6
+ self.task_id: str = task_id
6
7
  self.input: Dict[str, Any] = input
7
8
  self.env: Dict[str, str] = env
9
+ self.context: Dict[str, Any] = { "task_id": task_id }
8
10
  self.sources: Dict[str, Any] = { "jobs": {} }
9
11
  self.renderer = TemplateRenderer(self._resolve_source)
10
12
 
@@ -24,4 +26,6 @@ class WorkflowContext:
24
26
  return self.input
25
27
  if key == "env":
26
28
  return self.env
29
+ if key == "context":
30
+ return self.context
27
31
  raise KeyError(f"Unknown source: {key}")
@@ -3,6 +3,7 @@ from mindor.dsl.schema.workflow import JobConfig
3
3
  from mindor.dsl.schema.component import ComponentConfig
4
4
  from mindor.core.component import BaseComponent
5
5
  from .context import WorkflowContext
6
+ import ulid
6
7
 
7
8
  class Job:
8
9
  def __init__(self, id: str, config: JobConfig, component_provider: Callable[[Union[ComponentConfig, str]], BaseComponent]):
@@ -16,8 +17,9 @@ class Job:
16
17
  if not component.started:
17
18
  await component.start()
18
19
 
20
+ call_id = ulid.ulid()
19
21
  input = context.render_template(self.config.input)
20
- output = await component.run(self.config.action, input)
22
+ output = await component.run(self.config.action, call_id, input)
21
23
 
22
24
  context.register_source("output", output)
23
25
  output = context.render_template(self.config.output)
@@ -129,8 +129,8 @@ class Workflow:
129
129
  self.components: Dict[str, ComponentConfig] = components
130
130
  self.env: Dict[str, str] = env
131
131
 
132
- async def run(self, input: Dict[str, Any]) -> Dict[str, Any]:
133
- context = WorkflowContext(input, self.env)
132
+ async def run(self, task_id: str, input: Dict[str, Any]) -> Dict[str, Any]:
133
+ context = WorkflowContext(task_id, input, self.env)
134
134
  runner = WorkflowRunner(self.config.jobs, self._create_component)
135
135
 
136
136
  output = await runner.run(context)
@@ -1,9 +1,17 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
1
2
  from pydantic import BaseModel, Field, HttpUrl
2
3
  from pydantic import model_validator
3
- from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
4
4
  from .common import CommonActionConfig
5
5
 
6
- class HttpClientActionCompletionConfig(CommonActionConfig):
6
+ class CommonCompletionConfig(BaseModel):
7
+ type: Literal[ "callback", "polling" ]
8
+
9
+ class CallbackCompletionConfig(CommonCompletionConfig):
10
+ type: Literal["callback"]
11
+ wait_for: str
12
+
13
+ class PollingCompletionConfig(CommonCompletionConfig):
14
+ type: Literal["polling"]
7
15
  endpoint: Optional[HttpUrl] = None
8
16
  path: Optional[str] = None
9
17
  method: Literal[ "GET", "POST", "PUT", "DELETE", "PATCH" ] = "GET"
@@ -19,6 +27,14 @@ class HttpClientActionCompletionConfig(CommonActionConfig):
19
27
  raise ValueError("Either 'endpoint' or 'path' must be set, but not both.")
20
28
  return values
21
29
 
30
+ HttpClientCompletionConfig = Annotated[
31
+ Union[
32
+ CallbackCompletionConfig,
33
+ PollingCompletionConfig
34
+ ],
35
+ Field(discriminator="type")
36
+ ]
37
+
22
38
  class HttpClientActionConfig(CommonActionConfig):
23
39
  endpoint: Optional[HttpUrl] = None
24
40
  path: Optional[str] = None
@@ -27,7 +43,7 @@ class HttpClientActionConfig(CommonActionConfig):
27
43
  body: Optional[Dict[str, Any]] = Field(default_factory=dict)
28
44
  params: Optional[Dict[str, str]] = Field(default_factory=dict)
29
45
  deferred: bool = False
30
- completion: Optional[HttpClientActionCompletionConfig] = None
46
+ completion: Optional[HttpClientCompletionConfig] = None
31
47
 
32
48
  @model_validator(mode="before")
33
49
  def validate_endpoint_or_path(cls, values):
@@ -1,6 +1,6 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
1
2
  from pydantic import BaseModel, Field, HttpUrl
2
3
  from pydantic import model_validator
3
- from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
4
4
  from mindor.dsl.schema.action import HttpClientActionConfig
5
5
  from .common import ComponentType, CommonComponentConfig
6
6
 
@@ -27,7 +27,7 @@ class HttpClientComponentConfig(CommonComponentConfig):
27
27
 
28
28
  @model_validator(mode="after")
29
29
  def validate_baseurl_with_completion(self):
30
- for action_id, action in self.actions.items():
31
- if action.completion and action.completion.path and not self.base_url:
32
- raise ValueError(f"Completion for action '{action_id}' uses 'path' but 'base_url' is not set in the component.")
30
+ # for action_id, action in self.actions.items():
31
+ # if action.completion and action.completion.path and not self.base_url:
32
+ # raise ValueError(f"Completion for action '{action_id}' uses 'path' but 'base_url' is not set in the component.")
33
33
  return self
@@ -1,9 +1,10 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
1
2
  from pydantic import BaseModel, Field, HttpUrl
2
3
  from pydantic import model_validator
3
- from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
4
4
  from .common import ControllerType, CommonControllerConfig
5
5
 
6
6
  class HttpServerControllerConfig(CommonControllerConfig):
7
7
  type: Literal[ControllerType.HTTP_SERVER]
8
8
  host: Optional[str] = "0.0.0.0"
9
9
  port: Optional[int] = 8080
10
+ base_path: Optional[str] = None
@@ -0,0 +1 @@
1
+ from .listener import *
@@ -0,0 +1,2 @@
1
+ from .common import *
2
+ from .http_callback import *
@@ -0,0 +1,7 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from pydantic import BaseModel, Field
3
+ from .types import ListenerType
4
+
5
+ class CommonListenerConfig(BaseModel):
6
+ type: ListenerType
7
+ runtime: Literal[ "docker", "native" ] = "native"
@@ -0,0 +1,25 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from pydantic import BaseModel, Field, HttpUrl
3
+ from pydantic import model_validator
4
+ from .common import ListenerType, CommonListenerConfig
5
+
6
+ class HttpCallbackConfig(BaseModel):
7
+ path: str
8
+ method: Literal[ "GET", "POST", "PUT", "DELETE", "PATCH" ] = "GET"
9
+ bulk: bool = False
10
+ identify_by: str
11
+
12
+ class HttpCallbackListenerConfig(CommonListenerConfig):
13
+ type: Literal[ListenerType.HTTP_CALLBACK]
14
+ host: Optional[str] = "0.0.0.0"
15
+ port: Optional[int] = 8090
16
+ base_path: Optional[str] = None
17
+ callbacks: Optional[List[HttpCallbackConfig]] = Field(default_factory=list)
18
+
19
+ @model_validator(mode="before")
20
+ def inflate_single_callback(cls, values):
21
+ if "callbacks" not in values:
22
+ callback_keys = set(HttpCallbackConfig.model_fields.keys())
23
+ if any(k in values for k in callback_keys):
24
+ values["callbacks"] = [ { k: values.pop(k) for k in callback_keys if k in values } ]
25
+ return values
@@ -0,0 +1,4 @@
1
+ from enum import Enum
2
+
3
+ class ListenerType(str, Enum):
4
+ HTTP_CALLBACK = "http-callback"
@@ -0,0 +1,10 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from pydantic import BaseModel, Field, HttpUrl
3
+ from .impl import *
4
+
5
+ ListenerConfig = Annotated[
6
+ Union[
7
+ HttpCallbackListenerConfig
8
+ ],
9
+ Field(discriminator="type")
10
+ ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: model-compose
3
- Version: 0.0.3
3
+ Version: 0.0.5
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
@@ -21,10 +21,16 @@ src/mindor/core/controller/engine/__init__.py
21
21
  src/mindor/core/controller/engine/base.py
22
22
  src/mindor/core/controller/engine/http_server.py
23
23
  src/mindor/core/controller/engine/mcp_server.py
24
+ src/mindor/core/listener/__init__.py
25
+ src/mindor/core/listener/listener.py
26
+ src/mindor/core/listener/engine/__init__.py
27
+ src/mindor/core/listener/engine/base.py
28
+ src/mindor/core/listener/engine/http_callback.py
24
29
  src/mindor/core/runtime/__init__.py
25
30
  src/mindor/core/runtime/env.py
26
31
  src/mindor/core/utils/__init__.py
27
32
  src/mindor/core/utils/expiring.py
33
+ src/mindor/core/utils/http_headers.py
28
34
  src/mindor/core/utils/template.py
29
35
  src/mindor/core/utils/workqueue.py
30
36
  src/mindor/core/workflow/__init__.py
@@ -60,6 +66,12 @@ src/mindor/dsl/schema/controller/impl/common.py
60
66
  src/mindor/dsl/schema/controller/impl/http_server.py
61
67
  src/mindor/dsl/schema/controller/impl/mcp_server.py
62
68
  src/mindor/dsl/schema/controller/impl/types.py
69
+ src/mindor/dsl/schema/listener/__init__.py
70
+ src/mindor/dsl/schema/listener/listener.py
71
+ src/mindor/dsl/schema/listener/impl/__init__.py
72
+ src/mindor/dsl/schema/listener/impl/common.py
73
+ src/mindor/dsl/schema/listener/impl/http_callback.py
74
+ src/mindor/dsl/schema/listener/impl/types.py
63
75
  src/mindor/dsl/schema/runtime/__init__.py
64
76
  src/mindor/dsl/schema/runtime/runtime.py
65
77
  src/mindor/dsl/schema/runtime/impl/__init__.py
File without changes
File without changes
File without changes
@@ -1,5 +1,5 @@
1
- from pydantic import BaseModel, ValidationError
2
1
  from typing import Union, List, Type
2
+ from pydantic import BaseModel, ValidationError
3
3
  from pathlib import Path
4
4
  import yaml
5
5
 
@@ -1,5 +1,5 @@
1
- from pydantic import BaseModel, Field
2
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from pydantic import BaseModel, Field
3
3
  from .impl import *
4
4
 
5
5
  ActionConfig = Annotated[
@@ -1,5 +1,5 @@
1
- from pydantic import BaseModel, Field
2
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from pydantic import BaseModel, Field
3
3
 
4
4
  class CommonActionConfig(BaseModel):
5
5
  output: Optional[Dict[str, Any]] = Field(default_factory=dict)
@@ -1,6 +1,6 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
1
2
  from pydantic import BaseModel, Field, HttpUrl
2
3
  from pydantic import model_validator
3
- from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
4
4
  from .common import CommonActionConfig
5
5
 
6
6
  class HttpServerActionConfig(CommonActionConfig):
@@ -1,6 +1,6 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
1
2
  from pydantic import BaseModel, Field, HttpUrl
2
3
  from pydantic import model_validator
3
- from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
4
4
  from .common import CommonActionConfig
5
5
 
6
6
  class McpClientActionConfig(CommonActionConfig):
@@ -1,6 +1,6 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
1
2
  from pydantic import BaseModel, Field, HttpUrl
2
3
  from pydantic import model_validator
3
- from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
4
4
  from .common import CommonActionConfig
5
5
 
6
6
  class McpServerActionConfig(CommonActionConfig):
@@ -1,5 +1,5 @@
1
- from pydantic import BaseModel, Field, HttpUrl
2
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from pydantic import BaseModel, Field, HttpUrl
3
3
  from .types import ComponentType
4
4
 
5
5
  class CommonComponentConfig(BaseModel):
@@ -1,6 +1,6 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
1
2
  from pydantic import BaseModel, Field, HttpUrl
2
3
  from pydantic import model_validator
3
- from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
4
4
  from mindor.dsl.schema.action import HttpServerActionConfig
5
5
  from .common import ComponentType, CommonComponentConfig
6
6
 
@@ -1,6 +1,6 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
1
2
  from pydantic import BaseModel, Field, HttpUrl
2
3
  from pydantic import model_validator
3
- from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
4
4
  from mindor.dsl.schema.action import McpClientActionConfig
5
5
  from .common import ComponentType, CommonComponentConfig
6
6
 
@@ -1,6 +1,6 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
1
2
  from pydantic import BaseModel, Field, HttpUrl
2
3
  from pydantic import model_validator
3
- from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
4
4
  from mindor.dsl.schema.action import McpServerActionConfig
5
5
  from .common import ComponentType, CommonComponentConfig
6
6
 
@@ -1,6 +1,6 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
1
2
  from pydantic import BaseModel, Field
2
3
  from pydantic import model_validator
3
- from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
4
4
 
5
5
  from .controller import ControllerConfig
6
6
  from .component import ComponentConfig
@@ -1,5 +1,5 @@
1
- from pydantic import BaseModel, Field, HttpUrl
2
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from pydantic import BaseModel, Field, HttpUrl
3
3
  from .impl import *
4
4
 
5
5
  ControllerConfig = Annotated[
@@ -1,5 +1,5 @@
1
- from pydantic import BaseModel, Field
2
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from pydantic import BaseModel, Field
3
3
  from .types import ControllerType
4
4
 
5
5
  class CommonControllerConfig(BaseModel):
@@ -1,6 +1,6 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
1
2
  from pydantic import BaseModel, Field, HttpUrl
2
3
  from pydantic import model_validator
3
- from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
4
4
  from .common import ControllerType, CommonControllerConfig
5
5
 
6
6
  class McpServerControllerConfig(CommonControllerConfig):
@@ -1,5 +1,5 @@
1
- from pydantic import BaseModel, Field
2
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from pydantic import BaseModel, Field
3
3
  from .types import RuntimeType
4
4
 
5
5
  class CommonRumtimeConfig(BaseModel):
@@ -1,6 +1,6 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
1
2
  from pydantic import BaseModel, Field, HttpUrl
2
3
  from pydantic import model_validator
3
- from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
4
4
  from .common import RuntimeType, CommonRumtimeConfig
5
5
 
6
6
  class DockerRuntimeConfig(CommonRumtimeConfig):
@@ -1,5 +1,5 @@
1
- from pydantic import BaseModel, Field, HttpUrl
2
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from pydantic import BaseModel, Field, HttpUrl
3
3
  from .impl import *
4
4
 
5
5
  RuntimeConfig = Annotated[
@@ -1,6 +1,6 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
1
2
  from pydantic import BaseModel, Field
2
3
  from pydantic import model_validator
3
- from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
4
4
  from .component import ComponentConfig
5
5
 
6
6
  class JobConfig(BaseModel):