reactor-runtime 2.0.0__tar.gz → 2.0.2__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 (129) hide show
  1. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/PKG-INFO +1 -1
  2. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/pyproject.toml +1 -1
  3. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_cli/commands/init.py +2 -0
  4. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_cli/commands/run.py +6 -9
  5. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/runtime_api.py +8 -1
  6. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/runtimes/http/http_runtime.py +13 -1
  7. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/config.py +2 -11
  8. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/decoders/h264.py +1 -11
  9. reactor_runtime-2.0.2/src/reactor_runtime/utils/launch.py +103 -0
  10. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/utils/log.py +43 -7
  11. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime.egg-info/PKG-INFO +1 -1
  12. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime.egg-info/SOURCES.txt +1 -0
  13. reactor_runtime-2.0.2/src/template/README.md +45 -0
  14. reactor_runtime-2.0.2/src/template/config.yml +3 -0
  15. reactor_runtime-2.0.2/src/template/model.py +68 -0
  16. reactor_runtime-2.0.2/src/template/pipeline.py +125 -0
  17. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/template/reactor.yaml +2 -2
  18. reactor_runtime-2.0.2/src/template/requirements.txt +2 -0
  19. reactor_runtime-2.0.0/src/reactor_runtime/utils/launch.py +0 -64
  20. reactor_runtime-2.0.0/src/template/README.md +0 -34
  21. reactor_runtime-2.0.0/src/template/config.yml +0 -3
  22. reactor_runtime-2.0.0/src/template/model.py +0 -88
  23. reactor_runtime-2.0.0/src/template/requirements.txt +0 -1
  24. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/README.md +0 -0
  25. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/setup.cfg +0 -0
  26. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/api/__init__.py +0 -0
  27. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_cli/commands/__init__.py +0 -0
  28. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_cli/commands/capabilities.py +0 -0
  29. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_cli/main.py +0 -0
  30. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_cli/utils/__init__.py +0 -0
  31. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_cli/utils/config.py +0 -0
  32. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_cli/utils/runtime.py +0 -0
  33. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_cli/utils/version.py +0 -0
  34. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/__init__.py +0 -0
  35. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/capabilities.py +0 -0
  36. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/config.py +0 -0
  37. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/__init__.py +0 -0
  38. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/events/__init__.py +0 -0
  39. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/events/connected.py +0 -0
  40. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/events/event.py +0 -0
  41. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/events/messages.py +0 -0
  42. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/internal/__init__.py +0 -0
  43. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/internal/input_buffer.py +0 -0
  44. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/internal/output_buffer.py +0 -0
  45. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/internal/reactor_core.py +0 -0
  46. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/model/__init__.py +0 -0
  47. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/model/decorators.py +0 -0
  48. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/model/handlers.py +0 -0
  49. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/model/input_fields.py +0 -0
  50. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/model/reactor_model.py +0 -0
  51. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/pipeline/__init__.py +0 -0
  52. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/pipeline/idle.py +0 -0
  53. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/pipeline/input_state.py +0 -0
  54. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/pipeline/reactor_pipeline.py +0 -0
  55. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/tracks/__init__.py +0 -0
  56. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/tracks/descriptors.py +0 -0
  57. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/tracks/input.py +0 -0
  58. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/tracks/output.py +0 -0
  59. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/model_state.py +0 -0
  60. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/profiling/__init__.py +0 -0
  61. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/profiling/backends/__init__.py +0 -0
  62. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/profiling/backends/base.py +0 -0
  63. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/profiling/backends/file.py +0 -0
  64. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/profiling/backends/otlp.py +0 -0
  65. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/profiling/helpers.py +0 -0
  66. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/profiling/plotting/__init__.py +0 -0
  67. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/profiling/plotting/plot_profiling.py +0 -0
  68. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/profiling/profiler.py +0 -0
  69. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/profiling/singleton.py +0 -0
  70. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/runtimes/headless/config.py +0 -0
  71. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/runtimes/headless/headless_runtime.py +0 -0
  72. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/runtimes/headless/input_feeder.py +0 -0
  73. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/runtimes/http/config.py +0 -0
  74. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/runtimes/http/types.py +0 -0
  75. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/__init__.py +0 -0
  76. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/aiortc/__init__.py +0 -0
  77. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/aiortc/audio_track.py +0 -0
  78. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/aiortc/client.py +0 -0
  79. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/aiortc/frame_conversion.py +0 -0
  80. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/aiortc/ice_connection.py +0 -0
  81. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/aiortc/video_track.py +0 -0
  82. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/events.py +0 -0
  83. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/__init__.py +0 -0
  84. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/client.py +0 -0
  85. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/decoders/__init__.py +0 -0
  86. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/decoders/av1.py +0 -0
  87. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/decoders/base.py +0 -0
  88. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/decoders/factory.py +0 -0
  89. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/decoders/h265.py +0 -0
  90. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/decoders/vp8.py +0 -0
  91. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/decoders/vp9.py +0 -0
  92. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/encoders/__init__.py +0 -0
  93. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/encoders/av1.py +0 -0
  94. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/encoders/base.py +0 -0
  95. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/encoders/factory.py +0 -0
  96. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/encoders/h264.py +0 -0
  97. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/encoders/h265.py +0 -0
  98. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/encoders/opus.py +0 -0
  99. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/encoders/vp8.py +0 -0
  100. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/encoders/vp9.py +0 -0
  101. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/gst.py +0 -0
  102. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/gst_helpers.py +0 -0
  103. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/receiver/__init__.py +0 -0
  104. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/receiver/audio.py +0 -0
  105. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/receiver/base.py +0 -0
  106. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/receiver/video.py +0 -0
  107. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/sdp/__init__.py +0 -0
  108. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/sdp/bundle.py +0 -0
  109. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/sdp/codec.py +0 -0
  110. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/sdp/extmap.py +0 -0
  111. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/sdp/ice.py +0 -0
  112. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/sender/__init__.py +0 -0
  113. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/sender/audio.py +0 -0
  114. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/sender/base.py +0 -0
  115. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/sender/video.py +0 -0
  116. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/settings.py +0 -0
  117. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/signals.py +0 -0
  118. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/ice_uris.py +0 -0
  119. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/interface.py +0 -0
  120. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/media.py +0 -0
  121. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/types.py +0 -0
  122. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/utils/loader.py +0 -0
  123. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/utils/messages.py +0 -0
  124. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/utils/schema.py +0 -0
  125. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime.egg-info/dependency_links.txt +0 -0
  126. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime.egg-info/entry_points.txt +0 -0
  127. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime.egg-info/requires.txt +0 -0
  128. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime.egg-info/top_level.txt +0 -0
  129. {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/template/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reactor_runtime
3
- Version: 2.0.0
3
+ Version: 2.0.2
4
4
  Summary: Reactor runtime with public model API
5
5
  Author-email: Reactor <team@reactor.inc>
6
6
  Requires-Python: >=3.9
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "reactor_runtime"
7
- version = "2.0.0"
7
+ version = "2.0.2"
8
8
  description = "Reactor runtime with public model API"
9
9
  authors = [
10
10
  { name = "Reactor", email = "team@reactor.inc" }
@@ -6,6 +6,7 @@ import importlib.resources
6
6
 
7
7
 
8
8
  _TEMPLATE_FILES = {
9
+ "pipeline.py": "pipeline.py",
9
10
  "model.py": "model.py",
10
11
  "reactor.yaml": "reactor.yaml",
11
12
  "config.yml": "config.yml",
@@ -85,4 +86,5 @@ class InitCommand:
85
86
 
86
87
  print(f"\nReactor workspace '{model_name}' created.")
87
88
  print(f"\n cd {model_name}")
89
+ print(" pip install -r requirements.txt")
88
90
  print(" reactor run\n")
@@ -12,7 +12,6 @@ from reactor_cli.utils.config import (
12
12
  _valid_port,
13
13
  )
14
14
  from reactor_cli.utils.runtime import load_runtime
15
- from reactor_runtime import __version__
16
15
  from reactor_runtime.config import ReactorConfig
17
16
  from reactor_runtime.transports.config import (
18
17
  GSTREAMER_INSTALL_URL,
@@ -207,14 +206,13 @@ class RunCommand:
207
206
  _RESET = "\033[0m"
208
207
  if runtime_kwargs.get("transport_type") is None:
209
208
  runtime_kwargs["transport_type"] = resolve_default_transport()
210
- print(
211
- f"{_ORANGE}Transport: "
212
- f"{runtime_kwargs['transport_type'].name.lower()} "
213
- f"(auto-detected){_RESET}"
214
- )
215
209
  else:
216
210
  try:
217
211
  validate_transport_available(runtime_kwargs["transport_type"])
212
+ print(
213
+ f"{_ORANGE}Transport: "
214
+ f"{runtime_kwargs['transport_type'].name.lower()}{_RESET}"
215
+ )
218
216
  except ImportError as exc:
219
217
  name = runtime_kwargs["transport_type"].name.lower()
220
218
  print(
@@ -244,9 +242,7 @@ class RunCommand:
244
242
  # Run
245
243
  # ---------------------------------------------------------
246
244
  log_level = "DEBUG" if self.args.verbose else "INFO"
247
-
248
- print(f"Starting reactor runtime v{__version__} ({runtime_name})")
249
- print(f"Model: {reactor_config.name} ({reactor_config.model})")
245
+ detailed_logs = runtime_kwargs.pop("detailed_logs", False)
250
246
 
251
247
  try:
252
248
  run_reactor_runtime(
@@ -254,6 +250,7 @@ class RunCommand:
254
250
  reactor_config=reactor_config,
255
251
  model_root=str(model_path),
256
252
  log_level=log_level,
253
+ show_timestamps=detailed_logs,
257
254
  **runtime_kwargs,
258
255
  )
259
256
  except KeyboardInterrupt:
@@ -124,6 +124,13 @@ class RuntimeConfig:
124
124
  argparse.ArgumentParser with runtime-specific arguments
125
125
  """
126
126
  parser = argparse.ArgumentParser(add_help=False)
127
+ parser.add_argument(
128
+ "--detailed-logs",
129
+ action=argparse.BooleanOptionalAction,
130
+ default=False,
131
+ dest="detailed_logs",
132
+ help="Show timestamps and full log detail. Default: off.",
133
+ )
127
134
  parser.add_argument(
128
135
  "--orphan-timeout",
129
136
  type=float,
@@ -236,7 +243,7 @@ class Runtime(ModelStateMachine, ABC):
236
243
  )
237
244
  self.model_thread.start()
238
245
 
239
- logger.info(
246
+ logger.debug(
240
247
  "Model loaded, emission started, run() thread spawned",
241
248
  model=self.config.reactor_config.model,
242
249
  )
@@ -22,6 +22,7 @@ Other:
22
22
  """
23
23
 
24
24
  import asyncio
25
+ import os
25
26
  from typing import Optional
26
27
  import numpy as np
27
28
  from fastapi import FastAPI, HTTPException
@@ -510,6 +511,16 @@ class HttpRuntime(Runtime):
510
511
  if self._server is not None:
511
512
  self._server.should_exit = True
512
513
 
514
+ # -----------------------------------------------------------------
515
+ # State hooks
516
+ # -----------------------------------------------------------------
517
+
518
+ def on_before_initialization_success(self, **kwargs) -> None:
519
+ aqua, reset = "\033[96m", "\033[0m"
520
+ url = f"http://{self.config.host}:{self.config.port}"
521
+ logger.info("Started runtime process", pid=os.getpid())
522
+ logger.info(f"Reactor runtime running on {aqua}{url}{reset}")
523
+
513
524
  # -----------------------------------------------------------------
514
525
  # run()
515
526
  # -----------------------------------------------------------------
@@ -522,7 +533,8 @@ class HttpRuntime(Runtime):
522
533
  app=self.app,
523
534
  host=self.config.host,
524
535
  port=self.config.port,
525
- log_level="info",
536
+ log_level="warning",
537
+ access_log=False,
526
538
  )
527
539
  self._server = uvicorn.Server(uvicorn_config)
528
540
 
@@ -254,25 +254,16 @@ def resolve_default_transport() -> TransportType:
254
254
  """Determine the best available transport when none is explicitly specified.
255
255
 
256
256
  Tries GStreamer first (by importing the gst helper module which initializes
257
- GStreamer), falling back to aiortc with an error message if anything goes wrong.
257
+ GStreamer), falling back silently to aiortc if anything goes wrong.
258
258
  """
259
259
 
260
260
  try:
261
261
  from reactor_runtime.transports.gstreamer.gst import Gst # noqa: F401
262
- except Exception as e:
263
- print(
264
- f"{_ORANGE}GStreamer failed to initialize, falling back to aiortc. Error: {e}{_RESET}"
265
- )
262
+ except Exception:
266
263
  return TransportType.AIORTC
267
264
 
268
265
  missing = _check_gstreamer_elements()
269
266
  if missing:
270
- formatted = ", ".join(missing)
271
- print(
272
- f"{_ORANGE}GStreamer is missing required elements: {formatted}\n"
273
- f"Falling back to aiortc. "
274
- f"Install the missing plugins: {GSTREAMER_INSTALL_URL}{_RESET}"
275
- )
276
267
  return TransportType.AIORTC
277
268
 
278
269
  return TransportType.GSTREAMER
@@ -92,7 +92,6 @@ class H264DecoderBin(BaseRTPDecoderBin):
92
92
 
93
93
  1) NVIDIA hardware decoders (GPU accelerated)
94
94
  2) avdec_h264 (libav software decoder)
95
- 3) openh264dec (generic fallback)
96
95
 
97
96
  Hardware decoder names vary depending on:
98
97
  - Distribution
@@ -121,15 +120,6 @@ class H264DecoderBin(BaseRTPDecoderBin):
121
120
  )
