reactor-runtime 3.8.2__tar.gz → 3.8.3__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 (130) hide show
  1. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/PKG-INFO +3 -1
  2. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/README.md +2 -0
  3. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/pyproject.toml +2 -2
  4. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/pyproject.toml.orig +2 -2
  5. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/core/__init__.py +2 -0
  6. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/core/values.py +50 -12
  7. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/runner/client_stats.py +27 -2
  8. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/transport/webrtc/client_stats.py +45 -13
  9. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/transport/webrtc/pacer.py +44 -21
  10. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_wire/v1/platform_pb2.py +22 -20
  11. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_wire/v1/platform_pb2.pyi +26 -4
  12. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/LICENSE +0 -0
  13. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/NOTICE +0 -0
  14. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/__init__.py +0 -0
  15. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/codes.py +0 -0
  16. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/core/fields.py +0 -0
  17. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/core/model.py +0 -0
  18. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/core/naming.py +0 -0
  19. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/core/service.py +0 -0
  20. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/core/session.py +0 -0
  21. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/core/stats.py +0 -0
  22. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/core/transport.py +0 -0
  23. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/core/typespec.py +0 -0
  24. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/distributed/__init__.py +0 -0
  25. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/distributed/errors.py +0 -0
  26. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/distributed/ipc.py +0 -0
  27. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/distributed/protocol.py +0 -0
  28. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/distributed/rank.py +0 -0
  29. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/distributed/runner.py +0 -0
  30. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/distributed/worker.py +0 -0
  31. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/event_stream.py +0 -0
  32. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/http/__init__.py +0 -0
  33. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/http/events.py +0 -0
  34. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/http/routes.py +0 -0
  35. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/http/server.py +0 -0
  36. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/http/spec.py +0 -0
  37. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/http/step_usage.py +0 -0
  38. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/__init__.py +0 -0
  39. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/app/__init__.py +0 -0
  40. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/app/input_state.py +0 -0
  41. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/app/outcome.py +0 -0
  42. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/app/reactor_app.py +0 -0
  43. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/app/step_completed.py +0 -0
  44. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/client.py +0 -0
  45. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/events/__init__.py +0 -0
  46. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/events/decorators.py +0 -0
  47. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/events/errors.py +0 -0
  48. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/events/messages.py +0 -0
  49. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/internal/__init__.py +0 -0
  50. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/internal/aliases.py +0 -0
  51. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/internal/bridge.py +0 -0
  52. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/internal/input_buffer.py +0 -0
  53. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/internal/reactor_core.py +0 -0
  54. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/model/__init__.py +0 -0
  55. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/model/contract.py +0 -0
  56. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/model/reactor_model.py +0 -0
  57. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/model/schema.py +0 -0
  58. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/pipeline/__init__.py +0 -0
  59. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/pipeline/idle.py +0 -0
  60. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/pipeline/input_state.py +0 -0
  61. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/pipeline/reactor_pipeline.py +0 -0
  62. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/tracks/__init__.py +0 -0
  63. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/tracks/descriptors.py +0 -0
  64. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/tracks/input.py +0 -0
  65. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/interface/tracks/output.py +0 -0
  66. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/log.py +0 -0
  67. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/manifest.py +0 -0
  68. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/message_gateway.py +0 -0
  69. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/metrics/__init__.py +0 -0
  70. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/metrics/command.py +0 -0
  71. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/metrics/model.py +0 -0
  72. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/metrics/registry.py +0 -0
  73. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/metrics/session.py +0 -0
  74. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/paths.py +0 -0
  75. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/protocol/__init__.py +0 -0
  76. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/protocol/base.py +0 -0
  77. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/protocol/common.py +0 -0
  78. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/protocol/v0/__init__.py +0 -0
  79. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/protocol/v0/codec.py +0 -0
  80. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/protocol/v1/__init__.py +0 -0
  81. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/protocol/v1/codec.py +0 -0
  82. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/py.typed +0 -0
  83. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/recording/__init__.py +0 -0
  84. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/recording/chunk_encoder.py +0 -0
  85. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/recording/markers.py +0 -0
  86. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/recording/recorder.py +0 -0
  87. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/runner/__init__.py +0 -0
  88. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/runner/connection_manager.py +0 -0
  89. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/runner/offer_epochs.py +0 -0
  90. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/runner/runner.py +0 -0
  91. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/runner/runtime_stats.py +0 -0
  92. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/runner/session_start.py +0 -0
  93. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/runner/state_machine.py +0 -0
  94. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/runner/system_client.py +0 -0
  95. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/runner/upload_resolution.py +0 -0
  96. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/schema.py +0 -0
  97. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/serve.py +0 -0
  98. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/service.py +0 -0
  99. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/step_results/__init__.py +0 -0
  100. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/step_results/mp4.py +0 -0
  101. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/step_results/store.py +0 -0
  102. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/step_results/usage.py +0 -0
  103. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/transport/__init__.py +0 -0
  104. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/transport/acceptor.py +0 -0
  105. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/transport/router.py +0 -0
  106. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/transport/webrtc/__init__.py +0 -0
  107. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/transport/webrtc/acceptor.py +0 -0
  108. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/transport/webrtc/config.py +0 -0
  109. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/transport/webrtc/connection.py +0 -0
  110. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/transport/webrtc/frame_stages.py +0 -0
  111. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/transport/webrtc/frames.py +0 -0
  112. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/transport/webrtc/metrics.py +0 -0
  113. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/transport/webrtc/peer.py +0 -0
  114. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/transport/webrtc/reading.py +0 -0
  115. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/transport/webrtc/router.py +0 -0
  116. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/transport/webrtc/sdp.py +0 -0
  117. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/transport/webrtc/signaling.py +0 -0
  118. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/transport/webrtc/stats.py +0 -0
  119. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/transport/webrtc/version.py +0 -0
  120. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_runtime/upload_store.py +0 -0
  121. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_wire/v1/common_pb2.py +0 -0
  122. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_wire/v1/common_pb2.pyi +0 -0
  123. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_wire/v1/control_pb2.py +0 -0
  124. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_wire/v1/control_pb2.pyi +0 -0
  125. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_wire/v1/data_pb2.py +0 -0
  126. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_wire/v1/data_pb2.pyi +0 -0
  127. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_wire/v1/model_pb2.py +0 -0
  128. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_wire/v1/model_pb2.pyi +0 -0
  129. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_wire/v1/track_pb2.py +0 -0
  130. {reactor_runtime-3.8.2 → reactor_runtime-3.8.3}/src/reactor_wire/v1/track_pb2.pyi +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reactor-runtime
3
- Version: 3.8.2
3
+ Version: 3.8.3
4
4
  Summary: A Python framework for building real-time, interactive video models
5
5
  Author: Reactor
6
6
  Author-email: Reactor <team@reactor.inc>
@@ -124,6 +124,8 @@ class MyModel(ReactorApp):
124
124
 
