dinkster-api 0.0.2__py3-none-any.whl
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.
- dinkster_api/__init__.py +9 -0
- dinkster_api/py.typed +0 -0
- dinkster_api/v1.py +935 -0
- dinkster_api-0.0.2.dist-info/METADATA +16 -0
- dinkster_api-0.0.2.dist-info/RECORD +238 -0
- dinkster_api-0.0.2.dist-info/WHEEL +4 -0
- dinkster_api-0.0.2.dist-info/entry_points.txt +2 -0
- dinkster_assets/__init__.py +337 -0
- dinkster_assets/acquire.py +226 -0
- dinkster_assets/acquisition_plan.py +145 -0
- dinkster_assets/audio.py +119 -0
- dinkster_assets/catalog.py +156 -0
- dinkster_assets/component_manifest.py +110 -0
- dinkster_assets/declared.py +218 -0
- dinkster_assets/fetch.py +495 -0
- dinkster_assets/guess.py +136 -0
- dinkster_assets/identity.py +72 -0
- dinkster_assets/integrity.py +310 -0
- dinkster_assets/json_metadata.py +41 -0
- dinkster_assets/kind.py +52 -0
- dinkster_assets/latent_format.py +346 -0
- dinkster_assets/library.py +816 -0
- dinkster_assets/media.py +1385 -0
- dinkster_assets/migrations/env.py +38 -0
- dinkster_assets/migrations/script.py.mako +28 -0
- dinkster_assets/migrations/versions/0001_asset_records_create_asset_record_catalog.py +109 -0
- dinkster_assets/migrations/versions/0002_asset_observations.py +36 -0
- dinkster_assets/model.py +242 -0
- dinkster_assets/mounts.py +1278 -0
- dinkster_assets/need.py +168 -0
- dinkster_assets/pack_assets.py +217 -0
- dinkster_assets/probe.py +118 -0
- dinkster_assets/provenance.py +254 -0
- dinkster_assets/public_acquisition.py +424 -0
- dinkster_assets/py.typed +0 -0
- dinkster_assets/records.py +899 -0
- dinkster_assets/resolution.py +778 -0
- dinkster_assets/resolver_index.py +364 -0
- dinkster_assets/resolver_subscription.py +1149 -0
- dinkster_assets/save_target.py +139 -0
- dinkster_assets/schema.py +73 -0
- dinkster_assets/selections.py +256 -0
- dinkster_assets/transport.py +243 -0
- dinkster_assets/value.py +258 -0
- dinkster_assets/vault.py +276 -0
- dinkster_assets/writer.py +765 -0
- dinkster_caches/__init__.py +19 -0
- dinkster_caches/cas.py +310 -0
- dinkster_caches/disk.py +330 -0
- dinkster_caches/layered.py +150 -0
- dinkster_caches/memory.py +208 -0
- dinkster_caches/py.typed +0 -0
- dinkster_caches/wire.py +346 -0
- dinkster_image_document/__init__.py +39 -0
- dinkster_image_document/blend.py +264 -0
- dinkster_image_document/compat.py +211 -0
- dinkster_image_document/document.py +374 -0
- dinkster_image_document/format.py +569 -0
- dinkster_image_document/interchange.py +572 -0
- dinkster_image_document/py.typed +1 -0
- dinkster_image_document/render.py +455 -0
- dinkster_inference_wire/__init__.py +141 -0
- dinkster_inference_wire/camera_wire.py +127 -0
- dinkster_inference_wire/conditioning.py +752 -0
- dinkster_inference_wire/conditioning_wire.py +1143 -0
- dinkster_inference_wire/devices.py +285 -0
- dinkster_inference_wire/generation.py +446 -0
- dinkster_inference_wire/hook_values.py +168 -0
- dinkster_inference_wire/ic_lora_parameters.py +69 -0
- dinkster_inference_wire/latents.py +244 -0
- dinkster_inference_wire/media_wire.py +169 -0
- dinkster_inference_wire/openai_generation.py +1035 -0
- dinkster_inference_wire/pose_keypoint.py +83 -0
- dinkster_inference_wire/process.py +33 -0
- dinkster_inference_wire/py.typed +0 -0
- dinkster_inference_wire/registry.py +116 -0
- dinkster_inference_wire/sam3_wire.py +68 -0
- dinkster_inference_wire/sampling_wire.py +279 -0
- dinkster_inference_wire/sources.py +399 -0
- dinkster_inference_wire/spaces.py +344 -0
- dinkster_inference_wire/sparse.py +372 -0
- dinkster_inference_wire/splat_wire.py +23 -0
- dinkster_inference_wire/types.py +256 -0
- dinkster_inference_wire/vision_wire.py +53 -0
- dinkster_inference_wire/voxel_wire.py +72 -0
- dinkster_inference_wire/wan_wire.py +93 -0
- dinkster_inference_wire/weights.py +219 -0
- dinkster_memory/__init__.py +92 -0
- dinkster_memory/accelerator.py +96 -0
- dinkster_memory/budgets.py +96 -0
- dinkster_memory/details.py +91 -0
- dinkster_memory/governor.py +554 -0
- dinkster_memory/leases.py +176 -0
- dinkster_memory/py.typed +0 -0
- dinkster_memory/release.py +131 -0
- dinkster_memory/reported.py +101 -0
- dinkster_memory/reservations.py +101 -0
- dinkster_memory/resident.py +600 -0
- dinkster_memory/system.py +614 -0
- dinkster_memory/tenants.py +67 -0
- dinkster_protocol/__init__.py +1008 -0
- dinkster_protocol/attention.py +652 -0
- dinkster_protocol/extension_contribution_kinds.json +21 -0
- dinkster_protocol/extension_contribution_kinds.py +43 -0
- dinkster_protocol/extensions.py +742 -0
- dinkster_protocol/frontend_modules.py +145 -0
- dinkster_protocol/multigpu.py +107 -0
- dinkster_protocol/pack_surfaces.py +489 -0
- dinkster_protocol/payload.py +63 -0
- dinkster_protocol/preview.py +86 -0
- dinkster_protocol/py.typed +0 -0
- dinkster_protocol/result_algebra.py +380 -0
- dinkster_protocol/result_document.py +244 -0
- dinkster_protocol/run_control.py +96 -0
- dinkster_protocol/training.py +399 -0
- dinkster_protocol/workgroup.py +1014 -0
- dinkster_registry/__init__.py +135 -0
- dinkster_registry/admission.py +372 -0
- dinkster_registry/artifact.py +128 -0
- dinkster_registry/composition.py +152 -0
- dinkster_registry/grants.py +206 -0
- dinkster_registry/install.py +1017 -0
- dinkster_registry/model.py +414 -0
- dinkster_registry/principals.py +494 -0
- dinkster_registry/py.typed +0 -0
- dinkster_schema/__init__.py +347 -0
- dinkster_schema/capture.py +240 -0
- dinkster_schema/coerce.py +186 -0
- dinkster_schema/comfy_alias.py +496 -0
- dinkster_schema/comfy_group.py +768 -0
- dinkster_schema/elaborate.py +676 -0
- dinkster_schema/execution.py +24 -0
- dinkster_schema/expansion.py +123 -0
- dinkster_schema/log.py +206 -0
- dinkster_schema/media.py +77 -0
- dinkster_schema/model.py +2920 -0
- dinkster_schema/names.py +25 -0
- dinkster_schema/node.py +137 -0
- dinkster_schema/output_descriptors.py +63 -0
- dinkster_schema/py.typed +0 -0
- dinkster_schema/replace.py +1853 -0
- dinkster_schema/reporting.py +441 -0
- dinkster_schema/solve.py +178 -0
- dinkster_schema/wire.py +1876 -0
- dinkster_values/__init__.py +625 -0
- dinkster_values/_media_array.py +7 -0
- dinkster_values/absent.py +77 -0
- dinkster_values/asset_folders.py +64 -0
- dinkster_values/assets.py +106 -0
- dinkster_values/audio_codec.py +594 -0
- dinkster_values/audio_lazy.py +871 -0
- dinkster_values/builtin_types.py +181 -0
- dinkster_values/curve.py +260 -0
- dinkster_values/detection_codec.py +286 -0
- dinkster_values/image_codec.py +691 -0
- dinkster_values/image_components.py +219 -0
- dinkster_values/latent_codec.py +514 -0
- dinkster_values/limits.py +52 -0
- dinkster_values/lists.py +216 -0
- dinkster_values/media_containers.py +70 -0
- dinkster_values/model.py +417 -0
- dinkster_values/model3d_codec.py +167 -0
- dinkster_values/names.py +168 -0
- dinkster_values/pins.py +77 -0
- dinkster_values/py.typed +0 -0
- dinkster_values/registry.py +1105 -0
- dinkster_values/resident.py +210 -0
- dinkster_values/resources.py +210 -0
- dinkster_values/splat_codec.py +477 -0
- dinkster_values/storage.py +133 -0
- dinkster_values/streams.py +241 -0
- dinkster_values/svg_codec.py +120 -0
- dinkster_values/timeline_video.py +68 -0
- dinkster_values/tuples.py +61 -0
- dinkster_values/video_codec.py +897 -0
- dinkster_values/video_document.py +315 -0
- dinkster_values/video_edits.py +253 -0
- dinkster_values/video_probe.py +221 -0
- dinkster_video/__init__.py +58 -0
- dinkster_video/document.py +352 -0
- dinkster_video/formats.py +722 -0
- dinkster_video/image_math.py +175 -0
- dinkster_video/preview/dinkster-pack.toml +36 -0
- dinkster_video/preview/src/dinkster_video_preview/__init__.py +84 -0
- dinkster_video/preview/src/dinkster_video_preview/preview.js +45 -0
- dinkster_video/py.typed +1 -0
- dinkster_video/runtime.py +1561 -0
- dinkster_video/timeline.py +576 -0
- dinkster_video/timeline_runtime.py +305 -0
- dinkster_video_preview/__init__.py +84 -0
- dinkster_video_preview/preview.js +45 -0
- dinkster_workers/__init__.py +207 -0
- dinkster_workers/_doctor_probe.py +359 -0
- dinkster_workers/accelerator.py +156 -0
- dinkster_workers/aimdo_bootstrap.py +81 -0
- dinkster_workers/backend_env.py +1485 -0
- dinkster_workers/blobs.py +402 -0
- dinkster_workers/boundary.py +1918 -0
- dinkster_workers/catalog.py +182 -0
- dinkster_workers/devices.py +164 -0
- dinkster_workers/diagnostics.py +64 -0
- dinkster_workers/dispatch.py +298 -0
- dinkster_workers/doctor.py +1382 -0
- dinkster_workers/egress.py +344 -0
- dinkster_workers/error_hints.py +175 -0
- dinkster_workers/execution.py +160 -0
- dinkster_workers/governed.py +123 -0
- dinkster_workers/headroom.py +156 -0
- dinkster_workers/host.py +3116 -0
- dinkster_workers/in_process.py +1325 -0
- dinkster_workers/interpreter.py +84 -0
- dinkster_workers/isolated.py +862 -0
- dinkster_workers/launch.py +72 -0
- dinkster_workers/manifest.py +3332 -0
- dinkster_workers/media.py +196 -0
- dinkster_workers/pack_environment.py +473 -0
- dinkster_workers/pack_lock.py +359 -0
- dinkster_workers/placement.py +215 -0
- dinkster_workers/preview_artifacts.py +233 -0
- dinkster_workers/produced_assets.py +218 -0
- dinkster_workers/provision.py +597 -0
- dinkster_workers/py.typed +0 -0
- dinkster_workers/relay.py +762 -0
- dinkster_workers/remote.py +921 -0
- dinkster_workers/resume.py +490 -0
- dinkster_workers/routing.py +134 -0
- dinkster_workers/run_control.py +275 -0
- dinkster_workers/sandbox.py +798 -0
- dinkster_workers/saved_artifacts.py +275 -0
- dinkster_workers/service.py +593 -0
- dinkster_workers/session.py +2605 -0
- dinkster_workers/single_job_multi_gpu.py +7 -0
- dinkster_workers/staging.py +230 -0
- dinkster_workers/transport.py +161 -0
- dinkster_workers/workgroup.py +729 -0
- dinkster_workers/workgroup_compose.py +135 -0
- dinkster_workers/workgroup_config.py +196 -0
- dinkster_workers/workgroup_session.py +340 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"""Run-scoped control relay for boundary workers.
|
|
2
|
+
|
|
3
|
+
The relay carries requests, not callbacks. A request is always tied to the
|
|
4
|
+
boundary invocation that owns it, so a completed or cancelled invocation
|
|
5
|
+
cannot address a later run which happens to reuse a node id.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import json
|
|
12
|
+
from collections.abc import Awaitable, Callable, Mapping, Sequence
|
|
13
|
+
from multiprocessing.shared_memory import SharedMemory
|
|
14
|
+
from types import MappingProxyType
|
|
15
|
+
from typing import Any, cast
|
|
16
|
+
|
|
17
|
+
from dinkster_protocol import InvocationResult
|
|
18
|
+
from dinkster_schema import RunExecutionContext
|
|
19
|
+
from dinkster_values import Value
|
|
20
|
+
|
|
21
|
+
from .boundary import BoundaryError, ValueCodec, encode_result, release_segment
|
|
22
|
+
|
|
23
|
+
Send = Callable[[dict[str, object], Sequence[bytes], Sequence[SharedMemory]], Awaitable[None]]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _document(value: Mapping[str, object]) -> bytes:
|
|
27
|
+
return json.dumps(value, default=dict, separators=(",", ":")).encode("utf-8")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _freeze(value: object) -> object:
|
|
31
|
+
if isinstance(value, dict):
|
|
32
|
+
return MappingProxyType(
|
|
33
|
+
{key: _freeze(item) for key, item in cast("dict[str, object]", value).items()}
|
|
34
|
+
)
|
|
35
|
+
if isinstance(value, list):
|
|
36
|
+
return tuple(_freeze(item) for item in cast("list[object]", value))
|
|
37
|
+
return value
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class BoundaryRunContext:
|
|
41
|
+
"""Worker-local proxy for the parent invocation's live run context."""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
invocation_id: str,
|
|
46
|
+
node_id: str,
|
|
47
|
+
inputs: Mapping[str, Value],
|
|
48
|
+
codec: ValueCodec,
|
|
49
|
+
send: Send,
|
|
50
|
+
suspend: Callable[[], Awaitable[None]],
|
|
51
|
+
resume: Callable[[], Awaitable[None]],
|
|
52
|
+
prepare_values: Callable[[Mapping[str, Value]], Awaitable[Mapping[str, Value]]],
|
|
53
|
+
) -> None:
|
|
54
|
+
self._invocation_id = invocation_id
|
|
55
|
+
self._node_id = node_id
|
|
56
|
+
self._inputs = inputs
|
|
57
|
+
self._codec = codec
|
|
58
|
+
self._send = send
|
|
59
|
+
self._suspend = suspend
|
|
60
|
+
self._resume = resume
|
|
61
|
+
self._prepare_values = prepare_values
|
|
62
|
+
self._wait_lock = asyncio.Lock()
|
|
63
|
+
self._pending: dict[str, asyncio.Future[tuple[dict[str, Any], list[bytes]]]] = {}
|
|
64
|
+
self._completed: set[str] = set()
|
|
65
|
+
self._index = 0
|
|
66
|
+
self._closed = False
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def node_id(self) -> str:
|
|
70
|
+
return self._node_id
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def inputs(self) -> Mapping[str, Value]:
|
|
74
|
+
return self._inputs
|
|
75
|
+
|
|
76
|
+
def close(self) -> None:
|
|
77
|
+
self._closed = True
|
|
78
|
+
for future in self._pending.values():
|
|
79
|
+
if not future.done():
|
|
80
|
+
future.cancel()
|
|
81
|
+
self._pending.clear()
|
|
82
|
+
self._completed.clear()
|
|
83
|
+
|
|
84
|
+
def accept(self, header: dict[str, Any], blobs: list[bytes]) -> bool:
|
|
85
|
+
request_id = header.get("requestId")
|
|
86
|
+
if type(request_id) is not str or header.get("invocationId") != self._invocation_id:
|
|
87
|
+
return False
|
|
88
|
+
future = self._pending.pop(request_id, None)
|
|
89
|
+
if future is None:
|
|
90
|
+
return request_id in self._completed
|
|
91
|
+
if future.done():
|
|
92
|
+
return False
|
|
93
|
+
future.set_result((header, blobs))
|
|
94
|
+
self._completed.add(request_id)
|
|
95
|
+
return True
|
|
96
|
+
|
|
97
|
+
async def _request(
|
|
98
|
+
self,
|
|
99
|
+
operation: str,
|
|
100
|
+
fields: Mapping[str, object] = {},
|
|
101
|
+
blobs: Sequence[bytes] = (),
|
|
102
|
+
segments: Sequence[SharedMemory] = (),
|
|
103
|
+
) -> tuple[dict[str, Any], list[bytes]]:
|
|
104
|
+
if self._closed:
|
|
105
|
+
for segment in segments:
|
|
106
|
+
release_segment(segment)
|
|
107
|
+
raise RuntimeError("execution context is no longer active")
|
|
108
|
+
request_id = f"{self._invocation_id}:control:{self._index}"
|
|
109
|
+
self._index += 1
|
|
110
|
+
future: asyncio.Future[tuple[dict[str, Any], list[bytes]]] = (
|
|
111
|
+
asyncio.get_running_loop().create_future()
|
|
112
|
+
)
|
|
113
|
+
self._pending[request_id] = future
|
|
114
|
+
try:
|
|
115
|
+
await self._send(
|
|
116
|
+
{
|
|
117
|
+
"type": "runControl",
|
|
118
|
+
"invocationId": self._invocation_id,
|
|
119
|
+
"requestId": request_id,
|
|
120
|
+
"operation": operation,
|
|
121
|
+
**fields,
|
|
122
|
+
},
|
|
123
|
+
blobs,
|
|
124
|
+
segments,
|
|
125
|
+
)
|
|
126
|
+
header, reply_blobs = await future
|
|
127
|
+
finally:
|
|
128
|
+
self._pending.pop(request_id, None)
|
|
129
|
+
error = header.get("error")
|
|
130
|
+
if type(error) is str:
|
|
131
|
+
if header.get("errorType") == "ValueError":
|
|
132
|
+
raise ValueError(error)
|
|
133
|
+
raise RuntimeError(error)
|
|
134
|
+
return header, reply_blobs
|
|
135
|
+
|
|
136
|
+
async def get_graph(self) -> Mapping[str, object]:
|
|
137
|
+
_, blobs = await self._request("getGraph")
|
|
138
|
+
if len(blobs) != 1:
|
|
139
|
+
raise BoundaryError("run control graph response is malformed")
|
|
140
|
+
graph = json.loads(blobs[0])
|
|
141
|
+
if not isinstance(graph, Mapping):
|
|
142
|
+
raise BoundaryError("run control graph response is not a mapping")
|
|
143
|
+
return cast("Mapping[str, object]", _freeze(cast("Mapping[str, object]", graph)))
|
|
144
|
+
|
|
145
|
+
async def await_run_value(self, node_id: str) -> Mapping[str, Value]:
|
|
146
|
+
async with self._wait_lock:
|
|
147
|
+
await self._suspend()
|
|
148
|
+
try:
|
|
149
|
+
header, blobs = await self._request("awaitRunValue", {"nodeId": node_id})
|
|
150
|
+
except asyncio.CancelledError:
|
|
151
|
+
raise
|
|
152
|
+
except BaseException:
|
|
153
|
+
if not self._closed:
|
|
154
|
+
await self._resume()
|
|
155
|
+
raise
|
|
156
|
+
if self._closed:
|
|
157
|
+
raise RuntimeError("execution context is no longer active")
|
|
158
|
+
await self._resume()
|
|
159
|
+
values = header.get("values")
|
|
160
|
+
if not isinstance(values, Mapping):
|
|
161
|
+
raise BoundaryError("run control value response is malformed")
|
|
162
|
+
typed_values = cast("Mapping[object, object]", values)
|
|
163
|
+
consumed: list[str] = []
|
|
164
|
+
result: dict[str, Value] = {
|
|
165
|
+
name: self._codec.decode(cast("Mapping[str, Any]", wire), blobs, consumed)[0]
|
|
166
|
+
for name, wire in typed_values.items()
|
|
167
|
+
if type(name) is str and isinstance(wire, Mapping)
|
|
168
|
+
}
|
|
169
|
+
if len(result) != len(typed_values):
|
|
170
|
+
raise BoundaryError("run control value response is malformed")
|
|
171
|
+
if consumed:
|
|
172
|
+
await self._send({"type": "shmAck", "segments": consumed}, [], [])
|
|
173
|
+
return result
|
|
174
|
+
|
|
175
|
+
async def publish_run_value(self, node_id: str, value: Mapping[str, Value]) -> None:
|
|
176
|
+
if self._closed:
|
|
177
|
+
raise RuntimeError("execution context is no longer active")
|
|
178
|
+
value = await self._prepare_values(value)
|
|
179
|
+
with self._codec.suspend_persistent_cas():
|
|
180
|
+
encoded, blobs, segments = encode_result(
|
|
181
|
+
self._codec,
|
|
182
|
+
InvocationResult(outputs=value),
|
|
183
|
+
self._invocation_id,
|
|
184
|
+
0,
|
|
185
|
+
)
|
|
186
|
+
await self._request(
|
|
187
|
+
"publishRunValue", {"nodeId": node_id, "values": encoded["outputs"]}, blobs, segments
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
async def inhibit(self, node_ids: Sequence[str]) -> None:
|
|
191
|
+
await self._request("inhibit", {"nodeIds": list(node_ids)})
|
|
192
|
+
|
|
193
|
+
async def override_node(self, node_id: str, inputs: Mapping[str, object]) -> None:
|
|
194
|
+
await self._request("overrideNode", {"nodeId": node_id}, [_document(inputs)])
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
async def dispatch_run_control(
|
|
198
|
+
context: RunExecutionContext,
|
|
199
|
+
codec: ValueCodec,
|
|
200
|
+
header: Mapping[str, Any],
|
|
201
|
+
blobs: list[bytes],
|
|
202
|
+
send: Send,
|
|
203
|
+
publish: Callable[[str, Mapping[str, Value]], Awaitable[None]],
|
|
204
|
+
prepare_values: Callable[[Mapping[str, Value]], Awaitable[None]],
|
|
205
|
+
) -> None:
|
|
206
|
+
"""Run one parent-side request and send a single reply."""
|
|
207
|
+
request_id = header.get("requestId")
|
|
208
|
+
invocation_id = header.get("invocationId")
|
|
209
|
+
if type(request_id) is not str or type(invocation_id) is not str:
|
|
210
|
+
return
|
|
211
|
+
reply: dict[str, object] = {
|
|
212
|
+
"type": "runControlResult",
|
|
213
|
+
"invocationId": invocation_id,
|
|
214
|
+
"requestId": request_id,
|
|
215
|
+
}
|
|
216
|
+
reply_blobs: list[bytes] = []
|
|
217
|
+
segments: list[SharedMemory] = []
|
|
218
|
+
consumed: list[str] = []
|
|
219
|
+
try:
|
|
220
|
+
operation = header.get("operation")
|
|
221
|
+
if operation == "getGraph":
|
|
222
|
+
reply_blobs.append(_document(await context.get_graph()))
|
|
223
|
+
elif operation == "awaitRunValue":
|
|
224
|
+
node_id = header.get("nodeId")
|
|
225
|
+
if type(node_id) is not str:
|
|
226
|
+
raise BoundaryError("run control node id is malformed")
|
|
227
|
+
published = await context.await_run_value(node_id)
|
|
228
|
+
await prepare_values(published)
|
|
229
|
+
with codec.suspend_persistent_cas():
|
|
230
|
+
encoded, reply_blobs, segments = encode_result(
|
|
231
|
+
codec,
|
|
232
|
+
InvocationResult(outputs=published),
|
|
233
|
+
invocation_id,
|
|
234
|
+
0,
|
|
235
|
+
)
|
|
236
|
+
reply["values"] = encoded["outputs"]
|
|
237
|
+
elif operation == "publishRunValue":
|
|
238
|
+
raw_values = header.get("values")
|
|
239
|
+
node_id = header.get("nodeId")
|
|
240
|
+
if type(node_id) is not str or not isinstance(raw_values, Mapping):
|
|
241
|
+
raise BoundaryError("run control publish request is malformed")
|
|
242
|
+
typed_values = cast("Mapping[object, object]", raw_values)
|
|
243
|
+
values: dict[str, Value] = {
|
|
244
|
+
name: codec.decode(cast("Mapping[str, Any]", wire), blobs, consumed)[0]
|
|
245
|
+
for name, wire in typed_values.items()
|
|
246
|
+
if type(name) is str and isinstance(wire, Mapping)
|
|
247
|
+
}
|
|
248
|
+
if len(values) != len(typed_values):
|
|
249
|
+
raise BoundaryError("run control publish request is malformed")
|
|
250
|
+
await publish(node_id, values)
|
|
251
|
+
elif operation == "inhibit":
|
|
252
|
+
node_ids_raw = header.get("nodeIds")
|
|
253
|
+
if not isinstance(node_ids_raw, list):
|
|
254
|
+
raise BoundaryError("run control inhibit request is malformed")
|
|
255
|
+
node_id_items = cast("list[object]", node_ids_raw)
|
|
256
|
+
if not all(type(item) is str for item in node_id_items):
|
|
257
|
+
raise BoundaryError("run control inhibit request is malformed")
|
|
258
|
+
node_ids = cast("list[str]", cast("object", node_id_items))
|
|
259
|
+
await context.inhibit(node_ids)
|
|
260
|
+
elif operation == "overrideNode":
|
|
261
|
+
node_id = header.get("nodeId")
|
|
262
|
+
if type(node_id) is not str or len(blobs) != 1:
|
|
263
|
+
raise BoundaryError("run control override request is malformed")
|
|
264
|
+
inputs = json.loads(blobs[0])
|
|
265
|
+
if not isinstance(inputs, Mapping):
|
|
266
|
+
raise BoundaryError("run control override inputs are malformed")
|
|
267
|
+
await context.override_node(node_id, cast("Mapping[str, object]", inputs))
|
|
268
|
+
else:
|
|
269
|
+
raise BoundaryError("unknown run control operation")
|
|
270
|
+
except Exception as exc: # node-facing failures retain their normal messages
|
|
271
|
+
reply["error"] = str(exc)
|
|
272
|
+
reply["errorType"] = "ValueError" if isinstance(exc, ValueError) else "RuntimeError"
|
|
273
|
+
if consumed:
|
|
274
|
+
await send({"type": "shmAck", "segments": consumed}, [], [])
|
|
275
|
+
await send(reply, reply_blobs, segments)
|