122
121
  return "avdec_h264"
123
122
 
124
- # ---------------------------------------------------------
125
- # Generic fallback (if present in build)
126
- # ---------------------------------------------------------
127
- if Gst.ElementFactory.find("openh264dec") is not None:
128
- logger.info(
129
- "H264DecoderBin: using software (SW) decoder: openh264dec",
130
- )
131
- return "openh264dec"
132
-
133
123
  raise RuntimeError(
134
- "No H264 decoder available (tried: nvh264dec, nvdec_h264, nvv4l2decoder, avdec_h264, openh264dec)"
124
+ "No H264 decoder available (tried: nvh264dec, nvdec_h264, nvv4l2decoder, avdec_h264)"
135
125
  )
@@ -0,0 +1,103 @@
1
+ # Copyright (c) 2026 Reactor Technologies, Inc. All rights reserved.
2
+ """Entry point for running the Reactor runtime."""
3
+
4
+ import asyncio
5
+ import logging
6
+ import os
7
+ import sys
8
+ from typing import Callable, List, Optional
9
+
10
+ from reactor_runtime import __version__
11
+ from reactor_runtime.config import ReactorConfig
12
+ from reactor_runtime.utils.log import (
13
+ LOG_DATEFMT,
14
+ LOG_FORMAT,
15
+ LOG_FORMAT_BRIEF,
16
+ ColorFormatter,
17
+ get_logger,
18
+ )
19
+
20
+ logger = get_logger(__name__)
21
+
22
+
23
+ def add_import_paths(paths: List[str]) -> None:
24
+ """Prepend provided directories to sys.path if they exist."""
25
+ for p in paths:
26
+ if not p:
27
+ continue
28
+ ap = os.path.abspath(p)
29
+ if os.path.isdir(ap) and ap not in sys.path:
30
+ sys.path.insert(0, ap)
31
+
32
+
33
+ def configure_logging(level: str, show_timestamps: bool = True) -> None:
34
+ fmt = LOG_FORMAT if show_timestamps else LOG_FORMAT_BRIEF
35
+ handler = logging.StreamHandler()
36
+ handler.setFormatter(ColorFormatter(fmt, datefmt=LOG_DATEFMT))
37
+ logging.root.addHandler(handler)
38
+ logging.root.setLevel(getattr(logging, level.upper(), logging.INFO))
39
+ logging.getLogger("aiortc").setLevel(logging.WARNING)
40
+ logging.getLogger("av").setLevel(logging.WARNING)
41
+ logging.getLogger("aioice").setLevel(logging.WARNING)
42
+ logging.getLogger("uvicorn").setLevel(logging.WARNING)
43
+ logging.getLogger("uvicorn.error").setLevel(logging.WARNING)
44
+ logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
45
+
46
+
47
+ def _infer_runtime_name(serve_fn: Callable) -> str:
48
+ """Derive a short runtime label from the serve function's module path.
49
+
50
+ e.g. ``reactor_runtime.runtimes.http.http_runtime`` -> ``http``
51
+ ``reactor_runtime.runtimes._redis._redis_runtime`` -> ``redis``
52
+ """
53
+ parts = getattr(serve_fn, "__module__", "").split(".")
54
+ if len(parts) >= 3:
55
+ return parts[2].lstrip("_")
56
+ return "unknown"
57
+
58
+
59
+ def run_reactor_runtime(
60
+ runtime_serve_fn: Callable,
61
+ reactor_config: ReactorConfig,
62
+ model_root: Optional[str] = None,
63
+ log_level: str = "INFO",
64
+ show_timestamps: bool = True,
65
+ **runtime_kwargs,
66
+ ) -> None:
67
+ """Run the Reactor runtime.
68
+
69
+ Args:
70
+ runtime_serve_fn: The ``serve()`` function from the runtime module.
71
+ reactor_config: Parsed ``reactor.yaml`` (with ``config`` resolved
72
+ to an absolute path and ``config_overrides`` populated).
73
+ model_root: Directory to add to sys.path for model imports.
74
+ log_level: Logging level.
75
+ show_timestamps: Include timestamps in log output (disabled for local
76
+ HTTP runtime where output is watched interactively).
77
+ **runtime_kwargs: Runtime-specific arguments passed to serve().
78
+ """
79
+ configure_logging(log_level, show_timestamps=show_timestamps)
80
+
81
+ runtime_name = _infer_runtime_name(runtime_serve_fn)
82
+ logger.info(
83
+ "Starting reactor runtime",
84
+ version=__version__,
85
+ runtime=runtime_name,
86
+ )
87
+ model_kw: dict = {
88
+ "name": reactor_config.name,
89
+ "entrypoint": reactor_config.model,
90
+ }
91
+ if reactor_config.config:
92
+ model_kw["config"] = reactor_config.config
93
+ logger.info("Model detected", **model_kw)
94
+
95
+ if model_root is not None:
96
+ add_import_paths([model_root])
97
+
98
+ asyncio.run(
99
+ runtime_serve_fn(
100
+ reactor_config=reactor_config,
101
+ **runtime_kwargs,
102
+ )
103
+ )
@@ -20,13 +20,46 @@ from typing import Any, Optional
20
20
  session_id_var: ContextVar[Optional[str]] = ContextVar("session_id", default=None)