125
125
  Command handlers and lifecycle hooks run between steps, never during one. A model has one method per lifecycle hook kind (`@session_started`, `@session_ended`, `@connected`, `@disconnected`, `@file_uploaded`). A subclass extends an inherited hook by overriding that method and calling `super()`; the override runs whether or not it repeats the decorator. Marking a second method with the same kind, in one class or in a subclass, fails when the class is defined, so a hook is never dropped in silence. The default `run()` is the loop that drives the three hooks, and it reports each step that runs with `complete_step()`, which is what the runtime counts a session's steps by. To write that report yourself, for example to keep extra files with the step, return a `StepCompleted(output=..., files=...)` from `process_output()` instead of the `Output`. Override `run()` to write your own loop against `emit()`, `send()`, `complete_step()`, `@event`, `self.connected`, and the tracks; `process_input()`, `generate()`, and `process_output()` are then not called, and your loop calls `complete_step(StepCompleted(output=...))` each time it finishes a unit of work. Do that for a loop that is not one step per emit, such as a renderer that emits several times per step or a model that must block on an input.
126
126
 
127
+ A command handler refuses by raising `CommandError(code, message)`: `code` is a short, stable token the client branches on, and `message` reaches the client unchanged, so write it for them and name the codes in the command's description. The runtime answers that command's request with the failure, so the caller learns on its own call, no other client in the session hears about a command it did not send, and the runtime records that the command failed. Do not refuse by sending an error message and returning: the runtime counts that as success and acknowledges the command. Any other exception a handler raises reaches the client as an internal error with a fixed message, so catch what a check raises and raise a `CommandError` with a code of its own. The waypoint example's `set_image` does both.
128
+
127
129
  A session can start with its setup already in hand: `POST /start_session` takes a `starting_input` of state values and commands, which the runtime applies on the normal command path before any client connects. A system client sends them, a connection the runtime opens itself, so a handler that takes `client` gets a `ClientInfo` whose `system` is true. The step loop takes its first step only once every starting command has landed, uploads included, so no step runs on the defaults the starting input replaces. In a live session the system client then leaves and the loop waits for a viewer, as it always does; a model with its own `run()` sees the system client connect like any client. A live session stays `waiting` while its starting input applies, because the system client does not occupy it, and `/events` journals the system client's `connection_opened` and `connection_closed` as `waiting -> waiting`. A session with `steps` is occupied by the system client until it ends, so it streams with no other client. `steps` is a whole number from 1 to 10,000. The runtime ends it itself once the model has reported that many steps, with a normal stop whose close reason is "Session ended: the requested steps are complete." A session with `steps` has no client to correct its starting input, so a starting command the runtime cannot apply (an upload whose bytes never arrive, or arguments the contract refuses) ends it at once with the reason `start_refused` and the refusal as its close reason. If the handler of a starting command fails instead, by raising `CommandError` or any other exception, every step the model has not reported yet is recorded with the handler's message, so a session at step 3 of 4 saves steps 3 and 4 with that error, and the session ends with the reason `error`. The model keeps running and takes the next session.
128
130
 
129
131
  A model can keep each step it reports as a folder of files, turned on in `reactor.yaml`:
@@ -97,6 +97,8 @@ class MyModel(ReactorApp):
97
97
 
98
98
  Command handlers and lifecycle hooks run between steps, never during one. A model has one method per lifecycle hook kind (`@session_started`, `@session_ended`, `@connected`, `@disconnected`, `@file_uploaded`). A subclass extends an inherited hook by overriding that method and calling `super()`; the override runs whether or not it repeats the decorator. Marking a second method with the same kind, in one class or in a subclass, fails when the class is defined, so a hook is never dropped in silence. The default `run()` is the loop that drives the three hooks, and it reports each step that runs with `complete_step()`, which is what the runtime counts a session's steps by. To write that report yourself, for example to keep extra files with the step, return a `StepCompleted(output=..., files=...)` from `process_output()` instead of the `Output`. Override `run()` to write your own loop against `emit()`, `send()`, `complete_step()`, `@event`, `self.connected`, and the tracks; `process_input()`, `generate()`, and `process_output()` are then not called, and your loop calls `complete_step(StepCompleted(output=...))` each time it finishes a unit of work. Do that for a loop that is not one step per emit, such as a renderer that emits several times per step or a model that must block on an input.
99
99
 
100
+ A command handler refuses by raising `CommandError(code, message)`: `code` is a short, stable token the client branches on, and `message` reaches the client unchanged, so write it for them and name the codes in the command's description. The runtime answers that command's request with the failure, so the caller learns on its own call, no other client in the session hears about a command it did not send, and the runtime records that the command failed. Do not refuse by sending an error message and returning: the runtime counts that as success and acknowledges the command. Any other exception a handler raises reaches the client as an internal error with a fixed message, so catch what a check raises and raise a `CommandError` with a code of its own. The waypoint example's `set_image` does both.
101
+
100
102
  A session can start with its setup already in hand: `POST /start_session` takes a `starting_input` of state values and commands, which the runtime applies on the normal command path before any client connects. A system client sends them, a connection the runtime opens itself, so a handler that takes `client` gets a `ClientInfo` whose `system` is true. The step loop takes its first step only once every starting command has landed, uploads included, so no step runs on the defaults the starting input replaces. In a live session the system client then leaves and the loop waits for a viewer, as it always does; a model with its own `run()` sees the system client connect like any client. A live session stays `waiting` while its starting input applies, because the system client does not occupy it, and `/events` journals the system client's `connection_opened` and `connection_closed` as `waiting -> waiting`. A session with `steps` is occupied by the system client until it ends, so it streams with no other client. `steps` is a whole number from 1 to 10,000. The runtime ends it itself once the model has reported that many steps, with a normal stop whose close reason is "Session ended: the requested steps are complete." A session with `steps` has no client to correct its starting input, so a starting command the runtime cannot apply (an upload whose bytes never arrive, or arguments the contract refuses) ends it at once with the reason `start_refused` and the refusal as its close reason. If the handler of a starting command fails instead, by raising `CommandError` or any other exception, every step the model has not reported yet is recorded with the handler's message, so a session at step 3 of 4 saves steps 3 and 4 with that error, and the session ends with the reason `error`. The model keeps running and takes the next session.
101
103
 
102
104
  A model can keep each step it reports as a folder of files, turned on in `reactor.yaml`:
