reactor-runtime 2.7.6__tar.gz → 2.7.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/PKG-INFO +1 -1
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/pyproject.toml +1 -1
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/config.py +36 -1
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/defaults.py +12 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/internal/output_buffer.py +46 -87
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/recording/session_recorder.py +14 -2
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/runtime_api.py +262 -14
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/runtimes/headless/headless_runtime.py +35 -17
- reactor_runtime-2.7.8/src/reactor_runtime/schema_validator.py +231 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/utils/messages.py +4 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime.egg-info/PKG-INFO +1 -1
- reactor_runtime-2.7.6/src/reactor_runtime/schema_validator.py +0 -123
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/README.md +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/setup.cfg +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/api/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/experiment/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/experiment/session.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/driver/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/driver/pipeline_executor.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/driver/step_result.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/events/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/events/connected.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/events/event.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/events/messages.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/events/upload.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/internal/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/internal/input_buffer.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/internal/reactor_core.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/model/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/model/decorators.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/model/handlers.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/model/reactor_model.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/pipeline/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/pipeline/idle.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/pipeline/input_state.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/pipeline/reactor_pipeline.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/tracks/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/tracks/descriptors.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/tracks/input.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/tracks/output.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/interface/upload.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/model_state.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/profiling/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/profiling/backends/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/profiling/backends/base.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/profiling/backends/file.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/profiling/backends/otlp.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/profiling/helpers.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/profiling/nvml_sampler.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/profiling/plotting/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/profiling/plotting/plot_profiling.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/profiling/profiler.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/profiling/singleton.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/profiling/torch_chunk_profiler.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/recording/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/recording/chunk_encoder.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/recording/chunk_uploader.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/recording/config.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/recording/markers.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/recording/sinks.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/recording/track_resolver.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/runtimes/headless/config.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/runtimes/headless/input_feeder.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/runtimes/http/config.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/runtimes/http/http_runtime.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/runtimes/http/types.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/schema.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/serve/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/serve/__main__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/serve/commands/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/serve/commands/run.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/serve/commands/schema.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/serve/main.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/serve/utils/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/serve/utils/config.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/serve/utils/runtime.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/aiortc/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/aiortc/audio_track.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/aiortc/client.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/aiortc/frame_conversion.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/aiortc/ice_connection.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/aiortc/video_track.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/config.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/events.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/client.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/decoders/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/decoders/av1.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/decoders/base.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/decoders/factory.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/decoders/h264.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/decoders/h265.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/decoders/opus.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/decoders/vp8.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/decoders/vp9.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/encoders/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/encoders/av1.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/encoders/base.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/encoders/factory.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/encoders/h264.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/encoders/h265.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/encoders/opus.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/encoders/vp8.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/encoders/vp9.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/gst.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/gst_helpers.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/probes/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/probes/fps_probe.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/receiver/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/receiver/audio.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/receiver/base.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/receiver/video.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/sdp/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/sdp/bundle.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/sdp/codec.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/sdp/extmap.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/sdp/ice.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/sender/__init__.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/sender/audio.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/sender/base.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/sender/video.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/settings.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/gstreamer/signals.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/ice_uris.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/interface.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/media.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/transports/types.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/utils/launch.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/utils/loader.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/utils/log.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/utils/paths.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/utils/ports.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/utils/typing.py +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime.egg-info/SOURCES.txt +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime.egg-info/dependency_links.txt +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime.egg-info/requires.txt +0 -0
- {reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime.egg-info/top_level.txt +0 -0
|
@@ -81,6 +81,37 @@ def _reset_legacy_warning_for_tests() -> None:
|
|
|
81
81
|
_legacy_warned = False
|
|
82
82
|
|
|
83
83
|
|
|
84
|
+
@dataclass
|
|
85
|
+
class ModerationYamlConfig:
|
|
86
|
+
"""Top-level ``moderation:`` block parsed from ``reactor.yaml``.
|
|
87
|
+
|
|
88
|
+
Model authors declare here whether content moderation runs for their
|
|
89
|
+
model. Defaults to ``enabled: false`` so existing models opt in
|
|
90
|
+
explicitly. Operational tuning (thresholds, concurrency, backend
|
|
91
|
+
choice, ``OPENAI_API_KEY``) stays in environment variables — this
|
|
92
|
+
block carries only the policy decision the model author owns.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
enabled: bool = False
|
|
96
|
+
|
|
97
|
+
@classmethod
|
|
98
|
+
def from_dict(cls, raw: Optional[Dict[str, Any]]) -> "ModerationYamlConfig":
|
|
99
|
+
if not isinstance(raw, dict):
|
|
100
|
+
return cls()
|
|
101
|
+
enabled = raw.get("enabled", False)
|
|
102
|
+
# Strict bool check: `bool("false") is True` would silently flip
|
|
103
|
+
# moderation on for a YAML author who wrote `enabled: "false"`
|
|
104
|
+
# (quoted) or templated a `"0"` value. Safety-sensitive config —
|
|
105
|
+
# fail at parse time with a message that names the offending value.
|
|
106
|
+
if not isinstance(enabled, bool):
|
|
107
|
+
raise TypeError(
|
|
108
|
+
f"moderation.enabled in reactor.yaml must be a boolean, "
|
|
109
|
+
f"got {type(enabled).__name__}: {enabled!r}. "
|
|
110
|
+
'Did you accidentally quote the value (e.g. "false")?'
|
|
111
|
+
)
|
|
112
|
+
return cls(enabled=enabled)
|
|
113
|
+
|
|
114
|
+
|
|
84
115
|
@dataclass
|
|
85
116
|
class ReactorConfig:
|
|
86
117
|
"""Parsed ``reactor.yaml``.
|
|
@@ -119,6 +150,7 @@ class ReactorConfig:
|
|
|
119
150
|
config_overrides: List[str] = field(default_factory=list)
|
|
120
151
|
weights_path: Optional[str] = None
|
|
121
152
|
recording: RecordingConfig = field(default_factory=RecordingConfig)
|
|
153
|
+
moderation: ModerationYamlConfig = field(default_factory=ModerationYamlConfig)
|
|
122
154
|
|
|
123
155
|
@classmethod
|
|
124
156
|
def from_dict(cls, raw: Dict[str, Any]) -> "ReactorConfig":
|
|
@@ -135,8 +167,9 @@ class ReactorConfig:
|
|
|
135
167
|
runtime_section = raw.get("runtime")
|
|
136
168
|
model_section = raw.get("model")
|
|
137
169
|
|
|
138
|
-
# Recording
|
|
170
|
+
# Recording and moderation are top-level siblings to runtime: / model:.
|
|
139
171
|
recording = RecordingConfig.from_dict(raw.get("recording"))
|
|
172
|
+
moderation = ModerationYamlConfig.from_dict(raw.get("moderation"))
|
|
140
173
|
|
|
141
174
|
modern = isinstance(runtime_section, dict) or isinstance(model_section, dict)
|
|
142
175
|
|
|
@@ -161,6 +194,7 @@ class ReactorConfig:
|
|
|
161
194
|
config=runtime_section.get("config"),
|
|
162
195
|
weights_path=runtime_section.get("weights_path") or None,
|
|
163
196
|
recording=recording,
|
|
197
|
+
moderation=moderation,
|
|
164
198
|
)
|
|
165
199
|
|
|
166
200
|
if "model" not in raw:
|
|
@@ -177,4 +211,5 @@ class ReactorConfig:
|
|
|
177
211
|
config=raw.get("config"),
|
|
178
212
|
weights_path=raw.get("weights_path") or None,
|
|
179
213
|
recording=recording,
|
|
214
|
+
moderation=moderation,
|
|
180
215
|
)
|
|
@@ -51,6 +51,7 @@ class FieldInfo:
|
|
|
51
51
|
min_length: Optional[int] = None
|
|
52
52
|
max_length: Optional[int] = None
|
|
53
53
|
choices: Optional[List[Any]] = None
|
|
54
|
+
moderate: bool = True
|
|
54
55
|
|
|
55
56
|
|
|
56
57
|
def InputField(
|
|
@@ -63,6 +64,7 @@ def InputField(
|
|
|
63
64
|
min_length: Optional[int] = None,
|
|
64
65
|
max_length: Optional[int] = None,
|
|
65
66
|
choices: Optional[List[Any]] = None,
|
|
67
|
+
moderate: bool = True,
|
|
66
68
|
) -> Any:
|
|
67
69
|
"""Declare a default value and validation constraints for a field.
|
|
68
70
|
|
|
@@ -81,6 +83,13 @@ def InputField(
|
|
|
81
83
|
min_length: Minimum length for string/sequence values.
|
|
82
84
|
max_length: Maximum length for string/sequence values.
|
|
83
85
|
choices: Exhaustive list of allowed values.
|
|
86
|
+
moderate: Whether to submit this field's value to the content
|
|
87
|
+
moderation backend (when moderation is enabled platform-wide).
|
|
88
|
+
Defaults to ``True``. Only effective for free-text ``str``
|
|
89
|
+
fields (those without ``choices=``) and ``UploadedFile``
|
|
90
|
+
fields — typed, enum, and numeric fields are never moderated
|
|
91
|
+
because the schema validator rejects out-of-set payloads
|
|
92
|
+
before they reach the model.
|
|
84
93
|
"""
|
|
85
94
|
if default_factory is not None:
|
|
86
95
|
raise TypeError(
|
|
@@ -95,6 +104,7 @@ def InputField(
|
|
|
95
104
|
min_length=min_length,
|
|
96
105
|
max_length=max_length,
|
|
97
106
|
choices=choices,
|
|
107
|
+
moderate=moderate,
|
|
98
108
|
)
|
|
99
109
|
|
|
100
110
|
|
|
@@ -227,3 +237,5 @@ def field_info_to_json_schema(schema: Dict[str, Any], info: FieldInfo) -> None:
|
|
|
227
237
|
schema["maxLength"] = info.max_length
|
|
228
238
|
if info.choices is not None:
|
|
229
239
|
schema["enum"] = info.choices
|
|
240
|
+
if not info.moderate:
|
|
241
|
+
schema["x-reactor-moderate"] = False
|
|
@@ -105,25 +105,9 @@ def split_batch(bundle: MediaBundle) -> List[MediaBundle]:
|
|
|
105
105
|
class _FlushMarker:
|
|
106
106
|
"""Sentinel placed in :class:`OutputBuffer`'s queue by :meth:`flush`.
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
``_create_black_bundle()`` with ``duplicate=True``).
|
|
112
|
-
|
|
113
|
-
The point of routing the session-boundary reset through the queue
|
|
114
|
-
rather than writing ``_last_emitted = None`` directly from
|
|
115
|
-
:meth:`flush` is to make the reset atomic with respect to the
|
|
116
|
-
emission thread: only the emission thread reads or writes
|
|
117
|
-
``_last_emitted``, so the loop can never observe a half-applied
|
|
118
|
-
"queue drained but cached frame not yet cleared" state. Down-stream
|
|
119
|
-
callbacks already drop ``duplicate=True`` bundles (the wire
|
|
120
|
-
callback in ``_send_out_app_bundle_sync`` short-circuits, and the
|
|
121
|
-
:class:`~reactor_runtime.recording.session_recorder.SessionRecorder`
|
|
122
|
-
skips them when ``skip_leading_black`` is on and
|
|
123
|
-
``recording_started`` is still False), so no stale frame would
|
|
124
|
-
reach the client or recording even without this guarantee — but
|
|
125
|
-
closing the race architecturally is cheaper than re-deriving that
|
|
126
|
-
safety argument for every callback added in the future.
|
|
108
|
+
Consumed by the emission thread, which resets ``_last_emitted`` and
|
|
109
|
+
dispatches a fresh black bundle with ``is_fresh_black=True``. Doing
|
|
110
|
+
the reset in-thread keeps it atomic with the per-tick state read.
|
|
127
111
|
"""
|
|
128
112
|
|
|
129
113
|
|
|
@@ -152,13 +136,11 @@ class OutputBuffer:
|
|
|
152
136
|
# Ordered list of per-tick observers. Callbacks fire in
|
|
153
137
|
# registration order on every emission tick; an exception in
|
|
154
138
|
# one callback is logged and does not block the others.
|
|
155
|
-
self._callbacks: List[Callable[[MediaBundle, bool], None]] = []
|
|
139
|
+
self._callbacks: List[Callable[[MediaBundle, bool, bool], None]] = []
|
|
156
140
|
self._callbacks_lock: threading.Lock = threading.Lock()
|
|
157
141
|
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
# both shapes are valid; the emission loop discriminates with
|
|
161
|
-
# ``isinstance``.
|
|
142
|
+
# Holds MediaBundle plus the _FlushMarker sentinel; the
|
|
143
|
+
# emission loop discriminates by isinstance.
|
|
162
144
|
self._q: queue.Queue[object] = queue.Queue(maxsize=queue_depth)
|
|
163
145
|
|
|
164
146
|
# FPS control — store both rate and period to avoid 1/fps on every tick
|
|
@@ -182,29 +164,18 @@ class OutputBuffer:
|
|
|
182
164
|
# Callbacks
|
|
183
165
|
# ------------------------------------------------------------------
|
|
184
166
|
|
|
185
|
-
def add_callback(self, fn: Callable[[MediaBundle, bool], None]) -> None:
|
|
186
|
-
"""Register a per-tick callback
|
|
167
|
+
def add_callback(self, fn: Callable[[MediaBundle, bool, bool], None]) -> None:
|
|
168
|
+
"""Register a per-tick callback ``(bundle, duplicate, is_fresh_black)``.
|
|
187
169
|
|
|
188
|
-
Idempotent
|
|
189
|
-
|
|
190
|
-
:meth:`remove_callback` which has always been a no-op on
|
|
191
|
-
already-unregistered functions — callers shouldn't have to
|
|
192
|
-
track registration state.
|
|
193
|
-
|
|
194
|
-
The callback receives ``(bundle, duplicate)`` on every emission
|
|
195
|
-
tick. Callbacks fire in registration order and are isolated
|
|
196
|
-
from each other: an exception raised by one is logged and the
|
|
197
|
-
remaining callbacks still fire.
|
|
170
|
+
Idempotent — re-registering the same ``fn`` is a no-op.
|
|
171
|
+
Callbacks fire in registration order with exceptions isolated.
|
|
198
172
|
"""
|
|
199
173
|
with self._callbacks_lock:
|
|
200
174
|
if fn not in self._callbacks:
|
|
201
175
|
self._callbacks.append(fn)
|
|
202
176
|
|
|
203
|
-
def remove_callback(self, fn: Callable[[MediaBundle, bool], None]) -> None:
|
|
204
|
-
"""Deregister a previously added callback.
|
|
205
|
-
|
|
206
|
-
No-op if *fn* was never registered.
|
|
207
|
-
"""
|
|
177
|
+
def remove_callback(self, fn: Callable[[MediaBundle, bool, bool], None]) -> None:
|
|
178
|
+
"""Deregister a previously added callback (no-op if not registered)."""
|
|
208
179
|
with self._callbacks_lock:
|
|
209
180
|
try:
|
|
210
181
|
self._callbacks.remove(fn)
|
|
@@ -320,27 +291,30 @@ class OutputBuffer:
|
|
|
320
291
|
# Stage 3: Emission loop
|
|
321
292
|
# ------------------------------------------------------------------
|
|
322
293
|
|
|
323
|
-
def _dispatch(
|
|
294
|
+
def _dispatch(
|
|
295
|
+
self,
|
|
296
|
+
bundle: MediaBundle,
|
|
297
|
+
duplicate: bool,
|
|
298
|
+
*,
|
|
299
|
+
is_fresh_black: bool = False,
|
|
300
|
+
) -> None:
|
|
324
301
|
"""Fire every registered callback in order with error isolation.
|
|
325
302
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
303
|
+
Callbacks receive ``(bundle, duplicate, is_fresh_black)``.
|
|
304
|
+
``is_fresh_black`` is ``True`` only on the synthesised black
|
|
305
|
+
the sentinel path emits at session boundaries; callbacks
|
|
306
|
+
choose how to interpret that separately from ``duplicate``.
|
|
307
|
+
Exceptions in one callback are logged and don't block the
|
|
308
|
+
rest.
|
|
330
309
|
"""
|
|
331
310
|
with self._callbacks_lock:
|
|
332
311
|
callbacks = list(self._callbacks)
|
|
333
312
|
for fn in callbacks:
|
|
334
313
|
try:
|
|
335
|
-
fn(bundle, duplicate)
|
|
314
|
+
fn(bundle, duplicate, is_fresh_black)
|
|
336
315
|
except Exception as ex:
|
|
337
|
-
#
|
|
338
|
-
#
|
|
339
|
-
# structured fields so log queries on ``exc_type`` /
|
|
340
|
-
# ``exc_msg`` / ``callback`` work without traceback
|
|
341
|
-
# parsing. Without these explicit fields a silent
|
|
342
|
-
# malfunction in a downstream callback looks like
|
|
343
|
-
# "feature X didn't fire" with no obvious cause.
|
|
316
|
+
# Structured fields so log queries on exc_type /
|
|
317
|
+
# exc_msg / callback work without traceback parsing.
|
|
344
318
|
logger.exception(
|
|
345
319
|
"OutputBuffer callback raised; continuing with remaining callbacks",
|
|
346
320
|
callback=repr(fn),
|
|
@@ -361,19 +335,24 @@ class OutputBuffer:
|
|
|
361
335
|
item = None
|
|
362
336
|
|
|
363
337
|
bundle: Optional[MediaBundle] = None
|
|
338
|
+
sentinel_consumed = False
|
|
364
339
|
if isinstance(item, _FlushMarker):
|
|
365
|
-
# Session-boundary reset, processed in the emission
|
|
366
|
-
# thread so there is no cross-thread race with
|
|
367
|
-
# flush(): clear the cached frame and fall through
|
|
368
|
-
# to the empty-queue fallback below, which
|
|
369
|
-
# dispatches duplicate=True black. mark_first_real_frame()
|
|
370
|
-
# is gated on duplicate=False, so the recorder's
|
|
371
|
-
# latch (REA-2323 / #2325) stays unset.
|
|
372
340
|
self._last_emitted = None
|
|
341
|
+
sentinel_consumed = True
|
|
373
342
|
elif isinstance(item, MediaBundle):
|
|
374
343
|
bundle = item
|
|
375
344
|
|
|
376
|
-
if
|
|
345
|
+
if sentinel_consumed:
|
|
346
|
+
# Session-boundary black: tagged so the wire
|
|
347
|
+
# forwards it (otherwise the client would stay
|
|
348
|
+
# frozen on the previous frame), the recorder
|
|
349
|
+
# ignores it.
|
|
350
|
+
self._dispatch(
|
|
351
|
+
self._create_black_bundle(),
|
|
352
|
+
True,
|
|
353
|
+
is_fresh_black=True,
|
|
354
|
+
)
|
|
355
|
+
elif bundle is not None:
|
|
377
356
|
vtracks = bundle.get_tracks_by_kind(TrackKind.VIDEO)
|
|
378
357
|
if vtracks:
|
|
379
358
|
vd = vtracks[0].data
|
|
@@ -511,36 +490,16 @@ class OutputBuffer:
|
|
|
511
490
|
def flush(self) -> None:
|
|
512
491
|
"""Drop pending bundles and request a session-boundary reset.
|
|
513
492
|
|
|
514
|
-
The
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
sentinel is consumed, the next emission tick synthesises
|
|
519
|
-
``_create_black_bundle()`` with ``duplicate=True`` — the
|
|
520
|
-
correct pre-roll behaviour for a session boundary:
|
|
521
|
-
|
|
522
|
-
* the wire callback (``_send_out_app_bundle_sync``) drops
|
|
523
|
-
``duplicate=True`` outright;
|
|
524
|
-
* the recorder's ``mark_first_real_frame()`` latch is not
|
|
525
|
-
tripped (REA-2323 / #2325), so the recording timeline only
|
|
526
|
-
starts at the next real model frame.
|
|
527
|
-
|
|
528
|
-
Safe to call from any thread; in practice ``flush()`` is
|
|
529
|
-
called from the model thread (``model-run``), the same thread
|
|
530
|
-
that calls :meth:`submit`, so there is no submit/flush race
|
|
531
|
-
on the producer side either.
|
|
493
|
+
The emission thread performs the actual reset of
|
|
494
|
+
``_last_emitted`` when it dequeues the sentinel. Then the
|
|
495
|
+
next tick emits a fresh black bundle tagged ``is_fresh_black``
|
|
496
|
+
— forwarded by the wire, ignored by the recorder.
|
|
532
497
|
"""
|
|
533
498
|
self._drain_queue()
|
|
534
499
|
try:
|
|
535
500
|
self._q.put_nowait(_FLUSH_MARKER)
|
|
536
501
|
except queue.Full:
|
|
537
|
-
#
|
|
538
|
-
# only producer (submit) runs on the same thread as
|
|
539
|
-
# flush, so nothing can refill the queue between drain
|
|
540
|
-
# and put. Log loudly if it ever happens and continue —
|
|
541
|
-
# the next real frame from the new session will set
|
|
542
|
-
# _last_emitted correctly even if the sentinel never
|
|
543
|
-
# lands.
|
|
502
|
+
# Same thread as submit(), so unreachable in practice.
|
|
544
503
|
logger.warning(
|
|
545
504
|
"OutputBuffer.flush: queue full immediately after drain; "
|
|
546
505
|
"reset sentinel dropped"
|
{reactor_runtime-2.7.6 → reactor_runtime-2.7.8}/src/reactor_runtime/recording/session_recorder.py
RENAMED
|
@@ -250,10 +250,22 @@ class SessionRecorder:
|
|
|
250
250
|
# OutputBuffer integration
|
|
251
251
|
# ------------------------------------------------------------------
|
|
252
252
|
|
|
253
|
-
def _on_bundle_sync(
|
|
254
|
-
|
|
253
|
+
def _on_bundle_sync(
|
|
254
|
+
self,
|
|
255
|
+
bundle: MediaBundle,
|
|
256
|
+
duplicate: bool,
|
|
257
|
+
is_fresh_black: bool = False,
|
|
258
|
+
) -> None:
|
|
259
|
+
"""Enqueue a bundle for the feed worker; non-blocking.
|
|
260
|
+
|
|
261
|
+
Session-boundary synthesised black (``is_fresh_black``) is not
|
|
262
|
+
real model output and is invisible to the recording timeline:
|
|
263
|
+
no feed-queue write, no ``mark_first_real_frame`` consideration.
|
|
264
|
+
"""
|
|
255
265
|
if self._disabled or not self._started:
|
|
256
266
|
return
|
|
267
|
+
if is_fresh_black:
|
|
268
|
+
return
|
|
257
269
|
now = time.monotonic()
|
|
258
270
|
if duplicate:
|
|
259
271
|
if self._config.skip_leading_black and not self._markers.recording_started:
|