@memberjunction/ai-realtime-client 6.1.1 → 6.1.2

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.
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @fileoverview SHARED CHANNEL INBOUND VIDEO BRIDGE (Phase F5).
3
+ *
4
+ * Single shared implementation used by both Whiteboard and Remote Browser channels
5
+ * to source inbound video frames into a realtime session when the model supports it.
6
+ *
7
+ * Enforces:
8
+ * 1. Model capability & track gate: if the session has not established an inbound video track,
9
+ * falls back to tool-only behaviour — NO frames sent and NO errors raised.
10
+ * 2. Cadence throttling: capped at 1 frame per second maximum (1 fps ceiling per Live API capabilities guide).
11
+ * 3. Clean teardown on Stop().
12
+ *
13
+ * @module @memberjunction/ai-realtime-client
14
+ * @author MemberJunction.com
15
+ */
16
+ import type { BaseRealtimeClient } from '../generic/baseRealtimeClient.js';
17
+ /**
18
+ * Provider implemented by a channel or surface to produce visual frames.
19
+ */
20
+ export interface IChannelFrameProvider {
21
+ /**
22
+ * Returns the latest visual scene as a base64-encoded JPEG image, or null when
23
+ * no frame is available or the surface hasn't changed.
24
+ */
25
+ GetLatestFrame(): Promise<string | null> | string | null;
26
+ }
27
+ /**
28
+ * Options for the inbound channel video bridge.
29
+ */
30
+ export interface ChannelInboundVideoBridgeOptions {
31
+ /** Target frame rate in fps (capped at 1 fps maximum). Defaults to 1. */
32
+ Rate?: number;
33
+ /** Output MIME type. Defaults to 'image/jpeg'. */
34
+ MimeType?: string;
35
+ }
36
+ /**
37
+ * Shared bridge connecting a visual channel surface to the session's inbound video track.
38
+ */
39
+ export declare class ChannelInboundVideoBridge {
40
+ private readonly clientOrGetter;
41
+ private readonly provider;
42
+ private readonly options;
43
+ private timer;
44
+ private active;
45
+ private frameErrorLogged;
46
+ constructor(clientOrGetter: BaseRealtimeClient | (() => BaseRealtimeClient | null | undefined) | null | undefined, provider: IChannelFrameProvider, options?: ChannelInboundVideoBridgeOptions);
47
+ private get client();
48
+ /**
49
+ * Starts the periodic frame pump if the session has an established inbound video track.
50
+ *
51
+ * @returns `true` if video streaming was started; `false` if the model does not support
52
+ * inbound video (or track was not established), falling back to tool-only behaviour.
53
+ */
54
+ Start(): boolean;
55
+ /**
56
+ * Directly sends a frame through the client when the inbound video track is established
57
+ * and the client implements `SendVideoFrame`.
58
+ *
59
+ * @returns `true` if dispatched to the client; `false` otherwise.
60
+ */
61
+ private sendFrameDirect;
62
+ /**
63
+ * Pushes an event-driven frame (e.g. screencast push from RemoteBrowser or stroke completion).
64
+ * Dispatches directly to the client's authoritative throttle gate.
65
+ *
66
+ * @param base64Jpeg The base64-encoded image frame.
67
+ * @returns `true` if the frame was sent; `false` if dropped (throttled, track unestablished, or driver lacks video support).
68
+ */
69
+ PushFrame(base64Jpeg: string): boolean;
70
+ /**
71
+ * Periodic poll tick that fetches the latest frame from the provider and pushes it.
72
+ * Uses `sendFrameDirect` so periodic polling paced by `setInterval` does not fight
73
+ * the `PushFrame` delta guard when provider latency varies between ticks (Reviewer Item 25).
74
+ */
75
+ private pumpFrame;
76
+ /**
77
+ * Stops the bridge and clears any active polling timer.
78
+ */
79
+ Stop(): void;
80
+ /** Whether the video bridge is currently actively pumping frames. */
81
+ get IsActive(): boolean;
82
+ }
83
+ //# sourceMappingURL=channelVideoSource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channelVideoSource.d.ts","sourceRoot":"","sources":["../../src/media/channelVideoSource.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAExE;;GAEG;AACH,MAAM,WAAW,qBAAqB;IAClC;;;OAGG;IACH,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC;CAC5D;AAED;;GAEG;AACH,MAAM,WAAW,gCAAgC;IAC7C,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,qBAAa,yBAAyB;IAM9B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAP5B,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,gBAAgB,CAAS;gBAGZ,cAAc,EAAE,kBAAkB,GAAG,CAAC,MAAM,kBAAkB,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,IAAI,GAAG,SAAS,EACrG,QAAQ,EAAE,qBAAqB,EAC/B,OAAO,GAAE,gCAAqC;IAGnE,OAAO,KAAK,MAAM,GAEjB;IAED;;;;;OAKG;IACI,KAAK,IAAI,OAAO;IAiBvB;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAUvB;;;;;;OAMG;IACI,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAI7C;;;;OAIG;YACW,SAAS;IAkBvB;;OAEG;IACI,IAAI,IAAI,IAAI;IASnB,qEAAqE;IACrE,IAAW,QAAQ,IAAI,OAAO,CAE7B;CACJ"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @fileoverview SHARED CHANNEL INBOUND VIDEO BRIDGE (Phase F5).
3
+ *
4
+ * Single shared implementation used by both Whiteboard and Remote Browser channels
5
+ * to source inbound video frames into a realtime session when the model supports it.
6
+ *
7
+ * Enforces:
8
+ * 1. Model capability & track gate: if the session has not established an inbound video track,
9
+ * falls back to tool-only behaviour — NO frames sent and NO errors raised.
10
+ * 2. Cadence throttling: capped at 1 frame per second maximum (1 fps ceiling per Live API capabilities guide).
11
+ * 3. Clean teardown on Stop().
12
+ *
13
+ * @module @memberjunction/ai-realtime-client
14
+ * @author MemberJunction.com
15
+ */
16
+ /**
17
+ * Shared bridge connecting a visual channel surface to the session's inbound video track.
18
+ */
19
+ export class ChannelInboundVideoBridge {
20
+ constructor(clientOrGetter, provider, options = {}) {
21
+ this.clientOrGetter = clientOrGetter;
22
+ this.provider = provider;
23
+ this.options = options;
24
+ this.timer = null;
25
+ this.active = false;
26
+ this.frameErrorLogged = false;
27
+ }
28
+ get client() {
29
+ return typeof this.clientOrGetter === 'function' ? this.clientOrGetter() : this.clientOrGetter;
30
+ }
31
+ /**
32
+ * Starts the periodic frame pump if the session has an established inbound video track.
33
+ *
34
+ * @returns `true` if video streaming was started; `false` if the model does not support
35
+ * inbound video (or track was not established), falling back to tool-only behaviour.
36
+ */
37
+ Start() {
38
+ if (this.active) {
39
+ return true;
40
+ }
41
+ if (!this.client || !this.client.IsTrackEstablished('video', 'inbound')) {
42
+ // Non-video model or unrequested video track: graceful fallback with no error
43
+ return false;
44
+ }
45
+ this.active = true;
46
+ const rate = Math.min(Math.max(this.options.Rate ?? 1, 0.1), 1);
47
+ const intervalMs = Math.floor(1000 / rate);
48
+ this.timer = setInterval(() => {
49
+ void this.pumpFrame();
50
+ }, intervalMs);
51
+ return true;
52
+ }
53
+ /**
54
+ * Directly sends a frame through the client when the inbound video track is established
55
+ * and the client implements `SendVideoFrame`.
56
+ *
57
+ * @returns `true` if dispatched to the client; `false` otherwise.
58
+ */
59
+ sendFrameDirect(base64Jpeg) {
60
+ if (!this.client || !this.client.IsTrackEstablished('video', 'inbound')) {
61
+ return false;
62
+ }
63
+ if (typeof this.client.SendVideoFrame !== 'function') {
64
+ return false;
65
+ }
66
+ return Boolean(this.client.SendVideoFrame(base64Jpeg, this.options.MimeType ?? 'image/jpeg'));
67
+ }
68
+ /**
69
+ * Pushes an event-driven frame (e.g. screencast push from RemoteBrowser or stroke completion).
70
+ * Dispatches directly to the client's authoritative throttle gate.
71
+ *
72
+ * @param base64Jpeg The base64-encoded image frame.
73
+ * @returns `true` if the frame was sent; `false` if dropped (throttled, track unestablished, or driver lacks video support).
74
+ */
75
+ PushFrame(base64Jpeg) {
76
+ return this.sendFrameDirect(base64Jpeg);
77
+ }
78
+ /**
79
+ * Periodic poll tick that fetches the latest frame from the provider and pushes it.
80
+ * Uses `sendFrameDirect` so periodic polling paced by `setInterval` does not fight
81
+ * the `PushFrame` delta guard when provider latency varies between ticks (Reviewer Item 25).
82
+ */
83
+ async pumpFrame() {
84
+ if (!this.active) {
85
+ return;
86
+ }
87
+ try {
88
+ const frame = await this.provider.GetLatestFrame();
89
+ this.frameErrorLogged = false;
90
+ if (frame && frame.length > 0 && this.active) {
91
+ this.sendFrameDirect(frame);
92
+ }
93
+ }
94
+ catch (err) {
95
+ if (!this.frameErrorLogged) {
96
+ this.frameErrorLogged = true;
97
+ console.error('[ChannelInboundVideoBridge] Error fetching frame from provider (suppressing repeats until next success):', err);
98
+ }
99
+ }
100
+ }
101
+ /**
102
+ * Stops the bridge and clears any active polling timer.
103
+ */
104
+ Stop() {
105
+ this.active = false;
106
+ this.frameErrorLogged = false;
107
+ if (this.timer) {
108
+ clearInterval(this.timer);
109
+ this.timer = null;
110
+ }
111
+ }
112
+ /** Whether the video bridge is currently actively pumping frames. */
113
+ get IsActive() {
114
+ return this.active;
115
+ }
116
+ }
117
+ //# sourceMappingURL=channelVideoSource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channelVideoSource.js","sourceRoot":"","sources":["../../src/media/channelVideoSource.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * @fileoverview FRAME CAPTURE for realtime video tracks — camera and screen capture pipelines.
3
+ *
4
+ * Patterned beside `micCapture.ts`:
5
+ * - `createCameraCapture`: prompts via `getUserMedia` (consent-gated via {@link RealtimeTrackDescriptor.RequiresConsent}).
6
+ * - `createScreenCapture`: prompts via `getDisplayMedia` (consent-gated via {@link RealtimeTrackDescriptor.RequiresConsent}).
7
+ * - `createStreamFrameCapture`: extracts periodic JPEG frames from an already-acquired `MediaStream`.
8
+ *
9
+ * Cadence is throttled to at most 1 frame per second (1 fps ceiling per Live API capabilities guide).
10
+ *
11
+ * @module @memberjunction/ai-realtime-client
12
+ * @author MemberJunction.com
13
+ */
14
+ import type { RealtimeTrackDescriptor } from '@memberjunction/ai';
15
+ /**
16
+ * Handle returned by frame capture factories: stops capture and releases video resources.
17
+ */
18
+ export interface IFrameCapture {
19
+ /** Stops frame capture, cancels intervals, and releases any acquired tracks/elements. */
20
+ Stop(): void;
21
+ /** The underlying MediaStream being captured (if any). */
22
+ readonly Stream?: MediaStream;
23
+ }
24
+ /**
25
+ * Configuration options for frame capture.
26
+ */
27
+ export interface FrameCaptureOptions {
28
+ /** Track descriptor governing this capture. */
29
+ Descriptor?: RealtimeTrackDescriptor;
30
+ /**
31
+ * Whether explicit user consent was granted for tracks requiring consent.
32
+ * If `Descriptor.RequiresConsent` is true and this is false/omitted, capture is refused.
33
+ */
34
+ ConsentGranted?: boolean;
35
+ /**
36
+ * Capture cadence in frames per second (fps).
37
+ * Capped at 1 fps maximum (Gemini Live ceiling). Defaults to `Descriptor?.Rate ?? 1`.
38
+ */
39
+ Rate?: number;
40
+ /** Output image format ('image/jpeg' or 'image/png'). Defaults to 'image/jpeg'. */
41
+ MimeType?: 'image/jpeg' | 'image/png';
42
+ /** Image compression quality (0..1) for JPEG. Defaults to 0.8. */
43
+ Quality?: number;
44
+ /** Invoked with each captured frame as base64-encoded image data. */
45
+ OnFrame: (frame: {
46
+ data: string;
47
+ mimeType: string;
48
+ }) => void;
49
+ }
50
+ /**
51
+ * Creates a frame capture pump over an already-acquired {@link MediaStream}.
52
+ *
53
+ * @param stream The media stream containing one or more video tracks.
54
+ * @param options Frame capture options (cadence, format, frame callback).
55
+ */
56
+ export declare function createStreamFrameCapture(stream: MediaStream, options: FrameCaptureOptions): IFrameCapture;
57
+ /**
58
+ * Requests user camera video capture via `navigator.mediaDevices.getUserMedia`.
59
+ * Gated on {@link FrameCaptureOptions.ConsentGranted} when consent is required.
60
+ *
61
+ * @param options Frame capture options.
62
+ * @param constraints Video track constraints.
63
+ */
64
+ export declare function createCameraCapture(options: FrameCaptureOptions, constraints?: MediaTrackConstraints): Promise<IFrameCapture>;
65
+ /**
66
+ * Requests screen share video capture via `navigator.mediaDevices.getDisplayMedia`.
67
+ * Gated on {@link FrameCaptureOptions.ConsentGranted} when consent is required.
68
+ *
69
+ * @param options Frame capture options.
70
+ * @param displayMediaOptions Display media options.
71
+ */
72
+ export declare function createScreenCapture(options: FrameCaptureOptions, displayMediaOptions?: DisplayMediaStreamOptions): Promise<IFrameCapture>;
73
+ //# sourceMappingURL=frameCapture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frameCapture.d.ts","sourceRoot":"","sources":["../../src/media/frameCapture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAElE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B,yFAAyF;IACzF,IAAI,IAAI,IAAI,CAAC;IACb,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAChC,+CAA+C;IAC/C,UAAU,CAAC,EAAE,uBAAuB,CAAC;IACrC;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,QAAQ,CAAC,EAAE,YAAY,GAAG,WAAW,CAAC;IACtC,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,OAAO,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAChE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACpC,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,mBAAmB,GAC7B,aAAa,CAyEf;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACrC,OAAO,EAAE,mBAAmB,EAC5B,WAAW,CAAC,EAAE,qBAAqB,GACpC,OAAO,CAAC,aAAa,CAAC,CAqBxB;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACrC,OAAO,EAAE,mBAAmB,EAC5B,mBAAmB,CAAC,EAAE,yBAAyB,GAChD,OAAO,CAAC,aAAa,CAAC,CAqBxB"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * @fileoverview FRAME CAPTURE for realtime video tracks — camera and screen capture pipelines.
3
+ *
4
+ * Patterned beside `micCapture.ts`:
5
+ * - `createCameraCapture`: prompts via `getUserMedia` (consent-gated via {@link RealtimeTrackDescriptor.RequiresConsent}).
6
+ * - `createScreenCapture`: prompts via `getDisplayMedia` (consent-gated via {@link RealtimeTrackDescriptor.RequiresConsent}).
7
+ * - `createStreamFrameCapture`: extracts periodic JPEG frames from an already-acquired `MediaStream`.
8
+ *
9
+ * Cadence is throttled to at most 1 frame per second (1 fps ceiling per Live API capabilities guide).
10
+ *
11
+ * @module @memberjunction/ai-realtime-client
12
+ * @author MemberJunction.com
13
+ */
14
+ /**
15
+ * Creates a frame capture pump over an already-acquired {@link MediaStream}.
16
+ *
17
+ * @param stream The media stream containing one or more video tracks.
18
+ * @param options Frame capture options (cadence, format, frame callback).
19
+ */
20
+ export function createStreamFrameCapture(stream, options) {
21
+ const rawRate = options.Rate ?? options.Descriptor?.Rate ?? 1;
22
+ // Cadence ceiling: max 1 fps per Gemini Live API specs; minimum 0.1 fps
23
+ const rate = Math.min(Math.max(rawRate, 0.1), 1);
24
+ const intervalMs = Math.floor(1000 / rate);
25
+ const mimeType = options.MimeType ?? 'image/jpeg';
26
+ const quality = options.Quality ?? 0.8;
27
+ let stopped = false;
28
+ let timer = null;
29
+ let videoEl = null;
30
+ let canvasEl = null;
31
+ if (typeof document !== 'undefined' && typeof document.createElement === 'function') {
32
+ try {
33
+ videoEl = document.createElement('video');
34
+ videoEl.muted = true;
35
+ videoEl.playsInline = true;
36
+ videoEl.srcObject = stream;
37
+ void videoEl.play().catch(() => {
38
+ // Autoplay may be restricted without user interaction; non-fatal for capture
39
+ });
40
+ canvasEl = document.createElement('canvas');
41
+ }
42
+ catch (err) {
43
+ console.error('[FrameCapture] Failed to initialize DOM elements for video capture:', err);
44
+ }
45
+ }
46
+ const captureTick = () => {
47
+ if (stopped) {
48
+ return;
49
+ }
50
+ if (videoEl && canvasEl && videoEl.videoWidth > 0 && videoEl.videoHeight > 0) {
51
+ canvasEl.width = videoEl.videoWidth;
52
+ canvasEl.height = videoEl.videoHeight;
53
+ const ctx = canvasEl.getContext('2d');
54
+ if (ctx) {
55
+ ctx.drawImage(videoEl, 0, 0);
56
+ const dataUrl = canvasEl.toDataURL(mimeType, quality);
57
+ const base64 = dataUrl.split(',')[1] ?? '';
58
+ if (base64.length > 0) {
59
+ options.OnFrame({ data: base64, mimeType });
60
+ }
61
+ }
62
+ }
63
+ };
64
+ if (videoEl && canvasEl) {
65
+ timer = setInterval(captureTick, intervalMs);
66
+ }
67
+ else {
68
+ console.warn('[FrameCapture] No video/canvas element available (no DOM, or element construction failed) — ' +
69
+ 'this capture will emit no frames.');
70
+ }
71
+ return {
72
+ Stop: () => {
73
+ stopped = true;
74
+ if (timer) {
75
+ clearInterval(timer);
76
+ timer = null;
77
+ }
78
+ if (videoEl) {
79
+ videoEl.srcObject = null;
80
+ videoEl = null;
81
+ }
82
+ canvasEl = null;
83
+ },
84
+ get Stream() {
85
+ return stream;
86
+ },
87
+ };
88
+ }
89
+ /**
90
+ * Requests user camera video capture via `navigator.mediaDevices.getUserMedia`.
91
+ * Gated on {@link FrameCaptureOptions.ConsentGranted} when consent is required.
92
+ *
93
+ * @param options Frame capture options.
94
+ * @param constraints Video track constraints.
95
+ */
96
+ export async function createCameraCapture(options, constraints) {
97
+ const requiresConsent = options.Descriptor?.RequiresConsent ?? true;
98
+ if (requiresConsent && !options.ConsentGranted) {
99
+ throw new Error('Explicit consent is required to establish camera video capture.');
100
+ }
101
+ if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia) {
102
+ throw new Error('Camera capture is unavailable: navigator.mediaDevices.getUserMedia not supported in this environment.');
103
+ }
104
+ const stream = await navigator.mediaDevices.getUserMedia({
105
+ video: constraints ?? { width: { ideal: 1280 }, height: { ideal: 720 }, frameRate: { max: 1 } },
106
+ });
107
+ const capture = createStreamFrameCapture(stream, options);
108
+ return {
109
+ Stop: () => {
110
+ capture.Stop();
111
+ stream.getTracks().forEach((track) => track.stop());
112
+ },
113
+ get Stream() {
114
+ return stream;
115
+ },
116
+ };
117
+ }
118
+ /**
119
+ * Requests screen share video capture via `navigator.mediaDevices.getDisplayMedia`.
120
+ * Gated on {@link FrameCaptureOptions.ConsentGranted} when consent is required.
121
+ *
122
+ * @param options Frame capture options.
123
+ * @param displayMediaOptions Display media options.
124
+ */
125
+ export async function createScreenCapture(options, displayMediaOptions) {
126
+ const requiresConsent = options.Descriptor?.RequiresConsent ?? true;
127
+ if (requiresConsent && !options.ConsentGranted) {
128
+ throw new Error('Explicit consent is required to establish screen video capture.');
129
+ }
130
+ if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getDisplayMedia) {
131
+ throw new Error('Screen capture is unavailable: navigator.mediaDevices.getDisplayMedia not supported in this environment.');
132
+ }
133
+ const stream = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions ?? { video: { frameRate: { max: 1 } } });
134
+ const capture = createStreamFrameCapture(stream, options);
135
+ return {
136
+ Stop: () => {
137
+ capture.Stop();
138
+ stream.getTracks().forEach((track) => track.stop());
139
+ },
140
+ get Stream() {
141
+ return stream;
142
+ },
143
+ };
144
+ }
145
+ //# sourceMappingURL=frameCapture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frameCapture.js","sourceRoot":"","sources":["../../src/media/frameCapture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAsCH;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CACpC,MAAmB,EACnB,OAA4B;IAE5B,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,UAAU,EAAE,IAAI,IAAI,CAAC,CAAC;IAC9D,wEAAwE;IACxE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACjD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IAC3C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,YAAY,CAAC;IAClD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,GAAG,CAAC;IAEvC,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,KAAK,GAA0C,IAAI,CAAC;IACxD,IAAI,OAAO,GAA4B,IAAI,CAAC;IAC5C,IAAI,QAAQ,GAA6B,IAAI,CAAC;IAE9C,IAAI,OAAO,QAAQ,KAAK,WAAW,IAAI,OAAO,QAAQ,CAAC,aAAa,KAAK,UAAU,EAAE,CAAC;QAClF,IAAI,CAAC;YACD,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;YAC1C,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;YACrB,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;YAC3B,OAAO,CAAC,SAAS,GAAG,MAAM,CAAC;YAC3B,KAAK,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;gBAC3B,6EAA6E;YACjF,CAAC,CAAC,CAAC;YACH,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAChD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,qEAAqE,EAAE,GAAG,CAAC,CAAC;QAC9F,CAAC;IACL,CAAC;IAED,MAAM,WAAW,GAAG,GAAG,EAAE;QACrB,IAAI,OAAO,EAAE,CAAC;YACV,OAAO;QACX,CAAC;QACD,IAAI,OAAO,IAAI,QAAQ,IAAI,OAAO,CAAC,UAAU,GAAG,CAAC,IAAI,OAAO,CAAC,WAAW,GAAG,CAAC,EAAE,CAAC;YAC3E,QAAQ,CAAC,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC;YACpC,QAAQ,CAAC,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;YACtC,MAAM,GAAG,GAAG,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YACtC,IAAI,GAAG,EAAE,CAAC;gBACN,GAAG,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC7B,MAAM,OAAO,GAAG,QAAQ,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBACtD,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;gBAC3C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACpB,OAAO,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;gBAChD,CAAC;YACL,CAAC;QACL,CAAC;IACL,CAAC,CAAC;IAEF,IAAI,OAAO,IAAI,QAAQ,EAAE,CAAC;QACtB,KAAK,GAAG,WAAW,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;IACjD,CAAC;SAAM,CAAC;QACJ,OAAO,CAAC,IAAI,CACR,8FAA8F;YAC9F,mCAAmC,CACtC,CAAC;IACN,CAAC;IAED,OAAO;QACH,IAAI,EAAE,GAAG,EAAE;YACP,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,KAAK,EAAE,CAAC;gBACR,aAAa,CAAC,KAAK,CAAC,CAAC;gBACrB,KAAK,GAAG,IAAI,CAAC;YACjB,CAAC;YACD,IAAI,OAAO,EAAE,CAAC;gBACV,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;gBACzB,OAAO,GAAG,IAAI,CAAC;YACnB,CAAC;YACD,QAAQ,GAAG,IAAI,CAAC;QACpB,CAAC;QACD,IAAI,MAAM;YACN,OAAO,MAAM,CAAC;QAClB,CAAC;KACJ,CAAC;AACN,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACrC,OAA4B,EAC5B,WAAmC;IAEnC,MAAM,eAAe,GAAG,OAAO,CAAC,UAAU,EAAE,eAAe,IAAI,IAAI,CAAC;IACpE,IAAI,eAAe,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;IACvF,CAAC;IACD,IAAI,OAAO,SAAS,KAAK,WAAW,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,YAAY,EAAE,CAAC;QAC5E,MAAM,IAAI,KAAK,CAAC,uGAAuG,CAAC,CAAC;IAC7H,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,YAAY,CAAC;QACrD,KAAK,EAAE,WAAW,IAAI,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE;KAClG,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,wBAAwB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1D,OAAO;QACH,IAAI,EAAE,GAAG,EAAE;YACP,OAAO,CAAC,IAAI,EAAE,CAAC;YACf,MAAM,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QACxD,CAAC;QACD,IAAI,MAAM;YACN,OAAO,MAAM,CAAC;QAClB,CAAC;KACJ,CAAC;AACN,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACrC,OAA4B,EAC5B,mBAA+C;IAE/C,MAAM,eAAe,GAAG,OAAO,CAAC,UAAU,EAAE,eAAe,IAAI,IAAI,CAAC;IACpE,IAAI,eAAe,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;IACvF,CAAC;IACD,IAAI,OAAO,SAAS,KAAK,WAAW,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,eAAe,EAAE,CAAC;QAC/E,MAAM,IAAI,KAAK,CAAC,0GAA0G,CAAC,CAAC;IAChI,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,eAAe,CACvD,mBAAmB,IAAI,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,CAC9D,CAAC;IACF,MAAM,OAAO,GAAG,wBAAwB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1D,OAAO;QACH,IAAI,EAAE,GAAG,EAAE;YACP,OAAO,CAAC,IAAI,EAAE,CAAC;YACf,MAAM,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QACxD,CAAC;QACD,IAAI,MAAM;YACN,OAAO,MAAM,CAAC;QAClB,CAAC;KACJ,CAAC;AACN,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@memberjunction/ai-realtime-client",
3
3
  "type": "module",
4
- "version": "6.1.1",
4
+ "version": "6.1.2",
5
5
  "description": "MemberJunction: Framework-agnostic browser client abstraction for provider-direct realtime (voice) sessions - BaseRealtimeClient + provider drivers",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -11,9 +11,9 @@
11
11
  "author": "MemberJunction.com",
12
12
  "license": "BUSL-1.1",
13
13
  "dependencies": {
14
- "@memberjunction/global": "6.1.1",
15
- "@memberjunction/ai": "6.1.1",
16
- "@google/genai": "^1.40.0"
14
+ "@memberjunction/global": "6.1.2",
15
+ "@memberjunction/ai": "6.1.2",
16
+ "@google/genai": "^2.8.0"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@types/node": "24.10.11",