@@ -10,7 +10,7 @@ module-name = [
10
10
  namespace = true
11
11
 
12
12
  [tool.reactor-wire]
13
- version = "1.20261013.10"
13
+ version = "1.20261013.11"
14
14
 
15
15
  [tool.ruff]
16
16
  line-length = 100
@@ -95,7 +95,7 @@ pythonpath = ["."]
95
95
 
96
96
  [project]
97
97
  name = "reactor-runtime"
98
- version = "3.8.2"
98
+ version = "3.8.3"
99
99
  description = "A Python framework for building real-time, interactive video models"
100
100
  readme = "README.md"
101
101
  license = "Apache-2.0"
@@ -16,11 +16,11 @@ namespace = true
16
16
  # gates the pin against proto/, so a release cannot ship a schema older than the
17
17
  # sources it was built from.
18
18
  [tool.reactor-wire]
19
- version = "1.20261013.10"
19
+ version = "1.20261013.11"
20
20
 
21
21
  [project]
22
22
  name = "reactor-runtime"
23
- version = "3.8.2"
23
+ version = "3.8.3"
24
24
  description = "A Python framework for building real-time, interactive video models"
25
25
  readme = "README.md"
26
26
  license = "Apache-2.0"
@@ -43,6 +43,7 @@ from reactor_runtime.core.transport import (
43
43
  from reactor_runtime.core.typespec import TypeSpec
44
44
  from reactor_runtime.core.values import (
45
45
  ClientConnectionStat,
46
+ ClientEnvironment,
46
47
  ClientStatsBatch,
47
48
  ClientTrackDirection,
48
49
  ClientTrackStat,
@@ -68,6 +69,7 @@ __all__ = [
68
69
  "ClientConnected",
69
70
  "ClientConnectionStat",
70
71
  "ClientDisconnected",
72
+ "ClientEnvironment",
71
73
  "ClientStatsBatch",
72
74
  "ClientTrackDirection",
73
75
  "ClientTrackStat",
@@ -78,10 +78,13 @@ class ClientTrackStat:
78
78
  connection identity itself rather than trusting anything the payload might
79
79
  claim, because the payload carries none.
80
80
 
81
- ``kind``/``direction``/``codec`` are fixed, structural facts about the
82
- track — never a new value at runtime — so they stay their own fields;
83
- :attr:`metrics` is where the actual measurements live, because that set
84
- grows as new stats become worth reporting, without a schema change.
81
+ The typed fields carry the facts about the track that are not numbers:
82
+ ``kind``/``direction``/``codec`` are fixed for the track, and
83
+ ``codec_implementation``/``hardware_codec`` describe it at the time of
84
+ the reading, since the client's engine can switch implementation
85
+ mid-session. :attr:`metrics` is where the actual measurements live,
86
+ because that set grows as new stats become worth reporting, without a
87
+ schema change.
85
88
 
86
89
  Attributes:
87
90
  timestamp: When the client took this reading, on its own clock, in
@@ -105,6 +108,12 @@ class ClientTrackStat:
105
108
  query time.
106
109
  frame_stages: Where the track's frames spent their time since the
107
110
  client's previous batch, stage by stage.
111
+ codec_implementation: The encoder (a track the client sends) or
112
+ decoder (one it receives) the client runs for a video track, as
113
+ its media engine names it, e.g. ``"libvpx"``; empty when unknown.
114
+ hardware_codec: Whether the client's media engine reports that
115
+ implementation as power efficient, which in practice means
116
+ hardware accelerated; ``None`` when the client does not say.
108
117
  """
109
118
 
110
119
  timestamp: int
@@ -115,6 +124,33 @@ class ClientTrackStat:
115
124
  paused: bool
116
125
  metrics: Mapping[str, float]
117
126
  frame_stages: tuple[FrameStage, ...] = ()
127
+ codec_implementation: str = ""
128
+ hardware_codec: bool | None = None
129
+
130
+
131
+ @dataclass(frozen=True)
132
+ class ClientEnvironment:
133
+ """The SDK and the system a client runs on, as it describes them.
134
+
135
+ Every field is empty when the client does not know it.
136
+
137
+ Attributes:
138
+ sdk_type: The SDK, e.g. ``"python"``, ``"js"``, ``"cpp"``.
139
+ sdk_version: The SDK's version, e.g. ``"1.10.0"``.
140
+ os: The operating system, e.g. ``"macos"``, ``"linux"``, or
141
+ ``"browser"`` for the JavaScript SDK.
142
+ os_version: The operating system's version as the client reads it.
143
+ arch: The CPU architecture, e.g. ``"aarch64"``.
144
+ device_model: The device's hardware model as the system names it,
145
+ e.g. ``"iPhone15,2"``.
146
+ """
147
+
148
+ sdk_type: str = ""
149
+ sdk_version: str = ""
150
+ os: str = ""
151
+ os_version: str = ""
152
+ arch: str = ""
153
+ device_model: str = ""
118
154
 
119
155
 
120
156
  @dataclass(frozen=True)
@@ -124,18 +160,21 @@ class ClientConnectionStat:
124
160
  e.g. ``"available_outgoing_bitrate_bps"`` (the client engine's own
125
161
  estimate of outgoing headroom — the encoder's own belief, not a
126
162
  measurement of the user's internet) or ``"time_to_connect_ms"`` (how long
127
- this connection waited to establish, start to "ready") — the latter a
128
- one-time fact a client reports once rather than repeating on every batch,
129
- which is why :class:`ClientStatsBatch` carries this as optional.
163
+ this connection waited to establish, start to "ready"). A client sends
164
+ this reading on every batch; a one-time fact like ``"time_to_connect_ms"``
165
+ is in ``metrics`` only on the first batch after connecting.
130
166
 
131
167
  Attributes:
132
168
  timestamp: When the client took this reading, on its own clock, in
133
169
  epoch milliseconds.
134
170
  metrics: The reading's measurements, by name.
171
+ environment: What the client runs on, sent on every batch; ``None``
172
+ from a client that does not send it.
135
173
  """
136
174
 
137
175
  timestamp: int
138
176
  metrics: Mapping[str, float]
177
+ environment: ClientEnvironment | None = None
139
178
 
140
179
 
141
180
  @dataclass(frozen=True)
@@ -144,15 +183,14 @@ class ClientStatsBatch:
144
183
 
145
184
  Mirrors the wire's ``ClientStats`` message: per-track readings alongside
146
185
  a fact set that applies to the whole connection rather than any one
147
- track. A client typically sends ``connection_stat`` once, on its first
148
- batch after connecting, and omits it on every batch after — most batches
149
- therefore carry ``track_stats`` only.
186
+ track. A client sends ``connection_stat`` on every batch; a client that
187
+ predates it sends none.
150
188
 
151
189
  Attributes:
152
190
  track_stats: One reading per track this batch reports on. Empty on a
153
191
  batch that carries only ``connection_stat``.
154
- connection_stat: This batch's connection-wide reading, or ``None`` on
155
- a batch that doesn't carry one — the common case.
192
+ connection_stat: This batch's connection-wide reading, or ``None``
193
+ from a client that does not send one.
156
194
  """
157
195
 
158
196
  track_stats: Sequence[ClientTrackStat]
@@ -15,7 +15,7 @@ import time
15
15
  from collections.abc import Callable, Iterable, Mapping
16
16
  from typing import Any
17
17
 
18
- from reactor_runtime.core import ClientStatsBatch, ConnId, FrameStage
18
+ from reactor_runtime.core import ClientEnvironment, ClientStatsBatch, ConnId, FrameStage
19
19
 
20
20
  # The SDK sends a batch every few seconds, with a few tracks and a few dozen
21
21
  # metrics each; these limits are far above that.
@@ -75,7 +75,13 @@ def fits(batch: ClientStatsBatch) -> bool:
75
75
  return False
76
76
  names = [name for metrics in readings for name in metrics]
77
77
  names += [stage.name for stat in batch.track_stats for stage in stat.frame_stages]
78
- names += [field for stat in batch.track_stats for field in (stat.track_name, stat.codec)]
78
+ names += [
79
+ field
80
+ for stat in batch.track_stats
81
+ for field in (stat.track_name, stat.codec, stat.codec_implementation)
82
+ ]
83
+ if batch.connection_stat is not None and batch.connection_stat.environment is not None:
84
+ names += list(environment_detail(batch.connection_stat.environment).values())
79
85
  return all(len(name) <= MAX_NAME_LENGTH for name in names)
80
86
 
81
87
 
@@ -96,6 +102,8 @@ def to_detail(batch: ClientStatsBatch) -> dict[str, Any]:
96
102
  "paused": stat.paused,
97
103
  "metrics": _finite(stat.metrics),
98
104
  "frame_stages": frame_stages_detail(stat.frame_stages),
105
+ "codec_implementation": stat.codec_implementation,
106
+ "hardware_codec": stat.hardware_codec,
99
107
  }
100
108
  for stat in batch.track_stats
101
109
  ],
@@ -103,6 +111,11 @@ def to_detail(batch: ClientStatsBatch) -> dict[str, Any]:
103
111
  {
104
112
  "timestamp": batch.connection_stat.timestamp,
105
113
  "metrics": _finite(batch.connection_stat.metrics),
114
+ "environment": (
115
+ environment_detail(batch.connection_stat.environment)
116
+ if batch.connection_stat.environment is not None
117
+ else None
118
+ ),
106
119
  }
107
120
  if batch.connection_stat is not None
108
121
  else None
@@ -110,6 +123,18 @@ def to_detail(batch: ClientStatsBatch) -> dict[str, Any]:
110
123
  }
111
124
 
112
125
 
126
+ def environment_detail(environment: ClientEnvironment) -> dict[str, str]:
127
+ """Shape what a client runs on for a fact's detail."""
128
+ return {
129
+ "sdk_type": environment.sdk_type,
130
+ "sdk_version": environment.sdk_version,
131
+ "os": environment.os,
132
+ "os_version": environment.os_version,
133
+ "arch": environment.arch,
134
+ "device_model": environment.device_model,
135
+ }
136
+
137
+
113
138
  def frame_stages_detail(stages: Iterable[FrameStage]) -> list[dict[str, Any]]:
114
139
  """Shape a track's stage times for a fact's detail.
115
140
 
@@ -13,6 +13,7 @@ from google.protobuf.message import DecodeError
13
13
 
14
14
  from reactor_runtime.core import (
15
15
  ClientConnectionStat,
16
+ ClientEnvironment,
16
17
  ClientStatsBatch,
17
18
  ClientTrackDirection,
18
19
  ClientTrackStat,
@@ -105,25 +106,56 @@ def _decode_frame_stages(stat: platform_pb2.ClientTrackStat) -> tuple[FrameStage
105
106
  )
106
107
 
107
108
 
109
+ def _decode_codec_implementation(
110
+ stat: platform_pb2.ClientTrackStat,
111
+ ) -> tuple[str, bool | None]:
112
+ """Read which encoder or decoder a video track runs; an audio track has none."""
113
+ if stat.kind != platform_pb2.TrackKind.TRACK_KIND_VIDEO:
114
+ return "", None
115
+ hardware = stat.hardware_codec if stat.HasField("hardware_codec") else None
116
+ return stat.codec_implementation, hardware
117
+
118
+
119
+ def _decode_environment(stat: platform_pb2.ClientConnectionStat) -> ClientEnvironment | None:
120
+ """Read what the client runs on, or ``None`` from a client that does not say."""
121
+ if not stat.HasField("environment"):
122
+ return None
123
+ env = stat.environment
124
+ return ClientEnvironment(
125
+ sdk_type=env.sdk_type,
126
+ sdk_version=env.sdk_version,
127
+ os=env.os,
128
+ os_version=env.os_version,
129
+ arch=env.arch,
130
+ device_model=env.device_model,
131
+ )
132
+
133
+
134
+ def _decode_track_stat(stat: platform_pb2.ClientTrackStat) -> ClientTrackStat:
135
+ """Convert one decoded ``ClientTrackStat`` into a plain value."""
136
+ codec_implementation, hardware_codec = _decode_codec_implementation(stat)
137
+ return ClientTrackStat(
138
+ timestamp=stat.timestamp,
139
+ track_name=stat.track_name,
140
+ kind=_TRACK_KINDS.get(stat.kind),
141
+ direction=_TRACK_DIRECTIONS.get(stat.direction),
142
+ codec=_decode_codec(stat),
143
+ paused=stat.paused,
144
+ metrics=dict(stat.metrics),
145
+ frame_stages=_decode_frame_stages(stat),
146
+ codec_implementation=codec_implementation,
147
+ hardware_codec=hardware_codec,
148
+ )
149
+
150
+
108
151
  def _decode_client_stats(message: platform_pb2.ClientStats) -> ClientStatsBatch:
109
152
  """Convert a decoded ``ClientStats`` batch into plain values."""
110
- track_stats = [
111
- ClientTrackStat(
112
- timestamp=stat.timestamp,
113
- track_name=stat.track_name,
114
- kind=_TRACK_KINDS.get(stat.kind),
115
- direction=_TRACK_DIRECTIONS.get(stat.direction),
116
- codec=_decode_codec(stat),
117
- paused=stat.paused,
118
- metrics=dict(stat.metrics),
119
- frame_stages=_decode_frame_stages(stat),
120
- )
121
- for stat in message.track_stats
122
- ]
153
+ track_stats = [_decode_track_stat(stat) for stat in message.track_stats]
123
154
  connection_stat = None
124
155
  if message.HasField("connection_stat"):
125
156
  connection_stat = ClientConnectionStat(
126
157
  timestamp=message.connection_stat.timestamp,
127
158
  metrics=dict(message.connection_stat.metrics),
159
+ environment=_decode_environment(message.connection_stat),
128
160
  )
129
161
  return ClientStatsBatch(track_stats=track_stats, connection_stat=connection_stat)
@@ -102,6 +102,10 @@ class MediaPacer:
102
102
  self._stop = threading.Event()
103
103
  self._stop.set()
104
104
  self._lifecycle_lock = threading.Lock()
105
+ # Set once by stop(), under ``_room``: a stopped pacer is finished and
106
+ # takes no more media. ``_stop`` cannot mark this, because it is also
107
+ # set before start(), when submissions are still accepted.
108
+ self._closed = False
105
109
 
106
110
  # One black frame marks a boundary (start of the connection, or a
107
111
  # flush); after it the pacer repeats the last metadata-carrying frame
@@ -151,26 +155,32 @@ class MediaPacer:
151
155
  # chunk so a batching model's whole chunk always fits. Re-read
152
156
  # each iteration so a concurrent set_depth applies mid-chunk.
153
157
  capacity = max(self._depth, chunk.n_frames)
154
- if chunk.wait:
155
- with self._room:
158
+ # The checks and the put share one critical section with stop(),
159
+ # so a frame either lands before stop() drains the queue or is
160
+ # refused.
161
+ with self._room:
162
+ if self._closed:
163
+ aborted = True
164
+ break
165
+ if chunk.wait:
156
166
  while (
157
167
  not self._stop.is_set()
158
168
  and self._epoch == epoch
159
169
  and self._queue.qsize() >= capacity
160
170
  ):
161
171
  self._room.wait(timeout=0.1)
162
- if self._stop.is_set() or self._epoch != epoch:
163
- aborted = True
164
- break
165
- else:
166
- # A flush mid-chunk abandons the rest of it on this path too;
167
- # the flushed run's tail must not trickle in after the cut.
168
- if self._epoch != epoch:
169
- aborted = True
170
- break
171
- if self._queue.qsize() >= capacity:
172
- break
173
- self._queue.put_nowait((frame, time.perf_counter()))
172
+ if self._stop.is_set() or self._epoch != epoch:
173
+ aborted = True
174
+ break
175
+ else:
176
+ # A flush mid-chunk abandons the rest of it on this path too;
177
+ # the flushed run's tail must not trickle in after the cut.
178
+ if self._epoch != epoch:
179
+ aborted = True
180
+ break
181
+ if self._queue.qsize() >= capacity:
182
+ break
183
+ self._queue.put_nowait((frame, time.perf_counter()))
174
184
  enqueued += 1
175
185
  if enqueued < chunk.n_frames and not chunk.wait and not aborted:
176
186
  dropped = chunk.n_frames - enqueued
@@ -219,34 +229,47 @@ class MediaPacer:
219
229
  self._room.notify_all()
220
230
 
221
231
  def start(self) -> None:
222
- """Start the pacing thread, idempotently."""
232
+ """Start the pacing thread, idempotently. A stopped pacer stays stopped."""
223
233
  with self._lifecycle_lock:
224
- if not self._stop.is_set():
234
+ if not self._stop.is_set() or self._closed:
225
235
  return
226
236
  self._stop.clear()
227
237
  self._thread = threading.Thread(target=self._pacing_loop, name="media-pacer")
228
238
  self._thread.start()
229
239
 
230
240
  def stop(self) -> None:
231
- """Stop the pacing thread and wait for it to finish.
241
+ """Stop the pacing thread, wait for it to finish, and release held media.
242
+
243
+ Stopping is final: from then on :meth:`submit` enqueues nothing and
244
+ :meth:`start` does nothing. Once the thread has stopped, the queued
245
+ frames and the last delivered frame are dropped, also on a pacer that
246
+ was never started. A frame is a view into the model's whole output
247
+ array, so a stopped pacer that kept one would keep that array alive
248
+ for as long as the pacer itself is reachable.
232
249
 
233
250
  Raises:
234
251
  RuntimeError: If the thread does not stop within two seconds.
235
252
  """
236
253
  with self._lifecycle_lock:
237
- if self._stop.is_set():
238
- return
239
254
  self._stop.set()
240
255
  thread = self._thread
241
256
  self._thread = None
242
- # Wake any producer blocked in a backpressure submit, so teardown
243
- # cannot deadlock behind a queue nobody will drain.
257
+ # Refuse further media, and wake any producer blocked in a backpressure
258
+ # submit, so teardown cannot deadlock behind a queue nobody will drain.
244
259
  with self._room:
260
+ self._closed = True
245
261
  self._room.notify_all()
246
262
  if thread is not None:
247
263
  thread.join(timeout=2.0)
248
264
  if thread.is_alive():
249
265
  raise RuntimeError("media pacer thread did not stop within 2s")
266
+ with self._room:
267
+ while True:
268
+ try:
269
+ self._queue.get_nowait()
270
+ except queue.Empty:
271
+ break
272
+ self._last_frame = None
250
273
 
251
274
  def _pacing_loop(self) -> None:
252
275
  """Deliver one frame per tick at a steady cadence until stopped."""
@@ -25,7 +25,7 @@ _sym_db = _symbol_database.Default()
25
25
  from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2
26
26
 
27
27
 
28
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ereactor_wire/v1/platform.proto\x12\x0freactor_wire.v1\x1a\x1cgoogle/protobuf/struct.proto\"\x06\n\x04Ping\"\x0f\n\rRequestSchema\"@\n\x0bModelSchema\x12\x31\n\x07openapi\x18\x01 \x01(\x0b\x32\x17.google.protobuf.StructR\x07openapi\"p\n\x0c\x46ileUploaded\x12\x1b\n\tupload_id\x18\x01 \x01(\tR\x08uploadId\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name\x12\x1b\n\tmime_type\x18\x03 \x01(\tR\x08mimeType\x12\x12\n\x04size\x18\x04 \x01(\x03R\x04size\"8\n\x0bRequestClip\x12)\n\x10\x64uration_seconds\x18\x01 \x01(\x01R\x0f\x64urationSeconds\"\x12\n\x10RequestRecording\"\xf5\x01\n\tClipReady\x12\x1d\n\nsession_id\x18\x01 \x01(\tR\tsessionId\x12\x12\n\x04kind\x18\x02 \x01(\tR\x04kind\x12!\n\x0cstart_marker\x18\x03 \x01(\x01R\x0bstartMarker\x12\x1d\n\nend_marker\x18\x04 \x01(\x01R\tendMarker\x12\x1d\n\nnow_marker\x18\x05 \x01(\x01R\tnowMarker\x12\x31\n\x15predicted_ready_at_ms\x18\x06 \x01(\x03R\x12predictedReadyAtMs\x12!\n\x0cplaylist_url\x18\x07 \x01(\tR\x0bplaylistUrl\"$\n\nClipFailed\x12\x16\n\x06reason\x18\x01 \x01(\tR\x06reason\"\x97\x01\n\nModeration\x12\x16\n\x06\x61\x63tion\x18\x01 \x01(\tR\x06\x61\x63tion\x12\x1d\n\ninput_kind\x18\x02 \x01(\tR\tinputKind\x12\x18\n\x07\x63ommand\x18\x03 \x01(\tR\x07\x63ommand\x12\x1e\n\ncategories\x18\x04 \x03(\tR\ncategories\x12\x18\n\x07message\x18\x05 \x01(\tR\x07message\"&\n\x0cSessionEnded\x12\x16\n\x06reason\x18\x01 \x01(\tR\x06reason\"\xa3\x04\n\x0f\x43lientTrackStat\x12\x1c\n\ttimestamp\x18\x01 \x01(\x03R\ttimestamp\x12\x1d\n\ntrack_name\x18\x02 \x01(\tR\ttrackName\x12.\n\x04kind\x18\x03 \x01(\x0e\x32\x1a.reactor_wire.v1.TrackKindR\x04kind\x12=\n\tdirection\x18\x04 \x01(\x0e\x32\x1f.reactor_wire.v1.TrackDirectionR\tdirection\x12>\n\x0bvideo_codec\x18\x05 \x01(\x0e\x32\x1b.reactor_wire.v1.VideoCodecH\x00R\nvideoCodec\x12>\n\x0b\x61udio_codec\x18\x06 \x01(\x0e\x32\x1b.reactor_wire.v1.AudioCodecH\x00R\naudioCodec\x12\x16\n\x06paused\x18\x07 \x01(\x08R\x06paused\x12G\n\x07metrics\x18\x08 \x03(\x0b\x32-.reactor_wire.v1.ClientTrackStat.MetricsEntryR\x07metrics\x12>\n\x0c\x66rame_stages\x18\t \x03(\x0b\x32\x1b.reactor_wire.v1.FrameStageR\x0b\x66rameStages\x1a:\n\x0cMetricsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\x01R\x05value:\x02\x38\x01\x42\x07\n\x05\x63odec\"S\n\nFrameStage\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x19\n\x08total_ms\x18\x02 \x01(\x01R\x07totalMs\x12\x16\n\x06\x66rames\x18\x03 \x01(\x04R\x06\x66rames\"\xbe\x01\n\x14\x43lientConnectionStat\x12\x1c\n\ttimestamp\x18\x01 \x01(\x03R\ttimestamp\x12L\n\x07metrics\x18\x02 \x03(\x0b\x32\x32.reactor_wire.v1.ClientConnectionStat.MetricsEntryR\x07metrics\x1a:\n\x0cMetricsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\x01R\x05value:\x02\x38\x01\"\xa0\x01\n\x0b\x43lientStats\x12\x41\n\x0btrack_stats\x18\x01 \x03(\x0b\x32 .reactor_wire.v1.ClientTrackStatR\ntrackStats\x12N\n\x0f\x63onnection_stat\x18\x02 \x01(\x0b\x32%.reactor_wire.v1.ClientConnectionStatR\x0e\x63onnectionStat*\x94\x01\n\nVideoCodec\x12\x1b\n\x17VIDEO_CODEC_UNSPECIFIED\x10\x00\x12\x13\n\x0fVIDEO_CODEC_VP8\x10\x01\x12\x13\n\x0fVIDEO_CODEC_VP9\x10\x02\x12\x13\n\x0fVIDEO_CODEC_AV1\x10\x03\x12\x14\n\x10VIDEO_CODEC_H264\x10\x04\x12\x14\n\x10VIDEO_CODEC_H265\x10\x05*?\n\nAudioCodec\x12\x1b\n\x17\x41UDIO_CODEC_UNSPECIFIED\x10\x00\x12\x14\n\x10\x41UDIO_CODEC_OPUS\x10\x01*S\n\tTrackKind\x12\x1a\n\x16TRACK_KIND_UNSPECIFIED\x10\x00\x12\x14\n\x10TRACK_KIND_VIDEO\x10\x01\x12\x14\n\x10TRACK_KIND_AUDIO\x10\x02*m\n\x0eTrackDirection\x12\x1f\n\x1bTRACK_DIRECTION_UNSPECIFIED\x10\x00\x12\x1c\n\x18TRACK_DIRECTION_RECVONLY\x10\x01\x12\x1c\n\x18TRACK_DIRECTION_SENDONLY\x10\x02\x62\x06proto3')
28
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ereactor_wire/v1/platform.proto\x12\x0freactor_wire.v1\x1a\x1cgoogle/protobuf/struct.proto\"\x06\n\x04Ping\"\x0f\n\rRequestSchema\"@\n\x0bModelSchema\x12\x31\n\x07openapi\x18\x01 \x01(\x0b\x32\x17.google.protobuf.StructR\x07openapi\"p\n\x0c\x46ileUploaded\x12\x1b\n\tupload_id\x18\x01 \x01(\tR\x08uploadId\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name\x12\x1b\n\tmime_type\x18\x03 \x01(\tR\x08mimeType\x12\x12\n\x04size\x18\x04 \x01(\x03R\x04size\"8\n\x0bRequestClip\x12)\n\x10\x64uration_seconds\x18\x01 \x01(\x01R\x0f\x64urationSeconds\"\x12\n\x10RequestRecording\"\xf5\x01\n\tClipReady\x12\x1d\n\nsession_id\x18\x01 \x01(\tR\tsessionId\x12\x12\n\x04kind\x18\x02 \x01(\tR\x04kind\x12!\n\x0cstart_marker\x18\x03 \x01(\x01R\x0bstartMarker\x12\x1d\n\nend_marker\x18\x04 \x01(\x01R\tendMarker\x12\x1d\n\nnow_marker\x18\x05 \x01(\x01R\tnowMarker\x12\x31\n\x15predicted_ready_at_ms\x18\x06 \x01(\x03R\x12predictedReadyAtMs\x12!\n\x0cplaylist_url\x18\x07 \x01(\tR\x0bplaylistUrl\"$\n\nClipFailed\x12\x16\n\x06reason\x18\x01 \x01(\tR\x06reason\"\x97\x01\n\nModeration\x12\x16\n\x06\x61\x63tion\x18\x01 \x01(\tR\x06\x61\x63tion\x12\x1d\n\ninput_kind\x18\x02 \x01(\tR\tinputKind\x12\x18\n\x07\x63ommand\x18\x03 \x01(\tR\x07\x63ommand\x12\x1e\n\ncategories\x18\x04 \x03(\tR\ncategories\x12\x18\n\x07message\x18\x05 \x01(\tR\x07message\"&\n\x0cSessionEnded\x12\x16\n\x06reason\x18\x01 \x01(\tR\x06reason\"\x95\x05\n\x0f\x43lientTrackStat\x12\x1c\n\ttimestamp\x18\x01 \x01(\x03R\ttimestamp\x12\x1d\n\ntrack_name\x18\x02 \x01(\tR\ttrackName\x12.\n\x04kind\x18\x03 \x01(\x0e\x32\x1a.reactor_wire.v1.TrackKindR\x04kind\x12=\n\tdirection\x18\x04 \x01(\x0e\x32\x1f.reactor_wire.v1.TrackDirectionR\tdirection\x12>\n\x0bvideo_codec\x18\x05 \x01(\x0e\x32\x1b.reactor_wire.v1.VideoCodecH\x00R\nvideoCodec\x12>\n\x0b\x61udio_codec\x18\x06 \x01(\x0e\x32\x1b.reactor_wire.v1.AudioCodecH\x00R\naudioCodec\x12\x16\n\x06paused\x18\x07 \x01(\x08R\x06paused\x12G\n\x07metrics\x18\x08 \x03(\x0b\x32-.reactor_wire.v1.ClientTrackStat.MetricsEntryR\x07metrics\x12>\n\x0c\x66rame_stages\x18\t \x03(\x0b\x32\x1b.reactor_wire.v1.FrameStageR\x0b\x66rameStages\x12\x31\n\x14\x63odec_implementation\x18\n \x01(\tR\x13\x63odecImplementation\x12*\n\x0ehardware_codec\x18\x0b \x01(\x08H\x01R\rhardwareCodec\x88\x01\x01\x1a:\n\x0cMetricsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\x01R\x05value:\x02\x38\x01\x42\x07\n\x05\x63odecB\x11\n\x0f_hardware_codec\"S\n\nFrameStage\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x19\n\x08total_ms\x18\x02 \x01(\x01R\x07totalMs\x12\x16\n\x06\x66rames\x18\x03 \x01(\x04R\x06\x66rames\"\x84\x02\n\x14\x43lientConnectionStat\x12\x1c\n\ttimestamp\x18\x01 \x01(\x03R\ttimestamp\x12L\n\x07metrics\x18\x02 \x03(\x0b\x32\x32.reactor_wire.v1.ClientConnectionStat.MetricsEntryR\x07metrics\x12\x44\n\x0b\x65nvironment\x18\x03 \x01(\x0b\x32\".reactor_wire.v1.ClientEnvironmentR\x0b\x65nvironment\x1a:\n\x0cMetricsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\x01R\x05value:\x02\x38\x01\"\xb5\x01\n\x11\x43lientEnvironment\x12\x19\n\x08sdk_type\x18\x01 \x01(\tR\x07sdkType\x12\x1f\n\x0bsdk_version\x18\x02 \x01(\tR\nsdkVersion\x12\x0e\n\x02os\x18\x03 \x01(\tR\x02os\x12\x1d\n\nos_version\x18\x04 \x01(\tR\tosVersion\x12\x12\n\x04\x61rch\x18\x05 \x01(\tR\x04\x61rch\x12!\n\x0c\x64\x65vice_model\x18\x06 \x01(\tR\x0b\x64\x65viceModel\"\xa0\x01\n\x0b\x43lientStats\x12\x41\n\x0btrack_stats\x18\x01 \x03(\x0b\x32 .reactor_wire.v1.ClientTrackStatR\ntrackStats\x12N\n\x0f\x63onnection_stat\x18\x02 \x01(\x0b\x32%.reactor_wire.v1.ClientConnectionStatR\x0e\x63onnectionStat*\x94\x01\n\nVideoCodec\x12\x1b\n\x17VIDEO_CODEC_UNSPECIFIED\x10\x00\x12\x13\n\x0fVIDEO_CODEC_VP8\x10\x01\x12\x13\n\x0fVIDEO_CODEC_VP9\x10\x02\x12\x13\n\x0fVIDEO_CODEC_AV1\x10\x03\x12\x14\n\x10VIDEO_CODEC_H264\x10\x04\x12\x14\n\x10VIDEO_CODEC_H265\x10\x05*?\n\nAudioCodec\x12\x1b\n\x17\x41UDIO_CODEC_UNSPECIFIED\x10\x00\x12\x14\n\x10\x41UDIO_CODEC_OPUS\x10\x01*S\n\tTrackKind\x12\x1a\n\x16TRACK_KIND_UNSPECIFIED\x10\x00\x12\x14\n\x10TRACK_KIND_VIDEO\x10\x01\x12\x14\n\x10TRACK_KIND_AUDIO\x10\x02*m\n\x0eTrackDirection\x12\x1f\n\x1bTRACK_DIRECTION_UNSPECIFIED\x10\x00\x12\x1c\n\x18TRACK_DIRECTION_RECVONLY\x10\x01\x12\x1c\n\x18TRACK_DIRECTION_SENDONLY\x10\x02\x62\x06proto3')
29
29
 
30
30
  _globals = globals()
31
31
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -36,14 +36,14 @@ if not _descriptor._USE_C_DESCRIPTORS:
36
36
  _globals['_CLIENTTRACKSTAT_METRICSENTRY']._serialized_options = b'8\001'
37
37
  _globals['_CLIENTCONNECTIONSTAT_METRICSENTRY']._loaded_options = None
38
38
  _globals['_CLIENTCONNECTIONSTAT_METRICSENTRY']._serialized_options = b'8\001'
39
- _globals['_VIDEOCODEC']._serialized_start=1836
40
- _globals['_VIDEOCODEC']._serialized_end=1984
41
- _globals['_AUDIOCODEC']._serialized_start=1986
42
- _globals['_AUDIOCODEC']._serialized_end=2049
43
- _globals['_TRACKKIND']._serialized_start=2051
44
- _globals['_TRACKKIND']._serialized_end=2134
45
- _globals['_TRACKDIRECTION']._serialized_start=2136
46
- _globals['_TRACKDIRECTION']._serialized_end=2245
39
+ _globals['_VIDEOCODEC']._serialized_start=2204
40
+ _globals['_VIDEOCODEC']._serialized_end=2352
41
+ _globals['_AUDIOCODEC']._serialized_start=2354
42
+ _globals['_AUDIOCODEC']._serialized_end=2417
43
+ _globals['_TRACKKIND']._serialized_start=2419
44
+ _globals['_TRACKKIND']._serialized_end=2502
45
+ _globals['_TRACKDIRECTION']._serialized_start=2504
46
+ _globals['_TRACKDIRECTION']._serialized_end=2613
47
47
  _globals['_PING']._serialized_start=81
48
48
  _globals['_PING']._serialized_end=87
49
49
  _globals['_REQUESTSCHEMA']._serialized_start=89
@@ -65,15 +65,17 @@ if not _descriptor._USE_C_DESCRIPTORS:
65
65
  _globals['_SESSIONENDED']._serialized_start=804
66
66
  _globals['_SESSIONENDED']._serialized_end=842
67
67
  _globals['_CLIENTTRACKSTAT']._serialized_start=845
68
- _globals['_CLIENTTRACKSTAT']._serialized_end=1392
69
- _globals['_CLIENTTRACKSTAT_METRICSENTRY']._serialized_start=1325
70
- _globals['_CLIENTTRACKSTAT_METRICSENTRY']._serialized_end=1383
71
- _globals['_FRAMESTAGE']._serialized_start=1394
72
- _globals['_FRAMESTAGE']._serialized_end=1477
73
- _globals['_CLIENTCONNECTIONSTAT']._serialized_start=1480
74
- _globals['_CLIENTCONNECTIONSTAT']._serialized_end=1670
75
- _globals['_CLIENTCONNECTIONSTAT_METRICSENTRY']._serialized_start=1325
76
- _globals['_CLIENTCONNECTIONSTAT_METRICSENTRY']._serialized_end=1383
77
- _globals['_CLIENTSTATS']._serialized_start=1673
78
- _globals['_CLIENTSTATS']._serialized_end=1833
68
+ _globals['_CLIENTTRACKSTAT']._serialized_end=1506
69
+ _globals['_CLIENTTRACKSTAT_METRICSENTRY']._serialized_start=1420
70
+ _globals['_CLIENTTRACKSTAT_METRICSENTRY']._serialized_end=1478
71
+ _globals['_FRAMESTAGE']._serialized_start=1508
72
+ _globals['_FRAMESTAGE']._serialized_end=1591
73
+ _globals['_CLIENTCONNECTIONSTAT']._serialized_start=1594
74
+ _globals['_CLIENTCONNECTIONSTAT']._serialized_end=1854
75
+ _globals['_CLIENTCONNECTIONSTAT_METRICSENTRY']._serialized_start=1420
76
+ _globals['_CLIENTCONNECTIONSTAT_METRICSENTRY']._serialized_end=1478
77
+ _globals['_CLIENTENVIRONMENT']._serialized_start=1857
78
+ _globals['_CLIENTENVIRONMENT']._serialized_end=2038
79
+ _globals['_CLIENTSTATS']._serialized_start=2041
80
+ _globals['_CLIENTSTATS']._serialized_end=2201
79
81
  # @@protoc_insertion_point(module_scope)
@@ -129,7 +129,7 @@ class SessionEnded(_message.Message):
129
129
  def __init__(self, reason: _Optional[str] = ...) -> None: ...
130
130
 
131
131
  class ClientTrackStat(_message.Message):
132
- __slots__ = ("timestamp", "track_name", "kind", "direction", "video_codec", "audio_codec", "paused", "metrics", "frame_stages")
132
+ __slots__ = ("timestamp", "track_name", "kind", "direction", "video_codec", "audio_codec", "paused", "metrics", "frame_stages", "codec_implementation", "hardware_codec")
133
133
  class MetricsEntry(_message.Message):
134
134
  __slots__ = ("key", "value")
135
135
  KEY_FIELD_NUMBER: _ClassVar[int]
@@ -146,6 +146,8 @@ class ClientTrackStat(_message.Message):
146
146
  PAUSED_FIELD_NUMBER: _ClassVar[int]
147
147
  METRICS_FIELD_NUMBER: _ClassVar[int]
148
148
  FRAME_STAGES_FIELD_NUMBER: _ClassVar[int]
149
+ CODEC_IMPLEMENTATION_FIELD_NUMBER: _ClassVar[int]
150
+ HARDWARE_CODEC_FIELD_NUMBER: _ClassVar[int]
149
151
  timestamp: int
150
152
  track_name: str
151
153
  kind: TrackKind
@@ -155,7 +157,9 @@ class ClientTrackStat(_message.Message):
155
157
  paused: bool
156
158
  metrics: _containers.ScalarMap[str, float]
157
159
  frame_stages: _containers.RepeatedCompositeFieldContainer[FrameStage]
158
- def __init__(self, timestamp: _Optional[int] = ..., track_name: _Optional[str] = ..., kind: _Optional[_Union[TrackKind, str]] = ..., direction: _Optional[_Union[TrackDirection, str]] = ..., video_codec: _Optional[_Union[VideoCodec, str]] = ..., audio_codec: _Optional[_Union[AudioCodec, str]] = ..., paused: _Optional[bool] = ..., metrics: _Optional[_Mapping[str, float]] = ..., frame_stages: _Optional[_Iterable[_Union[FrameStage, _Mapping]]] = ...) -> None: ...
160
+ codec_implementation: str
161
+ hardware_codec: bool
162
+ def __init__(self, timestamp: _Optional[int] = ..., track_name: _Optional[str] = ..., kind: _Optional[_Union[TrackKind, str]] = ..., direction: _Optional[_Union[TrackDirection, str]] = ..., video_codec: _Optional[_Union[VideoCodec, str]] = ..., audio_codec: _Optional[_Union[AudioCodec, str]] = ..., paused: _Optional[bool] = ..., metrics: _Optional[_Mapping[str, float]] = ..., frame_stages: _Optional[_Iterable[_Union[FrameStage, _Mapping]]] = ..., codec_implementation: _Optional[str] = ..., hardware_codec: _Optional[bool] = ...) -> None: ...
159
163
 
160
164
  class FrameStage(_message.Message):
161
165
  __slots__ = ("name", "total_ms", "frames")
@@ -168,7 +172,7 @@ class FrameStage(_message.Message):
168
172
  def __init__(self, name: _Optional[str] = ..., total_ms: _Optional[float] = ..., frames: _Optional[int] = ...) -> None: ...
169
173
 
170
174
  class ClientConnectionStat(_message.Message):
171
- __slots__ = ("timestamp", "metrics")
175
+ __slots__ = ("timestamp", "metrics", "environment")
172
176
  class MetricsEntry(_message.Message):
173
177
  __slots__ = ("key", "value")
174
178
  KEY_FIELD_NUMBER: _ClassVar[int]
@@ -178,9 +182,27 @@ class ClientConnectionStat(_message.Message):
178
182
  def __init__(self, key: _Optional[str] = ..., value: _Optional[float] = ...) -> None: ...
179
183
  TIMESTAMP_FIELD_NUMBER: _ClassVar[int]
180
184
  METRICS_FIELD_NUMBER: _ClassVar[int]
185
+ ENVIRONMENT_FIELD_NUMBER: _ClassVar[int]
181
186
  timestamp: int
182
187
  metrics: _containers.ScalarMap[str, float]
183
- def __init__(self, timestamp: _Optional[int] = ..., metrics: _Optional[_Mapping[str, float]] = ...) -> None: ...
188
+ environment: ClientEnvironment
189
+ def __init__(self, timestamp: _Optional[int] = ..., metrics: _Optional[_Mapping[str, float]] = ..., environment: _Optional[_Union[ClientEnvironment, _Mapping]] = ...) -> None: ...
190
+
191
+ class ClientEnvironment(_message.Message):
192
+ __slots__ = ("sdk_type", "sdk_version", "os", "os_version", "arch", "device_model")
193
+ SDK_TYPE_FIELD_NUMBER: _ClassVar[int]
194
+ SDK_VERSION_FIELD_NUMBER: _ClassVar[int]
195
+ OS_FIELD_NUMBER: _ClassVar[int]
196
+ OS_VERSION_FIELD_NUMBER: _ClassVar[int]
197
+ ARCH_FIELD_NUMBER: _ClassVar[int]
198
+ DEVICE_MODEL_FIELD_NUMBER: _ClassVar[int]
199
+ sdk_type: str
200
+ sdk_version: str
201
+ os: str
202
+ os_version: str
203
+ arch: str
204
+ device_model: str
205
+ def __init__(self, sdk_type: _Optional[str] = ..., sdk_version: _Optional[str] = ..., os: _Optional[str] = ..., os_version: _Optional[str] = ..., arch: _Optional[str] = ..., device_model: _Optional[str] = ...) -> None: ...
184
206
 
185
207
  class ClientStats(_message.Message):
186
208
  __slots__ = ("track_stats", "connection_stat")
File without changes
File without changes