realtime-avatar 0.11.0 → 0.11.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.
@@ -1,4 +1,4 @@
1
- import { RealtimeAvatar, isQueued, RealtimeAvatarHttpError } from './chunk-MBQ2VKLC.js';
1
+ import { RealtimeAvatar, isQueued, RealtimeAvatarHttpError } from './chunk-SKXUGNEK.js';
2
2
 
3
3
  // ../proxy/src/config.ts
4
4
  var json = (body, status = 200) => new Response(JSON.stringify(body), {
@@ -218,7 +218,7 @@ function isTransient(cause) {
218
218
 
219
219
  // ../http-client/src/client.ts
220
220
  var DEFAULT_BASE_URL = "https://realtimeavatar.ai/api/v1";
221
- var SDK_VERSION = "0.11.0";
221
+ var SDK_VERSION = "0.11.2";
222
222
  var RealtimeAvatar = class {
223
223
  #apiKey;
224
224
  #baseUrl;
package/dist/express.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as ProxyConfig } from './types-DrgyCwLC.js';
2
- import './types-FoJMJimV.js';
1
+ import { P as ProxyConfig } from './types-9a0bRdXl.js';
2
+ import './types-nxdohaiH.js';
3
3
 
4
4
  type Expressish = {
5
5
  method: string;
package/dist/express.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-RVBBZCVM.js';
2
- import './chunk-MBQ2VKLC.js';
1
+ import { createProxyHandler } from './chunk-MJJ6GSAU.js';
2
+ import './chunk-SKXUGNEK.js';
3
3
 
4
4
  // ../proxy/src/express.ts
5
5
  function realtimeAvatarExpress(config) {
package/dist/hono.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as ProxyConfig } from './types-DrgyCwLC.js';
2
- import './types-FoJMJimV.js';
1
+ import { P as ProxyConfig } from './types-9a0bRdXl.js';
2
+ import './types-nxdohaiH.js';
3
3
 
4
4
  /**
5
5
  * Hono (and anything else built on Fetch handlers — Workers, Bun, Deno).
package/dist/hono.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-RVBBZCVM.js';
2
- import './chunk-MBQ2VKLC.js';
1
+ import { createProxyHandler } from './chunk-MJJ6GSAU.js';
2
+ import './chunk-SKXUGNEK.js';
3
3
 
4
4
  // ../proxy/src/hono.ts
5
5
  function realtimeAvatarHono(config) {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as CallPolicy, C as CallMode, S as StartCallResult, E as EndCallOptions, A as Avatar, b as AvatarUpdate, c as AvatarSourceSwap, d as ClipLibraryDeclaration, e as ClipLibraryUpdate, L as LoopRedirect, f as ClipLibrary, g as AssetKind, h as Asset, i as ListSessionsOptions, U as UsageSessionPage, j as UsageSession, k as CreditBalance, T as TranscriptPayload } from './types-FoJMJimV.js';
2
- export { l as AvatarClip, m as CallConnection, n as CallQueued, o as ClipDeclaration, p as ClipLibraryPlan, q as ClipSource, r as ContextMessage, s as EndCallReason, V as VideoPolicy, t as isQueued } from './types-FoJMJimV.js';
1
+ import { a as CallPolicy, C as CallMode, S as StartCallResult, E as EndCallOptions, A as Avatar, b as AvatarUpdate, c as AvatarSourceSwap, d as ClipLibraryDeclaration, e as ClipLibraryUpdate, L as LoopRedirect, f as ClipLibrary, g as AssetKind, h as Asset, i as ListSessionsOptions, U as UsageSessionPage, j as UsageSession, k as CreditBalance, T as TranscriptPayload } from './types-nxdohaiH.js';
2
+ export { l as AvatarClip, m as CallConnection, n as CallQueued, o as ClipDeclaration, p as ClipLibraryPlan, q as ClipSource, r as ContextMessage, s as EndCallReason, V as VideoPolicy, t as isQueued } from './types-nxdohaiH.js';
3
3
  import { z } from 'zod';
4
4
 
5
5
  interface RealtimeAvatarOptions {
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, verifyTranscript } from './chunk-MBQ2VKLC.js';
1
+ export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, verifyTranscript } from './chunk-SKXUGNEK.js';
package/dist/nextjs.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as ProxyConfig } from './types-DrgyCwLC.js';
2
- import './types-FoJMJimV.js';
1
+ import { P as ProxyConfig } from './types-9a0bRdXl.js';
2
+ import './types-nxdohaiH.js';
3
3
 
4
4
  /**
5
5
  * App Router. Mount at `app/api/realtime-avatar/[...path]/route.ts`:
package/dist/nextjs.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-RVBBZCVM.js';
2
- import './chunk-MBQ2VKLC.js';
1
+ import { createProxyHandler } from './chunk-MJJ6GSAU.js';
2
+ import './chunk-SKXUGNEK.js';
3
3
 
4
4
  // ../proxy/src/nextjs.ts
5
5
  function createRealtimeAvatarRoute(config) {
@@ -468,7 +468,14 @@ type AvatarVideoSurfaceProps = {
468
468
  idleReturnDelayMs?: number;
469
469
  /**
470
470
  * Maximum time without a newly presented decoded frame before the live layer is
471
- * treated as stalled and immediately replaced by the idle/poster floor. Default 800ms.
471
+ * treated as stalled and replaced by the idle/poster floor — i.e. how long the LAST
472
+ * live frame is held through a network gap. Default
473
+ * {@link DEFAULT_AVATAR_FRAME_STALL_MS} (2 s). Once two stalls land inside 15 s the
474
+ * link is treated as unstable and the hold steps up to
475
+ * {@link DEFAULT_AVATAR_UNSTABLE_STALL_MS} (4 s) until the window clears, so a
476
+ * flapping link settles on a briefly frozen face rather than a body swap per gap.
477
+ * A disconnect, a turn end (track muted) and a track that never presented a frame
478
+ * are not stalls and do not wait on this.
472
479
  */
473
480
  frameStallMs?: number;
474
481
  /** Extra className for the box (the layers fill it). */
@@ -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-CWvT16wz.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-CWvT16wz.js';
4
+ import { L as LiveKitSessionGrant, A as AvatarVideoFit } from './proxy-client-D1Zm7reb.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-D1Zm7reb.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';
@@ -837,9 +837,10 @@ var step = (g, s, nowMs, cfg = DEFAULT_GOVERNOR_CONFIG) => {
837
837
  return { governor: g };
838
838
  }
839
839
  };
840
- var resolveLowCapQuality = (declaredLayerQualities) => {
840
+ var resolveLowCapQuality = (declaredLayerQualities, failures = 0) => {
841
841
  const sorted = [...declaredLayerQualities].sort((a, b) => a - b);
842
- return sorted.length >= 2 ? sorted[sorted.length - 2] : 1;
842
+ if (sorted.length < 2) return 1;
843
+ return failures >= 1 ? sorted[0] : sorted[sorted.length - 2];
843
844
  };
844
845
 
845
846
  // ../client/src/react/use-quality-governor.ts
@@ -959,10 +960,11 @@ function useAvatarQualityGovernor(input) {
959
960
  return { freezeMs: 0, jitterRising: false };
960
961
  }
961
962
  };
962
- const applyCap = (cap) => {
963
+ const applyCap = (cap, failures = 0) => {
963
964
  try {
964
965
  const lowCap = resolveLowCapQuality(
965
- (targetPublication?.trackInfo?.layers ?? []).map((l) => l.quality)
966
+ (targetPublication?.trackInfo?.layers ?? []).map((l) => l.quality),
967
+ failures
966
968
  );
967
969
  targetPublication?.setVideoQuality?.(cap === "low" ? lowCap : VideoQuality.HIGH);
968
970
  } catch {
@@ -987,13 +989,13 @@ function useAvatarQualityGovernor(input) {
987
989
  pausedSinceTick = false;
988
990
  const { governor, action } = step(gov, signal, Date.now(), config);
989
991
  gov = governor;
990
- if (action) applyCap(action.setCap);
992
+ if (action) applyCap(action.setCap, gov.failures);
991
993
  } catch {
992
994
  } finally {
993
995
  tickRunning = false;
994
996
  }
995
997
  };
996
- applyCap(gov.cap);
998
+ applyCap(gov.cap, gov.failures);
997
999
  const handle = setInterval(() => void tick(), tickMs);
998
1000
  return () => {
999
1001
  disposed = true;
package/dist/react.d.ts CHANGED
@@ -1,12 +1,21 @@
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-CWvT16wz.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 PlayableVideoElement, Z as ProxyClientOptions, _ as QualityCap, $ as RECONNECT_BACKOFF_MS, aQ as RealtimeAvatarCapacityError, aR as RealtimeAvatarLiveKitRoom, aS 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, aT 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-CWvT16wz.js';
3
+ import { g as AvatarSessionClient, A as AvatarVideoFit } from './proxy-client-D1Zm7reb.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 PlayableVideoElement, Z as ProxyClientOptions, _ as QualityCap, $ as RECONNECT_BACKOFF_MS, aQ as RealtimeAvatarCapacityError, aR as RealtimeAvatarLiveKitRoom, aS 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, aT 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-D1Zm7reb.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';
8
8
  import 'livekit-client';
9
9
 
10
+ /**
11
+ * Once a link has proven unstable, hold the frozen live frame this long before
12
+ * falling back to the idle floor. Measured on a prod rtx6000 call through a
13
+ * 900 kbit / 150 ms / 10 % loss link (2026-09-09): after the opening, presented-frame
14
+ * gaps cluster at 650–1150 ms with two multi-second outages around simulcast layer
15
+ * switches. Nothing shorter than a few seconds is worth a body swap on such a link.
16
+ */
17
+ declare const DEFAULT_AVATAR_UNSTABLE_STALL_MS = 4000;
18
+
10
19
  declare function useCharacterTools(tools: Record<string, RegisteredTool>, options?: {
11
20
  onResult?: (event: {
12
21
  name: string;
@@ -120,4 +129,4 @@ declare function useAvatarCall(props: AvatarCallProps): {
120
129
  /** One component: a live character on screen. */
121
130
  declare function AvatarCall(props: AvatarCallProps): ReactNode;
122
131
 
123
- export { AvatarCall, type AvatarCallEndReason, type AvatarCallHandle, type AvatarCallProps, type AvatarCallStatus, AvatarSessionClient, AvatarVideoFit, useAvatarCall, useCharacterTools };
132
+ export { AvatarCall, type AvatarCallEndReason, type AvatarCallHandle, type AvatarCallProps, type AvatarCallStatus, AvatarSessionClient, AvatarVideoFit, DEFAULT_AVATAR_UNSTABLE_STALL_MS, useAvatarCall, useCharacterTools };
package/dist/react.js CHANGED
@@ -1038,9 +1038,10 @@ var step = (g, s, nowMs, cfg = DEFAULT_GOVERNOR_CONFIG) => {
1038
1038
  return { governor: g };
1039
1039
  }
1040
1040
  };
1041
- var resolveLowCapQuality = (declaredLayerQualities) => {
1041
+ var resolveLowCapQuality = (declaredLayerQualities, failures = 0) => {
1042
1042
  const sorted = [...declaredLayerQualities].sort((a, b) => a - b);
1043
- return sorted.length >= 2 ? sorted[sorted.length - 2] : 1;
1043
+ if (sorted.length < 2) return 1;
1044
+ return failures >= 1 ? sorted[0] : sorted[sorted.length - 2];
1044
1045
  };
1045
1046
 
1046
1047
  // ../client/src/react/use-quality-governor.ts
@@ -1160,10 +1161,11 @@ function useAvatarQualityGovernor(input) {
1160
1161
  return { freezeMs: 0, jitterRising: false };
1161
1162
  }
1162
1163
  };
1163
- const applyCap = (cap) => {
1164
+ const applyCap = (cap, failures = 0) => {
1164
1165
  try {
1165
1166
  const lowCap = resolveLowCapQuality(
1166
- (targetPublication?.trackInfo?.layers ?? []).map((l) => l.quality)
1167
+ (targetPublication?.trackInfo?.layers ?? []).map((l) => l.quality),
1168
+ failures
1167
1169
  );
1168
1170
  targetPublication?.setVideoQuality?.(cap === "low" ? lowCap : VideoQuality.HIGH);
1169
1171
  } catch {
@@ -1188,13 +1190,13 @@ function useAvatarQualityGovernor(input) {
1188
1190
  pausedSinceTick = false;
1189
1191
  const { governor, action } = step(gov, signal, Date.now(), config);
1190
1192
  gov = governor;
1191
- if (action) applyCap(action.setCap);
1193
+ if (action) applyCap(action.setCap, gov.failures);
1192
1194
  } catch {
1193
1195
  } finally {
1194
1196
  tickRunning = false;
1195
1197
  }
1196
1198
  };
1197
- applyCap(gov.cap);
1199
+ applyCap(gov.cap, gov.failures);
1198
1200
  const handle = setInterval(() => void tick(), tickMs);
1199
1201
  return () => {
1200
1202
  disposed = true;
@@ -1234,9 +1236,48 @@ var FrameRecovery = class {
1234
1236
  return this.ready;
1235
1237
  }
1236
1238
  };
1239
+ var AVATAR_FIRST_FRAME_GRACE_MS = 1e3;
1240
+ function firstFrameWaitFreezeMs(waitedMs) {
1241
+ if (!Number.isFinite(waitedMs) || waitedMs <= AVATAR_FIRST_FRAME_GRACE_MS) return 0;
1242
+ return waitedMs - AVATAR_FIRST_FRAME_GRACE_MS;
1243
+ }
1244
+ var DEFAULT_AVATAR_UNSTABLE_STALL_MS = 4e3;
1245
+ var AVATAR_UNSTABLE_LINK_STALLS = 2;
1246
+ var AVATAR_UNSTABLE_LINK_WINDOW_MS = 15e3;
1247
+ var StallEscalation = class {
1248
+ constructor(baseMs, unstableMs = DEFAULT_AVATAR_UNSTABLE_STALL_MS, windowMs = AVATAR_UNSTABLE_LINK_WINDOW_MS, stallsToEscalate = AVATAR_UNSTABLE_LINK_STALLS) {
1249
+ this.baseMs = baseMs;
1250
+ this.windowMs = windowMs;
1251
+ this.stallsToEscalate = stallsToEscalate;
1252
+ this.unstableMs = Math.max(baseMs, unstableMs);
1253
+ }
1254
+ baseMs;
1255
+ windowMs;
1256
+ stallsToEscalate;
1257
+ stallsAtMs = [];
1258
+ unstableMs;
1259
+ /** Record one stall EPISODE (the moment the live layer is hidden), not one poll tick. */
1260
+ recordStall(nowMs) {
1261
+ this.prune(nowMs);
1262
+ this.stallsAtMs.push(nowMs);
1263
+ }
1264
+ /** The threshold in force right now. */
1265
+ thresholdMs(nowMs) {
1266
+ this.prune(nowMs);
1267
+ return this.stallsAtMs.length >= this.stallsToEscalate ? this.unstableMs : this.baseMs;
1268
+ }
1269
+ /** Is the link currently considered unstable (escalated)? */
1270
+ unstable(nowMs) {
1271
+ return this.thresholdMs(nowMs) !== this.baseMs;
1272
+ }
1273
+ prune(nowMs) {
1274
+ const cutoff = nowMs - this.windowMs;
1275
+ while (this.stallsAtMs.length > 0 && this.stallsAtMs[0] < cutoff) this.stallsAtMs.shift();
1276
+ }
1277
+ };
1237
1278
 
1238
1279
  // ../client/src/react/avatar-video-surface.ts
1239
- var DEFAULT_AVATAR_FRAME_STALL_MS = 800;
1280
+ var DEFAULT_AVATAR_FRAME_STALL_MS = 2e3;
1240
1281
  var AVATAR_FRAME_GAP_FREEZE_FLOOR_MS = 100;
1241
1282
  function AvatarVideoSurface(props) {
1242
1283
  const {
@@ -1437,7 +1478,7 @@ function isFrameFlowingAt(snapshot) {
1437
1478
  }
1438
1479
  function freezeMsFromFrameGap(gapMs) {
1439
1480
  if (!Number.isFinite(gapMs) || gapMs <= AVATAR_FRAME_GAP_FREEZE_FLOOR_MS) return 0;
1440
- return gapMs;
1481
+ return gapMs - AVATAR_FRAME_GAP_FREEZE_FLOOR_MS;
1441
1482
  }
1442
1483
  function isFrameFreezeInhibited(snapshot) {
1443
1484
  return snapshot.hidden || snapshot.resumePending || !snapshot.trackProducing || !snapshot.seenFrame || snapshot.lastFrameAtMs === null;
@@ -1445,6 +1486,7 @@ function isFrameFreezeInhibited(snapshot) {
1445
1486
  function useLiveFrameFlow(wrapRef, trackProducing, trackIdentity, stallAfterMs) {
1446
1487
  const boundedStallMs = normalizeFrameStallMs(stallAfterMs);
1447
1488
  const recovery = useMemo(() => new FrameRecovery(boundedStallMs), [trackIdentity, boundedStallMs]);
1489
+ const escalation = useMemo(() => new StallEscalation(boundedStallMs), [trackIdentity, boundedStallMs]);
1448
1490
  const [flowing, setFlowing] = useState(false);
1449
1491
  const [seenFrame, setSeenFrame] = useState(false);
1450
1492
  const flowingRef = useRef(false);
@@ -1452,14 +1494,16 @@ function useLiveFrameFlow(wrapRef, trackProducing, trackIdentity, stallAfterMs)
1452
1494
  seenFrame: false,
1453
1495
  lastFrameAtMs: null,
1454
1496
  maxGapMs: 0,
1455
- resumePending: false
1497
+ resumePending: false,
1498
+ producingSinceMs: null
1456
1499
  });
1457
1500
  useEffect(() => {
1458
1501
  sampleRef.current = {
1459
1502
  seenFrame: false,
1460
1503
  lastFrameAtMs: null,
1461
1504
  maxGapMs: 0,
1462
- resumePending: document.visibilityState !== "visible"
1505
+ resumePending: document.visibilityState !== "visible",
1506
+ producingSinceMs: trackProducing ? Date.now() : null
1463
1507
  };
1464
1508
  flowingRef.current = false;
1465
1509
  setSeenFrame(false);
@@ -1477,6 +1521,7 @@ function useLiveFrameFlow(wrapRef, trackProducing, trackIdentity, stallAfterMs)
1477
1521
  lastCurrentTime = video?.currentTime ?? lastCurrentTime;
1478
1522
  const firstFrame = !previous.seenFrame;
1479
1523
  sampleRef.current = {
1524
+ producingSinceMs: previous.producingSinceMs,
1480
1525
  seenFrame: true,
1481
1526
  lastFrameAtMs: now,
1482
1527
  // A hidden tab/bfcache resume is a local scheduling gap, not network
@@ -1488,6 +1533,7 @@ function useLiveFrameFlow(wrapRef, trackProducing, trackIdentity, stallAfterMs)
1488
1533
  resumePending: false
1489
1534
  };
1490
1535
  if (firstFrame) setSeenFrame(true);
1536
+ recovery.stallAfterMs = escalation.thresholdMs(now);
1491
1537
  const nextFlowing = recovery.frame(now);
1492
1538
  if (flowingRef.current !== nextFlowing) {
1493
1539
  flowingRef.current = nextFlowing;
@@ -1533,13 +1579,19 @@ function useLiveFrameFlow(wrapRef, trackProducing, trackIdentity, stallAfterMs)
1533
1579
  lastCurrentTime = video.currentTime;
1534
1580
  markFrame();
1535
1581
  }
1582
+ const nowMs = Date.now();
1583
+ const stallAfterMs2 = escalation.thresholdMs(nowMs);
1584
+ recovery.stallAfterMs = stallAfterMs2;
1536
1585
  const frameFresh = isFrameFlowingAt({
1537
1586
  seenFrame: sampleRef.current.seenFrame,
1538
1587
  lastFrameAtMs: sampleRef.current.lastFrameAtMs,
1539
- nowMs: Date.now(),
1540
- stallAfterMs: boundedStallMs
1588
+ nowMs,
1589
+ stallAfterMs: stallAfterMs2
1541
1590
  });
1542
- if (!frameFresh && sampleRef.current.seenFrame) recovery.stall();
1591
+ if (!frameFresh && sampleRef.current.seenFrame) {
1592
+ if (flowingRef.current) escalation.recordStall(nowMs);
1593
+ recovery.stall();
1594
+ }
1543
1595
  const nextFlowing = frameFresh && recovery.ready;
1544
1596
  if (flowingRef.current !== nextFlowing) {
1545
1597
  flowingRef.current = nextFlowing;
@@ -1553,11 +1605,14 @@ function useLiveFrameFlow(wrapRef, trackProducing, trackIdentity, stallAfterMs)
1553
1605
  stopObserving();
1554
1606
  cancelFrameCallback();
1555
1607
  };
1556
- }, [wrapRef, trackProducing, trackIdentity, boundedStallMs, recovery]);
1608
+ }, [wrapRef, trackProducing, trackIdentity, boundedStallMs, recovery, escalation]);
1557
1609
  const freezeReading = useCallback(() => {
1558
1610
  const sample = sampleRef.current;
1559
1611
  const lastFrameAtMs = sample.lastFrameAtMs;
1560
1612
  const hidden = typeof document !== "undefined" && document.visibilityState !== "visible";
1613
+ if (!hidden && !sample.resumePending && trackProducing && !sample.seenFrame && sample.producingSinceMs !== null) {
1614
+ return { freezeMsInWindow: firstFrameWaitFreezeMs(Date.now() - sample.producingSinceMs), inhibited: false };
1615
+ }
1561
1616
  const inhibited = isFrameFreezeInhibited({
1562
1617
  hidden,
1563
1618
  resumePending: sample.resumePending,
@@ -3164,4 +3219,4 @@ function createProxyClient(options) {
3164
3219
  };
3165
3220
  }
3166
3221
 
3167
- export { AdaptivePlayoutController, AvatarCall, AvatarVideoSurface, DEFAULT_APPROACHING_END_LEAD_SECONDS, DEFAULT_AVATAR_PLAYOUT_DELAY_SECONDS, DEFAULT_CREDITS_LOW_LEAD_SECONDS, DEFAULT_GOVERNOR_CONFIG, DEFAULT_GRACE_CEILING_SECONDS, DEFAULT_GRACE_WINDOW_LEAD_SECONDS, DEFAULT_IDLE_SECONDS, DEFAULT_IDLE_WARN_LEAD_SECONDS, DEFAULT_TURN_TIMEOUT_SECONDS, MAX_RECONNECT_ATTEMPTS, MAX_SESSION_INSTRUCTIONS_CHARS, MIC_LEASE_ENDED_TIMEOUT_MS, RECONNECT_BACKOFF_MS, RealtimeAvatarCapacityError, RealtimeAvatarLiveKitRoom, SessionLifecycleRoomBridge, capacityErrorFromBusy, capacityStateFromGrant, createProxyClient, knownBehaviorStates, mapTurnState, readInboundRtp, sessionBehaviorSchema, sessionClipSchema, shouldReplayPendingTurn, splitCallTranscript, useAvatarAdaptivePlayoutDelay, useAvatarCall, useAvatarPlayoutDelay, useAvatarQualityGovernor, useCallTranscript, useCharacterTools, useLiveKitAvatarGrant, useMicLease, useRealtimeSession, useReleaseMicLeaseOnTrackEnded, useSessionLifecycle };
3222
+ export { AdaptivePlayoutController, AvatarCall, AvatarVideoSurface, DEFAULT_APPROACHING_END_LEAD_SECONDS, DEFAULT_AVATAR_PLAYOUT_DELAY_SECONDS, DEFAULT_AVATAR_UNSTABLE_STALL_MS, DEFAULT_CREDITS_LOW_LEAD_SECONDS, DEFAULT_GOVERNOR_CONFIG, DEFAULT_GRACE_CEILING_SECONDS, DEFAULT_GRACE_WINDOW_LEAD_SECONDS, DEFAULT_IDLE_SECONDS, DEFAULT_IDLE_WARN_LEAD_SECONDS, DEFAULT_TURN_TIMEOUT_SECONDS, MAX_RECONNECT_ATTEMPTS, MAX_SESSION_INSTRUCTIONS_CHARS, MIC_LEASE_ENDED_TIMEOUT_MS, RECONNECT_BACKOFF_MS, RealtimeAvatarCapacityError, RealtimeAvatarLiveKitRoom, SessionLifecycleRoomBridge, capacityErrorFromBusy, capacityStateFromGrant, createProxyClient, knownBehaviorStates, mapTurnState, readInboundRtp, sessionBehaviorSchema, sessionClipSchema, shouldReplayPendingTurn, splitCallTranscript, useAvatarAdaptivePlayoutDelay, useAvatarCall, useAvatarPlayoutDelay, useAvatarQualityGovernor, useCallTranscript, useCharacterTools, useLiveKitAvatarGrant, useMicLease, useRealtimeSession, useReleaseMicLeaseOnTrackEnded, useSessionLifecycle };
package/dist/server.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, RealtimeAvatarOptions, StartCallOptions, clipLibraryDeclarationSchema, verifyTranscript } from './index.js';
2
- export { h as Asset, g as AssetKind, A as Avatar, l as AvatarClip, c as AvatarSourceSwap, b as AvatarUpdate, m as CallConnection, C as CallMode, a as CallPolicy, n as CallQueued, o as ClipDeclaration, f as ClipLibrary, d as ClipLibraryDeclaration, p as ClipLibraryPlan, e as ClipLibraryUpdate, q as ClipSource, r as ContextMessage, k as CreditBalance, E as EndCallOptions, s as EndCallReason, i as ListSessionsOptions, L as LoopRedirect, S as StartCallResult, T as TranscriptPayload, j as UsageSession, U as UsageSessionPage, V as VideoPolicy, t as isQueued } from './types-FoJMJimV.js';
2
+ export { h as Asset, g as AssetKind, A as Avatar, l as AvatarClip, c as AvatarSourceSwap, b as AvatarUpdate, m as CallConnection, C as CallMode, a as CallPolicy, n as CallQueued, o as ClipDeclaration, f as ClipLibrary, d as ClipLibraryDeclaration, p as ClipLibraryPlan, e as ClipLibraryUpdate, q as ClipSource, r as ContextMessage, k as CreditBalance, E as EndCallOptions, s as EndCallReason, i as ListSessionsOptions, L as LoopRedirect, S as StartCallResult, T as TranscriptPayload, j as UsageSession, U as UsageSessionPage, V as VideoPolicy, t as isQueued } from './types-nxdohaiH.js';
3
3
  import 'zod';
package/dist/server.js CHANGED
@@ -1 +1 @@
1
- export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, verifyTranscript } from './chunk-MBQ2VKLC.js';
1
+ export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, verifyTranscript } from './chunk-SKXUGNEK.js';
@@ -1,5 +1,5 @@
1
- import { P as ProxyConfig } from './types-DrgyCwLC.js';
2
- import './types-FoJMJimV.js';
1
+ import { P as ProxyConfig } from './types-9a0bRdXl.js';
2
+ import './types-nxdohaiH.js';
3
3
 
4
4
  /**
5
5
  * TanStack Start. Mount at `routes/api/realtime-avatar/$.ts` — the trailing `$` is Start's
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-RVBBZCVM.js';
2
- import './chunk-MBQ2VKLC.js';
1
+ import { createProxyHandler } from './chunk-MJJ6GSAU.js';
2
+ import './chunk-SKXUGNEK.js';
3
3
 
4
4
  // src/tanstack-start.ts
5
5
  function realtimeAvatarServerRoute(config) {
@@ -1,4 +1,4 @@
1
- import { C as CallMode, a as CallPolicy } from './types-FoJMJimV.js';
1
+ import { C as CallMode, a as CallPolicy } from './types-nxdohaiH.js';
2
2
 
3
3
  /** The three things a caller can ask the proxy to do. Gate on these, not on URLs. */
4
4
  type ProxyOperation = "connect" | "end" | "avatars" | "credits";
@@ -645,6 +645,11 @@ interface components {
645
645
  documentation: string;
646
646
  /** @description Set when the failure is worth retrying as-is. */
647
647
  retryable?: boolean;
648
+ /**
649
+ * Format: uuid
650
+ * @description Server-generated correlation ID, also returned in X-Request-ID on correlated failures. Quote this when contacting support. Detailed diagnostics require separate operational access.
651
+ */
652
+ requestId?: string;
648
653
  /**
649
654
  * @description Present on `403 insufficient_scope` — the one scope this operation needed and the key did not carry. Repeated in the `WWW-Authenticate` challenge as `scope=`.
650
655
  * @enum {string}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "realtime-avatar",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
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",