realtime-avatar 0.14.0 → 0.15.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/README.md CHANGED
@@ -4,7 +4,7 @@ A live character your users can talk to — voice, or voice and video. She liste
4
4
  speaks, so you can interrupt her mid-sentence and she stops, the way a person stops.
5
5
 
6
6
  ```bash
7
- npm install --save-exact realtime-avatar@0.14.0
7
+ npm install --save-exact realtime-avatar@0.15.0
8
8
  ```
9
9
 
10
10
  ```ts
@@ -147,16 +147,58 @@ call. Policy — `instructions`, `maxSeconds`, `voice`, `video` — is decided i
147
147
  your server. A route that spreads the request body into `startCall` hands your caller your
148
148
  system prompt and your bill.
149
149
 
150
- ### Native connection facts
150
+ ### Optional connection details
151
151
 
152
- LiveKit owns media transport, connection state, and reconnection. Use its
153
- `useConnectionState()` and `useConnectionQualityIndicator({ participant })` hooks, or
154
- `RoomEvent` callbacks, to build your product's network UI. Their values already use
155
- `ConnectionState`, `ConnectionQuality`, and `Track.StreamState` from `livekit-client`.
156
- For current receiver measurements, `RemoteVideoTrack.getReceiverStats()` and
157
- `RemoteAudioTrack.getReceiverStats()` return LiveKit's exported `VideoReceiverStats` and
158
- `AudioReceiverStats` types.
159
- Keep these native types in process; there is no SDK-specific JSON mirror to maintain.
152
+ `AvatarCall` provides call status, actions and end reasons for your default UI. To show
153
+ connection diagnostics, opt in with `onConnectionDetailsChange`. It supplies data for
154
+ your own UI; enabling it adds no built-in network notice or controls.
155
+
156
+ ```tsx
157
+ import { useState } from "react";
158
+ import { AvatarCall, type AvatarCallProps, type AvatarConnectionDetails } from "realtime-avatar/react";
159
+
160
+ function CallWithDetails(props: Pick<AvatarCallProps, "client" | "avatarId">) {
161
+ const [details, setDetails] = useState<AvatarConnectionDetails | null>(null);
162
+
163
+ return (
164
+ <>
165
+ <AvatarCall {...props} onConnectionDetailsChange={setDetails} />
166
+ {details && <p>Connection: {details.connectionState}. Local quality: {details.localQuality}.</p>}
167
+ </>
168
+ );
169
+ }
170
+ ```
171
+
172
+ Once bound to a call, the callback receives an initial snapshot, then changed facts only. It receives `null`
173
+ when its room or session binding is retired, before a replacement binding's snapshot.
174
+ Clearing the session grant keeps details reset until a new grant arrives.
175
+ Keep this state scoped to one call and clear it on `null`, as the example does. This is
176
+ a current snapshot, not a lossless event history. Replacing the handler does not restart
177
+ the call, and errors in your handler do not interrupt it. Omitting the callback adds no
178
+ reporting listeners; the callback adds no timers, stats polling or uploads.
179
+
180
+ Each field preserves LiveKit's native type and meaning:
181
+
182
+ | Field | Source |
183
+ | --- | --- |
184
+ | `connectionState` | The bound room's `state`. |
185
+ | `localQuality` | The local participant's `connectionQuality`. |
186
+ | `audio.publisherQuality` | The selected audio publisher's `connectionQuality`. |
187
+ | `video.publisherQuality` | The selected video publisher's `connectionQuality`. |
188
+ | `audio.streamState`, `video.streamState` | The corresponding remote track's `streamState`, or `null` before the track exists. |
189
+
190
+ Audio and video publishers are selected independently and can differ. Neither field
191
+ substitutes the control agent or an arbitrary remote participant. `audio` or `video` is
192
+ `null` when no corresponding track reference is selected; LiveKit's `Unknown` quality
193
+ means a publisher exists but its quality is unknown. An active or subscribed track does
194
+ not prove that video has rendered or audio is audible. Continue using call status for
195
+ the call lifecycle.
196
+
197
+ Native and custom integrations pass the same optional callback to their existing
198
+ `SessionLifecycleRoomBridge` inside `RealtimeAvatarLiveKitRoom`. Both
199
+ `realtime-avatar/react` and `realtime-avatar/react-native` export `AvatarConnectionDetails`.
200
+ For deeper receiver measurements, use LiveKit's public `RemoteVideoTrack.getReceiverStats()`
201
+ and `RemoteAudioTrack.getReceiverStats()` methods and their native return types.
160
202
 
161
203
  Retain the app's session-to-room association (`room_name`, timestamps and participant
162
204
  identity), adding the server-observed room SID when exact room-lifetime lookup needs it.
@@ -1,4 +1,4 @@
1
- import { RealtimeAvatar, isQueued, RealtimeAvatarHttpError } from './chunk-UZQXZH2Z.js';
1
+ import { RealtimeAvatar, isQueued, RealtimeAvatarHttpError } from './chunk-PKKTFUA7.js';
2
2
 
3
3
  // ../proxy/src/config.ts
4
4
  var json = (body, status = 200) => new Response(JSON.stringify(body), {
@@ -236,7 +236,7 @@ function isTransient(cause) {
236
236
 
237
237
  // ../http-client/src/client.ts
238
238
  var DEFAULT_BASE_URL = "https://realtimeavatar.ai/api/v1";
239
- var SDK_VERSION = "0.14.0";
239
+ var SDK_VERSION = "0.15.0";
240
240
  var RealtimeAvatar = class {
241
241
  #apiKey;
242
242
  #baseUrl;
package/dist/express.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-B3AHTJ3J.js';
2
- import './chunk-UZQXZH2Z.js';
1
+ import { createProxyHandler } from './chunk-LZMJJX7C.js';
2
+ import './chunk-PKKTFUA7.js';
3
3
 
4
4
  // ../proxy/src/express.ts
5
5
  function realtimeAvatarExpress(config) {
package/dist/hono.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-B3AHTJ3J.js';
2
- import './chunk-UZQXZH2Z.js';
1
+ import { createProxyHandler } from './chunk-LZMJJX7C.js';
2
+ import './chunk-PKKTFUA7.js';
3
3
 
4
4
  // ../proxy/src/hono.ts
5
5
  function realtimeAvatarHono(config) {
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, verifyTranscript } from './chunk-UZQXZH2Z.js';
1
+ export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, verifyTranscript } from './chunk-PKKTFUA7.js';
package/dist/nextjs.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-B3AHTJ3J.js';
2
- import './chunk-UZQXZH2Z.js';
1
+ import { createProxyHandler } from './chunk-LZMJJX7C.js';
2
+ import './chunk-PKKTFUA7.js';
3
3
 
4
4
  // ../proxy/src/nextjs.ts
5
5
  function createRealtimeAvatarRoute(config) {
@@ -1,6 +1,6 @@
1
1
  import { TrackReferenceOrPlaceholder, useTranscriptions, useConnectionState, LiveKitRoomProps } from '@livekit/components-react';
2
2
  import { CSSProperties, ReactNode, ReactElement } from 'react';
3
- import { ConnectionQuality, DisconnectReason, SendTextOptions } from 'livekit-client';
3
+ import { ConnectionQuality, Room, Participant, Track, DisconnectReason, SendTextOptions } from 'livekit-client';
4
4
  import { z } from 'zod';
5
5
 
6
6
  /**
@@ -1291,8 +1291,24 @@ type RealtimeSessionRoomSinks = Partial<{
1291
1291
  audio: "flowing" | "silent";
1292
1292
  }) => void;
1293
1293
  }>;
1294
+ type PublisherConnectionDetails = Readonly<{
1295
+ publisherQuality: Participant["connectionQuality"];
1296
+ streamState: Track["streamState"] | null;
1297
+ }>;
1298
+ /** Current native connection facts. Track activity does not prove rendered or audible media. */
1299
+ type AvatarConnectionDetails = Readonly<{
1300
+ connectionState: Room["state"];
1301
+ localQuality: Participant["connectionQuality"];
1302
+ audio: PublisherConnectionDetails | null;
1303
+ video: PublisherConnectionDetails | null;
1304
+ }>;
1294
1305
  type SessionLifecycleRoomBridgeProps = {
1295
- lifecycle: Pick<SessionLifecycleApi, "onConnectionStateChange" | "setAgentPresent" | "registerLeaveRoom" | "markActivity"> & RealtimeSessionRoomSinks;
1306
+ /**
1307
+ * Opt in to an initial snapshot and changed facts. Null clears a retired binding.
1308
+ * Callback failures never affect the call; no stats polling or uploads are added.
1309
+ */
1310
+ onConnectionDetailsChange?: (details: AvatarConnectionDetails | null) => void;
1311
+ lifecycle: Pick<SessionLifecycleApi, "onConnectionStateChange" | "setAgentPresent" | "registerLeaveRoom" | "markActivity"> & Partial<Pick<SessionLifecycleApi, "grant">> & RealtimeSessionRoomSinks;
1296
1312
  };
1297
1313
  /**
1298
1314
  * Wire the in-room LiveKit facts into a {@link useSessionLifecycle} instance:
@@ -1311,7 +1327,7 @@ type SessionLifecycleRoomBridgeProps = {
1311
1327
  *
1312
1328
  * Renders nothing. Mount it once inside RealtimeAvatarLiveKitRoom.
1313
1329
  */
1314
- declare function SessionLifecycleRoomBridge({ lifecycle }: SessionLifecycleRoomBridgeProps): null;
1330
+ declare function SessionLifecycleRoomBridge({ lifecycle, onConnectionDetailsChange, }: SessionLifecycleRoomBridgeProps): null;
1315
1331
 
1316
1332
  /**
1317
1333
  * The terminal, LABELED end reason surfaced to the app via `onEnded`. This is the
@@ -1646,4 +1662,4 @@ interface ProxyClientOptions {
1646
1662
  }
1647
1663
  declare function createProxyClient(options: ProxyClientOptions): AvatarSessionClient;
1648
1664
 
1649
- export { RECONNECT_BACKOFF_MS as $, type AvatarVideoFit as A, type BehaviorSnapshot as B, type CallTranscript as C, DEFAULT_APPROACHING_END_LEAD_SECONDS as D, type EndReason as E, type FishTtsModel as F, type Governor as G, type GraceWindowClosedEvent as H, type GraceWindowOpenEvent as I, type GraceWindowState as J, type IdleWarningEvent as K, type LiveKitSessionGrant as L, type InboundRtpCursor as M, type InboundRtpReading as N, type KnownBehaviorState as O, type LLMProvider as P, type LLMSelection as Q, type LiveKitAvatarGrantState as R, type LiveKitAvatarGrantStatus as S, type LiveKitCapacityState as T, type LiveKitConnectionStatus as U, type LiveKitSessionRequest as V, type LiveKitSessionStartResult as W, MAX_RECONNECT_ATTEMPTS as X, MAX_SESSION_INSTRUCTIONS_CHARS as Y, type ProxyClientOptions as Z, type QualityCap as _, AdaptivePlayoutController as a, type RealtimeAvatarRequestOptions as a0, type RealtimeSessionApi as a1, type RealtimeSessionMedia as a2, type RealtimeSessionRoomSinks as a3, type ReconnectPolicy as a4, type ReconnectingEvent as a5, type RecoveryState as a6, type RetryStep as a7, type SessionBehavior as a8, type SessionClip as a9, useAvatarAdaptivePlayoutDelay as aA, useAvatarPlayoutDelay as aB, useAvatarQualityGovernor as aC, useCallTranscript as aD, useLiveKitAvatarGrant as aE, useLiveTrackProducing as aF, useMicLease as aG, useRealtimeSession as aH, useReleaseMicLeaseOnTrackEnded as aI, useSessionLifecycle as aJ, AvatarVideoSurface as aK, type AvatarVideoSurfaceProps as aL, type LivePlaybackKeeper as aM, MIC_LEASE_ENDED_TIMEOUT_MS as aN, type MicLease as aO, NETWORK_QUALITY_POLICY as aP, type NetworkQualityStatus as aQ, type PlayableVideoElement as aR, RealtimeAvatarCapacityError as aS, RealtimeAvatarLiveKitRoom as aT, type RealtimeAvatarLiveKitRoomProps as aU, shouldReplayPendingTurn as aV, type SessionClocks as aa, type SessionEndReason as ab, type SessionLifecycleApi as ac, type SessionLifecyclePhase as ad, type SessionLifecyclePhaseKind as ae, SessionLifecycleRoomBridge as af, type SessionLifecycleRoomBridgeProps as ag, type SurfaceLayers as ah, type TurnState as ai, type TurnTimeoutEvent as aj, type UseAvatarQualityGovernorInput as ak, type UseLiveKitAvatarGrantInput as al, type UseRealtimeSessionInput as am, type UseSessionLifecycleInput as an, type VoiceSpec as ao, type VoiceSpecInput as ap, capacityErrorFromBusy as aq, capacityStateFromGrant as ar, createProxyClient as as, isNativeLiveTrackSubscribed as at, knownBehaviorStates as au, mapTurnState as av, readInboundRtp as aw, sessionBehaviorSchema as ax, sessionClipSchema as ay, splitCallTranscript as az, type AdaptivePlayoutDecision as b, type AdaptivePlayoutOptions as c, type AdaptivePlayoutSample as d, type ApproachingEndEvent as e, type ApproachingEndReason as f, type AvatarSessionClient as g, type CallTranscriptSegment as h, type CartesiaTtsModel as i, type ClipResult as j, type ClosingTurnResult as k, type CreditsLowEvent as l, DEFAULT_CREDITS_LOW_LEAD_SECONDS as m, DEFAULT_GOVERNOR_CONFIG as n, DEFAULT_GRACE_CEILING_SECONDS as o, DEFAULT_GRACE_WINDOW_LEAD_SECONDS as p, DEFAULT_IDLE_SECONDS as q, DEFAULT_IDLE_WARN_LEAD_SECONDS as r, DEFAULT_TURN_TIMEOUT_SECONDS as s, type EndedEvent as t, type ExtendResult as u, type FreezeReadingFn as v, type GovernorAction as w, type GovernorConfig as x, type GovernorSignal as y, type GovernorState as z };
1665
+ export { type QualityCap as $, type AvatarVideoFit as A, type BehaviorSnapshot as B, type CallTranscript as C, DEFAULT_APPROACHING_END_LEAD_SECONDS as D, type EndReason as E, type FishTtsModel as F, type Governor as G, type GovernorState as H, type GraceWindowClosedEvent as I, type GraceWindowOpenEvent as J, type GraceWindowState as K, type LiveKitSessionGrant as L, type IdleWarningEvent as M, type InboundRtpCursor as N, type InboundRtpReading as O, type KnownBehaviorState as P, type LLMProvider as Q, type LLMSelection as R, type LiveKitAvatarGrantState as S, type LiveKitAvatarGrantStatus as T, type LiveKitCapacityState as U, type LiveKitConnectionStatus as V, type LiveKitSessionRequest as W, type LiveKitSessionStartResult as X, MAX_RECONNECT_ATTEMPTS as Y, MAX_SESSION_INSTRUCTIONS_CHARS as Z, type ProxyClientOptions as _, AdaptivePlayoutController as a, RECONNECT_BACKOFF_MS as a0, type RealtimeAvatarRequestOptions as a1, type RealtimeSessionApi as a2, type RealtimeSessionMedia as a3, type RealtimeSessionRoomSinks as a4, type ReconnectPolicy as a5, type ReconnectingEvent as a6, type RecoveryState as a7, type RetryStep as a8, type SessionBehavior as a9, splitCallTranscript as aA, useAvatarAdaptivePlayoutDelay as aB, useAvatarPlayoutDelay as aC, useAvatarQualityGovernor as aD, useCallTranscript as aE, useLiveKitAvatarGrant as aF, useLiveTrackProducing as aG, useMicLease as aH, useRealtimeSession as aI, useReleaseMicLeaseOnTrackEnded as aJ, useSessionLifecycle as aK, AvatarVideoSurface as aL, type AvatarVideoSurfaceProps as aM, type LivePlaybackKeeper as aN, MIC_LEASE_ENDED_TIMEOUT_MS as aO, type MicLease as aP, NETWORK_QUALITY_POLICY as aQ, type NetworkQualityStatus as aR, type PlayableVideoElement as aS, RealtimeAvatarCapacityError as aT, RealtimeAvatarLiveKitRoom as aU, type RealtimeAvatarLiveKitRoomProps as aV, shouldReplayPendingTurn as aW, type SessionClip as aa, type SessionClocks as ab, type SessionEndReason as ac, type SessionLifecycleApi as ad, type SessionLifecyclePhase as ae, type SessionLifecyclePhaseKind as af, SessionLifecycleRoomBridge as ag, type SessionLifecycleRoomBridgeProps as ah, type SurfaceLayers as ai, type TurnState as aj, type TurnTimeoutEvent as ak, type UseAvatarQualityGovernorInput as al, type UseLiveKitAvatarGrantInput as am, type UseRealtimeSessionInput as an, type UseSessionLifecycleInput as ao, type VoiceSpec as ap, type VoiceSpecInput as aq, capacityErrorFromBusy as ar, capacityStateFromGrant as as, createProxyClient as at, isNativeLiveTrackSubscribed as au, knownBehaviorStates as av, mapTurnState as aw, readInboundRtp as ax, sessionBehaviorSchema as ay, sessionClipSchema as az, type AdaptivePlayoutDecision as b, type AdaptivePlayoutOptions as c, type AdaptivePlayoutSample as d, type ApproachingEndEvent as e, type ApproachingEndReason as f, type AvatarConnectionDetails as g, type AvatarSessionClient as h, type CallTranscriptSegment as i, type CartesiaTtsModel as j, type ClipResult as k, type ClosingTurnResult as l, type CreditsLowEvent as m, DEFAULT_CREDITS_LOW_LEAD_SECONDS as n, DEFAULT_GOVERNOR_CONFIG as o, DEFAULT_GRACE_CEILING_SECONDS as p, DEFAULT_GRACE_WINDOW_LEAD_SECONDS as q, DEFAULT_IDLE_SECONDS as r, DEFAULT_IDLE_WARN_LEAD_SECONDS as s, DEFAULT_TURN_TIMEOUT_SECONDS as t, type EndedEvent as u, type ExtendResult as v, type FreezeReadingFn as w, type GovernorAction as x, type GovernorConfig as y, type GovernorSignal as z };
@@ -1,8 +1,8 @@
1
1
  import { LiveKitRoomProps } from '@livekit/react-native';
2
2
  export { AudioSession, VideoTrack, VideoTrackProps, registerGlobals } from '@livekit/react-native';
3
3
  import { ReactNode, ReactElement } from 'react';
4
- import { L as LiveKitSessionGrant, A as AvatarVideoFit } from './proxy-client-DhVdbfQX.js';
5
- export { a as AdaptivePlayoutController, b as AdaptivePlayoutDecision, c as AdaptivePlayoutOptions, d as AdaptivePlayoutSample, e as ApproachingEndEvent, f as ApproachingEndReason, g as AvatarSessionClient, B as BehaviorSnapshot, C as CallTranscript, h as CallTranscriptSegment, i as CartesiaTtsModel, j as ClipResult, k as ClosingTurnResult, l as CreditsLowEvent, D as DEFAULT_APPROACHING_END_LEAD_SECONDS, m as DEFAULT_CREDITS_LOW_LEAD_SECONDS, n as DEFAULT_GOVERNOR_CONFIG, o as DEFAULT_GRACE_CEILING_SECONDS, p as DEFAULT_GRACE_WINDOW_LEAD_SECONDS, q as DEFAULT_IDLE_SECONDS, r as DEFAULT_IDLE_WARN_LEAD_SECONDS, s as DEFAULT_TURN_TIMEOUT_SECONDS, E as EndReason, t as EndedEvent, u as ExtendResult, F as FishTtsModel, v as FreezeReadingFn, G as Governor, w as GovernorAction, x as GovernorConfig, y as GovernorSignal, z as GovernorState, H as GraceWindowClosedEvent, I as GraceWindowOpenEvent, J as GraceWindowState, K as IdleWarningEvent, M as InboundRtpCursor, N as InboundRtpReading, O as KnownBehaviorState, P as LLMProvider, Q as LLMSelection, R as LiveKitAvatarGrantState, S as LiveKitAvatarGrantStatus, T as LiveKitCapacityState, U as LiveKitConnectionStatus, V as LiveKitSessionRequest, W as LiveKitSessionStartResult, X as MAX_RECONNECT_ATTEMPTS, Y as MAX_SESSION_INSTRUCTIONS_CHARS, Z as ProxyClientOptions, _ as QualityCap, $ as RECONNECT_BACKOFF_MS, a0 as RealtimeAvatarRequestOptions, a1 as RealtimeSessionApi, a2 as RealtimeSessionMedia, a3 as RealtimeSessionRoomSinks, a4 as ReconnectPolicy, a5 as ReconnectingEvent, a6 as RecoveryState, a7 as RetryStep, a8 as SessionBehavior, a9 as SessionClip, aa as SessionClocks, ab as SessionEndReason, ac as SessionLifecycleApi, ad as SessionLifecyclePhase, ae as SessionLifecyclePhaseKind, af as SessionLifecycleRoomBridge, ag as SessionLifecycleRoomBridgeProps, ah as SurfaceLayers, ai as TurnState, aj as TurnTimeoutEvent, ak as UseAvatarQualityGovernorInput, al as UseLiveKitAvatarGrantInput, am as UseRealtimeSessionInput, an as UseSessionLifecycleInput, ao as VoiceSpec, ap as VoiceSpecInput, aq as capacityErrorFromBusy, ar as capacityStateFromGrant, as as createProxyClient, at as isNativeLiveTrackSubscribed, au as knownBehaviorStates, av as mapTurnState, aw as readInboundRtp, ax as sessionBehaviorSchema, ay as sessionClipSchema, az as splitCallTranscript, aA as useAvatarAdaptivePlayoutDelay, aB as useAvatarPlayoutDelay, aC as useAvatarQualityGovernor, aD as useCallTranscript, aE as useLiveKitAvatarGrant, aF as useLiveTrackProducing, aG as useMicLease, aH as useRealtimeSession, aI as useReleaseMicLeaseOnTrackEnded, aJ as useSessionLifecycle } from './proxy-client-DhVdbfQX.js';
4
+ import { L as LiveKitSessionGrant, A as AvatarVideoFit } from './proxy-client-BGPikYWc.js';
5
+ export { a as AdaptivePlayoutController, b as AdaptivePlayoutDecision, c as AdaptivePlayoutOptions, d as AdaptivePlayoutSample, e as ApproachingEndEvent, f as ApproachingEndReason, g as AvatarConnectionDetails, h as AvatarSessionClient, B as BehaviorSnapshot, C as CallTranscript, i as CallTranscriptSegment, j as CartesiaTtsModel, k as ClipResult, l as ClosingTurnResult, m as CreditsLowEvent, D as DEFAULT_APPROACHING_END_LEAD_SECONDS, n as DEFAULT_CREDITS_LOW_LEAD_SECONDS, o as DEFAULT_GOVERNOR_CONFIG, p as DEFAULT_GRACE_CEILING_SECONDS, q as DEFAULT_GRACE_WINDOW_LEAD_SECONDS, r as DEFAULT_IDLE_SECONDS, s as DEFAULT_IDLE_WARN_LEAD_SECONDS, t as DEFAULT_TURN_TIMEOUT_SECONDS, E as EndReason, u as EndedEvent, v as ExtendResult, F as FishTtsModel, w as FreezeReadingFn, G as Governor, x as GovernorAction, y as GovernorConfig, z as GovernorSignal, H as GovernorState, I as GraceWindowClosedEvent, J as GraceWindowOpenEvent, K as GraceWindowState, M as IdleWarningEvent, N as InboundRtpCursor, O as InboundRtpReading, P as KnownBehaviorState, Q as LLMProvider, R as LLMSelection, S as LiveKitAvatarGrantState, T as LiveKitAvatarGrantStatus, U as LiveKitCapacityState, V as LiveKitConnectionStatus, W as LiveKitSessionRequest, X as LiveKitSessionStartResult, Y as MAX_RECONNECT_ATTEMPTS, Z as MAX_SESSION_INSTRUCTIONS_CHARS, _ as ProxyClientOptions, $ as QualityCap, a0 as RECONNECT_BACKOFF_MS, a1 as RealtimeAvatarRequestOptions, a2 as RealtimeSessionApi, a3 as RealtimeSessionMedia, a4 as RealtimeSessionRoomSinks, a5 as ReconnectPolicy, a6 as ReconnectingEvent, a7 as RecoveryState, a8 as RetryStep, a9 as SessionBehavior, aa as SessionClip, ab as SessionClocks, ac as SessionEndReason, ad as SessionLifecycleApi, ae as SessionLifecyclePhase, af as SessionLifecyclePhaseKind, ag as SessionLifecycleRoomBridge, ah as SessionLifecycleRoomBridgeProps, ai as SurfaceLayers, aj as TurnState, ak as TurnTimeoutEvent, al as UseAvatarQualityGovernorInput, am as UseLiveKitAvatarGrantInput, an as UseRealtimeSessionInput, ao as UseSessionLifecycleInput, ap as VoiceSpec, aq as VoiceSpecInput, ar as capacityErrorFromBusy, as as capacityStateFromGrant, at as createProxyClient, au as isNativeLiveTrackSubscribed, av as knownBehaviorStates, aw as mapTurnState, ax as readInboundRtp, ay as sessionBehaviorSchema, az as sessionClipSchema, aA as splitCallTranscript, aB as useAvatarAdaptivePlayoutDelay, aC as useAvatarPlayoutDelay, aD as useAvatarQualityGovernor, aE as useCallTranscript, aF as useLiveKitAvatarGrant, aG as useLiveTrackProducing, aH as useMicLease, aI as useRealtimeSession, aJ as useReleaseMicLeaseOnTrackEnded, aK as useSessionLifecycle } from './proxy-client-BGPikYWc.js';
6
6
  import { StyleProp, ViewStyle } from 'react-native';
7
7
  export { useChat, useConnectionState, useLocalParticipant, useRoomContext, useTrackToggle, useTranscriptions, useVoiceAssistant } from '@livekit/components-react';
8
8
  export { D as DEFAULT_AVATAR_PLAYOUT_DELAY_SECONDS } from './playout-delay-DWOc8iCl.js';
@@ -2175,7 +2175,10 @@ function useSessionLifecycle(input) {
2175
2175
  ]
2176
2176
  );
2177
2177
  }
2178
- function SessionLifecycleRoomBridge({ lifecycle }) {
2178
+ function SessionLifecycleRoomBridge({
2179
+ lifecycle,
2180
+ onConnectionDetailsChange
2181
+ }) {
2179
2182
  const {
2180
2183
  onConnectionStateChange,
2181
2184
  setAgentPresent,
@@ -2198,6 +2201,87 @@ function SessionLifecycleRoomBridge({ lifecycle }) {
2198
2201
  const audioLive = Boolean(assistant.audioTrack);
2199
2202
  const transcriptionCount = transcriptions.length;
2200
2203
  const prevTranscriptionCountRef = useRef(0);
2204
+ const audio = assistant.audioTrack;
2205
+ const video = assistant.videoTrack;
2206
+ const detailsEnabled = onConnectionDetailsChange !== void 0 && lifecycle.grant !== null;
2207
+ const detailsSessionId = lifecycle.grant?.session_id;
2208
+ const updateDetailsRef = useRef(null);
2209
+ useEffect(() => {
2210
+ if (!detailsEnabled || !onConnectionDetailsChange) return;
2211
+ let callback = onConnectionDetailsChange;
2212
+ let audioSource = audio;
2213
+ let videoSource = video;
2214
+ let closed = false;
2215
+ let pending = false;
2216
+ let previous;
2217
+ const deliver = (details) => {
2218
+ try {
2219
+ void Promise.resolve(callback(details)).catch(() => {
2220
+ });
2221
+ } catch {
2222
+ }
2223
+ };
2224
+ const publisher = (source) => source ? {
2225
+ publisherQuality: source.participant.connectionQuality,
2226
+ streamState: source.publication.track?.streamState ?? null
2227
+ } : null;
2228
+ const schedule = () => {
2229
+ if (pending) return;
2230
+ pending = true;
2231
+ queueMicrotask(() => {
2232
+ pending = false;
2233
+ if (closed) return;
2234
+ const next = {
2235
+ connectionState: room.state,
2236
+ localQuality: room.localParticipant.connectionQuality,
2237
+ audio: publisher(audioSource),
2238
+ video: publisher(videoSource)
2239
+ };
2240
+ const last = previous;
2241
+ if (last?.connectionState === next.connectionState && last.localQuality === next.localQuality && ["audio", "video"].every((kind) => last[kind]?.publisherQuality === next[kind]?.publisherQuality && last[kind]?.streamState === next[kind]?.streamState)) return;
2242
+ previous = next;
2243
+ deliver(next);
2244
+ });
2245
+ };
2246
+ const onQuality = (_quality, participant) => {
2247
+ if (participant === room.localParticipant || participant === audioSource?.participant || participant === videoSource?.participant) schedule();
2248
+ };
2249
+ const onPublication = (publication) => {
2250
+ if (publication === audioSource?.publication || publication === videoSource?.publication) schedule();
2251
+ };
2252
+ const onTrack = (_track, publication) => onPublication(publication);
2253
+ room.on(RoomEvent.ConnectionStateChanged, schedule);
2254
+ room.on(RoomEvent.ConnectionQualityChanged, onQuality);
2255
+ room.on(RoomEvent.TrackStreamStateChanged, onPublication);
2256
+ room.on(RoomEvent.TrackSubscribed, onTrack);
2257
+ room.on(RoomEvent.TrackUnsubscribed, onTrack);
2258
+ updateDetailsRef.current = (nextCallback, nextAudio, nextVideo) => {
2259
+ callback = nextCallback;
2260
+ audioSource = nextAudio;
2261
+ videoSource = nextVideo;
2262
+ schedule();
2263
+ };
2264
+ schedule();
2265
+ return () => {
2266
+ closed = true;
2267
+ updateDetailsRef.current = null;
2268
+ room.off(RoomEvent.ConnectionStateChanged, schedule);
2269
+ room.off(RoomEvent.ConnectionQualityChanged, onQuality);
2270
+ room.off(RoomEvent.TrackStreamStateChanged, onPublication);
2271
+ room.off(RoomEvent.TrackSubscribed, onTrack);
2272
+ room.off(RoomEvent.TrackUnsubscribed, onTrack);
2273
+ deliver(null);
2274
+ };
2275
+ }, [room, detailsSessionId, detailsEnabled]);
2276
+ useEffect(() => {
2277
+ if (onConnectionDetailsChange) updateDetailsRef.current?.(onConnectionDetailsChange, audio, video);
2278
+ }, [
2279
+ onConnectionDetailsChange,
2280
+ detailsEnabled ? audio?.participant : void 0,
2281
+ detailsEnabled ? audio?.publication : void 0,
2282
+ detailsEnabled ? video?.participant : void 0,
2283
+ detailsEnabled ? video?.publication : void 0
2284
+ ]);
2201
2285
  useEffect(() => {
2202
2286
  onConnectionStateChange(connectionState);
2203
2287
  }, [connectionState, onConnectionStateChange]);
package/dist/react.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { RegisteredTool, ToolRegistration } from './tools.js';
2
2
  import { ReactNode } from 'react';
3
- import { g as AvatarSessionClient, A as AvatarVideoFit } from './proxy-client-DhVdbfQX.js';
4
- export { a as AdaptivePlayoutController, b as AdaptivePlayoutDecision, c as AdaptivePlayoutOptions, d as AdaptivePlayoutSample, e as ApproachingEndEvent, f as ApproachingEndReason, aK as AvatarVideoSurface, aL as AvatarVideoSurfaceProps, B as BehaviorSnapshot, C as CallTranscript, h as CallTranscriptSegment, i as CartesiaTtsModel, j as ClipResult, k as ClosingTurnResult, l as CreditsLowEvent, D as DEFAULT_APPROACHING_END_LEAD_SECONDS, m as DEFAULT_CREDITS_LOW_LEAD_SECONDS, n as DEFAULT_GOVERNOR_CONFIG, o as DEFAULT_GRACE_CEILING_SECONDS, p as DEFAULT_GRACE_WINDOW_LEAD_SECONDS, q as DEFAULT_IDLE_SECONDS, r as DEFAULT_IDLE_WARN_LEAD_SECONDS, s as DEFAULT_TURN_TIMEOUT_SECONDS, E as EndReason, t as EndedEvent, u as ExtendResult, F as FishTtsModel, v as FreezeReadingFn, G as Governor, w as GovernorAction, x as GovernorConfig, y as GovernorSignal, z as GovernorState, H as GraceWindowClosedEvent, I as GraceWindowOpenEvent, J as GraceWindowState, K as IdleWarningEvent, M as InboundRtpCursor, N as InboundRtpReading, O as KnownBehaviorState, P as LLMProvider, Q as LLMSelection, R as LiveKitAvatarGrantState, S as LiveKitAvatarGrantStatus, T as LiveKitCapacityState, U as LiveKitConnectionStatus, V as LiveKitSessionRequest, W as LiveKitSessionStartResult, aM as LivePlaybackKeeper, X as MAX_RECONNECT_ATTEMPTS, Y as MAX_SESSION_INSTRUCTIONS_CHARS, aN as MIC_LEASE_ENDED_TIMEOUT_MS, aO as MicLease, aP as NETWORK_QUALITY_POLICY, aQ as NetworkQualityStatus, aR as PlayableVideoElement, Z as ProxyClientOptions, _ as QualityCap, $ as RECONNECT_BACKOFF_MS, aS as RealtimeAvatarCapacityError, aT as RealtimeAvatarLiveKitRoom, aU as RealtimeAvatarLiveKitRoomProps, a0 as RealtimeAvatarRequestOptions, a1 as RealtimeSessionApi, a2 as RealtimeSessionMedia, a3 as RealtimeSessionRoomSinks, a4 as ReconnectPolicy, a5 as ReconnectingEvent, a6 as RecoveryState, a7 as RetryStep, a8 as SessionBehavior, a9 as SessionClip, aa as SessionClocks, ab as SessionEndReason, ac as SessionLifecycleApi, ad as SessionLifecyclePhase, ae as SessionLifecyclePhaseKind, af as SessionLifecycleRoomBridge, ag as SessionLifecycleRoomBridgeProps, ah as SurfaceLayers, ai as TurnState, aj as TurnTimeoutEvent, ak as UseAvatarQualityGovernorInput, al as UseLiveKitAvatarGrantInput, am as UseRealtimeSessionInput, an as UseSessionLifecycleInput, ao as VoiceSpec, ap as VoiceSpecInput, aq as capacityErrorFromBusy, ar as capacityStateFromGrant, as as createProxyClient, au as knownBehaviorStates, av as mapTurnState, aw as readInboundRtp, ax as sessionBehaviorSchema, ay as sessionClipSchema, aV as shouldReplayPendingTurn, az as splitCallTranscript, aA as useAvatarAdaptivePlayoutDelay, aB as useAvatarPlayoutDelay, aC as useAvatarQualityGovernor, aD as useCallTranscript, aE as useLiveKitAvatarGrant, aG as useMicLease, aH as useRealtimeSession, aI as useReleaseMicLeaseOnTrackEnded, aJ as useSessionLifecycle } from './proxy-client-DhVdbfQX.js';
3
+ import { ah as SessionLifecycleRoomBridgeProps, h as AvatarSessionClient, A as AvatarVideoFit } from './proxy-client-BGPikYWc.js';
4
+ export { a as AdaptivePlayoutController, b as AdaptivePlayoutDecision, c as AdaptivePlayoutOptions, d as AdaptivePlayoutSample, e as ApproachingEndEvent, f as ApproachingEndReason, g as AvatarConnectionDetails, aL as AvatarVideoSurface, aM as AvatarVideoSurfaceProps, B as BehaviorSnapshot, C as CallTranscript, i as CallTranscriptSegment, j as CartesiaTtsModel, k as ClipResult, l as ClosingTurnResult, m as CreditsLowEvent, D as DEFAULT_APPROACHING_END_LEAD_SECONDS, n as DEFAULT_CREDITS_LOW_LEAD_SECONDS, o as DEFAULT_GOVERNOR_CONFIG, p as DEFAULT_GRACE_CEILING_SECONDS, q as DEFAULT_GRACE_WINDOW_LEAD_SECONDS, r as DEFAULT_IDLE_SECONDS, s as DEFAULT_IDLE_WARN_LEAD_SECONDS, t as DEFAULT_TURN_TIMEOUT_SECONDS, E as EndReason, u as EndedEvent, v as ExtendResult, F as FishTtsModel, w as FreezeReadingFn, G as Governor, x as GovernorAction, y as GovernorConfig, z as GovernorSignal, H as GovernorState, I as GraceWindowClosedEvent, J as GraceWindowOpenEvent, K as GraceWindowState, M as IdleWarningEvent, N as InboundRtpCursor, O as InboundRtpReading, P as KnownBehaviorState, Q as LLMProvider, R as LLMSelection, S as LiveKitAvatarGrantState, T as LiveKitAvatarGrantStatus, U as LiveKitCapacityState, V as LiveKitConnectionStatus, W as LiveKitSessionRequest, X as LiveKitSessionStartResult, aN as LivePlaybackKeeper, Y as MAX_RECONNECT_ATTEMPTS, Z as MAX_SESSION_INSTRUCTIONS_CHARS, aO as MIC_LEASE_ENDED_TIMEOUT_MS, aP as MicLease, aQ as NETWORK_QUALITY_POLICY, aR as NetworkQualityStatus, aS as PlayableVideoElement, _ as ProxyClientOptions, $ as QualityCap, a0 as RECONNECT_BACKOFF_MS, aT as RealtimeAvatarCapacityError, aU as RealtimeAvatarLiveKitRoom, aV as RealtimeAvatarLiveKitRoomProps, a1 as RealtimeAvatarRequestOptions, a2 as RealtimeSessionApi, a3 as RealtimeSessionMedia, a4 as RealtimeSessionRoomSinks, a5 as ReconnectPolicy, a6 as ReconnectingEvent, a7 as RecoveryState, a8 as RetryStep, a9 as SessionBehavior, aa as SessionClip, ab as SessionClocks, ac as SessionEndReason, ad as SessionLifecycleApi, ae as SessionLifecyclePhase, af as SessionLifecyclePhaseKind, ag as SessionLifecycleRoomBridge, ai as SurfaceLayers, aj as TurnState, ak as TurnTimeoutEvent, al as UseAvatarQualityGovernorInput, am as UseLiveKitAvatarGrantInput, an as UseRealtimeSessionInput, ao as UseSessionLifecycleInput, ap as VoiceSpec, aq as VoiceSpecInput, ar as capacityErrorFromBusy, as as capacityStateFromGrant, at as createProxyClient, av as knownBehaviorStates, aw as mapTurnState, ax as readInboundRtp, ay as sessionBehaviorSchema, az as sessionClipSchema, aW as shouldReplayPendingTurn, aA as splitCallTranscript, aB as useAvatarAdaptivePlayoutDelay, aC as useAvatarPlayoutDelay, aD as useAvatarQualityGovernor, aE as useCallTranscript, aF as useLiveKitAvatarGrant, aH as useMicLease, aI as useRealtimeSession, aJ as useReleaseMicLeaseOnTrackEnded, aK as useSessionLifecycle } from './proxy-client-BGPikYWc.js';
5
5
  export { D as DEFAULT_AVATAR_PLAYOUT_DELAY_SECONDS } from './playout-delay-DWOc8iCl.js';
6
6
  import 'zod';
7
7
  import '@livekit/components-react';
@@ -82,7 +82,7 @@ type AvatarCallHandle = {
82
82
  /** End now. */
83
83
  end: () => void;
84
84
  };
85
- type AvatarCallProps = {
85
+ type AvatarCallProps = Pick<SessionLifecycleRoomBridgeProps, "onConnectionDetailsChange"> & {
86
86
  client: AvatarSessionClient;
87
87
  /** Which character. */
88
88
  avatarId: string;
@@ -129,4 +129,4 @@ declare function useAvatarCall(props: AvatarCallProps): {
129
129
  /** One component: a live character on screen. */
130
130
  declare function AvatarCall(props: AvatarCallProps): ReactNode;
131
131
 
132
- export { AvatarCall, type AvatarCallEndReason, type AvatarCallHandle, type AvatarCallProps, type AvatarCallStatus, AvatarSessionClient, AvatarVideoFit, DEFAULT_AVATAR_UNSTABLE_STALL_MS, useAvatarCall, useCharacterTools };
132
+ export { AvatarCall, type AvatarCallEndReason, type AvatarCallHandle, type AvatarCallProps, type AvatarCallStatus, AvatarSessionClient, AvatarVideoFit, DEFAULT_AVATAR_UNSTABLE_STALL_MS, SessionLifecycleRoomBridgeProps, useAvatarCall, useCharacterTools };
package/dist/react.js CHANGED
@@ -2714,7 +2714,10 @@ function useSessionLifecycle(input) {
2714
2714
  ]
2715
2715
  );
2716
2716
  }
2717
- function SessionLifecycleRoomBridge({ lifecycle }) {
2717
+ function SessionLifecycleRoomBridge({
2718
+ lifecycle,
2719
+ onConnectionDetailsChange
2720
+ }) {
2718
2721
  const {
2719
2722
  onConnectionStateChange,
2720
2723
  setAgentPresent,
@@ -2737,6 +2740,87 @@ function SessionLifecycleRoomBridge({ lifecycle }) {
2737
2740
  const audioLive = Boolean(assistant.audioTrack);
2738
2741
  const transcriptionCount = transcriptions.length;
2739
2742
  const prevTranscriptionCountRef = useRef(0);
2743
+ const audio = assistant.audioTrack;
2744
+ const video = assistant.videoTrack;
2745
+ const detailsEnabled = onConnectionDetailsChange !== void 0 && lifecycle.grant !== null;
2746
+ const detailsSessionId = lifecycle.grant?.session_id;
2747
+ const updateDetailsRef = useRef(null);
2748
+ useEffect(() => {
2749
+ if (!detailsEnabled || !onConnectionDetailsChange) return;
2750
+ let callback = onConnectionDetailsChange;
2751
+ let audioSource = audio;
2752
+ let videoSource = video;
2753
+ let closed = false;
2754
+ let pending = false;
2755
+ let previous;
2756
+ const deliver = (details) => {
2757
+ try {
2758
+ void Promise.resolve(callback(details)).catch(() => {
2759
+ });
2760
+ } catch {
2761
+ }
2762
+ };
2763
+ const publisher = (source) => source ? {
2764
+ publisherQuality: source.participant.connectionQuality,
2765
+ streamState: source.publication.track?.streamState ?? null
2766
+ } : null;
2767
+ const schedule = () => {
2768
+ if (pending) return;
2769
+ pending = true;
2770
+ queueMicrotask(() => {
2771
+ pending = false;
2772
+ if (closed) return;
2773
+ const next = {
2774
+ connectionState: room.state,
2775
+ localQuality: room.localParticipant.connectionQuality,
2776
+ audio: publisher(audioSource),
2777
+ video: publisher(videoSource)
2778
+ };
2779
+ const last = previous;
2780
+ if (last?.connectionState === next.connectionState && last.localQuality === next.localQuality && ["audio", "video"].every((kind) => last[kind]?.publisherQuality === next[kind]?.publisherQuality && last[kind]?.streamState === next[kind]?.streamState)) return;
2781
+ previous = next;
2782
+ deliver(next);
2783
+ });
2784
+ };
2785
+ const onQuality = (_quality, participant) => {
2786
+ if (participant === room.localParticipant || participant === audioSource?.participant || participant === videoSource?.participant) schedule();
2787
+ };
2788
+ const onPublication = (publication) => {
2789
+ if (publication === audioSource?.publication || publication === videoSource?.publication) schedule();
2790
+ };
2791
+ const onTrack = (_track, publication) => onPublication(publication);
2792
+ room.on(RoomEvent.ConnectionStateChanged, schedule);
2793
+ room.on(RoomEvent.ConnectionQualityChanged, onQuality);
2794
+ room.on(RoomEvent.TrackStreamStateChanged, onPublication);
2795
+ room.on(RoomEvent.TrackSubscribed, onTrack);
2796
+ room.on(RoomEvent.TrackUnsubscribed, onTrack);
2797
+ updateDetailsRef.current = (nextCallback, nextAudio, nextVideo) => {
2798
+ callback = nextCallback;
2799
+ audioSource = nextAudio;
2800
+ videoSource = nextVideo;
2801
+ schedule();
2802
+ };
2803
+ schedule();
2804
+ return () => {
2805
+ closed = true;
2806
+ updateDetailsRef.current = null;
2807
+ room.off(RoomEvent.ConnectionStateChanged, schedule);
2808
+ room.off(RoomEvent.ConnectionQualityChanged, onQuality);
2809
+ room.off(RoomEvent.TrackStreamStateChanged, onPublication);
2810
+ room.off(RoomEvent.TrackSubscribed, onTrack);
2811
+ room.off(RoomEvent.TrackUnsubscribed, onTrack);
2812
+ deliver(null);
2813
+ };
2814
+ }, [room, detailsSessionId, detailsEnabled]);
2815
+ useEffect(() => {
2816
+ if (onConnectionDetailsChange) updateDetailsRef.current?.(onConnectionDetailsChange, audio, video);
2817
+ }, [
2818
+ onConnectionDetailsChange,
2819
+ detailsEnabled ? audio?.participant : void 0,
2820
+ detailsEnabled ? audio?.publication : void 0,
2821
+ detailsEnabled ? video?.participant : void 0,
2822
+ detailsEnabled ? video?.publication : void 0
2823
+ ]);
2740
2824
  useEffect(() => {
2741
2825
  onConnectionStateChange(connectionState);
2742
2826
  }, [connectionState, onConnectionStateChange]);
@@ -3306,7 +3390,11 @@ function useAvatarCall(props) {
3306
3390
  onDisconnected: session.onDisconnected,
3307
3391
  onError: session.onConnectionError
3308
3392
  },
3309
- createElement(SessionLifecycleRoomBridge, { key: "bridge", lifecycle: session }),
3393
+ createElement(SessionLifecycleRoomBridge, {
3394
+ key: "bridge",
3395
+ lifecycle: session,
3396
+ onConnectionDetailsChange: props.onConnectionDetailsChange
3397
+ }),
3310
3398
  createElement(
3311
3399
  AvatarVideoSurface,
3312
3400
  {
package/dist/server.js CHANGED
@@ -1 +1 @@
1
- export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, verifyTranscript } from './chunk-UZQXZH2Z.js';
1
+ export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, verifyTranscript } from './chunk-PKKTFUA7.js';
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-B3AHTJ3J.js';
2
- import './chunk-UZQXZH2Z.js';
1
+ import { createProxyHandler } from './chunk-LZMJJX7C.js';
2
+ import './chunk-PKKTFUA7.js';
3
3
 
4
4
  // src/tanstack-start.ts
5
5
  function realtimeAvatarServerRoute(config) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "realtime-avatar",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "Realtime Avatar SDK. `realtime-avatar` and the route adapters hold your API key and are server-only; `realtime-avatar/react`, `/browser` and `/tools` never can — importing a server entry into a browser build throws at bundle time.",
5
5
  "license": "MIT",
6
6
  "author": "The Influence Company",