realtime-avatar 0.5.4 → 0.6.1

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-I3BFZHMQ.js';
1
+ import { RealtimeAvatar, isQueued, RealtimeAvatarHttpError } from './chunk-ZRJ6DJKX.js';
2
2
 
3
3
  // ../proxy/src/config.ts
4
4
  var json = (body, status = 200) => new Response(JSON.stringify(body), {
@@ -43,7 +43,7 @@ function isTransient(cause) {
43
43
 
44
44
  // ../http-client/src/client.ts
45
45
  var DEFAULT_BASE_URL = "https://realtimeavatar.ai/api/v1";
46
- var SDK_VERSION = "0.5.4";
46
+ var SDK_VERSION = "0.6.1";
47
47
  var RealtimeAvatar = class {
48
48
  #apiKey;
49
49
  #baseUrl;
@@ -301,7 +301,8 @@ var RealtimeAvatar = class {
301
301
  * concurrent writer surfaces as a 409 instead of a lost update. Omit it to declare
302
302
  * unconditionally.
303
303
  *
304
- * At most 12 clips: one `idle`, up to two `listen`, the rest `gesture`. An uploaded
304
+ * At most 12 clips: up to six `idle`, up to two `listen`, the rest `gesture`. Several
305
+ * idles are a resting ROTATION she drifts between, not alternatives to one. An uploaded
305
306
  * clip (`source: { assetId }`) must start AND end on the avatar's rest pose — pose
306
307
  * validation rejects it otherwise (`status: "failed"`, the verdict in `poseCheck`),
307
308
  * and the rest of the library is untouched.
@@ -541,6 +542,10 @@ function toUsageSessionPage(raw) {
541
542
  sessions: rows.filter(isRecord).map((row) => ({
542
543
  sessionId: String(row.sessionId ?? ""),
543
544
  avatarId: typeof row.avatarId === "string" ? row.avatarId : null,
545
+ // Added to the contract alongside avatarId so a usage row is readable without a
546
+ // second lookup. Defensive like its siblings: an older server that omits it
547
+ // yields null rather than the string "undefined".
548
+ avatarName: typeof row.avatarName === "string" ? row.avatarName : null,
544
549
  status: usageStatus(row.status),
545
550
  startedAt: typeof row.startedAt === "string" ? row.startedAt : null,
546
551
  endedAt: typeof row.endedAt === "string" ? row.endedAt : null,
package/dist/express.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as ProxyConfig } from './types-CHrhuhYb.js';
2
- import './types-BeGuvyra.js';
1
+ import { P as ProxyConfig } from './types-BvVsHgYE.js';
2
+ import './types-BvkgmmnO.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-XZ5DRDND.js';
2
- import './chunk-I3BFZHMQ.js';
1
+ import { createProxyHandler } from './chunk-2Q6BHD2C.js';
2
+ import './chunk-ZRJ6DJKX.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-CHrhuhYb.js';
2
- import './types-BeGuvyra.js';
1
+ import { P as ProxyConfig } from './types-BvVsHgYE.js';
2
+ import './types-BvkgmmnO.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-XZ5DRDND.js';
2
- import './chunk-I3BFZHMQ.js';
1
+ import { createProxyHandler } from './chunk-2Q6BHD2C.js';
2
+ import './chunk-ZRJ6DJKX.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 ClipDeclaration, e as ClipLibraryUpdate, L as LoopRedirect, f as ClipLibrary, g as ClipSyncResult, h as AssetKind, i as Asset, j as ListSessionsOptions, U as UsageSessionPage, k as UsageSession, l as CreditBalance, T as TranscriptPayload } from './types-BeGuvyra.js';
2
- export { m as AvatarClip, n as CallConnection, o as CallQueued, p as ClipLibraryPlan, q as ClipSource, r as ContextMessage, s as EndCallReason, V as VideoPolicy, t as VideoState, u as isQueued } from './types-BeGuvyra.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 ClipDeclaration, e as ClipLibraryUpdate, L as LoopRedirect, f as ClipLibrary, g as ClipSyncResult, h as AssetKind, i as Asset, j as ListSessionsOptions, U as UsageSessionPage, k as UsageSession, l as CreditBalance, T as TranscriptPayload } from './types-BvkgmmnO.js';
2
+ export { m as AvatarClip, n as CallConnection, o as CallQueued, p as ClipLibraryPlan, q as ClipSource, r as ContextMessage, s as EndCallReason, V as VideoPolicy, t as VideoState, u as isQueued } from './types-BvkgmmnO.js';
3
3
 
4
4
  interface RealtimeAvatarOptions {
5
5
  /** `tic_live_…` or `tic_test_…`. Server-side only — never ship this to a browser. */
@@ -182,7 +182,8 @@ declare class RealtimeAvatar {
182
182
  * concurrent writer surfaces as a 409 instead of a lost update. Omit it to declare
183
183
  * unconditionally.
184
184
  *
185
- * At most 12 clips: one `idle`, up to two `listen`, the rest `gesture`. An uploaded
185
+ * At most 12 clips: up to six `idle`, up to two `listen`, the rest `gesture`. Several
186
+ * idles are a resting ROTATION she drifts between, not alternatives to one. An uploaded
186
187
  * clip (`source: { assetId }`) must start AND end on the avatar's rest pose — pose
187
188
  * validation rejects it otherwise (`status: "failed"`, the verdict in `poseCheck`),
188
189
  * and the rest of the library is untouched.
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, isQueued, verifyTranscript } from './chunk-I3BFZHMQ.js';
1
+ export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, isQueued, verifyTranscript } from './chunk-ZRJ6DJKX.js';
package/dist/nextjs.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as ProxyConfig } from './types-CHrhuhYb.js';
2
- import './types-BeGuvyra.js';
1
+ import { P as ProxyConfig } from './types-BvVsHgYE.js';
2
+ import './types-BvkgmmnO.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-XZ5DRDND.js';
2
- import './chunk-I3BFZHMQ.js';
1
+ import { createProxyHandler } from './chunk-2Q6BHD2C.js';
2
+ import './chunk-ZRJ6DJKX.js';
3
3
 
4
4
  // ../proxy/src/nextjs.ts
5
5
  function createRealtimeAvatarRoute(config) {
@@ -1167,6 +1167,20 @@ type UseSessionLifecycleInput<T extends LLMProvider = LLMProvider> = {
1167
1167
  reconnectBackoffMs?: number[];
1168
1168
  /** Auto-reconnect give-up bound. Default {@link MAX_RECONNECT_ATTEMPTS}. */
1169
1169
  maxReconnectAttempts?: number;
1170
+ /**
1171
+ * How long a held grant may sit UNCONNECTED before we give the slot back
1172
+ * (seconds). Default {@link DEFAULT_CONNECT_WATCHDOG_SECONDS}.
1173
+ *
1174
+ * A grant that never reaches a live room is invisible to every other timer here:
1175
+ * the idle clock only runs once connected, and LiveKit reports no disconnect for
1176
+ * a room that was never joined. Without this the slot is held until the PLATFORM's
1177
+ * join timeout (75s at time of writing) — and on a plan whose ceiling is 1, that is
1178
+ * the caller's only slot, so their retry is refused by a call they never saw.
1179
+ * Measured in prod: 8 of 12 never-connected sessions held for 76-81s.
1180
+ *
1181
+ * Set 0 to disable (the platform timeout remains the backstop).
1182
+ */
1183
+ connectWatchdogSeconds?: number;
1170
1184
  /** Forwarded to {@link useLiveKitAvatarGrant}. Defaults true (queue auto-retry). */
1171
1185
  autoRetryBusy?: boolean;
1172
1186
  requestOptions?: UseLiveKitAvatarGrantInput<T>["requestOptions"];
@@ -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-DylBmlgX.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_AVATAR_PLAYOUT_DELAY_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-DylBmlgX.js';
4
+ import { L as LiveKitSessionGrant, A as AvatarVideoFit } from './proxy-client-BIadEcDj.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_AVATAR_PLAYOUT_DELAY_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-BIadEcDj.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 { DisconnectReason, RemoteAudioTrack, RemoteTrack, RemoteVideoTrack, Room, RoomEvent, SendTextOptions, Track } from 'livekit-client';
@@ -358,7 +358,12 @@ function useLiveKitAvatarGrant(input) {
358
358
  setState({ status: "requesting", grant: null, busy: null, error: null });
359
359
  }
360
360
  void client.createLiveKitSessionOrBusy(request, requestOptions).then((result) => {
361
- if (cancelled) return;
361
+ if (cancelled) {
362
+ if (result.status !== "busy") {
363
+ void client.releaseLiveKitSession(result.grant.session_id, "superseded");
364
+ }
365
+ return;
366
+ }
362
367
  if (result.status === "busy") {
363
368
  queueTicketRef.current = result.busy.queue_ticket_id ?? queueTicketRef.current;
364
369
  setState({ status: "busy", grant: null, busy: result.busy, error: null });
@@ -1494,6 +1499,7 @@ function resolveIdleTimeoutMs(args) {
1494
1499
  if (grantIdleSeconds > 0) return grantIdleSeconds * 1e3;
1495
1500
  return DEFAULT_IDLE_SECONDS * 1e3;
1496
1501
  }
1502
+ var DEFAULT_CONNECT_WATCHDOG_SECONDS = 12;
1497
1503
  function resolveWarnBeforeMs(idleTimeoutMs, override, idleWarnLeadSeconds) {
1498
1504
  if (idleTimeoutMs <= 0) return 0;
1499
1505
  const maxLeadMs = Math.max(0, idleTimeoutMs - 1e3);
@@ -1518,6 +1524,7 @@ function useSessionLifecycle(input) {
1518
1524
  reconnectBackoffMs,
1519
1525
  maxReconnectAttempts,
1520
1526
  autoRetryBusy = true,
1527
+ connectWatchdogSeconds,
1521
1528
  requestOptions,
1522
1529
  onBehaviorChange
1523
1530
  } = input;
@@ -1527,6 +1534,7 @@ function useSessionLifecycle(input) {
1527
1534
  );
1528
1535
  const reconnectPolicyRef = useRef(reconnectPolicy);
1529
1536
  reconnectPolicyRef.current = reconnectPolicy;
1537
+ const connectWatchdogMs = typeof connectWatchdogSeconds === "number" && Number.isFinite(connectWatchdogSeconds) ? Math.max(0, Math.floor(connectWatchdogSeconds)) * 1e3 : DEFAULT_CONNECT_WATCHDOG_SECONDS * 1e3;
1530
1538
  const grantState = useLiveKitAvatarGrant({
1531
1539
  client,
1532
1540
  session,
@@ -1685,6 +1693,16 @@ function useSessionLifecycle(input) {
1685
1693
  },
1686
1694
  [active, onConnected]
1687
1695
  );
1696
+ useEffect(() => {
1697
+ if (!active || connected || connectWatchdogMs <= 0) return;
1698
+ if (grantState.status !== "ready" || !grantState.grant) return;
1699
+ if (recovery.kind === "failed" || recovery.kind === "ended") return;
1700
+ const timer = window.setTimeout(() => {
1701
+ releaseRef.current("disconnected");
1702
+ setRecovery({ kind: "reconnecting", attempt: attemptRef.current });
1703
+ }, connectWatchdogMs);
1704
+ return () => window.clearTimeout(timer);
1705
+ }, [active, connected, connectWatchdogMs, grantState.status, grantState.grant, recovery.kind]);
1688
1706
  useEffect(() => {
1689
1707
  if (!active || !needsFreshGrant(recovery)) return;
1690
1708
  const step2 = retryStep(attemptRef.current, reconnectPolicyRef.current);
@@ -1700,6 +1718,7 @@ function useSessionLifecycle(input) {
1700
1718
  attemptRef.current = step2.attempt;
1701
1719
  setAttempt(step2.attempt);
1702
1720
  refreshBaselineCapacityRef.current = capacityRef.current;
1721
+ releaseRef.current("superseded");
1703
1722
  setRecovery({ kind: "refreshing", attempt: step2.attempt });
1704
1723
  refreshRef.current();
1705
1724
  }, step2.delayMs);
@@ -1712,6 +1731,7 @@ function useSessionLifecycle(input) {
1712
1731
  attemptRef.current = 0;
1713
1732
  setAttempt(0);
1714
1733
  refreshBaselineCapacityRef.current = capacityRef.current;
1734
+ releaseRef.current("superseded");
1715
1735
  setRecovery({ kind: "refreshing", attempt: 0 });
1716
1736
  refreshRef.current();
1717
1737
  }, [active, clearTimer, recovery.kind]);
package/dist/react.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ReactNode } from 'react';
2
- import { g as AvatarSessionClient, A as AvatarVideoFit } from './proxy-client-DylBmlgX.js';
3
- export { a as AdaptivePlayoutController, b as AdaptivePlayoutDecision, c as AdaptivePlayoutOptions, d as AdaptivePlayoutSample, e as ApproachingEndEvent, f as ApproachingEndReason, aL as AvatarVideoSurface, aM 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_AVATAR_PLAYOUT_DELAY_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 PlayableVideoElement, _ as ProxyClientOptions, $ as QualityCap, a0 as RECONNECT_BACKOFF_MS, aR as RealtimeAvatarCapacityError, aS as RealtimeAvatarLiveKitRoom, aT 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, 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, av as knownBehaviorStates, aw as mapTurnState, ax as readInboundRtp, ay as sessionBehaviorSchema, az as sessionClipSchema, aU 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-DylBmlgX.js';
2
+ import { g as AvatarSessionClient, A as AvatarVideoFit } from './proxy-client-BIadEcDj.js';
3
+ export { a as AdaptivePlayoutController, b as AdaptivePlayoutDecision, c as AdaptivePlayoutOptions, d as AdaptivePlayoutSample, e as ApproachingEndEvent, f as ApproachingEndReason, aL as AvatarVideoSurface, aM 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_AVATAR_PLAYOUT_DELAY_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 PlayableVideoElement, _ as ProxyClientOptions, $ as QualityCap, a0 as RECONNECT_BACKOFF_MS, aR as RealtimeAvatarCapacityError, aS as RealtimeAvatarLiveKitRoom, aT 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, 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, av as knownBehaviorStates, aw as mapTurnState, ax as readInboundRtp, ay as sessionBehaviorSchema, az as sessionClipSchema, aU 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-BIadEcDj.js';
4
4
  import '@livekit/components-react';
5
5
  import 'livekit-client';
6
6
  import 'zod';
package/dist/react.js CHANGED
@@ -317,7 +317,12 @@ function useLiveKitAvatarGrant(input) {
317
317
  setState({ status: "requesting", grant: null, busy: null, error: null });
318
318
  }
319
319
  void client.createLiveKitSessionOrBusy(request, requestOptions).then((result) => {
320
- if (cancelled) return;
320
+ if (cancelled) {
321
+ if (result.status !== "busy") {
322
+ void client.releaseLiveKitSession(result.grant.session_id, "superseded");
323
+ }
324
+ return;
325
+ }
321
326
  if (result.status === "busy") {
322
327
  queueTicketRef.current = result.busy.queue_ticket_id ?? queueTicketRef.current;
323
328
  setState({ status: "busy", grant: null, busy: result.busy, error: null });
@@ -1717,6 +1722,7 @@ function resolveIdleTimeoutMs(args) {
1717
1722
  if (grantIdleSeconds > 0) return grantIdleSeconds * 1e3;
1718
1723
  return DEFAULT_IDLE_SECONDS * 1e3;
1719
1724
  }
1725
+ var DEFAULT_CONNECT_WATCHDOG_SECONDS = 12;
1720
1726
  function resolveWarnBeforeMs(idleTimeoutMs, override, idleWarnLeadSeconds) {
1721
1727
  if (idleTimeoutMs <= 0) return 0;
1722
1728
  const maxLeadMs = Math.max(0, idleTimeoutMs - 1e3);
@@ -1741,6 +1747,7 @@ function useSessionLifecycle(input) {
1741
1747
  reconnectBackoffMs,
1742
1748
  maxReconnectAttempts,
1743
1749
  autoRetryBusy = true,
1750
+ connectWatchdogSeconds,
1744
1751
  requestOptions,
1745
1752
  onBehaviorChange
1746
1753
  } = input;
@@ -1750,6 +1757,7 @@ function useSessionLifecycle(input) {
1750
1757
  );
1751
1758
  const reconnectPolicyRef = useRef(reconnectPolicy);
1752
1759
  reconnectPolicyRef.current = reconnectPolicy;
1760
+ const connectWatchdogMs = typeof connectWatchdogSeconds === "number" && Number.isFinite(connectWatchdogSeconds) ? Math.max(0, Math.floor(connectWatchdogSeconds)) * 1e3 : DEFAULT_CONNECT_WATCHDOG_SECONDS * 1e3;
1753
1761
  const grantState = useLiveKitAvatarGrant({
1754
1762
  client,
1755
1763
  session,
@@ -1908,6 +1916,16 @@ function useSessionLifecycle(input) {
1908
1916
  },
1909
1917
  [active, onConnected]
1910
1918
  );
1919
+ useEffect(() => {
1920
+ if (!active || connected || connectWatchdogMs <= 0) return;
1921
+ if (grantState.status !== "ready" || !grantState.grant) return;
1922
+ if (recovery.kind === "failed" || recovery.kind === "ended") return;
1923
+ const timer = window.setTimeout(() => {
1924
+ releaseRef.current("disconnected");
1925
+ setRecovery({ kind: "reconnecting", attempt: attemptRef.current });
1926
+ }, connectWatchdogMs);
1927
+ return () => window.clearTimeout(timer);
1928
+ }, [active, connected, connectWatchdogMs, grantState.status, grantState.grant, recovery.kind]);
1911
1929
  useEffect(() => {
1912
1930
  if (!active || !needsFreshGrant(recovery)) return;
1913
1931
  const step2 = retryStep(attemptRef.current, reconnectPolicyRef.current);
@@ -1923,6 +1941,7 @@ function useSessionLifecycle(input) {
1923
1941
  attemptRef.current = step2.attempt;
1924
1942
  setAttempt(step2.attempt);
1925
1943
  refreshBaselineCapacityRef.current = capacityRef.current;
1944
+ releaseRef.current("superseded");
1926
1945
  setRecovery({ kind: "refreshing", attempt: step2.attempt });
1927
1946
  refreshRef.current();
1928
1947
  }, step2.delayMs);
@@ -1935,6 +1954,7 @@ function useSessionLifecycle(input) {
1935
1954
  attemptRef.current = 0;
1936
1955
  setAttempt(0);
1937
1956
  refreshBaselineCapacityRef.current = capacityRef.current;
1957
+ releaseRef.current("superseded");
1938
1958
  setRecovery({ kind: "refreshing", attempt: 0 });
1939
1959
  refreshRef.current();
1940
1960
  }, [active, clearTimer, recovery.kind]);
package/dist/server.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, RealtimeAvatarOptions, StartCallOptions, verifyTranscript } from './index.js';
2
- export { i as Asset, h as AssetKind, A as Avatar, m as AvatarClip, c as AvatarSourceSwap, b as AvatarUpdate, n as CallConnection, C as CallMode, a as CallPolicy, o as CallQueued, d as ClipDeclaration, f as ClipLibrary, p as ClipLibraryPlan, e as ClipLibraryUpdate, q as ClipSource, g as ClipSyncResult, r as ContextMessage, l as CreditBalance, E as EndCallOptions, s as EndCallReason, j as ListSessionsOptions, L as LoopRedirect, S as StartCallResult, T as TranscriptPayload, k as UsageSession, U as UsageSessionPage, V as VideoPolicy, t as VideoState, u as isQueued } from './types-BeGuvyra.js';
2
+ export { i as Asset, h as AssetKind, A as Avatar, m as AvatarClip, c as AvatarSourceSwap, b as AvatarUpdate, n as CallConnection, C as CallMode, a as CallPolicy, o as CallQueued, d as ClipDeclaration, f as ClipLibrary, p as ClipLibraryPlan, e as ClipLibraryUpdate, q as ClipSource, g as ClipSyncResult, r as ContextMessage, l as CreditBalance, E as EndCallOptions, s as EndCallReason, j as ListSessionsOptions, L as LoopRedirect, S as StartCallResult, T as TranscriptPayload, k as UsageSession, U as UsageSessionPage, V as VideoPolicy, t as VideoState, u as isQueued } from './types-BvkgmmnO.js';
package/dist/server.js CHANGED
@@ -1 +1 @@
1
- export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, isQueued, verifyTranscript } from './chunk-I3BFZHMQ.js';
1
+ export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, isQueued, verifyTranscript } from './chunk-ZRJ6DJKX.js';
@@ -1,5 +1,5 @@
1
- import { P as ProxyConfig } from './types-CHrhuhYb.js';
2
- import './types-BeGuvyra.js';
1
+ import { P as ProxyConfig } from './types-BvVsHgYE.js';
2
+ import './types-BvkgmmnO.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-XZ5DRDND.js';
2
- import './chunk-I3BFZHMQ.js';
1
+ import { createProxyHandler } from './chunk-2Q6BHD2C.js';
2
+ import './chunk-ZRJ6DJKX.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-BeGuvyra.js';
1
+ import { C as CallMode, a as CallPolicy } from './types-BvkgmmnO.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";
@@ -144,7 +144,9 @@ interface components {
144
144
  client_metadata?: {
145
145
  [key: string]: string;
146
146
  };
147
+ /** @description DEPRECATED, no-op. Accepted and ignored. The faster hesitation ceiling this requested is now the default for every session, so sending it changes nothing. Safe to stop sending; the field is retained only so existing callers do not break. */
147
148
  fast_endpointing?: boolean;
149
+ require_ready_clip_library?: boolean;
148
150
  };
149
151
  LiveKitSessionGrant: {
150
152
  /**
@@ -268,7 +270,7 @@ interface components {
268
270
  emotion?: string | null;
269
271
  language?: string | null;
270
272
  };
271
- };
273
+ } | unknown | unknown;
272
274
  llm?: {
273
275
  /** @enum {string} */
274
276
  provider: "local" | "gemini" | "openai";
@@ -392,6 +394,7 @@ interface components {
392
394
  data: {
393
395
  sessionId: string;
394
396
  avatarId: string | null;
397
+ avatarName: string | null;
395
398
  /** @enum {string} */
396
399
  status: "reserved" | "started" | "released" | "failed";
397
400
  startedAt: string | null;
@@ -410,6 +413,31 @@ interface components {
410
413
  /** Format: date-time */
411
414
  to: string;
412
415
  };
416
+ ListAvatarsResponse: {
417
+ data: {
418
+ id: string;
419
+ tenantId: string;
420
+ displayName: string;
421
+ /** @enum {string} */
422
+ sourceKind: "image" | "video";
423
+ /** @enum {string} */
424
+ modelId: "realtime-avatar-live-v1" | "realtime-avatar-video-v1";
425
+ sourceAssetId: string | null;
426
+ /** @enum {string} */
427
+ status: "draft" | "preprocessing" | "ready" | "failed" | "disabled" | "deleted";
428
+ error: string | null;
429
+ /** @enum {string} */
430
+ idleVideoStatus: "none" | "queued" | "generating" | "ready" | "failed";
431
+ defaultVoiceId: string | null;
432
+ llm: {
433
+ /** @enum {string} */
434
+ provider: "local" | "gemini" | "openai";
435
+ model?: string | null;
436
+ } | null;
437
+ createdAt: string;
438
+ updatedAt: string;
439
+ }[];
440
+ };
413
441
  ListAvatarClipsResponse: {
414
442
  data: {
415
443
  clipId: string;
@@ -421,6 +449,7 @@ interface components {
421
449
  whenHint: string | null;
422
450
  /** @enum {string} */
423
451
  source: "generated" | "uploaded";
452
+ uploadAssetId: string | null;
424
453
  motionPrompt: string | null;
425
454
  durationSeconds: number | null;
426
455
  anchorVersion: number;
@@ -484,6 +513,7 @@ interface components {
484
513
  whenHint: string | null;
485
514
  /** @enum {string} */
486
515
  source: "generated" | "uploaded";
516
+ uploadAssetId: string | null;
487
517
  motionPrompt: string | null;
488
518
  durationSeconds: number | null;
489
519
  anchorVersion: number;
@@ -546,6 +576,20 @@ interface components {
546
576
  OkResponse: {
547
577
  ok: boolean;
548
578
  };
579
+ Error: {
580
+ /** @description Human-readable message. Not a stable contract — switch on `code` or `status`. */
581
+ error: string;
582
+ /** @description Repeats the HTTP status in the body. Absent on unhandled 500s. */
583
+ status?: number;
584
+ /** @description Machine-readable discriminator, present where the failure has one — billing, clip-library and loop refusals all carry it. Switch on this rather than on `error`, whose wording is not a contract. The set is open and grows with the API; the codes a given endpoint can return are documented with that endpoint at https://realtimeavatar.ai/docs/api-reference. */
585
+ code?: string;
586
+ /** @description Set when the failure is worth retrying as-is. */
587
+ retryable?: boolean;
588
+ /** @description Present on 402 — where to send the user to top up. */
589
+ billingUrl?: string;
590
+ } & {
591
+ [key: string]: unknown;
592
+ };
549
593
  };
550
594
  responses: never;
551
595
  parameters: never;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "realtime-avatar",
3
- "version": "0.5.4",
3
+ "version": "0.6.1",
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",