realtime-avatar 0.6.0 → 0.7.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
@@ -25,6 +25,19 @@ return call.raw; // relay to the browser byte-for-byte
25
25
 
26
26
  That is the whole server half. The client joins with the payload and renders her.
27
27
 
28
+ New here? The [quickstart](https://realtimeavatar.ai/docs/quickstart) goes from an API key to a
29
+ working call, and a [sandbox key](https://realtimeavatar.ai/signup) is free with no card. Mount
30
+ the server half on your framework:
31
+ [Next.js](https://realtimeavatar.ai/docs/nextjs) ·
32
+ [Express](https://realtimeavatar.ai/docs/express) ·
33
+ [Hono, Workers, Bun, Deno](https://realtimeavatar.ai/docs/hono) ·
34
+ [TanStack Start](https://realtimeavatar.ai/docs/tanstack-start), and render it with
35
+ [React or React Native](https://realtimeavatar.ai/docs/react).
36
+
37
+ Using a coding agent? Point it at [`AGENTS.md`](../../AGENTS.md) and give it the
38
+ [MCP server](https://realtimeavatar.ai/docs/mcp) so it can read your real avatar ids instead of
39
+ inventing them.
40
+
28
41
  ---
29
42
 
30
43
  ## What you get
@@ -184,6 +197,14 @@ for building your own queue UI, and the zod schemas `sessionBehaviorSchema` / `s
184
197
  ## Docs and support
185
198
 
186
199
  - Full documentation: <https://realtimeavatar.ai/docs>
200
+ - [Quickstart](https://realtimeavatar.ai/docs/quickstart) — key to a live call
201
+ - [Authentication](https://realtimeavatar.ai/docs/authentication) — what belongs on your server, and why
202
+ - [Calls](https://realtimeavatar.ai/docs/sessions) — what your server decides and what the client reports
203
+ - [Creating an avatar](https://realtimeavatar.ai/docs/video) — one photo in, a moving character out
204
+ - [Tool calling](https://realtimeavatar.ai/docs/tool-calling) — your handlers, your server, no hosted executor
205
+ - [MCP server](https://realtimeavatar.ai/docs/mcp) — give a coding agent the account itself
206
+ - [API reference](https://realtimeavatar.ai/docs/api-reference) · [OpenAPI 3.1](https://realtimeavatar.ai/openapi.json)
207
+ - [Pricing](https://realtimeavatar.ai/pricing) — one meter, seconds on air
187
208
  - Issues and feature requests: this repo
188
209
  - The API is versioned at `/api/v1`; breaking changes get a new version, not a silent edit.
189
210
 
@@ -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.6.0";
46
+ var SDK_VERSION = "0.7.0";
47
47
  var RealtimeAvatar = class {
48
48
  #apiKey;
49
49
  #baseUrl;
@@ -542,6 +542,10 @@ function toUsageSessionPage(raw) {
542
542
  sessions: rows.filter(isRecord).map((row) => ({
543
543
  sessionId: String(row.sessionId ?? ""),
544
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,
545
549
  status: usageStatus(row.status),
546
550
  startedAt: typeof row.startedAt === "string" ? row.startedAt : null,
547
551
  endedAt: typeof row.endedAt === "string" ? row.endedAt : null,
@@ -1,4 +1,4 @@
1
- import { RealtimeAvatar, isQueued, RealtimeAvatarHttpError } from './chunk-GKV42PSY.js';
1
+ import { RealtimeAvatar, isQueued, RealtimeAvatarHttpError } from './chunk-OP32WHW7.js';
2
2
 
3
3
  // ../proxy/src/config.ts
4
4
  var json = (body, status = 200) => new Response(JSON.stringify(body), {
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-NKCoqM8E.js';
2
+ import './types-ZfY6oxQY.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-JLQWZF6B.js';
2
- import './chunk-GKV42PSY.js';
1
+ import { createProxyHandler } from './chunk-YRY3QUTC.js';
2
+ import './chunk-OP32WHW7.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-NKCoqM8E.js';
2
+ import './types-ZfY6oxQY.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-JLQWZF6B.js';
2
- import './chunk-GKV42PSY.js';
1
+ import { createProxyHandler } from './chunk-YRY3QUTC.js';
2
+ import './chunk-OP32WHW7.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-ZfY6oxQY.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-ZfY6oxQY.js';
3
3
 
4
4
  interface RealtimeAvatarOptions {
5
5
  /** `tic_live_…` or `tic_test_…`. Server-side only — never ship this to a browser. */
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, isQueued, verifyTranscript } from './chunk-GKV42PSY.js';
1
+ export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, isQueued, verifyTranscript } from './chunk-OP32WHW7.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-NKCoqM8E.js';
2
+ import './types-ZfY6oxQY.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-JLQWZF6B.js';
2
- import './chunk-GKV42PSY.js';
1
+ import { createProxyHandler } from './chunk-YRY3QUTC.js';
2
+ import './chunk-OP32WHW7.js';
3
3
 
4
4
  // ../proxy/src/nextjs.ts
5
5
  function createRealtimeAvatarRoute(config) {
@@ -112,7 +112,7 @@ type SessionBehavior = z.infer<typeof sessionBehaviorSchema>;
112
112
  * The cap the mint enforces on `instructions`. Exported so an app can budget its prompt
113
113
  * assembly against the real number instead of probing for it with a binary search.
114
114
  */
115
- declare const MAX_SESSION_INSTRUCTIONS_CHARS = 4000;
115
+ declare const MAX_SESSION_INSTRUCTIONS_CHARS = 8000;
116
116
  declare const liveKitSessionRequestSchema: z.ZodObject<{
117
117
  avatarId: z.ZodString;
118
118
  backgroundId: z.ZodDefault<z.ZodString>;
@@ -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-BzblQMvg.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-BzblQMvg.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 });
@@ -1156,7 +1161,7 @@ var transcriptWebhookSchema = z.object({
1156
1161
  var clientMetadataSchema = z.record(z.string().min(1).max(64), z.string().max(200)).refine((value) => Object.keys(value).length <= 16, {
1157
1162
  message: "client_metadata carries at most 16 entries"
1158
1163
  });
1159
- var MAX_SESSION_INSTRUCTIONS_CHARS = 4e3;
1164
+ var MAX_SESSION_INSTRUCTIONS_CHARS = 8e3;
1160
1165
  z.object({
1161
1166
  avatar_id: z.string().min(1).max(160).default(DEFAULT_AVATAR_ID),
1162
1167
  background_id: z.string().min(1).max(160).default(DEFAULT_BACKGROUND_ID),
@@ -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-BzblQMvg.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-BzblQMvg.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 });
@@ -1374,7 +1379,7 @@ var transcriptWebhookSchema = z.object({
1374
1379
  var clientMetadataSchema = z.record(z.string().min(1).max(64), z.string().max(200)).refine((value) => Object.keys(value).length <= 16, {
1375
1380
  message: "client_metadata carries at most 16 entries"
1376
1381
  });
1377
- var MAX_SESSION_INSTRUCTIONS_CHARS = 4e3;
1382
+ var MAX_SESSION_INSTRUCTIONS_CHARS = 8e3;
1378
1383
  z.object({
1379
1384
  avatar_id: z.string().min(1).max(160).default(DEFAULT_AVATAR_ID),
1380
1385
  background_id: z.string().min(1).max(160).default(DEFAULT_BACKGROUND_ID),
@@ -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-ZfY6oxQY.js';
package/dist/server.js CHANGED
@@ -1 +1 @@
1
- export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, isQueued, verifyTranscript } from './chunk-GKV42PSY.js';
1
+ export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, isQueued, verifyTranscript } from './chunk-OP32WHW7.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-NKCoqM8E.js';
2
+ import './types-ZfY6oxQY.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-JLQWZF6B.js';
2
- import './chunk-GKV42PSY.js';
1
+ import { createProxyHandler } from './chunk-YRY3QUTC.js';
2
+ import './chunk-OP32WHW7.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-ZfY6oxQY.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";
@@ -1,7 +1,6 @@
1
1
  interface components {
2
2
  schemas: {
3
3
  LiveKitSessionRequest: {
4
- /** @default maria */
5
4
  avatar_id: string;
6
5
  /** @default plain_white */
7
6
  background_id: string;
@@ -144,7 +143,9 @@ interface components {
144
143
  client_metadata?: {
145
144
  [key: string]: string;
146
145
  };
146
+ /** @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
147
  fast_endpointing?: boolean;
148
+ require_ready_clip_library?: boolean;
148
149
  };
149
150
  LiveKitSessionGrant: {
150
151
  /**
@@ -248,11 +249,15 @@ interface components {
248
249
  CreateAvatarRequest: {
249
250
  displayName: string;
250
251
  /**
252
+ * @description `image` is how avatars are created: upload a portrait, and the platform generates the looping source video and the motion library from it. `video` is CLOSED — it survives in this enum for a handful of grandfathered tenants, and `createAvatar` answers everyone else `422` ("Creating from a supplied video is closed"). The gate is a per-tenant flag that fails closed, so treat `video` as unavailable unless you already know yours is on.
251
253
  * @default image
252
254
  * @enum {string}
253
255
  */
254
256
  sourceKind: "image" | "video";
255
- /** @enum {string} */
257
+ /**
258
+ * @description Omit it. This is not a choice between renderers — the model is DERIVED from `sourceKind` (`image` → `realtime-avatar-live-v1`, `video` → `realtime-avatar-video-v1`), and sending one that disagrees with your `sourceKind` is `422 Avatar model is not compatible with its source kind`. So an explicit value can only ever restate what the source already decided, or fail. It follows that `realtime-avatar-video-v1` is unreachable on the `image` lane everyone uses: it needs `sourceKind: "video"`, which is closed.
259
+ * @enum {string}
260
+ */
256
261
  modelId?: "realtime-avatar-live-v1" | "realtime-avatar-video-v1";
257
262
  sourceAssetId?: string;
258
263
  motionPrompt?: string;
@@ -268,7 +273,7 @@ interface components {
268
273
  emotion?: string | null;
269
274
  language?: string | null;
270
275
  };
271
- };
276
+ } | unknown | unknown;
272
277
  llm?: {
273
278
  /** @enum {string} */
274
279
  provider: "local" | "gemini" | "openai";
@@ -312,7 +317,10 @@ interface components {
312
317
  artDirection?: string;
313
318
  /** @enum {string} */
314
319
  stylePreset?: "cinematic-founder" | "editorial-companion" | "warm-anime" | "luxury-realism" | "soft-3d" | "noir-avatar";
315
- /** Format: uri */
320
+ /**
321
+ * Format: uri
322
+ * @description Dashboard-only. `updateAvatar` needs a request/env context to pull the image into tenant storage, and the public route does not supply one, so sending this here is always `400 Portrait updates are not available on this route`. To change the portrait over this API, register the image with `POST /v1/assets` or `POST /v1/assets/remote` and PATCH the returned id as `sourceAssetId`, which is the swap lane and does work.
323
+ */
316
324
  portraitUrl?: string;
317
325
  sourceAssetId?: string;
318
326
  anchorTimeMs?: number;
@@ -392,6 +400,7 @@ interface components {
392
400
  data: {
393
401
  sessionId: string;
394
402
  avatarId: string | null;
403
+ avatarName: string | null;
395
404
  /** @enum {string} */
396
405
  status: "reserved" | "started" | "released" | "failed";
397
406
  startedAt: string | null;
@@ -410,6 +419,31 @@ interface components {
410
419
  /** Format: date-time */
411
420
  to: string;
412
421
  };
422
+ ListAvatarsResponse: {
423
+ data: {
424
+ id: string;
425
+ tenantId: string;
426
+ displayName: string;
427
+ /** @enum {string} */
428
+ sourceKind: "image" | "video";
429
+ /** @enum {string} */
430
+ modelId: "realtime-avatar-live-v1" | "realtime-avatar-video-v1";
431
+ sourceAssetId: string | null;
432
+ /** @enum {string} */
433
+ status: "draft" | "preprocessing" | "ready" | "failed" | "disabled" | "deleted";
434
+ error: string | null;
435
+ /** @enum {string} */
436
+ idleVideoStatus: "none" | "queued" | "generating" | "ready" | "failed";
437
+ defaultVoiceId: string | null;
438
+ llm: {
439
+ /** @enum {string} */
440
+ provider: "local" | "gemini" | "openai";
441
+ model?: string | null;
442
+ } | null;
443
+ createdAt: string;
444
+ updatedAt: string;
445
+ }[];
446
+ };
413
447
  ListAvatarClipsResponse: {
414
448
  data: {
415
449
  clipId: string;
@@ -421,6 +455,7 @@ interface components {
421
455
  whenHint: string | null;
422
456
  /** @enum {string} */
423
457
  source: "generated" | "uploaded";
458
+ uploadAssetId: string | null;
424
459
  motionPrompt: string | null;
425
460
  durationSeconds: number | null;
426
461
  anchorVersion: number;
@@ -484,6 +519,7 @@ interface components {
484
519
  whenHint: string | null;
485
520
  /** @enum {string} */
486
521
  source: "generated" | "uploaded";
522
+ uploadAssetId: string | null;
487
523
  motionPrompt: string | null;
488
524
  durationSeconds: number | null;
489
525
  anchorVersion: number;
@@ -546,6 +582,20 @@ interface components {
546
582
  OkResponse: {
547
583
  ok: boolean;
548
584
  };
585
+ Error: {
586
+ /** @description Human-readable message. Not a stable contract — switch on `code` or `status`. */
587
+ error: string;
588
+ /** @description Repeats the HTTP status in the body. Absent on unhandled 500s. */
589
+ status?: number;
590
+ /** @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. */
591
+ code?: string;
592
+ /** @description Set when the failure is worth retrying as-is. */
593
+ retryable?: boolean;
594
+ /** @description Present on 402 — where to send the user to top up. */
595
+ billingUrl?: string;
596
+ } & {
597
+ [key: string]: unknown;
598
+ };
549
599
  };
550
600
  responses: never;
551
601
  parameters: never;
@@ -721,7 +771,7 @@ type VideoPolicy = {
721
771
  };
722
772
  /** What YOUR SERVER decides about a call. Never accept any of this from a browser. */
723
773
  interface CallPolicy {
724
- /** Her behavior contract — who she is and how she speaks. Max 4000 chars. */
774
+ /** Her behavior contract — who she is and how she speaks. Max 8000 chars. */
725
775
  instructions?: string;
726
776
  /** Up to 32 prior messages, replayed as memory. */
727
777
  context?: readonly ContextMessage[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "realtime-avatar",
3
- "version": "0.6.0",
3
+ "version": "0.7.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",