21
21
 
22
22
 
23
- # Shared log format / date-format used across the runtime.
24
- LOG_FORMAT = "%(asctime)s %(levelname)-8s %(name)s: %(message)s"
23
+ LOG_FORMAT = "%(asctime)s %(levelname)s %(name)s: %(message)s"
24
+ LOG_FORMAT_BRIEF = "%(levelname)s %(message)s"
25
25
  LOG_DATEFMT = "%Y-%m-%dT%H:%M:%S%z"
26
26
 
27
- # Keep underscore-prefixed aliases so existing imports don't break.
28
- _LOG_FORMAT = LOG_FORMAT
29
- _LOG_DATEFMT = LOG_DATEFMT
27
+ _RESET = "\033[0m"
28
+
29
+ _LEVEL_COLORS: dict[int, str] = {
30
+ logging.DEBUG: "\033[37m", # gray
31
+ logging.INFO: "\033[32m", # green
32
+ logging.WARNING: "\033[33m", # yellow
33
+ logging.ERROR: "\033[31m", # red
34
+ logging.CRITICAL: "\033[1;31m", # bold red
35
+ }
36
+
37
+
38
+ _RUNTIME_PREFIXES = ("reactor_runtime.", "reactor_cli.")
39
+ _MODEL_COLOR = "\033[36m"
40
+
41
+
42
+ class ColorFormatter(logging.Formatter):
43
+ """Formatter that colorizes log levels and tags model logs."""
44
+
45
+ _COL = 12 # visual width: fits "CRITICAL MDL"
46
+
47
+ def format(self, record: logging.LogRecord) -> str:
48
+ orig = record.levelname
49
+ color = _LEVEL_COLORS.get(record.levelno, "")
50
+
51
+ if record.name.startswith(_RUNTIME_PREFIXES):
52
+ pad = self._COL - len(orig)
53
+ record.levelname = f"{color}{orig}{_RESET}{' ' * pad}"
54
+ else:
55
+ pad = self._COL - len(orig) - 4 # 4 = len(" MDL")
56
+ record.levelname = (
57
+ f"{color}{orig}{_RESET} {_MODEL_COLOR}MDL{_RESET}{' ' * max(pad, 0)}"
58
+ )
59
+
60
+ result = super().format(record)
61
+ record.levelname = orig
62
+ return result
30
63
 
