@memberjunction/ai-realtime-client 6.2.0-edge.2 → 6.2.0-edge.4

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 (206) hide show
  1. package/README.md +156 -29
  2. package/dist/audio/audioMeter.d.ts +71 -0
  3. package/dist/audio/audioMeter.d.ts.map +1 -1
  4. package/dist/audio/audioMeter.js +83 -0
  5. package/dist/audio/audioMeter.js.map +1 -1
  6. package/dist/audio/mediaElementAudioRouter.d.ts +41 -0
  7. package/dist/audio/mediaElementAudioRouter.d.ts.map +1 -0
  8. package/dist/audio/mediaElementAudioRouter.js +82 -0
  9. package/dist/audio/mediaElementAudioRouter.js.map +1 -0
  10. package/dist/audio/micCapture.d.ts +12 -3
  11. package/dist/audio/micCapture.d.ts.map +1 -1
  12. package/dist/audio/micCapture.js +15 -1
  13. package/dist/audio/micCapture.js.map +1 -1
  14. package/dist/audio/pcmPlayback.d.ts +72 -4
  15. package/dist/audio/pcmPlayback.d.ts.map +1 -1
  16. package/dist/audio/pcmPlayback.js +101 -1
  17. package/dist/audio/pcmPlayback.js.map +1 -1
  18. package/dist/drivers/assemblyAIRealtimeClient.d.ts +2 -0
  19. package/dist/drivers/assemblyAIRealtimeClient.d.ts.map +1 -1
  20. package/dist/drivers/assemblyAIRealtimeClient.js +14 -0
  21. package/dist/drivers/assemblyAIRealtimeClient.js.map +1 -1
  22. package/dist/drivers/elevenLabsRealtimeClient.d.ts +2 -0
  23. package/dist/drivers/elevenLabsRealtimeClient.d.ts.map +1 -1
  24. package/dist/drivers/elevenLabsRealtimeClient.js +14 -0
  25. package/dist/drivers/elevenLabsRealtimeClient.js.map +1 -1
  26. package/dist/drivers/geminiAvatarOutput.d.ts +132 -0
  27. package/dist/drivers/geminiAvatarOutput.d.ts.map +1 -0
  28. package/dist/drivers/geminiAvatarOutput.js +310 -0
  29. package/dist/drivers/geminiAvatarOutput.js.map +1 -0
  30. package/dist/drivers/geminiEnterpriseRealtimeClient.d.ts +38 -0
  31. package/dist/drivers/geminiEnterpriseRealtimeClient.d.ts.map +1 -0
  32. package/dist/drivers/geminiEnterpriseRealtimeClient.js +107 -0
  33. package/dist/drivers/geminiEnterpriseRealtimeClient.js.map +1 -0
  34. package/dist/drivers/geminiRealtimeClient.d.ts +314 -48
  35. package/dist/drivers/geminiRealtimeClient.d.ts.map +1 -1
  36. package/dist/drivers/geminiRealtimeClient.js +759 -176
  37. package/dist/drivers/geminiRealtimeClient.js.map +1 -1
  38. package/dist/drivers/openAILiveClient.d.ts +5 -8
  39. package/dist/drivers/openAILiveClient.d.ts.map +1 -1
  40. package/dist/drivers/openAILiveClient.js +19 -32
  41. package/dist/drivers/openAILiveClient.js.map +1 -1
  42. package/dist/drivers/openAIRealtimeClient.d.ts +23 -26
  43. package/dist/drivers/openAIRealtimeClient.d.ts.map +1 -1
  44. package/dist/drivers/openAIRealtimeClient.js +42 -57
  45. package/dist/drivers/openAIRealtimeClient.js.map +1 -1
  46. package/dist/generic/baseRealtimeClient.d.ts +188 -30
  47. package/dist/generic/baseRealtimeClient.d.ts.map +1 -1
  48. package/dist/generic/baseRealtimeClient.js +250 -14
  49. package/dist/generic/baseRealtimeClient.js.map +1 -1
  50. package/dist/generic/openAIProtocolClient.d.ts +57 -1
  51. package/dist/generic/openAIProtocolClient.d.ts.map +1 -1
  52. package/dist/generic/openAIProtocolClient.js +89 -4
  53. package/dist/generic/openAIProtocolClient.js.map +1 -1
  54. package/dist/index.d.ts +2 -3
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +3 -3
  57. package/dist/index.js.map +1 -1
  58. package/dist/media/attachVideoSource.d.ts +23 -0
  59. package/dist/media/attachVideoSource.d.ts.map +1 -0
  60. package/dist/media/attachVideoSource.js +49 -0
  61. package/dist/media/attachVideoSource.js.map +1 -0
  62. package/dist/media/channelVideoSource.d.ts +110 -14
  63. package/dist/media/channelVideoSource.d.ts.map +1 -1
  64. package/dist/media/channelVideoSource.js +183 -18
  65. package/dist/media/channelVideoSource.js.map +1 -1
  66. package/dist/media/decoders/frameScheduler.d.ts +119 -0
  67. package/dist/media/decoders/frameScheduler.d.ts.map +1 -0
  68. package/dist/media/decoders/frameScheduler.js +238 -0
  69. package/dist/media/decoders/frameScheduler.js.map +1 -0
  70. package/dist/media/decoders/imageFrameDecoder.d.ts +58 -0
  71. package/dist/media/decoders/imageFrameDecoder.d.ts.map +1 -0
  72. package/dist/media/decoders/imageFrameDecoder.js +110 -0
  73. package/dist/media/decoders/imageFrameDecoder.js.map +1 -0
  74. package/dist/media/decoders/mseFmp4Decoder.d.ts +130 -0
  75. package/dist/media/decoders/mseFmp4Decoder.d.ts.map +1 -0
  76. package/dist/media/decoders/mseFmp4Decoder.js +402 -0
  77. package/dist/media/decoders/mseFmp4Decoder.js.map +1 -0
  78. package/dist/media/decoders/streamCanvas.d.ts +46 -0
  79. package/dist/media/decoders/streamCanvas.d.ts.map +1 -0
  80. package/dist/media/decoders/streamCanvas.js +163 -0
  81. package/dist/media/decoders/streamCanvas.js.map +1 -0
  82. package/dist/media/decoders/videoMimeTypes.d.ts +29 -0
  83. package/dist/media/decoders/videoMimeTypes.d.ts.map +1 -0
  84. package/dist/media/decoders/videoMimeTypes.js +50 -0
  85. package/dist/media/decoders/videoMimeTypes.js.map +1 -0
  86. package/dist/media/decoders/webCodecsChunkDecoder.d.ts +99 -0
  87. package/dist/media/decoders/webCodecsChunkDecoder.d.ts.map +1 -0
  88. package/dist/media/decoders/webCodecsChunkDecoder.js +265 -0
  89. package/dist/media/decoders/webCodecsChunkDecoder.js.map +1 -0
  90. package/dist/media/displayCapture.d.ts +128 -0
  91. package/dist/media/displayCapture.d.ts.map +1 -0
  92. package/dist/media/displayCapture.js +245 -0
  93. package/dist/media/displayCapture.js.map +1 -0
  94. package/dist/media/frameCapture.d.ts +25 -8
  95. package/dist/media/frameCapture.d.ts.map +1 -1
  96. package/dist/media/frameCapture.js +42 -72
  97. package/dist/media/frameCapture.js.map +1 -1
  98. package/dist/media/frameSampler.d.ts +94 -0
  99. package/dist/media/frameSampler.d.ts.map +1 -0
  100. package/dist/media/frameSampler.js +134 -0
  101. package/dist/media/frameSampler.js.map +1 -0
  102. package/dist/media/index.d.ts +32 -0
  103. package/dist/media/index.d.ts.map +1 -0
  104. package/dist/media/index.js +32 -0
  105. package/dist/media/index.js.map +1 -0
  106. package/dist/media/localMediaController.d.ts +116 -0
  107. package/dist/media/localMediaController.d.ts.map +1 -0
  108. package/dist/media/localMediaController.js +281 -0
  109. package/dist/media/localMediaController.js.map +1 -0
  110. package/dist/media/mediaLayout.d.ts +93 -0
  111. package/dist/media/mediaLayout.d.ts.map +1 -0
  112. package/dist/media/mediaLayout.js +187 -0
  113. package/dist/media/mediaLayout.js.map +1 -0
  114. package/dist/media/mediaPreview.d.ts +94 -0
  115. package/dist/media/mediaPreview.d.ts.map +1 -0
  116. package/dist/media/mediaPreview.js +154 -0
  117. package/dist/media/mediaPreview.js.map +1 -0
  118. package/dist/media/mediaStage.d.ts +99 -0
  119. package/dist/media/mediaStage.d.ts.map +1 -0
  120. package/dist/media/mediaStage.js +171 -0
  121. package/dist/media/mediaStage.js.map +1 -0
  122. package/dist/media/model.d.ts +154 -0
  123. package/dist/media/model.d.ts.map +1 -0
  124. package/dist/media/model.js +9 -0
  125. package/dist/media/model.js.map +1 -0
  126. package/dist/media/playbackClock.d.ts +20 -0
  127. package/dist/media/playbackClock.d.ts.map +1 -0
  128. package/dist/media/playbackClock.js +13 -0
  129. package/dist/media/playbackClock.js.map +1 -0
  130. package/dist/media/videoFrameDecoder.d.ts +86 -0
  131. package/dist/media/videoFrameDecoder.d.ts.map +1 -0
  132. package/dist/media/videoFrameDecoder.js +2 -0
  133. package/dist/media/videoFrameDecoder.js.map +1 -0
  134. package/dist/media/videoFrameDecoderRegistry.d.ts +40 -0
  135. package/dist/media/videoFrameDecoderRegistry.d.ts.map +1 -0
  136. package/dist/media/videoFrameDecoderRegistry.js +69 -0
  137. package/dist/media/videoFrameDecoderRegistry.js.map +1 -0
  138. package/dist/media/videoPacing.d.ts +32 -0
  139. package/dist/media/videoPacing.d.ts.map +1 -0
  140. package/dist/media/videoPacing.js +37 -0
  141. package/dist/media/videoPacing.js.map +1 -0
  142. package/dist/media/videoPlayout.d.ts +168 -0
  143. package/dist/media/videoPlayout.d.ts.map +1 -0
  144. package/dist/media/videoPlayout.js +217 -0
  145. package/dist/media/videoPlayout.js.map +1 -0
  146. package/dist/media/videoSourceArbiter.d.ts +250 -0
  147. package/dist/media/videoSourceArbiter.d.ts.map +1 -0
  148. package/dist/media/videoSourceArbiter.js +423 -0
  149. package/dist/media/videoSourceArbiter.js.map +1 -0
  150. package/dist/testing/conformanceAssertions.d.ts +25 -0
  151. package/dist/testing/conformanceAssertions.d.ts.map +1 -0
  152. package/dist/testing/conformanceAssertions.js +64 -0
  153. package/dist/testing/conformanceAssertions.js.map +1 -0
  154. package/dist/testing/conformanceFixtures.d.ts +76 -0
  155. package/dist/testing/conformanceFixtures.d.ts.map +1 -0
  156. package/dist/testing/conformanceFixtures.js +169 -0
  157. package/dist/testing/conformanceFixtures.js.map +1 -0
  158. package/dist/testing/conformanceGates.d.ts +32 -0
  159. package/dist/testing/conformanceGates.d.ts.map +1 -0
  160. package/dist/testing/conformanceGates.js +35 -0
  161. package/dist/testing/conformanceGates.js.map +1 -0
  162. package/dist/testing/conformanceMicrophone.d.ts +10 -0
  163. package/dist/testing/conformanceMicrophone.d.ts.map +1 -0
  164. package/dist/testing/conformanceMicrophone.js +71 -0
  165. package/dist/testing/conformanceMicrophone.js.map +1 -0
  166. package/dist/testing/conformanceTimeline.d.ts +128 -0
  167. package/dist/testing/conformanceTimeline.d.ts.map +1 -0
  168. package/dist/testing/conformanceTimeline.js +32 -0
  169. package/dist/testing/conformanceTimeline.js.map +1 -0
  170. package/dist/testing/conformanceVideoStream.d.ts +29 -0
  171. package/dist/testing/conformanceVideoStream.d.ts.map +1 -0
  172. package/dist/testing/conformanceVideoStream.js +39 -0
  173. package/dist/testing/conformanceVideoStream.js.map +1 -0
  174. package/dist/testing/index.d.ts +29 -0
  175. package/dist/testing/index.d.ts.map +1 -0
  176. package/dist/testing/index.js +28 -0
  177. package/dist/testing/index.js.map +1 -0
  178. package/dist/testing/realtimeVideoConformance.d.ts +23 -0
  179. package/dist/testing/realtimeVideoConformance.d.ts.map +1 -0
  180. package/dist/testing/realtimeVideoConformance.js +139 -0
  181. package/dist/testing/realtimeVideoConformance.js.map +1 -0
  182. package/dist/testing/realtimeVideoConformanceChecks.d.ts +4 -0
  183. package/dist/testing/realtimeVideoConformanceChecks.d.ts.map +1 -0
  184. package/dist/testing/realtimeVideoConformanceChecks.js +164 -0
  185. package/dist/testing/realtimeVideoConformanceChecks.js.map +1 -0
  186. package/dist/testing/realtimeVideoConformanceFrameChecks.d.ts +4 -0
  187. package/dist/testing/realtimeVideoConformanceFrameChecks.d.ts.map +1 -0
  188. package/dist/testing/realtimeVideoConformanceFrameChecks.js +63 -0
  189. package/dist/testing/realtimeVideoConformanceFrameChecks.js.map +1 -0
  190. package/dist/testing/realtimeVideoConformanceSession.d.ts +108 -0
  191. package/dist/testing/realtimeVideoConformanceSession.d.ts.map +1 -0
  192. package/dist/testing/realtimeVideoConformanceSession.js +270 -0
  193. package/dist/testing/realtimeVideoConformanceSession.js.map +1 -0
  194. package/dist/testing/realtimeVideoConformanceTurnChecks.d.ts +4 -0
  195. package/dist/testing/realtimeVideoConformanceTurnChecks.d.ts.map +1 -0
  196. package/dist/testing/realtimeVideoConformanceTurnChecks.js +248 -0
  197. package/dist/testing/realtimeVideoConformanceTurnChecks.js.map +1 -0
  198. package/dist/testing/realtimeVideoConformanceTypes.d.ts +155 -0
  199. package/dist/testing/realtimeVideoConformanceTypes.d.ts.map +1 -0
  200. package/dist/testing/realtimeVideoConformanceTypes.js +2 -0
  201. package/dist/testing/realtimeVideoConformanceTypes.js.map +1 -0
  202. package/dist/testing/recordingPlayout.d.ts +84 -0
  203. package/dist/testing/recordingPlayout.d.ts.map +1 -0
  204. package/dist/testing/recordingPlayout.js +120 -0
  205. package/dist/testing/recordingPlayout.js.map +1 -0
  206. package/package.json +29 -4
