reactor-effect-client 0.7.0 → 0.8.0
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.
- package/NOTICE +7 -2
- package/README.md +103 -141
- package/dist/Coordinator.d.ts +382 -0
- package/dist/Coordinator.d.ts.map +1 -0
- package/dist/Coordinator.js +732 -0
- package/dist/Coordinator.js.map +1 -0
- package/dist/H3.d.ts +159 -0
- package/dist/H3.d.ts.map +1 -0
- package/dist/H3.js +21 -0
- package/dist/H3.js.map +1 -0
- package/dist/H3Source.d.ts +93 -0
- package/dist/H3Source.d.ts.map +1 -0
- package/dist/H3Source.js +401 -0
- package/dist/H3Source.js.map +1 -0
- package/dist/LocalSource.d.ts +68 -0
- package/dist/LocalSource.d.ts.map +1 -0
- package/dist/LocalSource.js +354 -0
- package/dist/LocalSource.js.map +1 -0
- package/dist/Media.d.ts +106 -0
- package/dist/Media.d.ts.map +1 -0
- package/dist/Media.js +16 -0
- package/dist/Media.js.map +1 -0
- package/dist/Peer.d.ts +134 -0
- package/dist/Peer.d.ts.map +1 -0
- package/dist/Peer.js +57 -0
- package/dist/Peer.js.map +1 -0
- package/dist/Playout.d.ts +724 -0
- package/dist/Playout.d.ts.map +1 -0
- package/dist/Playout.js +28 -0
- package/dist/Playout.js.map +1 -0
- package/dist/Reactor.d.ts +135 -0
- package/dist/Reactor.d.ts.map +1 -0
- package/dist/Reactor.js +158 -0
- package/dist/Reactor.js.map +1 -0
- package/dist/ReactorError.d.ts +331 -0
- package/dist/ReactorError.d.ts.map +1 -0
- package/dist/ReactorError.js +316 -0
- package/dist/ReactorError.js.map +1 -0
- package/dist/ReactorTest.d.ts +383 -0
- package/dist/ReactorTest.d.ts.map +1 -0
- package/dist/ReactorTest.js +317 -0
- package/dist/ReactorTest.js.map +1 -0
- package/dist/Session.d.ts +314 -0
- package/dist/Session.d.ts.map +1 -0
- package/dist/Session.js +51 -0
- package/dist/Session.js.map +1 -0
- package/dist/index.d.ts +12 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -11
- package/dist/index.js.map +1 -1
- package/dist/internal/correlator.d.ts +46 -0
- package/dist/internal/correlator.d.ts.map +1 -0
- package/dist/internal/correlator.js +150 -0
- package/dist/internal/correlator.js.map +1 -0
- package/dist/internal/deadline.d.ts +24 -0
- package/dist/internal/deadline.d.ts.map +1 -0
- package/dist/internal/deadline.js +44 -0
- package/dist/internal/deadline.js.map +1 -0
- package/dist/{h3/_internal/contracts.d.ts → internal/h3/commands.d.ts} +39 -33
- package/dist/internal/h3/commands.d.ts.map +1 -0
- package/dist/internal/h3/commands.js +128 -0
- package/dist/internal/h3/commands.js.map +1 -0
- package/dist/{h3 → internal/h3}/messages.d.ts +16 -11
- package/dist/internal/h3/messages.d.ts.map +1 -0
- package/dist/{h3 → internal/h3}/messages.js +19 -31
- package/dist/internal/h3/messages.js.map +1 -0
- package/dist/internal/h3/operations.d.ts +145 -0
- package/dist/internal/h3/operations.d.ts.map +1 -0
- package/dist/internal/h3/operations.js +284 -0
- package/dist/internal/h3/operations.js.map +1 -0
- package/dist/{h3 → internal/h3}/profile.d.ts +63 -65
- package/dist/internal/h3/profile.d.ts.map +1 -0
- package/dist/{h3 → internal/h3}/profile.js +39 -39
- package/dist/internal/h3/profile.js.map +1 -0
- package/dist/internal/h3/provider.d.ts +14 -0
- package/dist/internal/h3/provider.d.ts.map +1 -0
- package/dist/internal/h3/provider.js +672 -0
- package/dist/internal/h3/provider.js.map +1 -0
- package/dist/internal/h3/references.d.ts +68 -0
- package/dist/internal/h3/references.d.ts.map +1 -0
- package/dist/internal/h3/references.js +339 -0
- package/dist/internal/h3/references.js.map +1 -0
- package/dist/internal/h3/request.d.ts +56 -0
- package/dist/internal/h3/request.d.ts.map +1 -0
- package/dist/internal/h3/request.js +70 -0
- package/dist/internal/h3/request.js.map +1 -0
- package/dist/internal/h3/state.d.ts +120 -0
- package/dist/internal/h3/state.d.ts.map +1 -0
- package/dist/internal/h3/state.js +270 -0
- package/dist/internal/h3/state.js.map +1 -0
- package/dist/{Submission.d.ts → internal/h3/submission.d.ts} +1 -3
- package/dist/internal/h3/submission.d.ts.map +1 -0
- package/dist/{Submission.js → internal/h3/submission.js} +1 -9
- package/dist/internal/h3/submission.js.map +1 -0
- package/dist/internal/hub.d.ts +32 -0
- package/dist/internal/hub.d.ts.map +1 -0
- package/dist/internal/hub.js +92 -0
- package/dist/internal/hub.js.map +1 -0
- package/dist/internal/playout/errors.d.ts +51 -0
- package/dist/internal/playout/errors.d.ts.map +1 -0
- package/dist/internal/playout/errors.js +40 -0
- package/dist/internal/playout/errors.js.map +1 -0
- package/dist/internal/playout/policy.d.ts +559 -0
- package/dist/internal/playout/policy.d.ts.map +1 -0
- package/dist/internal/playout/policy.js +2338 -0
- package/dist/internal/playout/policy.js.map +1 -0
- package/dist/internal/playout/runtime.d.ts +28 -0
- package/dist/internal/playout/runtime.d.ts.map +1 -0
- package/dist/internal/playout/runtime.js +766 -0
- package/dist/internal/playout/runtime.js.map +1 -0
- package/dist/internal/playout/tag.d.ts +14 -0
- package/dist/internal/playout/tag.d.ts.map +1 -0
- package/dist/internal/playout/tag.js +43 -0
- package/dist/internal/playout/tag.js.map +1 -0
- package/dist/internal/proto/reactor_wire/v1/common_pb.d.ts +55 -0
- package/dist/internal/proto/reactor_wire/v1/common_pb.d.ts.map +1 -0
- package/dist/internal/proto/reactor_wire/v1/common_pb.js +42 -0
- package/dist/internal/proto/reactor_wire/v1/common_pb.js.map +1 -0
- package/dist/internal/proto/reactor_wire/v1/control_pb.d.ts +160 -0
- package/dist/internal/proto/reactor_wire/v1/control_pb.d.ts.map +1 -0
- package/dist/internal/proto/reactor_wire/v1/control_pb.js +21 -0
- package/dist/internal/proto/reactor_wire/v1/control_pb.js.map +1 -0
- package/dist/internal/proto/reactor_wire/v1/data_pb.d.ts +89 -0
- package/dist/internal/proto/reactor_wire/v1/data_pb.d.ts.map +1 -0
- package/dist/internal/proto/reactor_wire/v1/data_pb.js +20 -0
- package/dist/internal/proto/reactor_wire/v1/data_pb.js.map +1 -0
- package/dist/internal/proto/reactor_wire/v1/model_pb.d.ts +85 -0
- package/dist/internal/proto/reactor_wire/v1/model_pb.d.ts.map +1 -0
- package/dist/internal/proto/reactor_wire/v1/model_pb.js +24 -0
- package/dist/internal/proto/reactor_wire/v1/model_pb.js.map +1 -0
- package/dist/internal/proto/reactor_wire/v1/platform_pb.d.ts +190 -0
- package/dist/internal/proto/reactor_wire/v1/platform_pb.d.ts.map +1 -0
- package/dist/internal/proto/reactor_wire/v1/platform_pb.js +54 -0
- package/dist/internal/proto/reactor_wire/v1/platform_pb.js.map +1 -0
- package/dist/internal/proto/reactor_wire/v1/track_pb.d.ts +88 -0
- package/dist/internal/proto/reactor_wire/v1/track_pb.d.ts.map +1 -0
- package/dist/internal/proto/reactor_wire/v1/track_pb.js +33 -0
- package/dist/internal/proto/reactor_wire/v1/track_pb.js.map +1 -0
- package/dist/internal/queue.d.ts +4 -0
- package/dist/internal/queue.d.ts.map +1 -0
- package/dist/{_internal → internal}/queue.js +0 -9
- package/dist/internal/queue.js.map +1 -0
- package/dist/internal/reactorTest/coordinator.d.ts +5 -0
- package/dist/internal/reactorTest/coordinator.d.ts.map +1 -0
- package/dist/internal/reactorTest/coordinator.js +178 -0
- package/dist/internal/reactorTest/coordinator.js.map +1 -0
- package/dist/internal/reactorTest/faults.d.ts +172 -0
- package/dist/internal/reactorTest/faults.d.ts.map +1 -0
- package/dist/internal/reactorTest/faults.js +23 -0
- package/dist/internal/reactorTest/faults.js.map +1 -0
- package/dist/internal/reactorTest/h3.d.ts +431 -0
- package/dist/internal/reactorTest/h3.d.ts.map +1 -0
- package/dist/internal/reactorTest/h3.js +625 -0
- package/dist/internal/reactorTest/h3.js.map +1 -0
- package/dist/internal/reactorTest/media.d.ts +22 -0
- package/dist/internal/reactorTest/media.d.ts.map +1 -0
- package/dist/internal/reactorTest/media.js +34 -0
- package/dist/internal/reactorTest/media.js.map +1 -0
- package/dist/internal/reactorTest/peer.d.ts +318 -0
- package/dist/internal/reactorTest/peer.d.ts.map +1 -0
- package/dist/internal/reactorTest/peer.js +206 -0
- package/dist/internal/reactorTest/peer.js.map +1 -0
- package/dist/internal/reactorTest/playout.d.ts +34 -0
- package/dist/internal/reactorTest/playout.d.ts.map +1 -0
- package/dist/internal/reactorTest/playout.js +340 -0
- package/dist/internal/reactorTest/playout.js.map +1 -0
- package/dist/internal/reactorTest/references.d.ts +25 -0
- package/dist/internal/reactorTest/references.d.ts.map +1 -0
- package/dist/{testing/Png.js → internal/reactorTest/references.js} +50 -12
- package/dist/internal/reactorTest/references.js.map +1 -0
- package/dist/internal/reactorTest/sessions.d.ts +364 -0
- package/dist/internal/reactorTest/sessions.d.ts.map +1 -0
- package/dist/internal/reactorTest/sessions.js +592 -0
- package/dist/internal/reactorTest/sessions.js.map +1 -0
- package/dist/internal/reactorTest/timing.d.ts +13 -0
- package/dist/internal/reactorTest/timing.d.ts.map +1 -0
- package/dist/internal/reactorTest/timing.js +20 -0
- package/dist/internal/reactorTest/timing.js.map +1 -0
- package/dist/internal/recording.d.ts +52 -0
- package/dist/internal/recording.d.ts.map +1 -0
- package/dist/internal/recording.js +141 -0
- package/dist/internal/recording.js.map +1 -0
- package/dist/internal/session/connect.d.ts +34 -0
- package/dist/internal/session/connect.d.ts.map +1 -0
- package/dist/internal/session/connect.js +502 -0
- package/dist/internal/session/connect.js.map +1 -0
- package/dist/internal/session/generation.d.ts +35 -0
- package/dist/internal/session/generation.d.ts.map +1 -0
- package/dist/internal/session/generation.js +185 -0
- package/dist/internal/session/generation.js.map +1 -0
- package/dist/internal/session/ice.d.ts +23 -0
- package/dist/internal/session/ice.d.ts.map +1 -0
- package/dist/internal/session/ice.js +57 -0
- package/dist/internal/session/ice.js.map +1 -0
- package/dist/internal/session/inbound.d.ts +50 -0
- package/dist/internal/session/inbound.d.ts.map +1 -0
- package/dist/internal/session/inbound.js +202 -0
- package/dist/internal/session/inbound.js.map +1 -0
- package/dist/internal/session/model.d.ts +163 -0
- package/dist/internal/session/model.d.ts.map +1 -0
- package/dist/internal/session/model.js +78 -0
- package/dist/internal/session/model.js.map +1 -0
- package/dist/internal/session/requests.d.ts +71 -0
- package/dist/internal/session/requests.d.ts.map +1 -0
- package/dist/internal/session/requests.js +240 -0
- package/dist/internal/session/requests.js.map +1 -0
- package/dist/internal/session/tracks.d.ts +21 -0
- package/dist/internal/session/tracks.d.ts.map +1 -0
- package/dist/internal/session/tracks.js +166 -0
- package/dist/internal/session/tracks.js.map +1 -0
- package/dist/internal/session/upload.d.ts +21 -0
- package/dist/internal/session/upload.d.ts.map +1 -0
- package/dist/internal/session/upload.js +68 -0
- package/dist/internal/session/upload.js.map +1 -0
- package/dist/internal/session.d.ts +66 -0
- package/dist/internal/session.d.ts.map +1 -0
- package/dist/internal/session.js +280 -0
- package/dist/internal/session.js.map +1 -0
- package/dist/{stats.d.ts → internal/stats.d.ts} +30 -16
- package/dist/internal/stats.d.ts.map +1 -0
- package/dist/internal/stats.js +201 -0
- package/dist/internal/stats.js.map +1 -0
- package/dist/internal/token.d.ts +22 -0
- package/dist/internal/token.d.ts.map +1 -0
- package/dist/internal/token.js +65 -0
- package/dist/internal/token.js.map +1 -0
- package/dist/internal/wire.d.ts +37 -0
- package/dist/internal/wire.d.ts.map +1 -0
- package/dist/internal/wire.js +49 -0
- package/dist/internal/wire.js.map +1 -0
- package/notices/bufbuild-protobuf-LICENSE +201 -0
- package/package.json +17 -38
- package/dist/FetchHttp.d.ts +0 -9
- package/dist/FetchHttp.d.ts.map +0 -1
- package/dist/FetchHttp.js +0 -63
- package/dist/FetchHttp.js.map +0 -1
- package/dist/PeerFactory.d.ts +0 -26
- package/dist/PeerFactory.d.ts.map +0 -1
- package/dist/PeerFactory.js +0 -4
- package/dist/PeerFactory.js.map +0 -1
- package/dist/PeerTypes.d.ts +0 -64
- package/dist/PeerTypes.d.ts.map +0 -1
- package/dist/PeerTypes.js +0 -2
- package/dist/PeerTypes.js.map +0 -1
- package/dist/Sequence.d.ts +0 -85
- package/dist/Sequence.d.ts.map +0 -1
- package/dist/Sequence.js +0 -210
- package/dist/Sequence.js.map +0 -1
- package/dist/SessionTypes.d.ts +0 -312
- package/dist/SessionTypes.d.ts.map +0 -1
- package/dist/SessionTypes.js +0 -19
- package/dist/SessionTypes.js.map +0 -1
- package/dist/Submission.d.ts.map +0 -1
- package/dist/Submission.js.map +0 -1
- package/dist/_internal/queue.d.ts +0 -6
- package/dist/_internal/queue.d.ts.map +0 -1
- package/dist/_internal/queue.js.map +0 -1
- package/dist/bytes.d.ts +0 -6
- package/dist/bytes.d.ts.map +0 -1
- package/dist/bytes.js +0 -32
- package/dist/bytes.js.map +0 -1
- package/dist/contract.d.ts +0 -149
- package/dist/contract.d.ts.map +0 -1
- package/dist/contract.js +0 -119
- package/dist/contract.js.map +0 -1
- package/dist/coordinator/_internal/client.d.ts +0 -215
- package/dist/coordinator/_internal/client.d.ts.map +0 -1
- package/dist/coordinator/_internal/client.js +0 -563
- package/dist/coordinator/_internal/client.js.map +0 -1
- package/dist/coordinator/_internal/recording.d.ts +0 -31
- package/dist/coordinator/_internal/recording.d.ts.map +0 -1
- package/dist/coordinator/_internal/recording.js +0 -123
- package/dist/coordinator/_internal/recording.js.map +0 -1
- package/dist/coordinator/_internal/response.d.ts +0 -15
- package/dist/coordinator/_internal/response.d.ts.map +0 -1
- package/dist/coordinator/_internal/response.js +0 -55
- package/dist/coordinator/_internal/response.js.map +0 -1
- package/dist/coordinator/_internal/schemas.d.ts +0 -73
- package/dist/coordinator/_internal/schemas.d.ts.map +0 -1
- package/dist/coordinator/_internal/schemas.js +0 -200
- package/dist/coordinator/_internal/schemas.js.map +0 -1
- package/dist/coordinator/index.d.ts +0 -40
- package/dist/coordinator/index.d.ts.map +0 -1
- package/dist/coordinator/index.js +0 -21
- package/dist/coordinator/index.js.map +0 -1
- package/dist/correlation.d.ts +0 -47
- package/dist/correlation.d.ts.map +0 -1
- package/dist/correlation.js +0 -131
- package/dist/correlation.js.map +0 -1
- package/dist/duration.d.ts +0 -27
- package/dist/duration.d.ts.map +0 -1
- package/dist/duration.js +0 -47
- package/dist/duration.js.map +0 -1
- package/dist/errors.d.ts +0 -1303
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -745
- package/dist/errors.js.map +0 -1
- package/dist/h3/_internal/client.d.ts +0 -9
- package/dist/h3/_internal/client.d.ts.map +0 -1
- package/dist/h3/_internal/client.js +0 -652
- package/dist/h3/_internal/client.js.map +0 -1
- package/dist/h3/_internal/contracts.d.ts.map +0 -1
- package/dist/h3/_internal/contracts.js +0 -123
- package/dist/h3/_internal/contracts.js.map +0 -1
- package/dist/h3/_internal/deployment.d.ts +0 -8
- package/dist/h3/_internal/deployment.d.ts.map +0 -1
- package/dist/h3/_internal/deployment.js +0 -149
- package/dist/h3/_internal/deployment.js.map +0 -1
- package/dist/h3/_internal/evidence.d.ts +0 -17
- package/dist/h3/_internal/evidence.d.ts.map +0 -1
- package/dist/h3/_internal/evidence.js +0 -48
- package/dist/h3/_internal/evidence.js.map +0 -1
- package/dist/h3/_internal/operations.d.ts +0 -58
- package/dist/h3/_internal/operations.d.ts.map +0 -1
- package/dist/h3/_internal/operations.js +0 -268
- package/dist/h3/_internal/operations.js.map +0 -1
- package/dist/h3/_internal/references.d.ts +0 -24
- package/dist/h3/_internal/references.d.ts.map +0 -1
- package/dist/h3/_internal/references.js +0 -309
- package/dist/h3/_internal/references.js.map +0 -1
- package/dist/h3/_internal/request.d.ts +0 -21
- package/dist/h3/_internal/request.d.ts.map +0 -1
- package/dist/h3/_internal/request.js +0 -97
- package/dist/h3/_internal/request.js.map +0 -1
- package/dist/h3/_internal/retained.d.ts +0 -22
- package/dist/h3/_internal/retained.d.ts.map +0 -1
- package/dist/h3/_internal/retained.js +0 -89
- package/dist/h3/_internal/retained.js.map +0 -1
- package/dist/h3/_internal/state.d.ts +0 -44
- package/dist/h3/_internal/state.d.ts.map +0 -1
- package/dist/h3/_internal/state.js +0 -283
- package/dist/h3/_internal/state.js.map +0 -1
- package/dist/h3/index.d.ts +0 -10
- package/dist/h3/index.d.ts.map +0 -1
- package/dist/h3/index.js +0 -7
- package/dist/h3/index.js.map +0 -1
- package/dist/h3/messages.d.ts.map +0 -1
- package/dist/h3/messages.js.map +0 -1
- package/dist/h3/profile.d.ts.map +0 -1
- package/dist/h3/profile.js.map +0 -1
- package/dist/h3/types.d.ts +0 -291
- package/dist/h3/types.d.ts.map +0 -1
- package/dist/h3/types.js +0 -2
- package/dist/h3/types.js.map +0 -1
- package/dist/host.d.ts +0 -26
- package/dist/host.d.ts.map +0 -1
- package/dist/host.js +0 -20
- package/dist/host.js.map +0 -1
- package/dist/json.d.ts +0 -25
- package/dist/json.d.ts.map +0 -1
- package/dist/json.js +0 -143
- package/dist/json.js.map +0 -1
- package/dist/media-stream.d.ts +0 -10
- package/dist/media-stream.d.ts.map +0 -1
- package/dist/media-stream.js +0 -18
- package/dist/media-stream.js.map +0 -1
- package/dist/observation.d.ts +0 -38
- package/dist/observation.d.ts.map +0 -1
- package/dist/observation.js +0 -113
- package/dist/observation.js.map +0 -1
- package/dist/orchestration/elapsed.d.ts +0 -9
- package/dist/orchestration/elapsed.d.ts.map +0 -1
- package/dist/orchestration/elapsed.js +0 -8
- package/dist/orchestration/elapsed.js.map +0 -1
- package/dist/orchestration/h3-source.d.ts +0 -58
- package/dist/orchestration/h3-source.d.ts.map +0 -1
- package/dist/orchestration/h3-source.js +0 -362
- package/dist/orchestration/h3-source.js.map +0 -1
- package/dist/orchestration/index.d.ts +0 -17
- package/dist/orchestration/index.d.ts.map +0 -1
- package/dist/orchestration/index.js +0 -12
- package/dist/orchestration/index.js.map +0 -1
- package/dist/orchestration/media-buffer.d.ts +0 -32
- package/dist/orchestration/media-buffer.d.ts.map +0 -1
- package/dist/orchestration/media-buffer.js +0 -94
- package/dist/orchestration/media-buffer.js.map +0 -1
- package/dist/orchestration/open-h3.d.ts +0 -101
- package/dist/orchestration/open-h3.d.ts.map +0 -1
- package/dist/orchestration/open-h3.js +0 -127
- package/dist/orchestration/open-h3.js.map +0 -1
- package/dist/orchestration/queries.d.ts +0 -10
- package/dist/orchestration/queries.d.ts.map +0 -1
- package/dist/orchestration/queries.js +0 -43
- package/dist/orchestration/queries.js.map +0 -1
- package/dist/orchestration/references.d.ts +0 -18
- package/dist/orchestration/references.d.ts.map +0 -1
- package/dist/orchestration/references.js +0 -79
- package/dist/orchestration/references.js.map +0 -1
- package/dist/orchestration/renewal-state.d.ts +0 -84
- package/dist/orchestration/renewal-state.d.ts.map +0 -1
- package/dist/orchestration/renewal-state.js +0 -95
- package/dist/orchestration/renewal-state.js.map +0 -1
- package/dist/orchestration/renewal.d.ts +0 -103
- package/dist/orchestration/renewal.d.ts.map +0 -1
- package/dist/orchestration/renewal.js +0 -1088
- package/dist/orchestration/renewal.js.map +0 -1
- package/dist/orchestration/request.d.ts +0 -65
- package/dist/orchestration/request.d.ts.map +0 -1
- package/dist/orchestration/request.js +0 -134
- package/dist/orchestration/request.js.map +0 -1
- package/dist/orchestration/retention.d.ts +0 -100
- package/dist/orchestration/retention.d.ts.map +0 -1
- package/dist/orchestration/retention.js +0 -134
- package/dist/orchestration/retention.js.map +0 -1
- package/dist/orchestration/routing.d.ts +0 -21
- package/dist/orchestration/routing.d.ts.map +0 -1
- package/dist/orchestration/routing.js +0 -93
- package/dist/orchestration/routing.js.map +0 -1
- package/dist/orchestration/scheduler-key.d.ts +0 -14
- package/dist/orchestration/scheduler-key.d.ts.map +0 -1
- package/dist/orchestration/scheduler-key.js +0 -62
- package/dist/orchestration/scheduler-key.js.map +0 -1
- package/dist/orchestration/scheduler-policy.d.ts +0 -222
- package/dist/orchestration/scheduler-policy.d.ts.map +0 -1
- package/dist/orchestration/scheduler-policy.js +0 -582
- package/dist/orchestration/scheduler-policy.js.map +0 -1
- package/dist/orchestration/scheduler.d.ts +0 -383
- package/dist/orchestration/scheduler.d.ts.map +0 -1
- package/dist/orchestration/scheduler.js +0 -2597
- package/dist/orchestration/scheduler.js.map +0 -1
- package/dist/orchestration/source-slot.d.ts +0 -124
- package/dist/orchestration/source-slot.d.ts.map +0 -1
- package/dist/orchestration/source-slot.js +0 -348
- package/dist/orchestration/source-slot.js.map +0 -1
- package/dist/orchestration/types.d.ts +0 -1462
- package/dist/orchestration/types.d.ts.map +0 -1
- package/dist/orchestration/types.js +0 -135
- package/dist/orchestration/types.js.map +0 -1
- package/dist/protobuf.d.ts +0 -63
- package/dist/protobuf.d.ts.map +0 -1
- package/dist/protobuf.js +0 -252
- package/dist/protobuf.js.map +0 -1
- package/dist/session/_internal/acquire.d.ts +0 -21
- package/dist/session/_internal/acquire.d.ts.map +0 -1
- package/dist/session/_internal/acquire.js +0 -168
- package/dist/session/_internal/acquire.js.map +0 -1
- package/dist/session/_internal/cleanup.d.ts +0 -19
- package/dist/session/_internal/cleanup.d.ts.map +0 -1
- package/dist/session/_internal/cleanup.js +0 -102
- package/dist/session/_internal/cleanup.js.map +0 -1
- package/dist/session/_internal/lifecycle.d.ts +0 -67
- package/dist/session/_internal/lifecycle.d.ts.map +0 -1
- package/dist/session/_internal/lifecycle.js +0 -142
- package/dist/session/_internal/lifecycle.js.map +0 -1
- package/dist/session/_internal/remote.d.ts +0 -28
- package/dist/session/_internal/remote.d.ts.map +0 -1
- package/dist/session/_internal/remote.js +0 -71
- package/dist/session/_internal/remote.js.map +0 -1
- package/dist/session/_internal/uploads.d.ts +0 -7
- package/dist/session/_internal/uploads.d.ts.map +0 -1
- package/dist/session/_internal/uploads.js +0 -31
- package/dist/session/_internal/uploads.js.map +0 -1
- package/dist/session/commands.d.ts +0 -2
- package/dist/session/commands.d.ts.map +0 -1
- package/dist/session/commands.js +0 -2
- package/dist/session/commands.js.map +0 -1
- package/dist/session/files.d.ts +0 -21
- package/dist/session/files.d.ts.map +0 -1
- package/dist/session/files.js +0 -49
- package/dist/session/files.js.map +0 -1
- package/dist/session/index.d.ts +0 -84
- package/dist/session/index.d.ts.map +0 -1
- package/dist/session/index.js +0 -16
- package/dist/session/index.js.map +0 -1
- package/dist/session/media.d.ts +0 -114
- package/dist/session/media.d.ts.map +0 -1
- package/dist/session/media.js +0 -2
- package/dist/session/media.js.map +0 -1
- package/dist/session/recorder.d.ts +0 -24
- package/dist/session/recorder.d.ts.map +0 -1
- package/dist/session/recorder.js +0 -19
- package/dist/session/recorder.js.map +0 -1
- package/dist/session.d.ts +0 -105
- package/dist/session.d.ts.map +0 -1
- package/dist/session.js +0 -976
- package/dist/session.js.map +0 -1
- package/dist/simulation/_internal/source.d.ts +0 -12
- package/dist/simulation/_internal/source.d.ts.map +0 -1
- package/dist/simulation/_internal/source.js +0 -513
- package/dist/simulation/_internal/source.js.map +0 -1
- package/dist/simulation/index.d.ts +0 -13
- package/dist/simulation/index.d.ts.map +0 -1
- package/dist/simulation/index.js +0 -12
- package/dist/simulation/index.js.map +0 -1
- package/dist/simulation/types.d.ts +0 -39
- package/dist/simulation/types.d.ts.map +0 -1
- package/dist/simulation/types.js +0 -2
- package/dist/simulation/types.js.map +0 -1
- package/dist/stats.d.ts.map +0 -1
- package/dist/stats.js +0 -205
- package/dist/stats.js.map +0 -1
- package/dist/testing/Faults.d.ts +0 -4
- package/dist/testing/Faults.d.ts.map +0 -1
- package/dist/testing/Faults.js +0 -7
- package/dist/testing/Faults.js.map +0 -1
- package/dist/testing/Png.d.ts +0 -3
- package/dist/testing/Png.d.ts.map +0 -1
- package/dist/testing/Png.js.map +0 -1
- package/dist/testing/Wav.d.ts +0 -14
- package/dist/testing/Wav.d.ts.map +0 -1
- package/dist/testing/Wav.js +0 -37
- package/dist/testing/Wav.js.map +0 -1
- package/dist/testing/index.d.ts +0 -5
- package/dist/testing/index.d.ts.map +0 -1
- package/dist/testing/index.js +0 -4
- package/dist/testing/index.js.map +0 -1
- package/dist/wire.d.ts +0 -7
- package/dist/wire.d.ts.map +0 -1
- package/dist/wire.generated.d.ts +0 -305
- package/dist/wire.generated.d.ts.map +0 -1
- package/dist/wire.generated.js +0 -1257
- package/dist/wire.generated.js.map +0 -1
- package/dist/wire.js +0 -5
- package/dist/wire.js.map +0 -1
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactor's HTTP API: pricing, tokens, inspection, termination, recordings,
|
|
3
|
+
* and the signaling a session runs. Built over the application's
|
|
4
|
+
* `HttpClient`; constructing it makes no request.
|
|
5
|
+
*/
|
|
6
|
+
import * as Arr from "effect/Array";
|
|
7
|
+
import * as Clock from "effect/Clock";
|
|
8
|
+
import * as Config from "effect/Config";
|
|
9
|
+
import * as Context from "effect/Context";
|
|
10
|
+
import * as Duration from "effect/Duration";
|
|
11
|
+
import * as Effect from "effect/Effect";
|
|
12
|
+
import * as Layer from "effect/Layer";
|
|
13
|
+
import * as Option from "effect/Option";
|
|
14
|
+
import * as Predicate from "effect/Predicate";
|
|
15
|
+
import * as Redacted from "effect/Redacted";
|
|
16
|
+
import * as Ref from "effect/Ref";
|
|
17
|
+
import * as Schedule from "effect/Schedule";
|
|
18
|
+
import * as Schema from "effect/Schema";
|
|
19
|
+
import * as SchemaTransformation from "effect/SchemaTransformation";
|
|
20
|
+
import * as Stream from "effect/Stream";
|
|
21
|
+
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
|
|
22
|
+
import * as Headers from "effect/unstable/http/Headers";
|
|
23
|
+
import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
24
|
+
import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
|
|
25
|
+
import * as Recording from "./internal/recording.js";
|
|
26
|
+
import { FailureSummary, Http, ReactorError, summarize } from "./ReactorError.js";
|
|
27
|
+
export const defaultApiUrl = "https://api.reactor.inc";
|
|
28
|
+
const clientInfo = { sdk_version: "0.8.0", sdk_type: "typescript-effect-independent" };
|
|
29
|
+
/** A field the provider sends as `null` or omits for the same fact decodes as absent. */
|
|
30
|
+
const NullAsAbsent = (schema) => schema.pipe(Schema.NullOr, Schema.optionalKey, Schema.decodeTo(Schema.toType(schema).pipe(Schema.optionalKey), SchemaTransformation.transformOptional({
|
|
31
|
+
decode: Option.filter(Predicate.isNotNull),
|
|
32
|
+
encode: (value) => value,
|
|
33
|
+
})));
|
|
34
|
+
const Uint32 = Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 0xffffffff }));
|
|
35
|
+
const PositiveInt = Schema.Int.check(Schema.isGreaterThan(0));
|
|
36
|
+
export const Track = Schema.Struct({
|
|
37
|
+
name: Schema.NonEmptyString,
|
|
38
|
+
kind: Schema.Literals(["audio", "video"]),
|
|
39
|
+
direction: Schema.Literals(["recvonly", "sendonly"]),
|
|
40
|
+
});
|
|
41
|
+
/** A track and the media section a host negotiated for it. */
|
|
42
|
+
export const Mapping = Schema.Struct({ ...Track.fields, mid: Schema.String });
|
|
43
|
+
export const Capabilities = Schema.Struct({
|
|
44
|
+
protocol_version: Schema.NonEmptyString,
|
|
45
|
+
tracks: Schema.Array(Track).check(Schema.isMaxLength(64), Schema.makeFilter((tracks) => new Set(tracks.map((track) => track.name)).size === tracks.length
|
|
46
|
+
? undefined
|
|
47
|
+
: "duplicate track names are ambiguous for named track operations")),
|
|
48
|
+
commands: Schema.Struct({
|
|
49
|
+
name: Schema.NonEmptyString,
|
|
50
|
+
description: NullAsAbsent(Schema.String),
|
|
51
|
+
schema: Schema.optionalKey(Schema.Json),
|
|
52
|
+
}).pipe(Schema.Array, NullAsAbsent),
|
|
53
|
+
emission_fps: NullAsAbsent(Schema.Finite),
|
|
54
|
+
});
|
|
55
|
+
export const Transport = Schema.Struct({
|
|
56
|
+
protocol: Schema.NonEmptyString,
|
|
57
|
+
version: Schema.NonEmptyString,
|
|
58
|
+
});
|
|
59
|
+
/** A session as the coordinator describes it; unknown states stay as they are. */
|
|
60
|
+
export const Descriptor = Schema.Struct({
|
|
61
|
+
session_id: Schema.NonEmptyString,
|
|
62
|
+
state: Schema.NonEmptyString,
|
|
63
|
+
capabilities: NullAsAbsent(Capabilities),
|
|
64
|
+
selected_transport: NullAsAbsent(Transport),
|
|
65
|
+
});
|
|
66
|
+
/**
|
|
67
|
+
* Whether a session state is final. Only `CLOSED` is: hosted Reactor reads
|
|
68
|
+
* `INACTIVE` for a session whose last connection dropped, and that session still
|
|
69
|
+
* runs (and bills) for the documented 30 s reconnect window. A paid run adopted
|
|
70
|
+
* one 9 s after its owner was killed, then ended it with a DELETE that
|
|
71
|
+
* answered 200 and moved it to `CLOSED`.
|
|
72
|
+
*/
|
|
73
|
+
export const isTerminal = (state) => state === "CLOSED";
|
|
74
|
+
export const IceServer = Schema.Struct({
|
|
75
|
+
urls: Schema.Array(Schema.NonEmptyString),
|
|
76
|
+
username: Schema.optionalKey(Schema.String),
|
|
77
|
+
credential: Schema.optionalKey(Schema.String),
|
|
78
|
+
});
|
|
79
|
+
/** The coordinator's ICE server list, as it sends it. */
|
|
80
|
+
export const IceServersReply = Schema.Struct({
|
|
81
|
+
ice_servers: Schema.Array(Schema.Struct({
|
|
82
|
+
uris: Schema.Array(Schema.NonEmptyString),
|
|
83
|
+
credentials: NullAsAbsent(Schema.Struct({ username: Schema.String, password: Schema.String })),
|
|
84
|
+
})).check(Schema.isMaxLength(64)),
|
|
85
|
+
});
|
|
86
|
+
export const IceCandidate = Schema.Struct({
|
|
87
|
+
candidate: Schema.String,
|
|
88
|
+
sdp_mid: Schema.optionalKey(Schema.String),
|
|
89
|
+
sdp_mline_index: Schema.optionalKey(Schema.Int),
|
|
90
|
+
});
|
|
91
|
+
const Allocated = Schema.Struct({ session_id: Schema.NonEmptyString });
|
|
92
|
+
export const Registered = Schema.Struct({ connection_id: Uint32 });
|
|
93
|
+
export const SdpAnswer = Schema.Struct({
|
|
94
|
+
sdp_answer: Schema.NonEmptyString,
|
|
95
|
+
connection_id: NullAsAbsent(Uint32),
|
|
96
|
+
});
|
|
97
|
+
export const UploadSlot = Schema.Struct({
|
|
98
|
+
presigned_id: Schema.NonEmptyString,
|
|
99
|
+
presigned_url: Schema.NonEmptyString,
|
|
100
|
+
path: Schema.String,
|
|
101
|
+
});
|
|
102
|
+
/**
|
|
103
|
+
* A remote termination verdict. A DELETE response alone is never proof: an
|
|
104
|
+
* independent read confirms the session is gone or terminal.
|
|
105
|
+
*/
|
|
106
|
+
export const Termination = Schema.Struct({
|
|
107
|
+
attempted: Schema.Boolean,
|
|
108
|
+
responseReceived: Schema.Boolean,
|
|
109
|
+
confirmed: Schema.Boolean,
|
|
110
|
+
evidence: Schema.NullOr(Schema.Literals(["absent", "terminal"])),
|
|
111
|
+
/** DELETE status; null means no response arrived. */
|
|
112
|
+
deleteStatus: Schema.NullOr(Schema.Int),
|
|
113
|
+
/** State from the independent confirmation, when it was valid. */
|
|
114
|
+
state: Schema.NullOr(Schema.String),
|
|
115
|
+
error: Schema.optionalKey(FailureSummary),
|
|
116
|
+
});
|
|
117
|
+
export const notTerminated = {
|
|
118
|
+
attempted: false,
|
|
119
|
+
responseReceived: false,
|
|
120
|
+
confirmed: false,
|
|
121
|
+
evidence: null,
|
|
122
|
+
deleteStatus: null,
|
|
123
|
+
state: null,
|
|
124
|
+
};
|
|
125
|
+
/** A termination verdict as span attributes: a successful terminate says nothing about billing. */
|
|
126
|
+
export const terminationAttributes = (termination) => ({
|
|
127
|
+
"reactor.termination.attempted": termination.attempted,
|
|
128
|
+
"reactor.termination.confirmed": termination.confirmed,
|
|
129
|
+
...(termination.evidence === null
|
|
130
|
+
? {}
|
|
131
|
+
: { "reactor.termination.evidence": termination.evidence }),
|
|
132
|
+
});
|
|
133
|
+
const Pricing = Schema.Struct({
|
|
134
|
+
settings: Schema.Struct({
|
|
135
|
+
currency_code: Schema.optional(Schema.Literal("USD")),
|
|
136
|
+
credits_per_dollar: PositiveInt,
|
|
137
|
+
}),
|
|
138
|
+
models: Schema.Unknown.pipe(Schema.Array, Schema.optional),
|
|
139
|
+
});
|
|
140
|
+
const PricedModel = Schema.Struct({ name: Schema.String, rate: Schema.Unknown });
|
|
141
|
+
const isPricedModel = Schema.is(PricedModel);
|
|
142
|
+
const CreditsRate = Schema.Union([
|
|
143
|
+
Schema.Struct({
|
|
144
|
+
amount_per_sec: PositiveInt,
|
|
145
|
+
unit: Schema.Literal("credits"),
|
|
146
|
+
denomination: Schema.Literal("second"),
|
|
147
|
+
}),
|
|
148
|
+
Schema.Struct({
|
|
149
|
+
amount_per_min: PositiveInt,
|
|
150
|
+
unit: Schema.Literal("credits"),
|
|
151
|
+
denomination: Schema.Literal("minute"),
|
|
152
|
+
}),
|
|
153
|
+
]);
|
|
154
|
+
/**
|
|
155
|
+
* The rate of `model` in `pricing`, by its connect slug or bare name: the
|
|
156
|
+
* catalog lists `h3-reference-to-video-turbo-realtime` where sessions take
|
|
157
|
+
* `reactor/h3-reference-to-video-turbo-realtime`. Only the settings are read
|
|
158
|
+
* strictly, so an entry this client cannot price never stops it pricing another.
|
|
159
|
+
*/
|
|
160
|
+
export const modelRate = Effect.fnUntraced(function* (pricing, model) {
|
|
161
|
+
const bare = model.slice(model.lastIndexOf("/") + 1);
|
|
162
|
+
const decoded = yield* Schema.decodeUnknownEffect(Pricing)(pricing);
|
|
163
|
+
const matches = (decoded.models ?? [])
|
|
164
|
+
.filter(isPricedModel)
|
|
165
|
+
.filter((entry) => entry.name === model || entry.name === bare);
|
|
166
|
+
const rate = yield* Schema.decodeUnknownEffect(CreditsRate)(matches.length === 1 ? matches[0]?.rate : undefined);
|
|
167
|
+
return {
|
|
168
|
+
creditsPerDollar: decoded.settings.credits_per_dollar,
|
|
169
|
+
creditsPerSecond: "amount_per_sec" in rate ? rate.amount_per_sec : rate.amount_per_min / 60,
|
|
170
|
+
per: rate.denomination,
|
|
171
|
+
};
|
|
172
|
+
}, Effect.mapError((cause) => ReactorError.fromCode("Protocol", "Reactor pricing has an unknown model, currency or rate unit", {
|
|
173
|
+
operation: "pricing",
|
|
174
|
+
outcome: "replied",
|
|
175
|
+
detail: cause,
|
|
176
|
+
})));
|
|
177
|
+
/**
|
|
178
|
+
* Tokens that are never refreshed, for a session shorter than its token: the
|
|
179
|
+
* same `token` creates the session and serves its every later call, and the
|
|
180
|
+
* session's calls fail once it expires.
|
|
181
|
+
*/
|
|
182
|
+
export const fixedTokens = (token) => ({
|
|
183
|
+
create: Effect.succeed(token),
|
|
184
|
+
bind: () => Effect.succeed(token),
|
|
185
|
+
});
|
|
186
|
+
const Count = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 500 }));
|
|
187
|
+
const Seconds = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 86_400 }));
|
|
188
|
+
/** One `authorization_details` entry of a session-scoped token, as `POST /tokens` takes it. */
|
|
189
|
+
const SessionAuthorization = Schema.Struct({
|
|
190
|
+
type: Schema.Literal("session"),
|
|
191
|
+
resources: Schema.Struct({
|
|
192
|
+
models: Schema.Struct({ match: Schema.NonEmptyArray(Schema.NonEmptyString) }),
|
|
193
|
+
sessions: Schema.optionalKey(Schema.Struct({ bind: Schema.NonEmptyArray(Schema.NonEmptyString) })),
|
|
194
|
+
}),
|
|
195
|
+
constraints: Schema.optionalKey(Schema.Struct({
|
|
196
|
+
max_sessions: Schema.optionalKey(Count),
|
|
197
|
+
max_session_duration_seconds: Schema.optionalKey(Seconds),
|
|
198
|
+
})),
|
|
199
|
+
});
|
|
200
|
+
/** Reactor's echo of a grant: the request's shape, with the resolved count and bound set. */
|
|
201
|
+
const Stated = Schema.Int.pipe(Schema.NullOr, Schema.optionalKey);
|
|
202
|
+
const GrantEcho = Schema.Struct({
|
|
203
|
+
type: Schema.Literal("session"),
|
|
204
|
+
resources: Schema.Struct({
|
|
205
|
+
models: Schema.Struct({ match: Schema.Array(Schema.String) }),
|
|
206
|
+
sessions: Schema.Struct({
|
|
207
|
+
bind: Schema.String.pipe(Schema.Array, Schema.optionalKey),
|
|
208
|
+
}).pipe(Schema.NullOr, Schema.optionalKey),
|
|
209
|
+
}),
|
|
210
|
+
constraints: Schema.Struct({
|
|
211
|
+
max_sessions: Stated,
|
|
212
|
+
max_session_duration_seconds: Stated,
|
|
213
|
+
}).pipe(Schema.NullOr, Schema.optionalKey),
|
|
214
|
+
});
|
|
215
|
+
const TokenReply = Schema.Struct({
|
|
216
|
+
jwt: Schema.NonEmptyString,
|
|
217
|
+
expires_at: Schema.Finite,
|
|
218
|
+
authorization_details: GrantEcho.pipe(Schema.Array, Schema.optionalKey),
|
|
219
|
+
});
|
|
220
|
+
const TokenRequestBody = Schema.Struct({
|
|
221
|
+
authorization_details: Schema.Tuple([SessionAuthorization]),
|
|
222
|
+
expires_after: Schema.optionalKey(Schema.Int),
|
|
223
|
+
});
|
|
224
|
+
const ClientInfo = Schema.Struct({ sdk_version: Schema.String, sdk_type: Schema.String });
|
|
225
|
+
const CreateBody = Schema.Struct({
|
|
226
|
+
model: Schema.Struct({ name: Schema.NonEmptyString, version: Schema.optionalKey(Schema.String) }),
|
|
227
|
+
client_info: ClientInfo,
|
|
228
|
+
supported_transports: Schema.Array(Transport),
|
|
229
|
+
extra_args: Schema.optionalKey(Schema.Json),
|
|
230
|
+
});
|
|
231
|
+
const OfferBody = Schema.Struct({
|
|
232
|
+
sdp_offer: Schema.NonEmptyString,
|
|
233
|
+
client_info: ClientInfo,
|
|
234
|
+
track_mapping: Schema.Array(Mapping),
|
|
235
|
+
});
|
|
236
|
+
const IceBody = Schema.Struct({
|
|
237
|
+
candidates: Schema.Array(IceCandidate),
|
|
238
|
+
is_final: Schema.Boolean,
|
|
239
|
+
client_info: ClientInfo,
|
|
240
|
+
});
|
|
241
|
+
const UploadBody = Schema.Struct({
|
|
242
|
+
name: Schema.NonEmptyString,
|
|
243
|
+
mime_type: Schema.NonEmptyString,
|
|
244
|
+
size: PositiveInt,
|
|
245
|
+
});
|
|
246
|
+
export const Inspection = Schema.Struct({
|
|
247
|
+
observedAt: Schema.Finite,
|
|
248
|
+
state: Schema.String,
|
|
249
|
+
hasCapabilities: Schema.Boolean,
|
|
250
|
+
selectedTransport: Schema.NullOr(Transport),
|
|
251
|
+
cluster: Schema.NullOr(Schema.String),
|
|
252
|
+
zone: Schema.NullOr(Schema.String),
|
|
253
|
+
serverVersion: Schema.NullOr(Schema.String),
|
|
254
|
+
});
|
|
255
|
+
const SessionDescription = Schema.Struct({
|
|
256
|
+
session_id: Schema.NonEmptyString,
|
|
257
|
+
state: Schema.NonEmptyString,
|
|
258
|
+
capabilities: Schema.Unknown.pipe(Schema.NullOr, Schema.optionalKey),
|
|
259
|
+
selected_transport: Transport.pipe(Schema.NullOr, Schema.optionalKey),
|
|
260
|
+
cluster: Schema.String.pipe(Schema.NullOr, Schema.optionalKey),
|
|
261
|
+
zone: Schema.String.pipe(Schema.NullOr, Schema.optionalKey),
|
|
262
|
+
server_info: Schema.Struct({ server_version: Schema.String }).pipe(Schema.NullOr, Schema.optionalKey),
|
|
263
|
+
});
|
|
264
|
+
/** A termination probe promises state; when it names a session, it must be the one asked for. */
|
|
265
|
+
const TerminalState = Schema.Struct({
|
|
266
|
+
session_id: Schema.optionalKey(Schema.NonEmptyString),
|
|
267
|
+
state: Schema.NonEmptyString,
|
|
268
|
+
});
|
|
269
|
+
export const parsePlaylist = Recording.parsePlaylist;
|
|
270
|
+
export class Coordinator extends Context.Service()("reactor-effect-client/Coordinator") {
|
|
271
|
+
}
|
|
272
|
+
const checkedUrl = (url, base) => Effect.try({
|
|
273
|
+
try: () => new URL(url, base),
|
|
274
|
+
catch: (cause) => ReactorError.fromCode("Protocol", "HTTP URL is malformed", { detail: cause }),
|
|
275
|
+
}).pipe(Effect.filterOrFail((value) => (value.protocol === "https:" || value.protocol === "http:") &&
|
|
276
|
+
value.username === "" &&
|
|
277
|
+
value.password === "", () => ReactorError.fromCode("Protocol", "HTTP URL must use http(s) and contain no embedded credentials")));
|
|
278
|
+
/** A response's `Retry-After` when it names a delay in seconds; an HTTP date is not read. */
|
|
279
|
+
const retryAfterOf = (headers) => {
|
|
280
|
+
const raw = headers["retry-after"];
|
|
281
|
+
if (raw === undefined || !/^\s*(?:\d+(?:\.\d*)?|\.\d+)\s*$/.test(raw))
|
|
282
|
+
return undefined;
|
|
283
|
+
const seconds = Number(raw);
|
|
284
|
+
return Number.isFinite(seconds) ? Duration.seconds(seconds) : undefined;
|
|
285
|
+
};
|
|
286
|
+
/** The body within `maxBytes` and 16,384 chunks, since every chunk, even an empty one, costs memory. */
|
|
287
|
+
const bodyWithin = (response, maxBytes) => {
|
|
288
|
+
const overflow = ReactorError.fromCode("Overflow", `response exceeds its ${maxBytes} byte bound`, {
|
|
289
|
+
outcome: "replied",
|
|
290
|
+
});
|
|
291
|
+
const refused = Stream.fail(overflow);
|
|
292
|
+
return response.stream.pipe(Stream.catchIf((error) => error.reason._tag === "EmptyBodyError", () => Stream.empty), Stream.limitBytes(maxBytes, () => refused), Stream.zipWithIndex, Stream.mapEffect(([chunk, index]) => index < 16_384 ? Effect.succeed(chunk) : Effect.fail(overflow)), Stream.mkUint8Array);
|
|
293
|
+
};
|
|
294
|
+
/** A transport failure once a request may have reached the server: its outcome is unknown. */
|
|
295
|
+
const lost = (operation, status) => (cause) => ReactorError.is(cause)
|
|
296
|
+
? cause
|
|
297
|
+
: ReactorError.make({
|
|
298
|
+
reason: Http.make({
|
|
299
|
+
message: `${operation}: network/read failure`,
|
|
300
|
+
...(status === undefined ? {} : { status }),
|
|
301
|
+
}),
|
|
302
|
+
context: { operation, outcome: "unknown", detail: Redacted.make(cause) },
|
|
303
|
+
});
|
|
304
|
+
/** The reply when its call accepts the status; otherwise the failure the status says. */
|
|
305
|
+
const answered = (call, status, bytes, headers) => {
|
|
306
|
+
const retryAfter = retryAfterOf(headers);
|
|
307
|
+
if ((status >= 200 && status < 300) || call.accepted?.includes(status) === true)
|
|
308
|
+
return Effect.succeed({ status, bytes, retryAfter });
|
|
309
|
+
const message = `${call.operation}: HTTP ${String(status)}`;
|
|
310
|
+
const body = Redacted.make(new TextDecoder().decode(bytes));
|
|
311
|
+
const context = { operation: call.operation, outcome: "replied" };
|
|
312
|
+
return Effect.fail(status === 426 || status === 501
|
|
313
|
+
? ReactorError.fromCode("VersionMismatch", message, { ...context, detail: { status, body } })
|
|
314
|
+
: ReactorError.make({
|
|
315
|
+
reason: Http.make({
|
|
316
|
+
message,
|
|
317
|
+
status,
|
|
318
|
+
body,
|
|
319
|
+
...(retryAfter === undefined ? {} : { retryAfter }),
|
|
320
|
+
}),
|
|
321
|
+
context,
|
|
322
|
+
}));
|
|
323
|
+
};
|
|
324
|
+
const utf8 = new TextDecoder("utf-8", { fatal: true });
|
|
325
|
+
/** Decodes a JSON reply through its Schema; the SchemaError names a path, never the value. */
|
|
326
|
+
const decodeReply = (schema, operation) => {
|
|
327
|
+
const protocol = (message) => (detail) => ReactorError.fromCode("Protocol", message, { operation, outcome: "replied", detail });
|
|
328
|
+
const decode = Schema.decodeEffect(Schema.fromJsonString(schema));
|
|
329
|
+
return (reply) => Effect.try({
|
|
330
|
+
try: () => utf8.decode(reply.bytes),
|
|
331
|
+
catch: protocol(`${operation} reply is not UTF-8`),
|
|
332
|
+
}).pipe(Effect.flatMap((text) => Effect.mapError(decode(text), protocol(`invalid ${operation} reply`))));
|
|
333
|
+
};
|
|
334
|
+
/** `request` with `value` as its JSON body; a value its Schema refuses is never sent. */
|
|
335
|
+
const withBody = (schema, operation) => {
|
|
336
|
+
const encode = HttpClientRequest.schemaBodyJson(schema);
|
|
337
|
+
return (request, value) => Effect.mapError(encode(request, value), (cause) => ReactorError.fromCode("InvalidInput", `${operation} body cannot be encoded`, {
|
|
338
|
+
operation,
|
|
339
|
+
outcome: "not-submitted",
|
|
340
|
+
detail: cause,
|
|
341
|
+
}));
|
|
342
|
+
};
|
|
343
|
+
const sessionPath = (id) => `/sessions/${encodeURIComponent(id)}`;
|
|
344
|
+
const transportPath = (id) => `${sessionPath(id)}/transport/webrtc`;
|
|
345
|
+
const connectionPath = (id, cid) => `${transportPath(id)}/connections/${String(cid)}`;
|
|
346
|
+
/** Doubling waits from 200 ms, capped: at most 20 descriptor reads. */
|
|
347
|
+
const sessionPoll = Schedule.min([
|
|
348
|
+
Schedule.exponential("200 millis"),
|
|
349
|
+
Schedule.spaced("10 seconds"),
|
|
350
|
+
]).pipe(Schedule.upTo({ times: 19 }));
|
|
351
|
+
/** Doubling waits from 200 ms up to 2 s; the caller's connect deadline bounds the whole wait. */
|
|
352
|
+
const sdpPoll = Schedule.min([Schedule.exponential("200 millis"), Schedule.spaced("2 seconds")]);
|
|
353
|
+
const createBody = withBody(CreateBody, "create session");
|
|
354
|
+
const offerBody = withBody(OfferBody, "SDP offer");
|
|
355
|
+
const iceBody = withBody(IceBody, "ICE candidates");
|
|
356
|
+
const uploadBody = withBody(UploadBody, "allocate upload");
|
|
357
|
+
const tokenBody = withBody(TokenRequestBody, "token");
|
|
358
|
+
/** A Coordinator over the current `HttpClient`. No request runs while it is built. */
|
|
359
|
+
export const make = Effect.fnUntraced(function* (options = {}) {
|
|
360
|
+
const base = yield* checkedUrl(options.apiUrl ?? defaultApiUrl).pipe(Effect.filterOrFail((url) => url.search === "" && url.hash === "", () => ReactorError.fromCode("InvalidInput", "apiUrl cannot contain a query or fragment")));
|
|
361
|
+
const apiUrl = base.href.replace(/\/$/, "");
|
|
362
|
+
const http = (yield* HttpClient.HttpClient).pipe(
|
|
363
|
+
// Never follow a redirect with a credential, never send ambient cookies, and trace the
|
|
364
|
+
// operations rather than each request.
|
|
365
|
+
HttpClient.transform((response) => response.pipe(Effect.provideService(FetchHttpClient.RequestInit, {
|
|
366
|
+
credentials: "omit",
|
|
367
|
+
redirect: "error",
|
|
368
|
+
}), Effect.provideService(HttpClient.TracerDisabledWhen, () => true), Effect.updateService(Headers.CurrentRedactedNames, (names) => [
|
|
369
|
+
...names,
|
|
370
|
+
"reactor-api-key",
|
|
371
|
+
]))));
|
|
372
|
+
const api = http.pipe(HttpClient.mapRequest(HttpClientRequest.prependUrl(apiUrl)));
|
|
373
|
+
const versioned = api.pipe(HttpClient.mapRequest(HttpClientRequest.setHeaders({
|
|
374
|
+
"reactor-api-version": "1",
|
|
375
|
+
"reactor-api-accept-version": "1",
|
|
376
|
+
})));
|
|
377
|
+
const clients = {
|
|
378
|
+
session: versioned,
|
|
379
|
+
signaling: versioned.pipe(HttpClient.mapRequest(HttpClientRequest.setHeader("reactor-webrtc-version", "1.0"))),
|
|
380
|
+
public: api,
|
|
381
|
+
recording: http,
|
|
382
|
+
upload: http,
|
|
383
|
+
};
|
|
384
|
+
const call = Effect.fnUntraced(function* (credential, spec) {
|
|
385
|
+
const { operation } = spec;
|
|
386
|
+
const route = spec.route ?? "session";
|
|
387
|
+
const deadline = spec.timeout ?? "15 seconds";
|
|
388
|
+
const url = route === "recording" || route === "upload" ? yield* checkedUrl(spec.request.url) : base;
|
|
389
|
+
// A request whose token could not be had, at all or in time, was never sent. The token
|
|
390
|
+
// has its own deadline, before the request's starts: a slow mint is not a request that
|
|
391
|
+
// may have landed.
|
|
392
|
+
const token = route === "public" || route === "upload" || url.origin !== base.origin
|
|
393
|
+
? undefined
|
|
394
|
+
: yield* credential.pipe(Effect.timeoutOrElse({
|
|
395
|
+
duration: deadline,
|
|
396
|
+
orElse: () => Effect.fail(ReactorError.fromCode("Timeout", `${operation}: no token in time`)),
|
|
397
|
+
}), Effect.mapError((error) => ReactorError.make({
|
|
398
|
+
reason: error.reason,
|
|
399
|
+
context: { ...error.context, operation, outcome: "not-submitted" },
|
|
400
|
+
})));
|
|
401
|
+
const request = token === undefined ? spec.request : HttpClientRequest.bearerToken(spec.request, token);
|
|
402
|
+
return yield* clients[route].execute(request).pipe(
|
|
403
|
+
// Crossing execute is where a mutation may have reached the server: a transport
|
|
404
|
+
// failure after it cannot prove non-delivery.
|
|
405
|
+
Effect.mapError(lost(operation)), Effect.tap((response) => spec.onStatus?.(response.status) ?? Effect.void), Effect.flatMap((response) => bodyWithin(response, spec.maxBytes ?? 2_097_152).pipe(Effect.mapError(lost(operation, response.status)), Effect.flatMap((bytes) => answered(spec, response.status, bytes, response.headers)))), Effect.scoped, Effect.timeoutOrElse({
|
|
406
|
+
duration: deadline,
|
|
407
|
+
orElse: () => Effect.fail(ReactorError.fromCode("Timeout", `${operation}: deadline`, {
|
|
408
|
+
operation,
|
|
409
|
+
outcome: "unknown",
|
|
410
|
+
})),
|
|
411
|
+
}));
|
|
412
|
+
});
|
|
413
|
+
const signaling = (credential) => {
|
|
414
|
+
const request = (spec) => call(credential, spec);
|
|
415
|
+
const read = (sessionId) => request({
|
|
416
|
+
operation: "read session",
|
|
417
|
+
request: HttpClientRequest.get(sessionPath(sessionId)),
|
|
418
|
+
}).pipe(Effect.flatMap(decodeReply(Descriptor, "session descriptor")), Effect.filterOrFail((descriptor) => descriptor.session_id === sessionId, () => ReactorError.fromCode("Protocol", "session descriptor identity mismatch")));
|
|
419
|
+
const terminate = Effect.fnUntraced(function* (sessionId) {
|
|
420
|
+
const status = yield* Ref.make(null);
|
|
421
|
+
const path = sessionPath(sessionId);
|
|
422
|
+
const removal = yield* Effect.result(request({
|
|
423
|
+
operation: "terminate",
|
|
424
|
+
request: HttpClientRequest.delete(path),
|
|
425
|
+
accepted: [404],
|
|
426
|
+
timeout: "3 seconds",
|
|
427
|
+
onStatus: (value) => Ref.set(status, value),
|
|
428
|
+
}));
|
|
429
|
+
const deleteStatus = yield* Ref.get(status);
|
|
430
|
+
const base = {
|
|
431
|
+
attempted: removal._tag === "Success" || removal.failure.context.outcome !== "not-submitted",
|
|
432
|
+
responseReceived: deleteStatus !== null,
|
|
433
|
+
deleteStatus,
|
|
434
|
+
};
|
|
435
|
+
const failed = (error) => ({
|
|
436
|
+
...base,
|
|
437
|
+
confirmed: false,
|
|
438
|
+
evidence: null,
|
|
439
|
+
state: null,
|
|
440
|
+
error: summarize(error),
|
|
441
|
+
});
|
|
442
|
+
const confirmation = yield* Effect.result(request({
|
|
443
|
+
operation: "terminate",
|
|
444
|
+
request: HttpClientRequest.get(path),
|
|
445
|
+
accepted: [404],
|
|
446
|
+
timeout: "3 seconds",
|
|
447
|
+
}));
|
|
448
|
+
if (confirmation._tag === "Failure")
|
|
449
|
+
return failed(confirmation.failure);
|
|
450
|
+
if (confirmation.success.status === 404)
|
|
451
|
+
return deleteStatus === 401 || deleteStatus === 403
|
|
452
|
+
? failed(ReactorError.make({
|
|
453
|
+
reason: Http.make({
|
|
454
|
+
message: "remote termination could not be confirmed after authority refusal",
|
|
455
|
+
status: deleteStatus,
|
|
456
|
+
}),
|
|
457
|
+
context: { operation: "terminate", outcome: "replied" },
|
|
458
|
+
}))
|
|
459
|
+
: { ...base, confirmed: true, evidence: "absent", state: null };
|
|
460
|
+
const described = yield* Effect.result(decodeReply(TerminalState, "termination description")(confirmation.success).pipe(Effect.filterOrFail((value) => value.session_id === undefined || value.session_id === sessionId, () => ReactorError.fromCode("Protocol", "termination names another session"))));
|
|
461
|
+
if (described._tag === "Failure")
|
|
462
|
+
return failed(described.failure);
|
|
463
|
+
const state = described.success.state;
|
|
464
|
+
const terminal = isTerminal(state);
|
|
465
|
+
return {
|
|
466
|
+
...base,
|
|
467
|
+
confirmed: terminal,
|
|
468
|
+
evidence: terminal ? "terminal" : null,
|
|
469
|
+
state,
|
|
470
|
+
...(!terminal && removal._tag === "Failure" ? { error: summarize(removal.failure) } : {}),
|
|
471
|
+
};
|
|
472
|
+
}, Effect.tap((termination) => Effect.annotateCurrentSpan({
|
|
473
|
+
...terminationAttributes(termination),
|
|
474
|
+
...(termination.error === undefined ? {} : { "error.type": termination.error.reason }),
|
|
475
|
+
})), Effect.withSpan("Coordinator.terminate", (sessionId) => ({ kind: "client", attributes: { "reactor.session.id": sessionId } }), { captureStackTrace: false }));
|
|
476
|
+
return {
|
|
477
|
+
create: (model, extraArgs) => createBody(HttpClientRequest.post("/sessions"), {
|
|
478
|
+
model: {
|
|
479
|
+
name: model.name,
|
|
480
|
+
...(model.version === undefined ? {} : { version: model.version }),
|
|
481
|
+
},
|
|
482
|
+
client_info: clientInfo,
|
|
483
|
+
supported_transports: [{ protocol: "webrtc", version: "1.0" }],
|
|
484
|
+
...(extraArgs === undefined ? {} : { extra_args: extraArgs }),
|
|
485
|
+
}).pipe(Effect.flatMap((body) => request({ operation: "create session", request: body })),
|
|
486
|
+
// A refusal (4xx) proves nothing was allocated; a server error may follow an allocation.
|
|
487
|
+
Effect.mapError((error) => error.context.outcome === "replied" &&
|
|
488
|
+
error.reason._tag === "Http" &&
|
|
489
|
+
(error.reason.status ?? 0) >= 500
|
|
490
|
+
? ReactorError.make({
|
|
491
|
+
reason: error.reason,
|
|
492
|
+
context: { ...error.context, outcome: "unknown" },
|
|
493
|
+
})
|
|
494
|
+
: error), Effect.flatMap(decodeReply(Schema.Unknown, "create session")), Effect.flatMap((reply) => Schema.decodeUnknownEffect(Allocated)(reply).pipe(Effect.map((allocated) => ({ sessionId: allocated.session_id, reply })),
|
|
495
|
+
// A session may have been made all the same: the reply's body goes with the
|
|
496
|
+
// failure, redacted as provider text is, for whoever must find that session.
|
|
497
|
+
Effect.mapError((cause) => ReactorError.fromCode("Protocol", "create reply names no session", {
|
|
498
|
+
operation: "create session",
|
|
499
|
+
outcome: "unknown",
|
|
500
|
+
detail: { body: reply, cause },
|
|
501
|
+
}))))),
|
|
502
|
+
describe: (allocation) => Schema.decodeUnknownEffect(Descriptor)(allocation.reply).pipe(Effect.mapError((cause) => ReactorError.fromCode("Protocol", "invalid session descriptor", {
|
|
503
|
+
operation: "create session",
|
|
504
|
+
sessionId: allocation.sessionId,
|
|
505
|
+
outcome: "replied",
|
|
506
|
+
detail: cause,
|
|
507
|
+
}))),
|
|
508
|
+
read,
|
|
509
|
+
ready: Effect.fnUntraced(function* (sessionId, initial) {
|
|
510
|
+
const first = yield* Ref.make(initial);
|
|
511
|
+
const poll = Ref.getAndSet(first, undefined).pipe(Effect.filterOrElse((cached) => cached !== undefined, () => read(sessionId)), Effect.filterOrFail((descriptor) => !isTerminal(descriptor.state), (descriptor) => ReactorError.fromCode("TerminalSession", descriptor.state, { sessionId })));
|
|
512
|
+
const ready = (descriptor) => descriptor.capabilities !== undefined && descriptor.selected_transport !== undefined;
|
|
513
|
+
return yield* poll.pipe(Effect.repeat({ schedule: sessionPoll, until: ready }), Effect.filterOrFail(ready, () => ReactorError.fromCode("Timeout", "session capabilities/transport not ready", {
|
|
514
|
+
sessionId,
|
|
515
|
+
})));
|
|
516
|
+
}),
|
|
517
|
+
iceServers: (sessionId) => request({
|
|
518
|
+
operation: "ICE servers",
|
|
519
|
+
route: "signaling",
|
|
520
|
+
request: HttpClientRequest.get(`${transportPath(sessionId)}/ice_servers`),
|
|
521
|
+
}).pipe(Effect.flatMap(decodeReply(IceServersReply, "ICE servers")), Effect.map((reply) => reply.ice_servers.map((server) => server.credentials === undefined
|
|
522
|
+
? { urls: server.uris }
|
|
523
|
+
: {
|
|
524
|
+
urls: server.uris,
|
|
525
|
+
username: server.credentials.username,
|
|
526
|
+
credential: server.credentials.password,
|
|
527
|
+
}))),
|
|
528
|
+
register: (sessionId) => request({
|
|
529
|
+
operation: "register connection",
|
|
530
|
+
route: "signaling",
|
|
531
|
+
request: HttpClientRequest.post(`${transportPath(sessionId)}/connections`).pipe(HttpClientRequest.bodyText("{}", "application/json")),
|
|
532
|
+
}).pipe(Effect.flatMap(decodeReply(Registered, "connection")), Effect.map((reply) => reply.connection_id)),
|
|
533
|
+
offer: (sessionId, cid, sdp, mapping, replace) => offerBody(HttpClientRequest.make(replace ? "PUT" : "POST")(`${connectionPath(sessionId, cid)}/sdp_params`), { sdp_offer: sdp, client_info: clientInfo, track_mapping: mapping }).pipe(Effect.flatMap((body) => request({ operation: "SDP offer", route: "signaling", request: body })), Effect.asVoid),
|
|
534
|
+
answer: (sessionId, cid) => request({
|
|
535
|
+
operation: "SDP answer",
|
|
536
|
+
route: "signaling",
|
|
537
|
+
request: HttpClientRequest.get(`${connectionPath(sessionId, cid)}/sdp_params`),
|
|
538
|
+
}).pipe(Effect.repeat({ schedule: sdpPoll, until: (reply) => reply.status !== 202 }), Effect.flatMap(decodeReply(SdpAnswer, "SDP answer"))),
|
|
539
|
+
ice: (sessionId, cid, candidates, isFinal) => iceBody(HttpClientRequest.post(`${connectionPath(sessionId, cid)}/ice_candidates`), {
|
|
540
|
+
candidates,
|
|
541
|
+
is_final: isFinal,
|
|
542
|
+
client_info: clientInfo,
|
|
543
|
+
}).pipe(Effect.flatMap((body) => request({ operation: "ICE candidates", route: "signaling", request: body })), Effect.asVoid),
|
|
544
|
+
allocateUpload: (sessionId, name, mimeType, size) => uploadBody(HttpClientRequest.post(`${sessionPath(sessionId)}/uploads`), {
|
|
545
|
+
name,
|
|
546
|
+
mime_type: mimeType,
|
|
547
|
+
size,
|
|
548
|
+
}).pipe(Effect.flatMap((body) => request({ operation: "allocate upload", request: body })), Effect.flatMap(decodeReply(UploadSlot, "upload allocation")), Effect.tap((slot) => checkedUrl(slot.presigned_url))),
|
|
549
|
+
putUpload: (slot, bytes, mimeType) => request({
|
|
550
|
+
operation: "transfer upload",
|
|
551
|
+
route: "upload",
|
|
552
|
+
request: HttpClientRequest.put(slot.presigned_url).pipe(HttpClientRequest.bodyUint8Array(bytes, mimeType)),
|
|
553
|
+
}).pipe(Effect.asVoid),
|
|
554
|
+
terminate,
|
|
555
|
+
downloadClip: (clip, downloadOptions) => Recording.download({
|
|
556
|
+
fetcher: {
|
|
557
|
+
fetch: (operation, url, maxBytes) => request({
|
|
558
|
+
operation,
|
|
559
|
+
route: "recording",
|
|
560
|
+
request: HttpClientRequest.get(url),
|
|
561
|
+
maxBytes,
|
|
562
|
+
}),
|
|
563
|
+
read,
|
|
564
|
+
},
|
|
565
|
+
clip,
|
|
566
|
+
...downloadOptions,
|
|
567
|
+
}),
|
|
568
|
+
};
|
|
569
|
+
};
|
|
570
|
+
// A server holding the key reads, ends and downloads any session of its account with the
|
|
571
|
+
// key as the bearer. It goes only to the API's own origin, as every credential does.
|
|
572
|
+
const configured = options.credential ??
|
|
573
|
+
(options.apiKey === undefined ? Effect.undefined : Effect.succeed(options.apiKey));
|
|
574
|
+
const app = (spec) => call(configured, spec);
|
|
575
|
+
const appSignaling = signaling(configured);
|
|
576
|
+
const mintToken = Effect.fn("Coordinator.mintToken")(function* (input) {
|
|
577
|
+
const invalid = (message) => ReactorError.fromCode("InvalidInput", message, {
|
|
578
|
+
operation: "token",
|
|
579
|
+
outcome: "not-submitted",
|
|
580
|
+
});
|
|
581
|
+
const apiKey = input.apiKey ?? options.apiKey;
|
|
582
|
+
if (apiKey === undefined || Redacted.value(apiKey).length === 0)
|
|
583
|
+
return yield* invalid("a token needs an API key");
|
|
584
|
+
if (input.modelName.length === 0)
|
|
585
|
+
return yield* invalid("a token needs a model");
|
|
586
|
+
const bind = input.bind ?? [];
|
|
587
|
+
if (bind.some((id) => id.length === 0))
|
|
588
|
+
return yield* invalid("a bound session needs an id");
|
|
589
|
+
const maxSessions = input.maxSessions ?? (bind.length === 0 ? 1 : undefined);
|
|
590
|
+
if (maxSessions !== undefined &&
|
|
591
|
+
!(Number.isInteger(maxSessions) &&
|
|
592
|
+
maxSessions >= Math.max(1, bind.length) &&
|
|
593
|
+
maxSessions <= 500))
|
|
594
|
+
return yield* invalid("maxSessions is an integer from 1, or the bound count, to 500");
|
|
595
|
+
// Reactor's default leaves a bound token no room to create; any other token creates.
|
|
596
|
+
const creates = maxSessions !== undefined && maxSessions > bind.length;
|
|
597
|
+
const cap = input.maxSessionDuration;
|
|
598
|
+
let seconds;
|
|
599
|
+
if (cap === undefined) {
|
|
600
|
+
if (creates)
|
|
601
|
+
return yield* invalid('a token that creates sessions needs a maxSessionDuration: a duration or "unlimited"');
|
|
602
|
+
}
|
|
603
|
+
else if (cap !== "unlimited") {
|
|
604
|
+
const duration = Duration.fromInput(cap);
|
|
605
|
+
seconds = Option.isSome(duration) ? Duration.toSeconds(duration.value) : Number.NaN;
|
|
606
|
+
if (!(Number.isInteger(seconds) && seconds >= 1 && seconds <= 86_400))
|
|
607
|
+
return yield* invalid("maxSessionDuration is whole seconds from one to a day");
|
|
608
|
+
}
|
|
609
|
+
let expiresAfter;
|
|
610
|
+
if (input.expiresAfter !== undefined) {
|
|
611
|
+
const expiry = Duration.fromInput(input.expiresAfter);
|
|
612
|
+
expiresAfter = Option.isSome(expiry) ? Duration.toSeconds(expiry.value) : Number.NaN;
|
|
613
|
+
if (!(Number.isSafeInteger(expiresAfter) && expiresAfter >= 1))
|
|
614
|
+
return yield* invalid("expiresAfter is whole seconds, at least one");
|
|
615
|
+
}
|
|
616
|
+
const request = yield* tokenBody(HttpClientRequest.post("/tokens").pipe(HttpClientRequest.setHeader("reactor-api-key", Redacted.value(apiKey))), {
|
|
617
|
+
authorization_details: [
|
|
618
|
+
{
|
|
619
|
+
type: "session",
|
|
620
|
+
resources: {
|
|
621
|
+
models: { match: [input.modelName] },
|
|
622
|
+
...(Arr.isReadonlyArrayNonEmpty(bind) ? { sessions: { bind } } : {}),
|
|
623
|
+
},
|
|
624
|
+
...(maxSessions === undefined && seconds === undefined
|
|
625
|
+
? {}
|
|
626
|
+
: {
|
|
627
|
+
constraints: {
|
|
628
|
+
...(maxSessions === undefined ? {} : { max_sessions: maxSessions }),
|
|
629
|
+
...(seconds === undefined ? {} : { max_session_duration_seconds: seconds }),
|
|
630
|
+
},
|
|
631
|
+
}),
|
|
632
|
+
},
|
|
633
|
+
],
|
|
634
|
+
...(expiresAfter === undefined ? {} : { expires_after: expiresAfter }),
|
|
635
|
+
});
|
|
636
|
+
const token = yield* app({
|
|
637
|
+
operation: "token",
|
|
638
|
+
request,
|
|
639
|
+
route: "public",
|
|
640
|
+
timeout: "8 seconds",
|
|
641
|
+
}).pipe(Effect.flatMap(decodeReply(TokenReply, "token")));
|
|
642
|
+
const protocol = (message) => ReactorError.fromCode("Protocol", message, { operation: "token", outcome: "replied" });
|
|
643
|
+
if (token.expires_at * 1_000 <= (yield* Clock.currentTimeMillis))
|
|
644
|
+
return yield* protocol("the token has already expired");
|
|
645
|
+
const [entry, ...others] = token.authorization_details ?? [];
|
|
646
|
+
if (others.length > 0)
|
|
647
|
+
return yield* protocol("the token grants other authority than asked");
|
|
648
|
+
const stated = entry?.constraints?.max_session_duration_seconds;
|
|
649
|
+
const granted = entry === undefined
|
|
650
|
+
? undefined
|
|
651
|
+
: {
|
|
652
|
+
models: entry.resources.models.match,
|
|
653
|
+
maxSessions: entry.constraints?.max_sessions ?? undefined,
|
|
654
|
+
// A null cap says there is none; an absent one says nothing.
|
|
655
|
+
maxSessionSeconds: stated === null ? "unlimited" : stated,
|
|
656
|
+
bound: entry.resources.sessions?.bind ?? [],
|
|
657
|
+
};
|
|
658
|
+
if (granted !== undefined &&
|
|
659
|
+
(granted.models.some((model) => model !== input.modelName) ||
|
|
660
|
+
granted.bound.some((id) => !bind.includes(id)) ||
|
|
661
|
+
(granted.maxSessions ?? 0) > (maxSessions ?? bind.length) ||
|
|
662
|
+
(seconds !== undefined &&
|
|
663
|
+
(granted.maxSessionSeconds === "unlimited" ||
|
|
664
|
+
(granted.maxSessionSeconds ?? 0) > seconds))))
|
|
665
|
+
return yield* protocol("the token grants more than was asked");
|
|
666
|
+
// A narrower grant caps each session at what it grants.
|
|
667
|
+
const narrower = Predicate.isNumber(granted?.maxSessionSeconds) &&
|
|
668
|
+
(seconds === undefined || granted.maxSessionSeconds < seconds);
|
|
669
|
+
return {
|
|
670
|
+
jwt: Redacted.make(token.jwt),
|
|
671
|
+
expiresAt: token.expires_at,
|
|
672
|
+
maxSessionSeconds: narrower ? granted.maxSessionSeconds : seconds,
|
|
673
|
+
...(granted === undefined ? {} : { granted }),
|
|
674
|
+
};
|
|
675
|
+
});
|
|
676
|
+
return Coordinator.of({
|
|
677
|
+
apiUrl,
|
|
678
|
+
signaling,
|
|
679
|
+
mintToken,
|
|
680
|
+
tokens: (input) => ({
|
|
681
|
+
create: mintToken({ ...input, maxSessions: 1 }),
|
|
682
|
+
bind: (sessionId) => mintToken({
|
|
683
|
+
modelName: input.modelName,
|
|
684
|
+
bind: [sessionId],
|
|
685
|
+
...(input.apiKey === undefined ? {} : { apiKey: input.apiKey }),
|
|
686
|
+
...(input.expiresAfter === undefined ? {} : { expiresAfter: input.expiresAfter }),
|
|
687
|
+
}),
|
|
688
|
+
}),
|
|
689
|
+
pricing: app({
|
|
690
|
+
operation: "pricing",
|
|
691
|
+
request: HttpClientRequest.get("/pricing"),
|
|
692
|
+
route: "public",
|
|
693
|
+
timeout: "8 seconds",
|
|
694
|
+
}).pipe(Effect.flatMap(decodeReply(Schema.Json, "pricing")), Effect.withSpan("Coordinator.pricing", { kind: "client" }, { captureStackTrace: false })),
|
|
695
|
+
inspect: Effect.fn("Coordinator.inspect")(function* (sessionId) {
|
|
696
|
+
const value = yield* app({
|
|
697
|
+
operation: "inspect",
|
|
698
|
+
request: HttpClientRequest.get(sessionPath(sessionId)),
|
|
699
|
+
timeout: "1 second",
|
|
700
|
+
}).pipe(Effect.flatMap(decodeReply(SessionDescription, "inspect")));
|
|
701
|
+
if (value.session_id !== sessionId)
|
|
702
|
+
return yield* ReactorError.fromCode("Protocol", "inspection names another session", {
|
|
703
|
+
operation: "inspect",
|
|
704
|
+
outcome: "replied",
|
|
705
|
+
});
|
|
706
|
+
return {
|
|
707
|
+
observedAt: yield* Clock.currentTimeMillis,
|
|
708
|
+
state: value.state,
|
|
709
|
+
hasCapabilities: value.capabilities != null,
|
|
710
|
+
selectedTransport: value.selected_transport ?? null,
|
|
711
|
+
cluster: value.cluster ?? null,
|
|
712
|
+
zone: value.zone ?? null,
|
|
713
|
+
serverVersion: value.server_info?.server_version ?? null,
|
|
714
|
+
};
|
|
715
|
+
}),
|
|
716
|
+
terminate: appSignaling.terminate,
|
|
717
|
+
downloadClip: appSignaling.downloadClip,
|
|
718
|
+
});
|
|
719
|
+
});
|
|
720
|
+
/** A Coordinator service over the application's `HttpClient`. */
|
|
721
|
+
export const layer = (options = {}) => Layer.effect(Coordinator, make(options));
|
|
722
|
+
/**
|
|
723
|
+
* A Coordinator configured from the environment: `REACTOR_API_URL` (optional)
|
|
724
|
+
* and `REACTOR_API_KEY` (optional, for `mintToken`, `inspect`, `terminate`
|
|
725
|
+
* and `downloadClip`).
|
|
726
|
+
*/
|
|
727
|
+
export const layerConfig = Layer.effect(Coordinator, Effect.gen(function* () {
|
|
728
|
+
const apiUrl = yield* Config.String("REACTOR_API_URL").pipe(Config.withDefault(defaultApiUrl));
|
|
729
|
+
const apiKey = yield* Config.Redacted("REACTOR_API_KEY").pipe(Config.option);
|
|
730
|
+
return yield* make({ apiUrl, ...(Option.isSome(apiKey) ? { apiKey: apiKey.value } : {}) });
|
|
731
|
+
}));
|
|
732
|
+
//# sourceMappingURL=Coordinator.js.map
|