31
64
 
32
65
  class StructuredLogger:
@@ -39,15 +72,18 @@ class StructuredLogger:
39
72
 
40
73
  # -- internal helpers --------------------------------------------------
41
74
 
75
+ _BLUE = "\033[38;5;25m"
76
+ _RESET = "\033[0m"
77
+
42
78
  @staticmethod
43
79
  def _fmt(msg: str, kw: dict[str, Any]) -> str:
44
- # Auto-inject session_id from ContextVar if not explicitly provided.
45
80
  sid = session_id_var.get()
46
81
  if sid and "session_id" not in kw:
47
82
  kw["session_id"] = sid
48
83
  if not kw:
49
84
  return msg
50
- pairs = " ".join(f"{k}={v}" for k, v in kw.items())
85
+ b, r = StructuredLogger._BLUE, StructuredLogger._RESET
86
+ pairs = " ".join(f"{b}{k}={r}{v}" for k, v in kw.items())
51
87
  return f"{msg} {pairs}"
52
88
 
53
89
  # -- public API --------------------------------------------------------
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reactor_runtime
3
- Version: 2.0.0
3
+ Version: 2.0.2
4
4
  Summary: Reactor runtime with public model API
5
5
  Author-email: Reactor <team@reactor.inc>
6
6
  Requires-Python: >=3.9