@@ -0,0 +1,310 @@
1
+ /**
2
+ * @fileoverview The avatar half of a Gemini Live browser session: where each model part goes once the server has granted
3
+ * an avatar and the host shows it, and what each turn boundary does to the avatar's video and voice.
4
+ *
5
+ * - **Parts.** A part that opens with an MP4 box (`ftyp`, `moov`, `moof`, `styp`) goes to the video player whatever MIME
6
+ * type it names, and so does a `video/*` part, each as a `RealtimeVideoFrame` (Core's `Fmp4PieceToVideoFrame`: an init
7
+ * segment or a fragment, with its time and key-frame flag). A `video/*` part that is neither labelled `video/mp4` nor
8
+ * opens with an MP4 box is dropped and reported once per type. A PCM part follows the voice rule below; a part with no
9
+ * MIME type that is not MP4 is PCM. Any other part is dropped and reported once per type: unknown data never plays.
10
+ * - **Who carries the voice.** The grant says whether the avatar's MP4 carries the voice (`audioMuxed`); the session's
11
+ * first readable init segment decides: an audio track means it does, none means the video plays muted and the voice
12
+ * comes as PCM. A disagreement with the grant is logged once.
13
+ * - **The voice rule.** When the video carries the voice, a PCM part plays only while the turn has no video yet; the
14
+ * turn's first video part flushes the PCM the turn queued, and later PCM in that turn is dropped (reported once), so
15
+ * the voice never plays twice. Otherwise PCM always plays, not lip-synced to the video; one line per turn logs how far
16
+ * apart the two started.
17
+ * - **Turns.** `generationComplete` ends the turn's video, so it plays to its true end and holds the last frame;
18
+ * `turnComplete` is the fallback. `interrupted` stops the video at once and drops the turn's late parts until its
19
+ * `turnComplete`. A resume lets a turn the drop cut off play out what arrived, without a flush.
20
+ * - **Answer or idle.** Vertex AI streams the avatar's video between answers too. The model's answer is under way from
21
+ * its first spoken words (the client reports its output transcription with {@link GeminiAvatarOutput.AnswerStarted})
22
+ * or a PCM voice part that plays, until its `generationComplete`, the turn's `turnComplete`, a barge-in or cancel, a
23
+ * tool call, or a resume. Video inside the answer is the answer's; video outside it is idle: it plays (the avatar keeps
24
+ * its face) but is not the agent speaking. {@link GeminiAvatarOutput.Accept} says which, so the client marks the agent
25
+ * speaking and busy for the answer only, and {@link GeminiAvatarOutput.IsPlaying} counts the avatar only while some of
26
+ * the answer's media is still ahead of the player's playhead.
27
+ * - **Usage.** The seconds of video the model generated count from the fragments' durations (Core's reader, timed by
28
+ * the latest init segment): video that arrives inside a model turn, from its first part until `generationComplete`;
29
+ * an interrupted turn counts what arrived before `interrupted`. Video after `generationComplete` (idle frames while
30
+ * the avatar listens) does not count. A flush never subtracts: generated video is billed whether or not it played.
31
+ * The count does not follow the answer above: all video from the session's start, a `turnComplete` or a resume until
32
+ * the next `generationComplete` counts, idle or not, while how Google bills avatar video is open (#5312).
33
+ *
34
+ * @module @memberjunction/ai-realtime-client
35
+ */
36
+ import { Fmp4PieceToVideoFrame, Fmp4VideoSeconds, IsPcmAudioMimeType, ReadFmp4Init, SniffFmp4Piece } from '@memberjunction/ai';
37
+ function newTurn() {
38
+ return { HasVideo: false, HasVoice: false, VideoEnded: false, Dropping: false, GenerationComplete: false, VoiceStartedAt: null, VideoStartedAt: null };
39
+ }
40
+ /**
41
+ * Routes a Gemini Live avatar session's model parts to the avatar's video player and the session's PCM playback, applies
42
+ * each turn boundary to both, and tells the answer's media from the idle video between answers. The Gemini client
43
+ * creates one at connect, only when the server granted an avatar and the host established the outbound video track.
44
+ */
45
+ export class GeminiAvatarOutput {
46
+ /**
47
+ * @param playout The avatar's video player, created with `CarriesVoice` from the grant.
48
+ * @param voice The session's PCM playback.
49
+ * @param grant What the server minted.
50
+ */
51
+ constructor(playout, voice, grant) {
52
+ this.playout = playout;
53
+ this.voice = voice;
54
+ this.grant = grant;
55
+ this.reported = new Set();
56
+ this.turn = newTurn();
57
+ this.voiceDecided = false;
58
+ /** The latest readable init segment: its video track's timescale times the fragments. */
59
+ this.init = null;
60
+ /** Seconds of video generated and not yet taken by {@link TakeVideoSeconds}. */
61
+ this.pendingVideoSeconds = 0;
62
+ /** Whether the model's answer is under way (see the module's "Answer or idle"): video outside it is idle. */
63
+ this.answering = false;
64
+ /** Frames handed to the player so far. */
65
+ this.framesHanded = 0;
66
+ /** {@link framesHanded} just after the latest frame of an answer: the answers' media ends there. 0 until one. */
67
+ this.answerEndsAtFrame = 0;
68
+ this.videoCarriesVoice = grant.AudioMuxed;
69
+ }
70
+ /** The avatar's video, for the host. */
71
+ get Source() {
72
+ return this.playout.Source;
73
+ }
74
+ /**
75
+ * Whether the avatar is audibly answering: its video plays with media buffered ahead of the playhead, and the playhead
76
+ * has not yet passed the last frame of an answer (the player's {@link IAvatarVideoPlayout.FramesAhead}). The idle
77
+ * video that follows an answer plays on, but does not count.
78
+ */
79
+ get IsPlaying() {
80
+ if (this.answerEndsAtFrame === 0 || !this.playout.IsPlaying) {
81
+ return false;
82
+ }
83
+ const played = this.framesHanded - this.playout.FramesAhead;
84
+ return played < this.answerEndsAtFrame;
85
+ }
86
+ /**
87
+ * Plays one model part, or drops it, and says which: the answer's video or voice (the caller then counts the turn's
88
+ * generation as started), idle video (it plays, but no answer is under way, so the agent is not speaking), or dropped.
89
+ *
90
+ * @param mimeType The part's `inlineData.mimeType`, when it names one.
91
+ * @param data The part's bytes.
92
+ */
93
+ Accept(mimeType, data) {
94
+ if (this.turn.Dropping) {
95
+ return 'dropped';
96
+ }
97
+ const kind = GeminiAvatarOutput.kindOf(mimeType, data);
98
+ if (kind === 'video') {
99
+ return this.acceptVideo(mimeType, data);
100
+ }
101
+ if (kind === 'voice') {
102
+ return this.acceptVoice(data);
103
+ }
104
+ this.reportOnce(`type:${mimeType}`, `[GeminiRealtimeClient] Dropped model output of type ${mimeType}: an avatar session plays its video and PCM audio only.`);
105
+ return 'dropped';
106
+ }
107
+ /**
108
+ * The model's answer is under way: its output transcription has words. The video that follows is the answer's until
109
+ * its generation completes, the turn completes, a barge-in or cancel stops it, or the model calls a tool.
110
+ */
111
+ AnswerStarted() {
112
+ this.answering = true;
113
+ }
114
+ /** The model called a tool: its answer stops there, and the video until it speaks again is idle. */
115
+ ToolCalled() {
116
+ this.answering = false;
117
+ }
118
+ /**
119
+ * Takes the seconds of avatar video generated since the last call, for the session's usage, and starts counting
120
+ * from 0 again: each second is taken once.
121
+ */
122
+ TakeVideoSeconds() {
123
+ const seconds = this.pendingVideoSeconds;
124
+ this.pendingVideoSeconds = 0;
125
+ return seconds;
126
+ }
127
+ /**
128
+ * `generationComplete`: the turn's media is all in, so its video plays to the true end and holds the last frame, and
129
+ * the answer is over. Video that still arrives in this turn is idle, and not counted as generated.
130
+ */
131
+ GenerationComplete() {
132
+ this.turn.GenerationComplete = true;
133
+ this.answering = false;
134
+ this.endTurnVideo();
135
+ }
136
+ /**
137
+ * `turnComplete`: ends the turn's video if `generationComplete` didn't, ends the answer and a barge-in's drop window,
138
+ * starts a new turn.
139
+ */
140
+ TurnComplete() {
141
+ this.endTurnVideo();
142
+ this.answering = false;
143
+ this.turn = newTurn();
144
+ }
145
+ /**
146
+ * `interrupted`: the user cut the turn off. Stops its video now (the last frame stays), ends the answer, and drops the
147
+ * turn's late parts until its `turnComplete`. The caller flushes the PCM.
148
+ */
149
+ Interrupted() {
150
+ this.playout.Flush();
151
+ this.answering = false;
152
+ this.turn.VideoEnded = true;
153
+ this.turn.Dropping = true;
154
+ }
155
+ /**
156
+ * The client cancelled the response (`CancelActiveResponse`): stops the video now and ends the answer; what still
157
+ * arrives of it is idle until the model speaks again. The caller flushes the PCM.
158
+ */
159
+ Cancel() {
160
+ this.playout.Flush();
161
+ this.answering = false;
162
+ }
163
+ /**
164
+ * The session resumed on a new connection, where a turn the drop cut off never completes: its video plays out what
165
+ * arrived and holds the last frame (no flush), the answer ends, and a new turn starts.
166
+ */
167
+ Resumed() {
168
+ this.endTurnVideo();
169
+ this.answering = false;
170
+ this.turn = newTurn();
171
+ }
172
+ /** Stops the video and releases its element. */
173
+ Dispose() {
174
+ this.playout.Dispose();
175
+ }
176
+ /**
177
+ * How a part plays. Its bytes decide first: a part that opens with an MP4 box is the avatar's whatever type it names,
178
+ * since Google does not document the type of avatar parts. Then its type: none means PCM; a `video/*` type is the
179
+ * avatar's (a `video/mp4` piece may start inside a box, which Media Source still plays; any other video type that
180
+ * isn't MP4 is then dropped); a PCM type is the voice; anything else never plays, as video or as voice.
181
+ */
182
+ static kindOf(mimeType, data) {
183
+ if (SniffFmp4Piece(data)) {
184
+ return 'video';
185
+ }
186
+ const type = mimeType?.trim() ?? '';
187
+ if (type.length === 0) {
188
+ return 'voice';
189
+ }
190
+ if (/^video\//i.test(type)) {
191
+ return 'video';
192
+ }
193
+ return IsPcmAudioMimeType(type) ? 'voice' : 'other';
194
+ }
195
+ /**
196
+ * Hands a video part to the player as a frame, the answer's while one is under way and idle otherwise; a part that
197
+ * isn't MP4 is dropped, reported once per type.
198
+ */
199
+ acceptVideo(mimeType, piece) {
200
+ const frame = Fmp4PieceToVideoFrame(piece, mimeType, this.init);
201
+ if (!frame) {
202
+ const message = `[GeminiRealtimeClient] Dropped model output of type ${mimeType}: it is not fragmented MP4, the only video an avatar plays.`;
203
+ this.reportOnce(`type:${mimeType}`, message);
204
+ return 'dropped';
205
+ }
206
+ if (frame.Piece === 'init') {
207
+ this.takeInit(piece);
208
+ }
209
+ if (!this.turn.HasVideo) {
210
+ this.startTurnVideo();
211
+ }
212
+ // More media came, so the turn's video is not over even if generationComplete said so.
213
+ this.turn.VideoEnded = false;
214
+ this.playout.Append(frame);
215
+ this.framesHanded++;
216
+ if (this.answering) {
217
+ this.answerEndsAtFrame = this.framesHanded;
218
+ }
219
+ this.countGeneratedVideo(piece);
220
+ return this.answering ? 'answer' : 'idle';
221
+ }
222
+ /** Keeps an init segment's tracks to time the fragments by; the session's first readable one also decides the voice. */
223
+ takeInit(piece) {
224
+ const init = ReadFmp4Init(piece);
225
+ if (!init) {
226
+ return;
227
+ }
228
+ this.init = init;
229
+ if (!this.voiceDecided) {
230
+ this.followInit(init);
231
+ }
232
+ }
233
+ /** Adds a video piece's seconds to the generated video, while its turn's generation has not completed. */
234
+ countGeneratedVideo(piece) {
235
+ if (this.turn.GenerationComplete) {
236
+ return;
237
+ }
238
+ const seconds = this.init ? Fmp4VideoSeconds(piece, this.init) : null;
239
+ if (seconds === null) {
240
+ this.reportOnce('uncounted-video', "[GeminiRealtimeClient] Could not read the avatar video's duration; that video is not counted in usage.");
241
+ return;
242
+ }
243
+ this.pendingVideoSeconds += seconds;
244
+ }
245
+ /** The turn's first video part: when the video carries the voice, PCM the turn queued would double it, so it stops. */
246
+ startTurnVideo() {
247
+ this.turn.HasVideo = true;
248
+ this.turn.VideoStartedAt = Date.now();
249
+ if (this.videoCarriesVoice && this.turn.HasVoice) {
250
+ this.voice.Flush();
251
+ }
252
+ this.logSeparateVoiceOffset();
253
+ }
254
+ /** Queues a PCM part as the voice, unless the turn's video carries it. Voice that plays means the answer is under way. */
255
+ acceptVoice(pcm) {
256
+ if (this.videoCarriesVoice && this.turn.HasVideo) {
257
+ this.reportOnce('pcm-with-video', '[GeminiRealtimeClient] Dropped PCM audio in a turn whose avatar video carries the voice.');
258
+ return 'dropped';
259
+ }
260
+ this.voice.Enqueue(pcm);
261
+ this.answering = true;
262
+ if (!this.turn.HasVoice) {
263
+ this.turn.HasVoice = true;
264
+ this.turn.VoiceStartedAt = Date.now();
265
+ this.logSeparateVoiceOffset();
266
+ }
267
+ return 'answer';
268
+ }
269
+ /**
270
+ * Follows the session's first readable init segment: an audio track means the video carries the voice; none means the
271
+ * video plays muted and the voice comes as PCM. Logged once when that disagrees with the grant.
272
+ */
273
+ followInit(init) {
274
+ this.voiceDecided = true;
275
+ const hasAudio = init.Tracks.some((track) => track.Handler === 'soun');
276
+ if (hasAudio === this.videoCarriesVoice) {
277
+ return;
278
+ }
279
+ this.videoCarriesVoice = hasAudio;
280
+ this.playout.CarriesVoice = hasAudio;
281
+ const following = hasAudio ? 'its audio is the voice' : 'it plays muted and the voice plays as PCM';
282
+ console.warn(`[GeminiRealtimeClient] The avatar's video ${hasAudio ? 'has an' : 'has no'} audio track, but the session config said ` +
283
+ `audioMuxed: ${this.grant.AudioMuxed}. Following the video: ${following}.`);
284
+ }
285
+ /** Separate voice: one line per turn with how far apart the voice and the video started (they are not lip-synced). */
286
+ logSeparateVoiceOffset() {
287
+ const voiceAt = this.turn.VoiceStartedAt;
288
+ const videoAt = this.turn.VideoStartedAt;
289
+ if (this.videoCarriesVoice || voiceAt === null || videoAt === null) {
290
+ return;
291
+ }
292
+ const offset = videoAt - voiceAt;
293
+ const order = offset >= 0 ? 'after' : 'before';
294
+ console.info(`[GeminiRealtimeClient] Avatar turn: the video started ${Math.abs(offset)} ms ${order} the voice (separate audio, not lip-synced).`);
295
+ }
296
+ endTurnVideo() {
297
+ if (this.turn.HasVideo && !this.turn.VideoEnded) {
298
+ this.turn.VideoEnded = true;
299
+ this.playout.EndOfTurn();
300
+ }
301
+ }
302
+ reportOnce(key, message) {
303
+ if (this.reported.has(key)) {
304
+ return;
305
+ }
306
+ this.reported.add(key);
307
+ console.warn(message);
308
+ }
309
+ }
310
+ //# sourceMappingURL=geminiAvatarOutput.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geminiAvatarOutput.js","sourceRoot":"","sources":["../../src/drivers/geminiAvatarOutput.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import { GeminiRealtimeClient, type GeminiClientConnectArgs, type GeminiLiveClientSession } from './geminiRealtimeClient.js';
2
+ /**
3
+ * Gemini Live on **Gemini Enterprise**, in the browser: a session through MJAPI's realtime relay.
4
+ *
5
+ * Registered under `'gemini-enterprise'`, the `Provider` the server's `GeminiEnterpriseRealtime` driver stamps. Gemini
6
+ * Enterprise has no browser credential, so the mint makes a relay session: `Transport` `'relay'`, the relay URL as
7
+ * `RelayUrl` (the ticket is in its path) and no `EphemeralToken`. MJAPI holds the Google credential, writes the setup and
8
+ * filters what the browser sends. The client opens only relay sessions. Everything else is {@link GeminiRealtimeClient}'s:
9
+ * audio, transcripts, tools, the avatar playout, resumption (which reuses the URL).
10
+ *
11
+ * The web SDK runs in Vertex mode against the relay: `vertexai: true` (the browser build ignores `enterprise`), a
12
+ * placeholder API key (the build requires one; it never leaves the page), and the relay URL as the base URL, to which the
13
+ * SDK appends `/ws/google.cloud.aiplatform.v1.LlmBidiService/BidiGenerateContent`.
14
+ */
15
+ export declare class GeminiEnterpriseRealtimeClient extends GeminiRealtimeClient {
16
+ /** Opts in to relay sessions: this client speaks Gemini Live to MJAPI's relay, which speaks it to Google. */
17
+ protected get SupportsRelayTransport(): boolean;
18
+ /**
19
+ * Opens one Live connection through the relay; a resume opens another with the same URL and the new handle.
20
+ *
21
+ * The config states the response modalities: the web SDK fills in AUDIO when it has none, and the relay reads an
22
+ * AUDIO-only setup as a request to drop the avatar. So a session that shows the avatar always asks for VIDEO.
23
+ *
24
+ * @throws When the session is not a relay session, or its relay URL is not a ws(s) URL (see {@link relayBaseUrl}).
25
+ */
26
+ protected connectLiveSession(args: GeminiClientConnectArgs): Promise<GeminiLiveClientSession>;
27
+ /**
28
+ * A copy of the config that states its response modalities: VIDEO while the avatar shows (the outbound video track is
29
+ * live), else what the config says (the minted modalities, or AUDIO when the host shows no avatar), else AUDIO.
30
+ */
31
+ private withStatedModalities;
32
+ }
33
+ /**
34
+ * Tree-shaking prevention: bundlers cannot see that {@link GeminiEnterpriseRealtimeClient} is instantiated through the
35
+ * ClassFactory, so a consumer calls this no-op to keep the `@RegisterClass` side effect alive.
36
+ */
37
+ export declare function LoadGeminiEnterpriseRealtimeClient(): void;
38
+ //# sourceMappingURL=geminiEnterpriseRealtimeClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geminiEnterpriseRealtimeClient.d.ts","sourceRoot":"","sources":["../../src/drivers/geminiEnterpriseRealtimeClient.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,oBAAoB,EAAE,KAAK,uBAAuB,EAAE,KAAK,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAc1H;;;;;;;;;;;;GAYG;AACH,qBACa,8BAA+B,SAAQ,oBAAoB;IACpE,6GAA6G;IAC7G,cAAuB,sBAAsB,IAAI,OAAO,CAEvD;IAED;;;;;;;OAOG;cACsB,kBAAkB,CAAC,IAAI,EAAE,uBAAuB,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAiB5G;;;OAGG;IACH,OAAO,CAAC,oBAAoB;CAM/B;AA0BD;;;GAGG;AACH,wBAAgB,kCAAkC,IAAI,IAAI,CAEzD"}
@@ -0,0 +1,107 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { RegisterClass } from '@memberjunction/global';
8
+ import { GoogleGenAI, Modality } from '@google/genai';
9
+ import { BaseRealtimeClient } from '../generic/baseRealtimeClient.js';
10
+ import { GeminiRealtimeClient } from './geminiRealtimeClient.js';
11
+ /**
12
+ * The API key the web SDK requires before it opens a socket. A placeholder: in Vertex mode the SDK puts a key only in a
13
+ * header, and a browser socket sends no headers, so it never leaves the page. The relay URL is the credential.
14
+ */
15
+ const RELAY_PLACEHOLDER_API_KEY = 'mjapi-relay';
16
+ /** The Vertex AI API version; MJAPI's relay opens the same one upstream. */
17
+ const VERTEX_LIVE_API_VERSION = 'v1';
18
+ /** A relay URL's scheme, and the rest of it. */
19
+ const RELAY_URL_PATTERN = /^(wss?|https?):\/\/(.+)$/i;
20
+ /**
21
+ * Gemini Live on **Gemini Enterprise**, in the browser: a session through MJAPI's realtime relay.
22
+ *
23
+ * Registered under `'gemini-enterprise'`, the `Provider` the server's `GeminiEnterpriseRealtime` driver stamps. Gemini
24
+ * Enterprise has no browser credential, so the mint makes a relay session: `Transport` `'relay'`, the relay URL as
25
+ * `RelayUrl` (the ticket is in its path) and no `EphemeralToken`. MJAPI holds the Google credential, writes the setup and
26
+ * filters what the browser sends. The client opens only relay sessions. Everything else is {@link GeminiRealtimeClient}'s:
27
+ * audio, transcripts, tools, the avatar playout, resumption (which reuses the URL).
28
+ *
29
+ * The web SDK runs in Vertex mode against the relay: `vertexai: true` (the browser build ignores `enterprise`), a
30
+ * placeholder API key (the build requires one; it never leaves the page), and the relay URL as the base URL, to which the
31
+ * SDK appends `/ws/google.cloud.aiplatform.v1.LlmBidiService/BidiGenerateContent`.
32
+ */
33
+ let GeminiEnterpriseRealtimeClient = class GeminiEnterpriseRealtimeClient extends GeminiRealtimeClient {
34
+ /** Opts in to relay sessions: this client speaks Gemini Live to MJAPI's relay, which speaks it to Google. */
35
+ get SupportsRelayTransport() {
36
+ return true;
37
+ }
38
+ /**
39
+ * Opens one Live connection through the relay; a resume opens another with the same URL and the new handle.
40
+ *
41
+ * The config states the response modalities: the web SDK fills in AUDIO when it has none, and the relay reads an
42
+ * AUDIO-only setup as a request to drop the avatar. So a session that shows the avatar always asks for VIDEO.
43
+ *
44
+ * @throws When the session is not a relay session, or its relay URL is not a ws(s) URL (see {@link relayBaseUrl}).
45
+ */
46
+ async connectLiveSession(args) {
47
+ const ai = new GoogleGenAI({
48
+ vertexai: true,
49
+ apiKey: RELAY_PLACEHOLDER_API_KEY,
50
+ httpOptions: { baseUrl: relayBaseUrl(args), apiVersion: VERTEX_LIVE_API_VERSION },
51
+ });
52
+ return ai.live.connect({
53
+ model: args.Model,
54
+ config: this.withStatedModalities(args.Config),
55
+ callbacks: {
56
+ onmessage: args.OnMessage,
57
+ onerror: args.OnError,
58
+ onclose: args.OnClose,
59
+ },
60
+ });
61
+ }
62
+ /**
63
+ * A copy of the config that states its response modalities: VIDEO while the avatar shows (the outbound video track is
64
+ * live), else what the config says (the minted modalities, or AUDIO when the host shows no avatar), else AUDIO.
65
+ */
66
+ withStatedModalities(config) {
67
+ if (this.IsTrackEstablished('video', 'outbound')) {
68
+ return { ...config, responseModalities: [Modality.VIDEO] };
69
+ }
70
+ return { ...config, responseModalities: config.responseModalities ?? [Modality.AUDIO] };
71
+ }
72
+ };
73
+ GeminiEnterpriseRealtimeClient = __decorate([
74
+ RegisterClass(BaseRealtimeClient, 'gemini-enterprise')
75
+ ], GeminiEnterpriseRealtimeClient);
76
+ export { GeminiEnterpriseRealtimeClient };
77
+ /**
78
+ * The relay URL as the web SDK's base URL. The SDK turns an `http:` base into `ws:` and any other into `wss:`, so a
79
+ * `ws://` relay URL (MJAPI without TLS, in development) goes in as `http://`, and a `wss://` one as `https://`.
80
+ *
81
+ * @param args The session's transport and relay URL, as minted.
82
+ * @throws When the session is not a relay session (a Gemini Enterprise session has no direct path), has no relay URL, or
83
+ * its relay URL is not a ws(s) or http(s) URL. No message quotes the URL or the token: the URL carries the ticket.
84
+ */
85
+ function relayBaseUrl(args) {
86
+ if (args.Transport !== 'relay') {
87
+ throw new Error("GeminiEnterpriseRealtimeClient: the session is not a relay session (Transport 'relay'); Gemini Enterprise runs only through MJAPI's relay.");
88
+ }
89
+ if (!args.RelayUrl) {
90
+ throw new Error('GeminiEnterpriseRealtimeClient: the relay session has no relay URL.');
91
+ }
92
+ const match = RELAY_URL_PATTERN.exec(args.RelayUrl);
93
+ if (!match) {
94
+ throw new Error('GeminiEnterpriseRealtimeClient: the relay URL is not a ws:// or wss:// URL.');
95
+ }
96
+ const scheme = match[1].toLowerCase();
97
+ const httpScheme = scheme === 'ws' || scheme === 'http' ? 'http' : 'https';
98
+ return `${httpScheme}://${match[2]}`;
99
+ }
100
+ /**
101
+ * Tree-shaking prevention: bundlers cannot see that {@link GeminiEnterpriseRealtimeClient} is instantiated through the
102
+ * ClassFactory, so a consumer calls this no-op to keep the `@RegisterClass` side effect alive.
103
+ */
104
+ export function LoadGeminiEnterpriseRealtimeClient() {
105
+ // intentional no-op: the static import of this module is the point
106
+ }
107
+ //# sourceMappingURL=geminiEnterpriseRealtimeClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geminiEnterpriseRealtimeClient.js","sourceRoot":"","sources":["../../src/drivers/geminiEnterpriseRealtimeClient.ts"],"names":[],"mappings":"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"}