@@ -117,5 +117,6 @@ src/template/README.md
117
117
  src/template/__init__.py
118
118
  src/template/config.yml
119
119
  src/template/model.py
120
+ src/template/pipeline.py
120
121
  src/template/reactor.yaml
121
122
  src/template/requirements.txt
@@ -0,0 +1,45 @@
1
+ # My Reactor Model
2
+
3
+ A real-time video model built with Reactor Runtime.
4
+
5
+ ## Setup and run
6
+
7
+ ```bash
8
+ pip install -r requirements.txt
9
+ reactor run
10
+ ```
11
+
12
+ This starts the model locally. Connect with a WebRTC client at the URL printed by the runtime.
13
+
14
+ ## Project structure
15
+
16
+ | File | Purpose |
17
+ |------|---------|
18
+ | `pipeline.py` | **ReactorPipeline** — generator pattern with auto state management (default) |
19
+ | `model.py` | **ReactorModel** — manual run loop with explicit events and lifecycle |
20
+ | `reactor.yaml` | Tells the runtime where to find your model class |
21
+ | `config.yml` | Model configuration (passed to `load()` as a dict) |
22
+ | `requirements.txt` | Extra Python dependencies |
23
+
24
+ ## How it works
25
+
26
+ - **`MyOutput`** declares the video track the model sends to clients.
27
+ - **`MyState`** declares parameters clients can change in real-time. Each field auto-generates a `set_<field>` event — no handler code needed.
28
+ - **`inference()`** is a generator that yields frames in batches. Read `self.state` to pick up the latest client values.
29
+ - **`load()`** runs once at startup — put weight loading here.
30
+
31
+ ## Switching to the ReactorModel version
32
+
33
+ Edit `reactor.yaml` and change the model entry:
34
+
35
+ ```yaml
36
+ model: model:MyModel
37
+ ```
38
+
39
+ Then run again. The ReactorModel version gives you full control over the run loop, events, and session lifecycle.
40
+
41
+ ## Next steps
42
+
43
+ - Add `Input` tracks to receive webcam video from the client
44
+ - Add `@event` handlers for custom logic (e.g. encoding a prompt)
45
+ - Add `ModelMessage` subclasses to send structured data back to the client
@@ -0,0 +1,3 @@
1
+ width: 640
2
+ height: 480
3
+ chunk_size: 4
@@ -0,0 +1,68 @@
1
+ # Copyright (c) 2026 Reactor Technologies, Inc. All rights reserved.
2
+ """
3
+ My first Reactor model — ReactorModel version.
4
+
5
+ Same model as pipeline.py, but using the lower-level ReactorModel base
6
+ class where you manage the run loop, events, and session lifecycle
7
+ explicitly.
8
+
9
+ Run with: reactor run (after updating reactor.yaml to point here)
10
+ """
11
+
12
+ from typing import Any
13
+
14
+
15
+ from reactor_runtime.interface import (
16
+ InputField,
17
+ ReactorModel,
18
+ connected,
19
+ disconnected,
20
+ event,
21
+ )
22
+
23
+ from pipeline import MyOutput, render_batch
24
+
25
+
26
+ # -- Model ---------------------------------------------------------------------
27
+
28
+
29
+ class MyModel(ReactorModel):
30
+ fps = 30
31
+ buffer_size = 8
32
+
33
+ def load(self, config: dict[str, Any]) -> None:
34
+ self.width = config.get("width", 640)
35
+ self.height = config.get("height", 480)
36
+ self.chunk_size = config.get("chunk_size", 4)
37
+ self.brightness = 1.0
38
+
39
+ @connected
40
+ def on_connect(self):
41
+ self.brightness = 1.0
42
+
43
+ @disconnected
44
+ def on_disconnect(self):
45
+ self.output_buffer.flush()
46
+
47
+ @event(name="set_brightness", description="Brightness multiplier")
48
+ def set_brightness(
49
+ self,
50
+ brightness: float = InputField(default=1.0, ge=0.0, le=2.0),
51
+ ):
52
+ self.brightness = brightness
53
+
54
+ async def run(self) -> None:
55
+ while True:
56
+ await self.connected.wait()
57
+ frame_idx = 0
58
+
59
+ while self.connected.is_set():
60
+ # Sync work is fine here — events dispatch at await points
61
+ batch, frame_idx = render_batch(
62
+ self.width,
63
+ self.height,
64
+ self.chunk_size,
65
+ frame_idx,
66
+ self.brightness,
67
+ )
68
+ await self.emit(MyOutput(main_video=batch))
@@ -0,0 +1,125 @@
1
+ # Copyright (c) 2026 Reactor Technologies, Inc. All rights reserved.
2
+ """
3
+ My first Reactor model — ReactorPipeline version.
4
+
5
+ Generates animated gradient frames with a client-controllable brightness
6
+ multiplier. Frames are produced in configurable chunks to demonstrate
7
+ batch yielding.
8
+
9
+ Run with: reactor run
10
+ """
11
+
12
+ from dataclasses import dataclass
13
+ from typing import Any
14
+ import time
15
+
16
+ import numpy as np
17
+ from PIL import Image, ImageDraw
18
+
19
+ from reactor_runtime.interface import (
20
+ InputField,
21
+ InputState,
22
+ Output,
23
+ ReactorPipeline,
24
+ Video,
25
+ )
26
+
27
+
28
+ # -- Tracks --------------------------------------------------------------------
29
+
30
+
31
+ @dataclass
32
+ class MyOutput(Output):
33
+ main_video: Video
34
+
35
+
36
+ # -- State ---------------------------------------------------------------------
37
+
38
+
39
+ @dataclass
40
+ class MyState(InputState):
41
+ brightness: float = InputField(
42
+ default=1.0, ge=0.0, le=2.0, description="Brightness multiplier"
43
+ )
44
+
45
+
46
+ # -- Model ---------------------------------------------------------------------
47
+
48
+
49
+ class MyModel(ReactorPipeline):
50
+ state: MyState
51
+ buffer_size = 8
52
+
53
+ def load(self, config: dict[str, Any]) -> None:
54
+ self.width = config.get("width", 640)
55
+ self.height = config.get("height", 480)
56
+ self.chunk_size = config.get("chunk_size", 4)
57
+
58
+ def inference(self):
59
+ frame_idx = 0
60
+ while True:
61
+ batch, frame_idx = render_batch(
62
+ self.width,
63
+ self.height,
64
+ self.chunk_size,
65
+ frame_idx,
66
+ self.state.brightness,
67
+ )
68
+ yield MyOutput(main_video=batch)
69
+
70
+
71
+ # -- Rendering -----------------------------------------------------------------
72
+
73
+
74
+ _FRAME_BUDGET = 1 / 30
75
+
76
+
77
+ def render_batch(
78
+ width: int,
79
+ height: int,
80
+ chunk_size: int,
81
+ start_idx: int,
82
+ brightness: float,
83
+ ) -> tuple[np.ndarray, int]:
84
+ """Generate a batch of animated gradient frames with brightness applied.
85
+
86
+ Returns the batch as ``(N, H, W, 3)`` uint8 and the next frame index.
87
+ """
88
+ frames = []
89
+ for i in range(chunk_size):
90
+ idx = start_idx + i
91
+ t0 = time.perf_counter()
92
+
93
+ img = np.zeros((height, width, 3), dtype=np.float32)
94
+ ys = np.linspace(0, 1, height, dtype=np.float32)[:, None]
95
+ img[:, :, 0] = ys * 128
96
+ img[:, :, 2] = (1 - ys) * 128
97
+
98
+ bar_x = (idx * 4) % width
99
+ bar_w = max(1, width // 20)
100
+ img[:, bar_x : min(width, bar_x + bar_w), 1] = 128
101
+
102
+ img = np.clip(img * brightness, 0, 255).astype(np.uint8)
103
+ img = draw_text(img, f"Frame {idx} | Brightness {brightness:.1f}")
104
+ frames.append(img)
105
+
106
+ remaining = _FRAME_BUDGET - (time.perf_counter() - t0)
107
+ if remaining > 0:
108
+ time.sleep(remaining)
109
+
110
+ return np.stack(frames), start_idx + chunk_size
111
+
112
+
113
+ def draw_text(
114
+ frame: np.ndarray,
115
+ text: str,
116
+ position: tuple = (16, 30),
117
+ ) -> np.ndarray:
118
+ """Draw white text with a dark outline onto a frame."""
119
+ img = Image.fromarray(frame)
120
+ draw = ImageDraw.Draw(img)
121
+ x, y = position
122
+ for dx, dy in [(-1, -1), (-1, 1), (1, -1), (1, 1)]:
123
+ draw.text((x + dx, y + dy), text, fill=(0, 0, 0))
124
+ draw.text((x, y), text, fill=(255, 255, 255))
125
+ return np.array(img)
@@ -1,4 +1,4 @@
1
1
  # Copyright (c) 2026 Reactor Technologies, Inc. All rights reserved.
2
- model: model:MyModel
2
+ model: pipeline:MyModel
3
3
  name: my-model
4
- config: config.yml
4
+ config: config.yml
@@ -0,0 +1,2 @@
1
+ numpy
2
+ Pillow