realtime-avatar 0.27.0 → 0.27.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.
- package/README.md +22 -0
- package/dist/{chunk-NMJQEGYW.js → chunk-4IEGR75H.js} +1 -1
- package/dist/{chunk-SUKZ77GL.js → chunk-DOHZ365B.js} +1 -1
- package/dist/express.js +2 -2
- package/dist/hono.js +2 -2
- package/dist/index.js +1 -1
- package/dist/nextjs.js +2 -2
- package/dist/{proxy-client-D75TLU5X.d.ts → proxy-client-Cs2azqD1.d.ts} +24 -5
- package/dist/react-native.d.ts +2 -2
- package/dist/react-native.js +162 -34
- package/dist/react.d.ts +2 -2
- package/dist/react.js +162 -34
- package/dist/server.js +1 -1
- package/dist/tanstack-start.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -353,6 +353,28 @@ a last line first.
|
|
|
353
353
|
On the lower-level hooks, `reconnect()` redials (a double tap mints once), and so does setting
|
|
354
354
|
`active` to false and back. `reset()` does not undo an end.
|
|
355
355
|
|
|
356
|
+
### Waiting for the character
|
|
357
|
+
|
|
358
|
+
Joining the room is not enough to start a conversation: the character's agent must also
|
|
359
|
+
arrive. The shared React and React Native lifecycle allows at most 30 seconds from the first
|
|
360
|
+
held grant to that readiness, including automatic retries and signaling reconnects. The
|
|
361
|
+
existing connect watchdog (12 seconds by default) also recovers a connected room whose agent
|
|
362
|
+
never arrives. A transport connection alone does not reset the retry count or buy more time.
|
|
363
|
+
|
|
364
|
+
When the budget or retry count runs out, the SDK releases the session, leaves the room and
|
|
365
|
+
ends with `phase.reason: "error"` / `onEnded({ reason: "failed" })`. The optional `phase.code`
|
|
366
|
+
distinguishes `agent_timeout` from `connection_timeout`; these are client observations, not
|
|
367
|
+
HTTP status codes. `agent_timeout` means at least one room connected but the call never became
|
|
368
|
+
ready; retries may also have transport delays. `connection_timeout` means no room connected.
|
|
369
|
+
While connecting, `phase.waitingFor` distinguishes `agent` from `transport`.
|
|
370
|
+
No automatic mint follows that failure; `reconnect()` starts a fresh budget. A call that
|
|
371
|
+
already reached live keeps LiveKit's normal recovery of its existing room.
|
|
372
|
+
|
|
373
|
+
On `useRealtimeSession` or `useSessionLifecycle`, `readyTimeoutSeconds` changes the total
|
|
374
|
+
budget (positive, default 30). `connectWatchdogSeconds` changes the per-attempt wait; disabling
|
|
375
|
+
that watchdog with zero still leaves the total budget in force. Neither setting fabricates
|
|
376
|
+
media readiness or changes the application's first-video-frame gate.
|
|
377
|
+
|
|
356
378
|
### Optional connection details
|
|
357
379
|
|
|
358
380
|
`AvatarCall` provides call status, actions and end reasons for your default UI. To show
|
|
@@ -665,7 +665,7 @@ var liveKitSessionReleaseReasonSchema = z.enum([
|
|
|
665
665
|
|
|
666
666
|
// ../http-client/src/client.ts
|
|
667
667
|
var DEFAULT_BASE_URL = "https://realtimeavatar.ai/api/v1";
|
|
668
|
-
var SDK_VERSION = "0.27.
|
|
668
|
+
var SDK_VERSION = "0.27.1";
|
|
669
669
|
var RealtimeAvatar = class {
|
|
670
670
|
#apiKey;
|
|
671
671
|
#baseUrl;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sessionModeSchema, liveKitSessionReleaseReasonSchema, RealtimeAvatar, ROUTE_TIMEOUT_MS, isQueued, RealtimeAvatarHttpError, RealtimeAvatarError } from './chunk-
|
|
1
|
+
import { sessionModeSchema, liveKitSessionReleaseReasonSchema, RealtimeAvatar, ROUTE_TIMEOUT_MS, isQueued, RealtimeAvatarHttpError, RealtimeAvatarError } from './chunk-4IEGR75H.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
4
|
// ../http-client/src/generated/error-semantics.ts
|
package/dist/express.js
CHANGED
package/dist/hono.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, listRecordingsResponseSchema, recordingAccessResponseSchema, recordingArtifactSchema, recordingModeSchema, verifyTranscript } from './chunk-
|
|
1
|
+
export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, listRecordingsResponseSchema, recordingAccessResponseSchema, recordingArtifactSchema, recordingModeSchema, verifyTranscript } from './chunk-4IEGR75H.js';
|
|
2
2
|
import './chunk-MCIXU6XP.js';
|
package/dist/nextjs.js
CHANGED
|
@@ -1370,6 +1370,9 @@ type ReconnectPolicy = {
|
|
|
1370
1370
|
* {@link SessionLifecycleApi.reconnect}.
|
|
1371
1371
|
*/
|
|
1372
1372
|
type SessionEndReason = "idle" | "disconnected" | "error" | "user";
|
|
1373
|
+
/** Client-observed readiness failure, not an HTTP/provider error code. `agent_timeout` means
|
|
1374
|
+
* at least one room connected without reaching agent readiness; retries may also have transport delays. */
|
|
1375
|
+
type SessionReadinessFailure = "agent_timeout" | "connection_timeout";
|
|
1373
1376
|
type SessionLifecyclePhase = {
|
|
1374
1377
|
kind: "idle";
|
|
1375
1378
|
} | {
|
|
@@ -1380,6 +1383,7 @@ type SessionLifecyclePhase = {
|
|
|
1380
1383
|
} | {
|
|
1381
1384
|
kind: "connecting";
|
|
1382
1385
|
grant: LiveKitSessionGrant;
|
|
1386
|
+
waitingFor?: "transport" | "agent";
|
|
1383
1387
|
} | {
|
|
1384
1388
|
kind: "live";
|
|
1385
1389
|
} | {
|
|
@@ -1392,9 +1396,11 @@ type SessionLifecyclePhase = {
|
|
|
1392
1396
|
attempt: number;
|
|
1393
1397
|
/** Native LiveKit recovery keeps the same grant; fresh-grant recovery re-mints. */
|
|
1394
1398
|
strategy: "in-place" | "fresh-grant";
|
|
1399
|
+
reason?: SessionReadinessFailure;
|
|
1395
1400
|
} | {
|
|
1396
1401
|
kind: "ended";
|
|
1397
1402
|
reason?: SessionEndReason;
|
|
1403
|
+
code?: SessionReadinessFailure;
|
|
1398
1404
|
};
|
|
1399
1405
|
type SessionLifecyclePhaseKind = SessionLifecyclePhase["kind"];
|
|
1400
1406
|
/**
|
|
@@ -1409,14 +1415,18 @@ type RecoveryState = {
|
|
|
1409
1415
|
} | {
|
|
1410
1416
|
kind: "refreshing";
|
|
1411
1417
|
attempt: number;
|
|
1418
|
+
reason?: SessionReadinessFailure;
|
|
1412
1419
|
} | {
|
|
1413
1420
|
kind: "reconnecting";
|
|
1414
1421
|
attempt: number;
|
|
1422
|
+
reason?: SessionReadinessFailure;
|
|
1415
1423
|
} | {
|
|
1416
1424
|
kind: "failed";
|
|
1425
|
+
reason?: SessionReadinessFailure;
|
|
1417
1426
|
} | {
|
|
1418
1427
|
kind: "ended";
|
|
1419
1428
|
reason?: SessionEndReason;
|
|
1429
|
+
code?: SessionReadinessFailure;
|
|
1420
1430
|
};
|
|
1421
1431
|
/**
|
|
1422
1432
|
* The CLIENT-OWNED idle budget (seconds): how long "quiet" before the client-
|
|
@@ -1466,8 +1476,8 @@ type UseSessionLifecycleInput<T extends LLMProvider = LLMProvider> = {
|
|
|
1466
1476
|
/** Auto-reconnect give-up bound. Default {@link MAX_RECONNECT_ATTEMPTS}. */
|
|
1467
1477
|
maxReconnectAttempts?: number;
|
|
1468
1478
|
/**
|
|
1469
|
-
*
|
|
1470
|
-
* (seconds). Default {@link DEFAULT_CONNECT_WATCHDOG_SECONDS}.
|
|
1479
|
+
* Per-attempt wait for the room connection, then for its first agent, before giving the
|
|
1480
|
+
* slot back (seconds). Default {@link DEFAULT_CONNECT_WATCHDOG_SECONDS}.
|
|
1471
1481
|
*
|
|
1472
1482
|
* A grant that never reaches a live room is invisible to every other timer here:
|
|
1473
1483
|
* the idle clock only runs once connected, and LiveKit reports no disconnect for
|
|
@@ -1476,9 +1486,16 @@ type UseSessionLifecycleInput<T extends LLMProvider = LLMProvider> = {
|
|
|
1476
1486
|
* the caller's only slot, so their retry is refused by a call they never saw.
|
|
1477
1487
|
* Measured in prod: 8 of 12 never-connected sessions held for 76-81s.
|
|
1478
1488
|
*
|
|
1479
|
-
* Set 0 to disable
|
|
1489
|
+
* Set 0 to disable per-attempt recovery; the total readiness budget still applies.
|
|
1480
1490
|
*/
|
|
1481
1491
|
connectWatchdogSeconds?: number;
|
|
1492
|
+
/**
|
|
1493
|
+
* Total pre-live readiness budget from the first held grant, including automatic retries.
|
|
1494
|
+
* A transport connection alone never resets it. Default {@link DEFAULT_READY_TIMEOUT_SECONDS}.
|
|
1495
|
+
* Non-positive/non-finite overrides use the default. Explicit reconnect starts a new budget.
|
|
1496
|
+
* The existing connect watchdog also bounds each connected room's wait for its first agent.
|
|
1497
|
+
*/
|
|
1498
|
+
readyTimeoutSeconds?: number;
|
|
1482
1499
|
/** Forwarded to {@link useLiveKitAvatarGrant}. Defaults true (queue auto-retry). */
|
|
1483
1500
|
autoRetryBusy?: boolean;
|
|
1484
1501
|
requestOptions?: UseLiveKitAvatarGrantInput<T>["requestOptions"];
|
|
@@ -1499,7 +1516,7 @@ type SessionLifecycleApi = {
|
|
|
1499
1516
|
grant: LiveKitSessionGrant | null;
|
|
1500
1517
|
/** The underlying capacity signal (queue position/size, error) — unchanged. */
|
|
1501
1518
|
capacity: LiveKitCapacityState;
|
|
1502
|
-
/** The DOM recovery contract: auto-reconnect attempts since the last
|
|
1519
|
+
/** The DOM recovery contract: auto-reconnect attempts since the last room + agent readiness. */
|
|
1503
1520
|
attempt: number;
|
|
1504
1521
|
/**
|
|
1505
1522
|
* ms until the CLIENT-ENFORCED idle end, or null when not connected/live. Drives
|
|
@@ -1558,6 +1575,8 @@ type SessionLifecycleApi = {
|
|
|
1558
1575
|
/** Reset to idle (mode/avatar change). Does not undo {@link end}: an ended call stays ended. */
|
|
1559
1576
|
reset: () => void;
|
|
1560
1577
|
};
|
|
1578
|
+
/** Maximum automatic startup wait across grants; successful room + agent readiness ends it. */
|
|
1579
|
+
declare const DEFAULT_READY_TIMEOUT_SECONDS = 30;
|
|
1561
1580
|
declare function useSessionLifecycle<T extends LLMProvider = LLMProvider>(input: UseSessionLifecycleInput<T>): SessionLifecycleApi;
|
|
1562
1581
|
/**
|
|
1563
1582
|
* The optional realtime-session sinks the bridge ALSO fills when given the richer
|
|
@@ -2026,4 +2045,4 @@ interface ProxyClientOptions {
|
|
|
2026
2045
|
}
|
|
2027
2046
|
declare function createProxyClient(options: ProxyClientOptions): AvatarSessionClient;
|
|
2028
2047
|
|
|
2029
|
-
export {
|
|
2048
|
+
export { MAX_SESSION_INSTRUCTIONS_CHARS as $, type AvatarVideoFit as A, type BehaviorSnapshot as B, type CallTranscript as C, DEFAULT_APPROACHING_END_LEAD_SECONDS as D, type EndReason as E, type FishTtsModel as F, type Governor as G, type GovernorConfig as H, type GovernorSignal as I, type GovernorState as J, type GraceWindowClosedEvent as K, type LiveKitSessionGrant as L, type GraceWindowOpenEvent as M, type GraceWindowState as N, type IdleWarningEvent as O, type InboundRtpCursor as P, type InboundRtpReading as Q, type KnownBehaviorState as R, type LLMProvider as S, type LLMSelection as T, type LiveKitAvatarGrantState as U, type LiveKitAvatarGrantStatus as V, type LiveKitCapacityState as W, type LiveKitConnectionStatus as X, type LiveKitSessionRequest as Y, type LiveKitSessionStartResult as Z, MAX_RECONNECT_ATTEMPTS as _, AdaptivePlayoutController as a, type NetworkQualityStatus as a$, type ProxyClientOptions as a0, type QualityCap as a1, RECONNECT_BACKOFF_MS as a2, type RealtimeAvatarRequestOptions as a3, type RealtimeSessionApi as a4, type RealtimeSessionMedia as a5, type RealtimeSessionRoomSinks as a6, type ReconnectPolicy as a7, type ReconnectingEvent as a8, type RecoveryState as a9, isNativeLiveTrackSubscribed as aA, knownBehaviorStates as aB, mapTurnState as aC, readInboundRtp as aD, sessionBehaviorSchema as aE, sessionClipSchema as aF, splitCallTranscript as aG, useAvatarAdaptivePlayoutDelay as aH, useAvatarCamera as aI, useAvatarPlayoutDelay as aJ, useAvatarQualityGovernor as aK, useCallTranscript as aL, useLiveKitAvatarGrant as aM, useLiveTrackProducing as aN, useMicLease as aO, useRealtimeSession as aP, useReleaseMicLeaseOnTrackEnded as aQ, useSessionLifecycle as aR, type CallMicrophone as aS, type CallAudioPlayback as aT, type CallMicrophoneProblem as aU, AvatarVideoSurface as aV, type AvatarVideoSurfaceProps as aW, type LivePlaybackKeeper as aX, MIC_LEASE_ENDED_TIMEOUT_MS as aY, type MicLease as aZ, NETWORK_QUALITY_POLICY as a_, type RetryStep as aa, type SendTurnOptions as ab, type SessionBehavior as ac, type SessionClip as ad, type SessionClocks as ae, type SessionEndReason as af, type SessionLifecycleApi as ag, type SessionLifecyclePhase as ah, type SessionLifecyclePhaseKind as ai, SessionLifecycleRoomBridge as aj, type SessionLifecycleRoomBridgeProps as ak, type SessionReadinessFailure as al, type SurfaceLayers as am, type TranscriptSender as an, type TranscriptSenderOptions as ao, type TurnState as ap, type TurnTimeoutEvent as aq, type UseAvatarQualityGovernorInput as ar, type UseLiveKitAvatarGrantInput as as, type UseRealtimeSessionInput as at, type UseSessionLifecycleInput as au, type VoiceSpec as av, type VoiceSpecInput as aw, capacityErrorFromBusy as ax, capacityStateFromGrant as ay, createProxyClient as az, type AdaptivePlayoutDecision as b, type PlayableVideoElement as b0, RealtimeAvatarCapacityError as b1, RealtimeAvatarLiveKitRoom as b2, type RealtimeAvatarLiveKitRoomProps as b3, shouldReplayPendingTurn as b4, type AdaptivePlayoutOptions as c, type AdaptivePlayoutSample as d, type ApproachingEndEvent as e, type ApproachingEndReason as f, type AvatarConnectionDetails as g, type AvatarSessionClient as h, type CallTranscriptSegment as i, type CartesiaTtsModel as j, type ClipResult as k, type ClosingTurnResult as l, type CreditsLowEvent as m, DEFAULT_CREDITS_LOW_LEAD_SECONDS as n, DEFAULT_GOVERNOR_CONFIG as o, DEFAULT_GRACE_CEILING_SECONDS as p, DEFAULT_GRACE_WINDOW_LEAD_SECONDS as q, DEFAULT_IDLE_SECONDS as r, DEFAULT_IDLE_WARN_LEAD_SECONDS as s, DEFAULT_READY_TIMEOUT_SECONDS as t, DEFAULT_TURN_TIMEOUT_SECONDS as u, type DeclaredInputSource as v, type EndedEvent as w, type ExtendResult as x, type FreezeReadingFn as y, type GovernorAction as z };
|
package/dist/react-native.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { LiveKitRoomProps } from '@livekit/react-native';
|
|
2
2
|
export { AudioSession, VideoTrack, VideoTrackProps, registerGlobals } from '@livekit/react-native';
|
|
3
|
-
import { L as LiveKitSessionGrant, A as AvatarVideoFit } from './proxy-client-
|
|
4
|
-
export { a as AdaptivePlayoutController, b as AdaptivePlayoutDecision, c as AdaptivePlayoutOptions, d as AdaptivePlayoutSample, e as ApproachingEndEvent, f as ApproachingEndReason, g as AvatarConnectionDetails, h as AvatarSessionClient, B as BehaviorSnapshot, C as CallTranscript, i as CallTranscriptSegment, j as CartesiaTtsModel, k as ClipResult, l as ClosingTurnResult, m as CreditsLowEvent, D as DEFAULT_APPROACHING_END_LEAD_SECONDS, n as DEFAULT_CREDITS_LOW_LEAD_SECONDS, o as DEFAULT_GOVERNOR_CONFIG, p as DEFAULT_GRACE_CEILING_SECONDS, q as DEFAULT_GRACE_WINDOW_LEAD_SECONDS, r as DEFAULT_IDLE_SECONDS, s as DEFAULT_IDLE_WARN_LEAD_SECONDS, t as
|
|
3
|
+
import { L as LiveKitSessionGrant, A as AvatarVideoFit } from './proxy-client-Cs2azqD1.js';
|
|
4
|
+
export { a as AdaptivePlayoutController, b as AdaptivePlayoutDecision, c as AdaptivePlayoutOptions, d as AdaptivePlayoutSample, e as ApproachingEndEvent, f as ApproachingEndReason, g as AvatarConnectionDetails, h as AvatarSessionClient, B as BehaviorSnapshot, C as CallTranscript, i as CallTranscriptSegment, j as CartesiaTtsModel, k as ClipResult, l as ClosingTurnResult, m as CreditsLowEvent, D as DEFAULT_APPROACHING_END_LEAD_SECONDS, n as DEFAULT_CREDITS_LOW_LEAD_SECONDS, o as DEFAULT_GOVERNOR_CONFIG, p as DEFAULT_GRACE_CEILING_SECONDS, q as DEFAULT_GRACE_WINDOW_LEAD_SECONDS, r as DEFAULT_IDLE_SECONDS, s as DEFAULT_IDLE_WARN_LEAD_SECONDS, t as DEFAULT_READY_TIMEOUT_SECONDS, u as DEFAULT_TURN_TIMEOUT_SECONDS, v as DeclaredInputSource, E as EndReason, w as EndedEvent, x as ExtendResult, F as FishTtsModel, y as FreezeReadingFn, G as Governor, z as GovernorAction, H as GovernorConfig, I as GovernorSignal, J as GovernorState, K as GraceWindowClosedEvent, M as GraceWindowOpenEvent, N as GraceWindowState, O as IdleWarningEvent, P as InboundRtpCursor, Q as InboundRtpReading, R as KnownBehaviorState, S as LLMProvider, T as LLMSelection, U as LiveKitAvatarGrantState, V as LiveKitAvatarGrantStatus, W as LiveKitCapacityState, X as LiveKitConnectionStatus, Y as LiveKitSessionRequest, Z as LiveKitSessionStartResult, _ as MAX_RECONNECT_ATTEMPTS, $ as MAX_SESSION_INSTRUCTIONS_CHARS, a0 as ProxyClientOptions, a1 as QualityCap, a2 as RECONNECT_BACKOFF_MS, a3 as RealtimeAvatarRequestOptions, a4 as RealtimeSessionApi, a5 as RealtimeSessionMedia, a6 as RealtimeSessionRoomSinks, a7 as ReconnectPolicy, a8 as ReconnectingEvent, a9 as RecoveryState, aa as RetryStep, ab as SendTurnOptions, ac as SessionBehavior, ad as SessionClip, ae as SessionClocks, af as SessionEndReason, ag as SessionLifecycleApi, ah as SessionLifecyclePhase, ai as SessionLifecyclePhaseKind, aj as SessionLifecycleRoomBridge, ak as SessionLifecycleRoomBridgeProps, al as SessionReadinessFailure, am as SurfaceLayers, an as TranscriptSender, ao as TranscriptSenderOptions, ap as TurnState, aq as TurnTimeoutEvent, ar as UseAvatarQualityGovernorInput, as as UseLiveKitAvatarGrantInput, at as UseRealtimeSessionInput, au as UseSessionLifecycleInput, av as VoiceSpec, aw as VoiceSpecInput, ax as capacityErrorFromBusy, ay as capacityStateFromGrant, az as createProxyClient, aA as isNativeLiveTrackSubscribed, aB as knownBehaviorStates, aC as mapTurnState, aD as readInboundRtp, aE as sessionBehaviorSchema, aF as sessionClipSchema, aG as splitCallTranscript, aH as useAvatarAdaptivePlayoutDelay, aI as useAvatarCamera, aJ as useAvatarPlayoutDelay, aK as useAvatarQualityGovernor, aL as useCallTranscript, aM as useLiveKitAvatarGrant, aN as useLiveTrackProducing, aO as useMicLease, aP as useRealtimeSession, aQ as useReleaseMicLeaseOnTrackEnded, aR as useSessionLifecycle } from './proxy-client-Cs2azqD1.js';
|
|
5
5
|
import { ReactNode, ReactElement } from 'react';
|
|
6
6
|
import { StyleProp, ViewStyle } from 'react-native';
|
|
7
7
|
export { useChat, useConnectionState, useLocalParticipant, useRoomContext, useTrackToggle, useTranscriptions, useVoiceAssistant } from '@livekit/components-react';
|
package/dist/react-native.js
CHANGED
|
@@ -177,6 +177,8 @@ function RealtimeAvatarLiveKitRoom(props) {
|
|
|
177
177
|
LiveKitRoom,
|
|
178
178
|
{
|
|
179
179
|
...roomProps,
|
|
180
|
+
// Keep the room and its in-room observers scoped to the grant, as on web.
|
|
181
|
+
key: grant?.session_id ?? "no-session",
|
|
180
182
|
serverUrl: grant?.livekit_url,
|
|
181
183
|
token: grant?.participant_token,
|
|
182
184
|
connect: shouldConnect,
|
|
@@ -2395,7 +2397,8 @@ function lifecyclePhaseFrom(args) {
|
|
|
2395
2397
|
kind: "reconnectable",
|
|
2396
2398
|
reconnecting: true,
|
|
2397
2399
|
attempt: recovery.attempt,
|
|
2398
|
-
strategy: "fresh-grant"
|
|
2400
|
+
strategy: "fresh-grant",
|
|
2401
|
+
...recovery.reason ? { reason: recovery.reason } : {}
|
|
2399
2402
|
};
|
|
2400
2403
|
}
|
|
2401
2404
|
if (recovery.kind === "failed") {
|
|
@@ -2403,11 +2406,16 @@ function lifecyclePhaseFrom(args) {
|
|
|
2403
2406
|
kind: "reconnectable",
|
|
2404
2407
|
reconnecting: false,
|
|
2405
2408
|
attempt: MAX_RECONNECT_ATTEMPTS,
|
|
2406
|
-
strategy: "fresh-grant"
|
|
2409
|
+
strategy: "fresh-grant",
|
|
2410
|
+
...recovery.reason ? { reason: recovery.reason } : {}
|
|
2407
2411
|
};
|
|
2408
2412
|
}
|
|
2409
2413
|
if (recovery.kind === "ended") {
|
|
2410
|
-
return
|
|
2414
|
+
return {
|
|
2415
|
+
kind: "ended",
|
|
2416
|
+
...recovery.reason ? { reason: recovery.reason } : {},
|
|
2417
|
+
...recovery.code ? { code: recovery.code } : {}
|
|
2418
|
+
};
|
|
2411
2419
|
}
|
|
2412
2420
|
switch (capacity.kind) {
|
|
2413
2421
|
case "idle":
|
|
@@ -2420,7 +2428,7 @@ function lifecyclePhaseFrom(args) {
|
|
|
2420
2428
|
return { kind: "ended", reason: "error" };
|
|
2421
2429
|
case "active": {
|
|
2422
2430
|
if (!connected || !agentPresent) {
|
|
2423
|
-
return { kind: "connecting", grant: capacity.grant };
|
|
2431
|
+
return { kind: "connecting", grant: capacity.grant, waitingFor: connected ? "agent" : "transport" };
|
|
2424
2432
|
}
|
|
2425
2433
|
if (idle.kind === "idle-warning") {
|
|
2426
2434
|
return { kind: "idle-warning", secondsRemaining: idle.secondsRemaining, deadlineAt: idle.deadlineAt };
|
|
@@ -2448,6 +2456,7 @@ function resolveIdleTimeoutMs(args) {
|
|
|
2448
2456
|
return DEFAULT_IDLE_SECONDS * 1e3;
|
|
2449
2457
|
}
|
|
2450
2458
|
var DEFAULT_CONNECT_WATCHDOG_SECONDS = 12;
|
|
2459
|
+
var DEFAULT_READY_TIMEOUT_SECONDS = 30;
|
|
2451
2460
|
function resolveWarnBeforeMs(idleTimeoutMs, override, idleWarnLeadSeconds) {
|
|
2452
2461
|
if (idleTimeoutMs <= 0) return 0;
|
|
2453
2462
|
const maxLeadMs = Math.max(0, idleTimeoutMs - 1e3);
|
|
@@ -2473,6 +2482,7 @@ function useSessionLifecycle(input) {
|
|
|
2473
2482
|
maxReconnectAttempts,
|
|
2474
2483
|
autoRetryBusy = true,
|
|
2475
2484
|
connectWatchdogSeconds,
|
|
2485
|
+
readyTimeoutSeconds,
|
|
2476
2486
|
requestOptions,
|
|
2477
2487
|
onBehaviorChange
|
|
2478
2488
|
} = input;
|
|
@@ -2483,17 +2493,27 @@ function useSessionLifecycle(input) {
|
|
|
2483
2493
|
const reconnectPolicyRef = useRef(reconnectPolicy);
|
|
2484
2494
|
reconnectPolicyRef.current = reconnectPolicy;
|
|
2485
2495
|
const connectWatchdogMs = typeof connectWatchdogSeconds === "number" && Number.isFinite(connectWatchdogSeconds) ? Math.max(0, Math.floor(connectWatchdogSeconds)) * 1e3 : DEFAULT_CONNECT_WATCHDOG_SECONDS * 1e3;
|
|
2496
|
+
const readyTimeoutMs = typeof readyTimeoutSeconds === "number" && Number.isFinite(readyTimeoutSeconds) && readyTimeoutSeconds > 0 ? Math.max(1, Math.floor(readyTimeoutSeconds)) * 1e3 : DEFAULT_READY_TIMEOUT_SECONDS * 1e3;
|
|
2486
2497
|
const [userEnded, setUserEnded] = useState(false);
|
|
2487
2498
|
const userEndedRef = useRef(false);
|
|
2499
|
+
const [readinessFailed, setReadinessFailed] = useState(false);
|
|
2500
|
+
const readinessFailureRef = useRef(null);
|
|
2501
|
+
const readinessRef = useRef(null);
|
|
2502
|
+
const readyGrantRef = useRef(null);
|
|
2503
|
+
const agentWaitRef = useRef(null);
|
|
2488
2504
|
const phaseRef = useRef({ kind: "idle" });
|
|
2489
2505
|
const grantState = useLiveKitAvatarGrant({
|
|
2490
2506
|
client,
|
|
2491
2507
|
session,
|
|
2492
|
-
active: active && !userEnded,
|
|
2508
|
+
active: active && !userEnded && !readinessFailed,
|
|
2493
2509
|
autoRetryBusy,
|
|
2494
2510
|
requestOptions
|
|
2495
2511
|
});
|
|
2496
2512
|
const capacity = grantState.capacity;
|
|
2513
|
+
const grantId = grantState.grant?.session_id ?? null;
|
|
2514
|
+
const grantIdRef = useRef(null);
|
|
2515
|
+
grantIdRef.current = grantId;
|
|
2516
|
+
const retiredGrantRef = useRef(null);
|
|
2497
2517
|
const [recovery, setRecovery] = useState({ kind: "connected" });
|
|
2498
2518
|
const recoveryRef = useRef(recovery);
|
|
2499
2519
|
recoveryRef.current = recovery;
|
|
@@ -2516,8 +2536,19 @@ function useSessionLifecycle(input) {
|
|
|
2516
2536
|
grantIdleSeconds: grantState.grant?.idle_timeout_seconds ?? 0
|
|
2517
2537
|
});
|
|
2518
2538
|
const warnAtMs = resolveWarnBeforeMs(idleTimeoutMs, warnBeforeMs, idleWarnLeadSeconds);
|
|
2519
|
-
const [
|
|
2520
|
-
const [
|
|
2539
|
+
const [agentObservation, setAgentObservation] = useState({ grantId: null, present: false });
|
|
2540
|
+
const [connectionObservation, setConnectionObservation] = useState({ grantId: null, connected: false });
|
|
2541
|
+
const agentPresent = agentObservation.grantId === grantId && agentObservation.present;
|
|
2542
|
+
const connected = connectionObservation.grantId === grantId && connectionObservation.connected;
|
|
2543
|
+
connectedRef.current = connected;
|
|
2544
|
+
const setAgentPresentState = useCallback((present) => {
|
|
2545
|
+
const owner = grantIdRef.current;
|
|
2546
|
+
setAgentObservation((prev) => prev.grantId === owner && prev.present === present ? prev : { grantId: owner, present });
|
|
2547
|
+
}, []);
|
|
2548
|
+
const setConnected = useCallback((value) => {
|
|
2549
|
+
const owner = grantIdRef.current;
|
|
2550
|
+
setConnectionObservation((prev) => prev.grantId === owner && prev.connected === value ? prev : { grantId: owner, connected: value });
|
|
2551
|
+
}, []);
|
|
2521
2552
|
const lastActivityRef = useRef(Date.now());
|
|
2522
2553
|
const leaveRoomRef = useRef(null);
|
|
2523
2554
|
const [, setClockTick] = useState(0);
|
|
@@ -2534,7 +2565,11 @@ function useSessionLifecycle(input) {
|
|
|
2534
2565
|
refreshBaselineCapacityRef.current = null;
|
|
2535
2566
|
attemptRef.current = 0;
|
|
2536
2567
|
setAttempt(0);
|
|
2537
|
-
|
|
2568
|
+
readinessRef.current = null;
|
|
2569
|
+
readyGrantRef.current = null;
|
|
2570
|
+
agentWaitRef.current = null;
|
|
2571
|
+
retiredGrantRef.current = null;
|
|
2572
|
+
setRecovery(userEndedRef.current ? { kind: "ended", reason: "user" } : readinessFailureRef.current ? { kind: "ended", reason: "error", code: readinessFailureRef.current } : { kind: "connected" });
|
|
2538
2573
|
setConnected(false);
|
|
2539
2574
|
setAgentPresentState(false);
|
|
2540
2575
|
lastActivityRef.current = Date.now();
|
|
@@ -2543,26 +2578,33 @@ function useSessionLifecycle(input) {
|
|
|
2543
2578
|
if (active) return;
|
|
2544
2579
|
userEndedRef.current = false;
|
|
2545
2580
|
setUserEnded(false);
|
|
2581
|
+
readinessFailureRef.current = null;
|
|
2582
|
+
setReadinessFailed(false);
|
|
2546
2583
|
reset();
|
|
2547
2584
|
}, [active, reset]);
|
|
2548
2585
|
const markActivity = useCallback(() => {
|
|
2586
|
+
if (grantId === null || grantIdRef.current !== grantId) return;
|
|
2549
2587
|
lastActivityRef.current = Date.now();
|
|
2550
2588
|
setClockTick((value) => value + 1);
|
|
2551
|
-
}, []);
|
|
2589
|
+
}, [grantId]);
|
|
2552
2590
|
const stayConnected = useCallback(() => {
|
|
2591
|
+
if (grantId === null || grantIdRef.current !== grantId) return;
|
|
2553
2592
|
if (!connectedRef.current) return;
|
|
2554
2593
|
lastActivityRef.current = Date.now();
|
|
2555
2594
|
setClockTick((value) => value + 1);
|
|
2556
|
-
}, []);
|
|
2595
|
+
}, [grantId]);
|
|
2557
2596
|
const setAgentPresent = useCallback((present) => {
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2597
|
+
if (grantId === null || grantIdRef.current !== grantId) return;
|
|
2598
|
+
if (userEndedRef.current || readinessFailureRef.current || retiredGrantRef.current !== null && retiredGrantRef.current === grantIdRef.current) return;
|
|
2599
|
+
setAgentObservation((prev) => {
|
|
2600
|
+
if (present && (prev.grantId !== grantId || !prev.present)) lastActivityRef.current = Date.now();
|
|
2601
|
+
return prev.grantId === grantId && prev.present === present ? prev : { grantId, present };
|
|
2561
2602
|
});
|
|
2562
|
-
}, []);
|
|
2603
|
+
}, [grantId]);
|
|
2563
2604
|
const registerLeaveRoom = useCallback((leave) => {
|
|
2605
|
+
if (grantId === null || grantIdRef.current !== grantId) return;
|
|
2564
2606
|
leaveRoomRef.current = leave;
|
|
2565
|
-
}, []);
|
|
2607
|
+
}, [grantId]);
|
|
2566
2608
|
const endIdle = useCallback(() => {
|
|
2567
2609
|
if (!connectedRef.current) return;
|
|
2568
2610
|
clearTimer();
|
|
@@ -2574,24 +2616,25 @@ function useSessionLifecycle(input) {
|
|
|
2574
2616
|
leaveRoomRef.current?.();
|
|
2575
2617
|
}, [clearTimer]);
|
|
2576
2618
|
const onConnected = useCallback(() => {
|
|
2577
|
-
if (
|
|
2619
|
+
if (grantId === null || grantIdRef.current !== grantId) return;
|
|
2620
|
+
if (userEndedRef.current || readinessFailureRef.current || retiredGrantRef.current !== null && retiredGrantRef.current === grantIdRef.current) return;
|
|
2578
2621
|
clearTimer();
|
|
2579
2622
|
connectedRef.current = true;
|
|
2580
2623
|
manualReconnectPendingRef.current = false;
|
|
2581
2624
|
refreshBaselineCapacityRef.current = null;
|
|
2582
2625
|
setConnected(true);
|
|
2583
|
-
|
|
2584
|
-
setAttempt(0);
|
|
2626
|
+
if (readinessRef.current) readinessRef.current.sawTransport = true;
|
|
2585
2627
|
setRecovery({ kind: "connected" });
|
|
2586
2628
|
lastActivityRef.current = Date.now();
|
|
2587
|
-
}, [clearTimer]);
|
|
2629
|
+
}, [clearTimer, grantId]);
|
|
2588
2630
|
const onDisconnected = useCallback(
|
|
2589
2631
|
(reason) => {
|
|
2590
|
-
if (
|
|
2632
|
+
if (grantId === null || grantIdRef.current !== grantId) return;
|
|
2633
|
+
if (!active || userEndedRef.current || readinessFailureRef.current) return;
|
|
2591
2634
|
const action = disconnectAction(reason, connectedRef.current);
|
|
2592
2635
|
if (action === "noop") return;
|
|
2593
2636
|
if (action === "reset") {
|
|
2594
|
-
if (recoveryRef.current.kind === "refreshing") return;
|
|
2637
|
+
if (recoveryRef.current.kind === "refreshing" || recoveryRef.current.kind === "reconnecting") return;
|
|
2595
2638
|
reset();
|
|
2596
2639
|
return;
|
|
2597
2640
|
}
|
|
@@ -2608,11 +2651,12 @@ function useSessionLifecycle(input) {
|
|
|
2608
2651
|
refreshBaselineCapacityRef.current = null;
|
|
2609
2652
|
setRecovery({ kind: "reconnecting", attempt: attemptRef.current });
|
|
2610
2653
|
},
|
|
2611
|
-
[active, reset]
|
|
2654
|
+
[active, reset, grantId]
|
|
2612
2655
|
);
|
|
2613
2656
|
const onConnectionError = useCallback(
|
|
2614
2657
|
(error) => {
|
|
2615
|
-
if (
|
|
2658
|
+
if (grantId === null || grantIdRef.current !== grantId) return;
|
|
2659
|
+
if (!active || userEndedRef.current || readinessFailureRef.current || !isRecoverableConnectionError(error)) return;
|
|
2616
2660
|
connectedRef.current = false;
|
|
2617
2661
|
setConnected(false);
|
|
2618
2662
|
setAgentPresentState(false);
|
|
@@ -2623,7 +2667,7 @@ function useSessionLifecycle(input) {
|
|
|
2623
2667
|
return { kind: "reconnecting", attempt: attemptRef.current };
|
|
2624
2668
|
});
|
|
2625
2669
|
},
|
|
2626
|
-
[active]
|
|
2670
|
+
[active, grantId]
|
|
2627
2671
|
);
|
|
2628
2672
|
useEffect(() => {
|
|
2629
2673
|
if (capacity.kind === "error" && recovery.kind === "connected") manualReconnectPendingRef.current = false;
|
|
@@ -2633,11 +2677,12 @@ function useSessionLifecycle(input) {
|
|
|
2633
2677
|
const baseline = refreshBaselineCapacityRef.current;
|
|
2634
2678
|
if (baseline?.kind === "error" && baseline.error === capacity.error) return;
|
|
2635
2679
|
refreshBaselineCapacityRef.current = null;
|
|
2636
|
-
setRecovery({ kind: "reconnecting", attempt: recovery.attempt });
|
|
2680
|
+
setRecovery({ kind: "reconnecting", attempt: recovery.attempt, ...recovery.reason ? { reason: recovery.reason } : {} });
|
|
2637
2681
|
}, [capacity, recovery]);
|
|
2638
2682
|
const onConnectionStateChange = useCallback(
|
|
2639
2683
|
(state) => {
|
|
2640
|
-
if (
|
|
2684
|
+
if (grantId === null || grantIdRef.current !== grantId) return;
|
|
2685
|
+
if (!active || userEndedRef.current || readinessFailureRef.current) return;
|
|
2641
2686
|
if (state === "connected") {
|
|
2642
2687
|
onConnected();
|
|
2643
2688
|
return;
|
|
@@ -2651,14 +2696,78 @@ function useSessionLifecycle(input) {
|
|
|
2651
2696
|
}
|
|
2652
2697
|
}
|
|
2653
2698
|
},
|
|
2654
|
-
[active, onConnected]
|
|
2699
|
+
[active, onConnected, grantId]
|
|
2655
2700
|
);
|
|
2701
|
+
useEffect(() => {
|
|
2702
|
+
if (!active || userEnded || readinessFailed || !connected || !agentPresent || !grantState.grant) return;
|
|
2703
|
+
readyGrantRef.current = grantState.grant.session_id;
|
|
2704
|
+
readinessRef.current = null;
|
|
2705
|
+
agentWaitRef.current = null;
|
|
2706
|
+
attemptRef.current = 0;
|
|
2707
|
+
setAttempt(0);
|
|
2708
|
+
}, [active, userEnded, readinessFailed, connected, agentPresent, grantState.grant]);
|
|
2709
|
+
const failReadiness = useCallback((code) => {
|
|
2710
|
+
if (userEndedRef.current || readinessFailureRef.current) return;
|
|
2711
|
+
readinessFailureRef.current = code;
|
|
2712
|
+
clearTimer();
|
|
2713
|
+
connectedRef.current = false;
|
|
2714
|
+
manualReconnectPendingRef.current = false;
|
|
2715
|
+
refreshBaselineCapacityRef.current = null;
|
|
2716
|
+
const ended = { kind: "ended", reason: "error", code };
|
|
2717
|
+
recoveryRef.current = ended;
|
|
2718
|
+
setRecovery(ended);
|
|
2719
|
+
setConnected(false);
|
|
2720
|
+
setAgentPresentState(false);
|
|
2721
|
+
releaseRef.current("disconnected");
|
|
2722
|
+
setReadinessFailed(true);
|
|
2723
|
+
leaveRoomRef.current?.();
|
|
2724
|
+
}, [clearTimer]);
|
|
2725
|
+
useEffect(() => {
|
|
2726
|
+
if (!active || userEnded || readinessFailed) return;
|
|
2727
|
+
if (recovery.kind === "ended" || recovery.kind === "failed") return;
|
|
2728
|
+
if (grantState.grant && readyGrantRef.current === grantState.grant.session_id) return;
|
|
2729
|
+
if (!readinessRef.current && grantState.grant) {
|
|
2730
|
+
readinessRef.current = { startedAt: Date.now(), sawTransport: connected };
|
|
2731
|
+
}
|
|
2732
|
+
const budget = readinessRef.current;
|
|
2733
|
+
if (!budget) return;
|
|
2734
|
+
const timer = window.setTimeout(() => {
|
|
2735
|
+
if (readinessRef.current !== budget) return;
|
|
2736
|
+
failReadiness(budget.sawTransport ? "agent_timeout" : "connection_timeout");
|
|
2737
|
+
}, Math.max(0, budget.startedAt + readyTimeoutMs - Date.now()));
|
|
2738
|
+
return () => window.clearTimeout(timer);
|
|
2739
|
+
}, [active, userEnded, readinessFailed, connected, agentPresent, grantState.grant, recovery.kind, readyTimeoutMs, failReadiness]);
|
|
2740
|
+
useEffect(() => {
|
|
2741
|
+
const sessionId = grantState.grant?.session_id;
|
|
2742
|
+
if (!active || userEnded || readinessFailed || !sessionId || readyGrantRef.current === sessionId || agentPresent) return;
|
|
2743
|
+
if (recovery.kind === "reconnecting" || recovery.kind === "failed" || recovery.kind === "ended") return;
|
|
2744
|
+
if (agentWaitRef.current?.sessionId !== sessionId) {
|
|
2745
|
+
if (!connected) return;
|
|
2746
|
+
agentWaitRef.current = { sessionId, startedAt: Date.now() };
|
|
2747
|
+
}
|
|
2748
|
+
const wait = agentWaitRef.current;
|
|
2749
|
+
if (connectWatchdogMs <= 0) return;
|
|
2750
|
+
const timer = window.setTimeout(() => {
|
|
2751
|
+
if (agentWaitRef.current !== wait || readinessFailureRef.current || userEndedRef.current) return;
|
|
2752
|
+
retiredGrantRef.current = sessionId;
|
|
2753
|
+
connectedRef.current = false;
|
|
2754
|
+
setConnected(false);
|
|
2755
|
+
setAgentPresentState(false);
|
|
2756
|
+
const next = { kind: "reconnecting", attempt: attemptRef.current, reason: "agent_timeout" };
|
|
2757
|
+
recoveryRef.current = next;
|
|
2758
|
+
setRecovery(next);
|
|
2759
|
+
releaseRef.current("disconnected");
|
|
2760
|
+
leaveRoomRef.current?.();
|
|
2761
|
+
}, Math.max(0, wait.startedAt + connectWatchdogMs - Date.now()));
|
|
2762
|
+
return () => window.clearTimeout(timer);
|
|
2763
|
+
}, [active, userEnded, readinessFailed, connected, agentPresent, grantState.grant, recovery.kind, connectWatchdogMs]);
|
|
2656
2764
|
useEffect(() => {
|
|
2657
2765
|
if (!active || connected || connectWatchdogMs <= 0) return;
|
|
2658
2766
|
if (grantState.status !== "ready" || !grantState.grant) return;
|
|
2659
2767
|
if (recovery.kind === "failed" || recovery.kind === "ended") return;
|
|
2660
2768
|
if (recovery.kind === "in-place-reconnecting") return;
|
|
2661
2769
|
const timer = window.setTimeout(() => {
|
|
2770
|
+
if (readinessFailureRef.current || userEndedRef.current) return;
|
|
2662
2771
|
releaseRef.current("disconnected");
|
|
2663
2772
|
setRecovery({ kind: "reconnecting", attempt: attemptRef.current });
|
|
2664
2773
|
}, connectWatchdogMs);
|
|
@@ -2668,6 +2777,10 @@ function useSessionLifecycle(input) {
|
|
|
2668
2777
|
if (!active || !needsFreshGrant(recovery)) return;
|
|
2669
2778
|
const step2 = retryStep(attemptRef.current, reconnectPolicyRef.current);
|
|
2670
2779
|
if (step2.kind === "give-up") {
|
|
2780
|
+
if (readinessRef.current) {
|
|
2781
|
+
failReadiness(readinessRef.current.sawTransport ? "agent_timeout" : "connection_timeout");
|
|
2782
|
+
return;
|
|
2783
|
+
}
|
|
2671
2784
|
manualReconnectPendingRef.current = false;
|
|
2672
2785
|
refreshBaselineCapacityRef.current = null;
|
|
2673
2786
|
releaseRef.current("disconnected");
|
|
@@ -2676,15 +2789,16 @@ function useSessionLifecycle(input) {
|
|
|
2676
2789
|
}
|
|
2677
2790
|
timerRef.current = window.setTimeout(() => {
|
|
2678
2791
|
timerRef.current = null;
|
|
2792
|
+
if (readinessFailureRef.current || userEndedRef.current) return;
|
|
2679
2793
|
attemptRef.current = step2.attempt;
|
|
2680
2794
|
setAttempt(step2.attempt);
|
|
2681
2795
|
refreshBaselineCapacityRef.current = capacityRef.current;
|
|
2682
2796
|
releaseRef.current("superseded");
|
|
2683
|
-
setRecovery({ kind: "refreshing", attempt: step2.attempt });
|
|
2797
|
+
setRecovery({ kind: "refreshing", attempt: step2.attempt, ...recovery.reason ? { reason: recovery.reason } : {} });
|
|
2684
2798
|
refreshRef.current();
|
|
2685
2799
|
}, step2.delayMs);
|
|
2686
2800
|
return clearTimer;
|
|
2687
|
-
}, [recovery.kind, active, attempt, clearTimer]);
|
|
2801
|
+
}, [recovery.kind, active, attempt, clearTimer, failReadiness]);
|
|
2688
2802
|
const end = useCallback(() => {
|
|
2689
2803
|
if (!active || userEndedRef.current || phaseRef.current.kind === "ended") return;
|
|
2690
2804
|
userEndedRef.current = true;
|
|
@@ -2701,6 +2815,13 @@ function useSessionLifecycle(input) {
|
|
|
2701
2815
|
}, [active, clearTimer]);
|
|
2702
2816
|
const reconnect = useCallback(() => {
|
|
2703
2817
|
if (!active) return;
|
|
2818
|
+
if (readinessFailureRef.current) {
|
|
2819
|
+
readinessFailureRef.current = null;
|
|
2820
|
+
setReadinessFailed(false);
|
|
2821
|
+
reset();
|
|
2822
|
+
manualReconnectPendingRef.current = true;
|
|
2823
|
+
return;
|
|
2824
|
+
}
|
|
2704
2825
|
if (userEndedRef.current) {
|
|
2705
2826
|
userEndedRef.current = false;
|
|
2706
2827
|
setUserEnded(false);
|
|
@@ -2711,6 +2832,12 @@ function useSessionLifecycle(input) {
|
|
|
2711
2832
|
if (manualReconnectPendingRef.current || recovery.kind === "in-place-reconnecting") return;
|
|
2712
2833
|
manualReconnectPendingRef.current = true;
|
|
2713
2834
|
clearTimer();
|
|
2835
|
+
readinessRef.current = null;
|
|
2836
|
+
agentWaitRef.current = null;
|
|
2837
|
+
readyGrantRef.current = null;
|
|
2838
|
+
connectedRef.current = false;
|
|
2839
|
+
setConnected(false);
|
|
2840
|
+
setAgentPresentState(false);
|
|
2714
2841
|
attemptRef.current = 0;
|
|
2715
2842
|
setAttempt(0);
|
|
2716
2843
|
refreshBaselineCapacityRef.current = capacityRef.current;
|
|
@@ -2830,6 +2957,7 @@ function SessionLifecycleRoomBridge({
|
|
|
2830
2957
|
const assistant = useVoiceAssistant();
|
|
2831
2958
|
const transcriptions = useTranscriptions();
|
|
2832
2959
|
const room = useRoomContext();
|
|
2960
|
+
const roomMatchesGrant = lifecycle.grant === void 0 || lifecycle.grant !== null && room.name === lifecycle.grant.room_name;
|
|
2833
2961
|
useCallMedia(room, {
|
|
2834
2962
|
setMicrophoneFacts,
|
|
2835
2963
|
setAudioPlayback,
|
|
@@ -2983,11 +3111,11 @@ function SessionLifecycleRoomBridge({
|
|
|
2983
3111
|
detailsEnabled ? video?.publication : void 0
|
|
2984
3112
|
]);
|
|
2985
3113
|
useEffect(() => {
|
|
2986
|
-
onConnectionStateChange(connectionState);
|
|
2987
|
-
}, [connectionState, onConnectionStateChange]);
|
|
3114
|
+
if (roomMatchesGrant) onConnectionStateChange(connectionState);
|
|
3115
|
+
}, [connectionState, onConnectionStateChange, roomMatchesGrant]);
|
|
2988
3116
|
useEffect(() => {
|
|
2989
|
-
setAgentPresent(agentPresent);
|
|
2990
|
-
}, [agentPresent, setAgentPresent]);
|
|
3117
|
+
if (roomMatchesGrant) setAgentPresent(agentPresent);
|
|
3118
|
+
}, [agentPresent, setAgentPresent, roomMatchesGrant]);
|
|
2991
3119
|
useEffect(() => {
|
|
2992
3120
|
const active = isCallActivity({
|
|
2993
3121
|
assistantState,
|
|
@@ -3695,4 +3823,4 @@ function createProxyClient(options) {
|
|
|
3695
3823
|
};
|
|
3696
3824
|
}
|
|
3697
3825
|
|
|
3698
|
-
export { AdaptivePlayoutController, 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, RECONNECT_BACKOFF_MS, RealtimeAvatarLiveKitRoom, SessionLifecycleRoomBridge, capacityErrorFromBusy, capacityStateFromGrant, createProxyClient, isNativeLiveTrackSubscribed, knownBehaviorStates, mapTurnState, readInboundRtp, sessionBehaviorSchema, sessionClipSchema, splitCallTranscript, useAvatarAdaptivePlayoutDelay, useAvatarCamera, useAvatarPlayoutDelay, useAvatarQualityGovernor, useCallTranscript, useLiveKitAvatarGrant, useLiveTrackProducing, useMicLease, useRealtimeAvatarAudioSession, useRealtimeSession, useReleaseMicLeaseOnTrackEnded, useSessionLifecycle };
|
|
3826
|
+
export { AdaptivePlayoutController, 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_READY_TIMEOUT_SECONDS, DEFAULT_TURN_TIMEOUT_SECONDS, MAX_RECONNECT_ATTEMPTS, MAX_SESSION_INSTRUCTIONS_CHARS, RECONNECT_BACKOFF_MS, RealtimeAvatarLiveKitRoom, SessionLifecycleRoomBridge, capacityErrorFromBusy, capacityStateFromGrant, createProxyClient, isNativeLiveTrackSubscribed, knownBehaviorStates, mapTurnState, readInboundRtp, sessionBehaviorSchema, sessionClipSchema, splitCallTranscript, useAvatarAdaptivePlayoutDelay, useAvatarCamera, useAvatarPlayoutDelay, useAvatarQualityGovernor, useCallTranscript, useLiveKitAvatarGrant, useLiveTrackProducing, useMicLease, useRealtimeAvatarAudioSession, useRealtimeSession, useReleaseMicLeaseOnTrackEnded, useSessionLifecycle };
|
package/dist/react.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ToolRegistration, RegisteredTool } from './tools.js';
|
|
2
|
-
import {
|
|
3
|
-
export { a as AdaptivePlayoutController, b as AdaptivePlayoutDecision, c as AdaptivePlayoutOptions, d as AdaptivePlayoutSample, e as ApproachingEndEvent, f as ApproachingEndReason, g as AvatarConnectionDetails,
|
|
2
|
+
import { ak as SessionLifecycleRoomBridgeProps, h as AvatarSessionClient, A as AvatarVideoFit, aS as CallMicrophone, aT as CallAudioPlayback, aU as CallMicrophoneProblem } from './proxy-client-Cs2azqD1.js';
|
|
3
|
+
export { a as AdaptivePlayoutController, b as AdaptivePlayoutDecision, c as AdaptivePlayoutOptions, d as AdaptivePlayoutSample, e as ApproachingEndEvent, f as ApproachingEndReason, g as AvatarConnectionDetails, aV as AvatarVideoSurface, aW as AvatarVideoSurfaceProps, B as BehaviorSnapshot, C as CallTranscript, i as CallTranscriptSegment, j as CartesiaTtsModel, k as ClipResult, l as ClosingTurnResult, m as CreditsLowEvent, D as DEFAULT_APPROACHING_END_LEAD_SECONDS, n as DEFAULT_CREDITS_LOW_LEAD_SECONDS, o as DEFAULT_GOVERNOR_CONFIG, p as DEFAULT_GRACE_CEILING_SECONDS, q as DEFAULT_GRACE_WINDOW_LEAD_SECONDS, r as DEFAULT_IDLE_SECONDS, s as DEFAULT_IDLE_WARN_LEAD_SECONDS, t as DEFAULT_READY_TIMEOUT_SECONDS, u as DEFAULT_TURN_TIMEOUT_SECONDS, v as DeclaredInputSource, E as EndReason, w as EndedEvent, x as ExtendResult, F as FishTtsModel, y as FreezeReadingFn, G as Governor, z as GovernorAction, H as GovernorConfig, I as GovernorSignal, J as GovernorState, K as GraceWindowClosedEvent, M as GraceWindowOpenEvent, N as GraceWindowState, O as IdleWarningEvent, P as InboundRtpCursor, Q as InboundRtpReading, R as KnownBehaviorState, S as LLMProvider, T as LLMSelection, U as LiveKitAvatarGrantState, V as LiveKitAvatarGrantStatus, W as LiveKitCapacityState, X as LiveKitConnectionStatus, Y as LiveKitSessionRequest, Z as LiveKitSessionStartResult, aX as LivePlaybackKeeper, _ as MAX_RECONNECT_ATTEMPTS, $ as MAX_SESSION_INSTRUCTIONS_CHARS, aY as MIC_LEASE_ENDED_TIMEOUT_MS, aZ as MicLease, a_ as NETWORK_QUALITY_POLICY, a$ as NetworkQualityStatus, b0 as PlayableVideoElement, a0 as ProxyClientOptions, a1 as QualityCap, a2 as RECONNECT_BACKOFF_MS, b1 as RealtimeAvatarCapacityError, b2 as RealtimeAvatarLiveKitRoom, b3 as RealtimeAvatarLiveKitRoomProps, a3 as RealtimeAvatarRequestOptions, a4 as RealtimeSessionApi, a5 as RealtimeSessionMedia, a6 as RealtimeSessionRoomSinks, a7 as ReconnectPolicy, a8 as ReconnectingEvent, a9 as RecoveryState, aa as RetryStep, ab as SendTurnOptions, ac as SessionBehavior, ad as SessionClip, ae as SessionClocks, af as SessionEndReason, ag as SessionLifecycleApi, ah as SessionLifecyclePhase, ai as SessionLifecyclePhaseKind, aj as SessionLifecycleRoomBridge, al as SessionReadinessFailure, am as SurfaceLayers, an as TranscriptSender, ao as TranscriptSenderOptions, ap as TurnState, aq as TurnTimeoutEvent, ar as UseAvatarQualityGovernorInput, as as UseLiveKitAvatarGrantInput, at as UseRealtimeSessionInput, au as UseSessionLifecycleInput, av as VoiceSpec, aw as VoiceSpecInput, ax as capacityErrorFromBusy, ay as capacityStateFromGrant, az as createProxyClient, aB as knownBehaviorStates, aC as mapTurnState, aD as readInboundRtp, aE as sessionBehaviorSchema, aF as sessionClipSchema, b4 as shouldReplayPendingTurn, aG as splitCallTranscript, aH as useAvatarAdaptivePlayoutDelay, aI as useAvatarCamera, aJ as useAvatarPlayoutDelay, aK as useAvatarQualityGovernor, aL as useCallTranscript, aM as useLiveKitAvatarGrant, aO as useMicLease, aP as useRealtimeSession, aQ as useReleaseMicLeaseOnTrackEnded, aR as useSessionLifecycle } from './proxy-client-Cs2azqD1.js';
|
|
4
4
|
import { CSSProperties, ReactNode } from 'react';
|
|
5
5
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
6
6
|
import { R as RecordingArtifact } from './recording-DjswTx5B.js';
|
package/dist/react.js
CHANGED
|
@@ -1074,6 +1074,8 @@ function RealtimeAvatarLiveKitRoom(props) {
|
|
|
1074
1074
|
LiveKitRoom,
|
|
1075
1075
|
{
|
|
1076
1076
|
...roomProps,
|
|
1077
|
+
// A new grant owns a new room and bridge; an old room must not report its agent into it.
|
|
1078
|
+
key: grant?.session_id ?? "no-session",
|
|
1077
1079
|
serverUrl: grant?.livekit_url,
|
|
1078
1080
|
token: grant?.participant_token,
|
|
1079
1081
|
connect: shouldConnect,
|
|
@@ -3019,7 +3021,8 @@ function lifecyclePhaseFrom(args) {
|
|
|
3019
3021
|
kind: "reconnectable",
|
|
3020
3022
|
reconnecting: true,
|
|
3021
3023
|
attempt: recovery.attempt,
|
|
3022
|
-
strategy: "fresh-grant"
|
|
3024
|
+
strategy: "fresh-grant",
|
|
3025
|
+
...recovery.reason ? { reason: recovery.reason } : {}
|
|
3023
3026
|
};
|
|
3024
3027
|
}
|
|
3025
3028
|
if (recovery.kind === "failed") {
|
|
@@ -3027,11 +3030,16 @@ function lifecyclePhaseFrom(args) {
|
|
|
3027
3030
|
kind: "reconnectable",
|
|
3028
3031
|
reconnecting: false,
|
|
3029
3032
|
attempt: MAX_RECONNECT_ATTEMPTS,
|
|
3030
|
-
strategy: "fresh-grant"
|
|
3033
|
+
strategy: "fresh-grant",
|
|
3034
|
+
...recovery.reason ? { reason: recovery.reason } : {}
|
|
3031
3035
|
};
|
|
3032
3036
|
}
|
|
3033
3037
|
if (recovery.kind === "ended") {
|
|
3034
|
-
return
|
|
3038
|
+
return {
|
|
3039
|
+
kind: "ended",
|
|
3040
|
+
...recovery.reason ? { reason: recovery.reason } : {},
|
|
3041
|
+
...recovery.code ? { code: recovery.code } : {}
|
|
3042
|
+
};
|
|
3035
3043
|
}
|
|
3036
3044
|
switch (capacity.kind) {
|
|
3037
3045
|
case "idle":
|
|
@@ -3044,7 +3052,7 @@ function lifecyclePhaseFrom(args) {
|
|
|
3044
3052
|
return { kind: "ended", reason: "error" };
|
|
3045
3053
|
case "active": {
|
|
3046
3054
|
if (!connected || !agentPresent) {
|
|
3047
|
-
return { kind: "connecting", grant: capacity.grant };
|
|
3055
|
+
return { kind: "connecting", grant: capacity.grant, waitingFor: connected ? "agent" : "transport" };
|
|
3048
3056
|
}
|
|
3049
3057
|
if (idle.kind === "idle-warning") {
|
|
3050
3058
|
return { kind: "idle-warning", secondsRemaining: idle.secondsRemaining, deadlineAt: idle.deadlineAt };
|
|
@@ -3072,6 +3080,7 @@ function resolveIdleTimeoutMs(args) {
|
|
|
3072
3080
|
return DEFAULT_IDLE_SECONDS * 1e3;
|
|
3073
3081
|
}
|
|
3074
3082
|
var DEFAULT_CONNECT_WATCHDOG_SECONDS = 12;
|
|
3083
|
+
var DEFAULT_READY_TIMEOUT_SECONDS = 30;
|
|
3075
3084
|
function resolveWarnBeforeMs(idleTimeoutMs, override, idleWarnLeadSeconds) {
|
|
3076
3085
|
if (idleTimeoutMs <= 0) return 0;
|
|
3077
3086
|
const maxLeadMs = Math.max(0, idleTimeoutMs - 1e3);
|
|
@@ -3097,6 +3106,7 @@ function useSessionLifecycle(input) {
|
|
|
3097
3106
|
maxReconnectAttempts,
|
|
3098
3107
|
autoRetryBusy = true,
|
|
3099
3108
|
connectWatchdogSeconds,
|
|
3109
|
+
readyTimeoutSeconds,
|
|
3100
3110
|
requestOptions,
|
|
3101
3111
|
onBehaviorChange
|
|
3102
3112
|
} = input;
|
|
@@ -3107,17 +3117,27 @@ function useSessionLifecycle(input) {
|
|
|
3107
3117
|
const reconnectPolicyRef = useRef(reconnectPolicy);
|
|
3108
3118
|
reconnectPolicyRef.current = reconnectPolicy;
|
|
3109
3119
|
const connectWatchdogMs = typeof connectWatchdogSeconds === "number" && Number.isFinite(connectWatchdogSeconds) ? Math.max(0, Math.floor(connectWatchdogSeconds)) * 1e3 : DEFAULT_CONNECT_WATCHDOG_SECONDS * 1e3;
|
|
3120
|
+
const readyTimeoutMs = typeof readyTimeoutSeconds === "number" && Number.isFinite(readyTimeoutSeconds) && readyTimeoutSeconds > 0 ? Math.max(1, Math.floor(readyTimeoutSeconds)) * 1e3 : DEFAULT_READY_TIMEOUT_SECONDS * 1e3;
|
|
3110
3121
|
const [userEnded, setUserEnded] = useState(false);
|
|
3111
3122
|
const userEndedRef = useRef(false);
|
|
3123
|
+
const [readinessFailed, setReadinessFailed] = useState(false);
|
|
3124
|
+
const readinessFailureRef = useRef(null);
|
|
3125
|
+
const readinessRef = useRef(null);
|
|
3126
|
+
const readyGrantRef = useRef(null);
|
|
3127
|
+
const agentWaitRef = useRef(null);
|
|
3112
3128
|
const phaseRef = useRef({ kind: "idle" });
|
|
3113
3129
|
const grantState = useLiveKitAvatarGrant({
|
|
3114
3130
|
client,
|
|
3115
3131
|
session,
|
|
3116
|
-
active: active && !userEnded,
|
|
3132
|
+
active: active && !userEnded && !readinessFailed,
|
|
3117
3133
|
autoRetryBusy,
|
|
3118
3134
|
requestOptions
|
|
3119
3135
|
});
|
|
3120
3136
|
const capacity = grantState.capacity;
|
|
3137
|
+
const grantId = grantState.grant?.session_id ?? null;
|
|
3138
|
+
const grantIdRef = useRef(null);
|
|
3139
|
+
grantIdRef.current = grantId;
|
|
3140
|
+
const retiredGrantRef = useRef(null);
|
|
3121
3141
|
const [recovery, setRecovery] = useState({ kind: "connected" });
|
|
3122
3142
|
const recoveryRef = useRef(recovery);
|
|
3123
3143
|
recoveryRef.current = recovery;
|
|
@@ -3140,8 +3160,19 @@ function useSessionLifecycle(input) {
|
|
|
3140
3160
|
grantIdleSeconds: grantState.grant?.idle_timeout_seconds ?? 0
|
|
3141
3161
|
});
|
|
3142
3162
|
const warnAtMs = resolveWarnBeforeMs(idleTimeoutMs, warnBeforeMs, idleWarnLeadSeconds);
|
|
3143
|
-
const [
|
|
3144
|
-
const [
|
|
3163
|
+
const [agentObservation, setAgentObservation] = useState({ grantId: null, present: false });
|
|
3164
|
+
const [connectionObservation, setConnectionObservation] = useState({ grantId: null, connected: false });
|
|
3165
|
+
const agentPresent = agentObservation.grantId === grantId && agentObservation.present;
|
|
3166
|
+
const connected = connectionObservation.grantId === grantId && connectionObservation.connected;
|
|
3167
|
+
connectedRef.current = connected;
|
|
3168
|
+
const setAgentPresentState = useCallback((present) => {
|
|
3169
|
+
const owner = grantIdRef.current;
|
|
3170
|
+
setAgentObservation((prev) => prev.grantId === owner && prev.present === present ? prev : { grantId: owner, present });
|
|
3171
|
+
}, []);
|
|
3172
|
+
const setConnected = useCallback((value) => {
|
|
3173
|
+
const owner = grantIdRef.current;
|
|
3174
|
+
setConnectionObservation((prev) => prev.grantId === owner && prev.connected === value ? prev : { grantId: owner, connected: value });
|
|
3175
|
+
}, []);
|
|
3145
3176
|
const lastActivityRef = useRef(Date.now());
|
|
3146
3177
|
const leaveRoomRef = useRef(null);
|
|
3147
3178
|
const [, setClockTick] = useState(0);
|
|
@@ -3158,7 +3189,11 @@ function useSessionLifecycle(input) {
|
|
|
3158
3189
|
refreshBaselineCapacityRef.current = null;
|
|
3159
3190
|
attemptRef.current = 0;
|
|
3160
3191
|
setAttempt(0);
|
|
3161
|
-
|
|
3192
|
+
readinessRef.current = null;
|
|
3193
|
+
readyGrantRef.current = null;
|
|
3194
|
+
agentWaitRef.current = null;
|
|
3195
|
+
retiredGrantRef.current = null;
|
|
3196
|
+
setRecovery(userEndedRef.current ? { kind: "ended", reason: "user" } : readinessFailureRef.current ? { kind: "ended", reason: "error", code: readinessFailureRef.current } : { kind: "connected" });
|
|
3162
3197
|
setConnected(false);
|
|
3163
3198
|
setAgentPresentState(false);
|
|
3164
3199
|
lastActivityRef.current = Date.now();
|
|
@@ -3167,26 +3202,33 @@ function useSessionLifecycle(input) {
|
|
|
3167
3202
|
if (active) return;
|
|
3168
3203
|
userEndedRef.current = false;
|
|
3169
3204
|
setUserEnded(false);
|
|
3205
|
+
readinessFailureRef.current = null;
|
|
3206
|
+
setReadinessFailed(false);
|
|
3170
3207
|
reset();
|
|
3171
3208
|
}, [active, reset]);
|
|
3172
3209
|
const markActivity = useCallback(() => {
|
|
3210
|
+
if (grantId === null || grantIdRef.current !== grantId) return;
|
|
3173
3211
|
lastActivityRef.current = Date.now();
|
|
3174
3212
|
setClockTick((value) => value + 1);
|
|
3175
|
-
}, []);
|
|
3213
|
+
}, [grantId]);
|
|
3176
3214
|
const stayConnected = useCallback(() => {
|
|
3215
|
+
if (grantId === null || grantIdRef.current !== grantId) return;
|
|
3177
3216
|
if (!connectedRef.current) return;
|
|
3178
3217
|
lastActivityRef.current = Date.now();
|
|
3179
3218
|
setClockTick((value) => value + 1);
|
|
3180
|
-
}, []);
|
|
3219
|
+
}, [grantId]);
|
|
3181
3220
|
const setAgentPresent = useCallback((present) => {
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3221
|
+
if (grantId === null || grantIdRef.current !== grantId) return;
|
|
3222
|
+
if (userEndedRef.current || readinessFailureRef.current || retiredGrantRef.current !== null && retiredGrantRef.current === grantIdRef.current) return;
|
|
3223
|
+
setAgentObservation((prev) => {
|
|
3224
|
+
if (present && (prev.grantId !== grantId || !prev.present)) lastActivityRef.current = Date.now();
|
|
3225
|
+
return prev.grantId === grantId && prev.present === present ? prev : { grantId, present };
|
|
3185
3226
|
});
|
|
3186
|
-
}, []);
|
|
3227
|
+
}, [grantId]);
|
|
3187
3228
|
const registerLeaveRoom = useCallback((leave) => {
|
|
3229
|
+
if (grantId === null || grantIdRef.current !== grantId) return;
|
|
3188
3230
|
leaveRoomRef.current = leave;
|
|
3189
|
-
}, []);
|
|
3231
|
+
}, [grantId]);
|
|
3190
3232
|
const endIdle = useCallback(() => {
|
|
3191
3233
|
if (!connectedRef.current) return;
|
|
3192
3234
|
clearTimer();
|
|
@@ -3198,24 +3240,25 @@ function useSessionLifecycle(input) {
|
|
|
3198
3240
|
leaveRoomRef.current?.();
|
|
3199
3241
|
}, [clearTimer]);
|
|
3200
3242
|
const onConnected = useCallback(() => {
|
|
3201
|
-
if (
|
|
3243
|
+
if (grantId === null || grantIdRef.current !== grantId) return;
|
|
3244
|
+
if (userEndedRef.current || readinessFailureRef.current || retiredGrantRef.current !== null && retiredGrantRef.current === grantIdRef.current) return;
|
|
3202
3245
|
clearTimer();
|
|
3203
3246
|
connectedRef.current = true;
|
|
3204
3247
|
manualReconnectPendingRef.current = false;
|
|
3205
3248
|
refreshBaselineCapacityRef.current = null;
|
|
3206
3249
|
setConnected(true);
|
|
3207
|
-
|
|
3208
|
-
setAttempt(0);
|
|
3250
|
+
if (readinessRef.current) readinessRef.current.sawTransport = true;
|
|
3209
3251
|
setRecovery({ kind: "connected" });
|
|
3210
3252
|
lastActivityRef.current = Date.now();
|
|
3211
|
-
}, [clearTimer]);
|
|
3253
|
+
}, [clearTimer, grantId]);
|
|
3212
3254
|
const onDisconnected = useCallback(
|
|
3213
3255
|
(reason) => {
|
|
3214
|
-
if (
|
|
3256
|
+
if (grantId === null || grantIdRef.current !== grantId) return;
|
|
3257
|
+
if (!active || userEndedRef.current || readinessFailureRef.current) return;
|
|
3215
3258
|
const action = disconnectAction(reason, connectedRef.current);
|
|
3216
3259
|
if (action === "noop") return;
|
|
3217
3260
|
if (action === "reset") {
|
|
3218
|
-
if (recoveryRef.current.kind === "refreshing") return;
|
|
3261
|
+
if (recoveryRef.current.kind === "refreshing" || recoveryRef.current.kind === "reconnecting") return;
|
|
3219
3262
|
reset();
|
|
3220
3263
|
return;
|
|
3221
3264
|
}
|
|
@@ -3232,11 +3275,12 @@ function useSessionLifecycle(input) {
|
|
|
3232
3275
|
refreshBaselineCapacityRef.current = null;
|
|
3233
3276
|
setRecovery({ kind: "reconnecting", attempt: attemptRef.current });
|
|
3234
3277
|
},
|
|
3235
|
-
[active, reset]
|
|
3278
|
+
[active, reset, grantId]
|
|
3236
3279
|
);
|
|
3237
3280
|
const onConnectionError = useCallback(
|
|
3238
3281
|
(error) => {
|
|
3239
|
-
if (
|
|
3282
|
+
if (grantId === null || grantIdRef.current !== grantId) return;
|
|
3283
|
+
if (!active || userEndedRef.current || readinessFailureRef.current || !isRecoverableConnectionError(error)) return;
|
|
3240
3284
|
connectedRef.current = false;
|
|
3241
3285
|
setConnected(false);
|
|
3242
3286
|
setAgentPresentState(false);
|
|
@@ -3247,7 +3291,7 @@ function useSessionLifecycle(input) {
|
|
|
3247
3291
|
return { kind: "reconnecting", attempt: attemptRef.current };
|
|
3248
3292
|
});
|
|
3249
3293
|
},
|
|
3250
|
-
[active]
|
|
3294
|
+
[active, grantId]
|
|
3251
3295
|
);
|
|
3252
3296
|
useEffect(() => {
|
|
3253
3297
|
if (capacity.kind === "error" && recovery.kind === "connected") manualReconnectPendingRef.current = false;
|
|
@@ -3257,11 +3301,12 @@ function useSessionLifecycle(input) {
|
|
|
3257
3301
|
const baseline = refreshBaselineCapacityRef.current;
|
|
3258
3302
|
if (baseline?.kind === "error" && baseline.error === capacity.error) return;
|
|
3259
3303
|
refreshBaselineCapacityRef.current = null;
|
|
3260
|
-
setRecovery({ kind: "reconnecting", attempt: recovery.attempt });
|
|
3304
|
+
setRecovery({ kind: "reconnecting", attempt: recovery.attempt, ...recovery.reason ? { reason: recovery.reason } : {} });
|
|
3261
3305
|
}, [capacity, recovery]);
|
|
3262
3306
|
const onConnectionStateChange = useCallback(
|
|
3263
3307
|
(state) => {
|
|
3264
|
-
if (
|
|
3308
|
+
if (grantId === null || grantIdRef.current !== grantId) return;
|
|
3309
|
+
if (!active || userEndedRef.current || readinessFailureRef.current) return;
|
|
3265
3310
|
if (state === "connected") {
|
|
3266
3311
|
onConnected();
|
|
3267
3312
|
return;
|
|
@@ -3275,14 +3320,78 @@ function useSessionLifecycle(input) {
|
|
|
3275
3320
|
}
|
|
3276
3321
|
}
|
|
3277
3322
|
},
|
|
3278
|
-
[active, onConnected]
|
|
3323
|
+
[active, onConnected, grantId]
|
|
3279
3324
|
);
|
|
3325
|
+
useEffect(() => {
|
|
3326
|
+
if (!active || userEnded || readinessFailed || !connected || !agentPresent || !grantState.grant) return;
|
|
3327
|
+
readyGrantRef.current = grantState.grant.session_id;
|
|
3328
|
+
readinessRef.current = null;
|
|
3329
|
+
agentWaitRef.current = null;
|
|
3330
|
+
attemptRef.current = 0;
|
|
3331
|
+
setAttempt(0);
|
|
3332
|
+
}, [active, userEnded, readinessFailed, connected, agentPresent, grantState.grant]);
|
|
3333
|
+
const failReadiness = useCallback((code) => {
|
|
3334
|
+
if (userEndedRef.current || readinessFailureRef.current) return;
|
|
3335
|
+
readinessFailureRef.current = code;
|
|
3336
|
+
clearTimer();
|
|
3337
|
+
connectedRef.current = false;
|
|
3338
|
+
manualReconnectPendingRef.current = false;
|
|
3339
|
+
refreshBaselineCapacityRef.current = null;
|
|
3340
|
+
const ended = { kind: "ended", reason: "error", code };
|
|
3341
|
+
recoveryRef.current = ended;
|
|
3342
|
+
setRecovery(ended);
|
|
3343
|
+
setConnected(false);
|
|
3344
|
+
setAgentPresentState(false);
|
|
3345
|
+
releaseRef.current("disconnected");
|
|
3346
|
+
setReadinessFailed(true);
|
|
3347
|
+
leaveRoomRef.current?.();
|
|
3348
|
+
}, [clearTimer]);
|
|
3349
|
+
useEffect(() => {
|
|
3350
|
+
if (!active || userEnded || readinessFailed) return;
|
|
3351
|
+
if (recovery.kind === "ended" || recovery.kind === "failed") return;
|
|
3352
|
+
if (grantState.grant && readyGrantRef.current === grantState.grant.session_id) return;
|
|
3353
|
+
if (!readinessRef.current && grantState.grant) {
|
|
3354
|
+
readinessRef.current = { startedAt: Date.now(), sawTransport: connected };
|
|
3355
|
+
}
|
|
3356
|
+
const budget = readinessRef.current;
|
|
3357
|
+
if (!budget) return;
|
|
3358
|
+
const timer = window.setTimeout(() => {
|
|
3359
|
+
if (readinessRef.current !== budget) return;
|
|
3360
|
+
failReadiness(budget.sawTransport ? "agent_timeout" : "connection_timeout");
|
|
3361
|
+
}, Math.max(0, budget.startedAt + readyTimeoutMs - Date.now()));
|
|
3362
|
+
return () => window.clearTimeout(timer);
|
|
3363
|
+
}, [active, userEnded, readinessFailed, connected, agentPresent, grantState.grant, recovery.kind, readyTimeoutMs, failReadiness]);
|
|
3364
|
+
useEffect(() => {
|
|
3365
|
+
const sessionId = grantState.grant?.session_id;
|
|
3366
|
+
if (!active || userEnded || readinessFailed || !sessionId || readyGrantRef.current === sessionId || agentPresent) return;
|
|
3367
|
+
if (recovery.kind === "reconnecting" || recovery.kind === "failed" || recovery.kind === "ended") return;
|
|
3368
|
+
if (agentWaitRef.current?.sessionId !== sessionId) {
|
|
3369
|
+
if (!connected) return;
|
|
3370
|
+
agentWaitRef.current = { sessionId, startedAt: Date.now() };
|
|
3371
|
+
}
|
|
3372
|
+
const wait = agentWaitRef.current;
|
|
3373
|
+
if (connectWatchdogMs <= 0) return;
|
|
3374
|
+
const timer = window.setTimeout(() => {
|
|
3375
|
+
if (agentWaitRef.current !== wait || readinessFailureRef.current || userEndedRef.current) return;
|
|
3376
|
+
retiredGrantRef.current = sessionId;
|
|
3377
|
+
connectedRef.current = false;
|
|
3378
|
+
setConnected(false);
|
|
3379
|
+
setAgentPresentState(false);
|
|
3380
|
+
const next = { kind: "reconnecting", attempt: attemptRef.current, reason: "agent_timeout" };
|
|
3381
|
+
recoveryRef.current = next;
|
|
3382
|
+
setRecovery(next);
|
|
3383
|
+
releaseRef.current("disconnected");
|
|
3384
|
+
leaveRoomRef.current?.();
|
|
3385
|
+
}, Math.max(0, wait.startedAt + connectWatchdogMs - Date.now()));
|
|
3386
|
+
return () => window.clearTimeout(timer);
|
|
3387
|
+
}, [active, userEnded, readinessFailed, connected, agentPresent, grantState.grant, recovery.kind, connectWatchdogMs]);
|
|
3280
3388
|
useEffect(() => {
|
|
3281
3389
|
if (!active || connected || connectWatchdogMs <= 0) return;
|
|
3282
3390
|
if (grantState.status !== "ready" || !grantState.grant) return;
|
|
3283
3391
|
if (recovery.kind === "failed" || recovery.kind === "ended") return;
|
|
3284
3392
|
if (recovery.kind === "in-place-reconnecting") return;
|
|
3285
3393
|
const timer = window.setTimeout(() => {
|
|
3394
|
+
if (readinessFailureRef.current || userEndedRef.current) return;
|
|
3286
3395
|
releaseRef.current("disconnected");
|
|
3287
3396
|
setRecovery({ kind: "reconnecting", attempt: attemptRef.current });
|
|
3288
3397
|
}, connectWatchdogMs);
|
|
@@ -3292,6 +3401,10 @@ function useSessionLifecycle(input) {
|
|
|
3292
3401
|
if (!active || !needsFreshGrant(recovery)) return;
|
|
3293
3402
|
const step2 = retryStep(attemptRef.current, reconnectPolicyRef.current);
|
|
3294
3403
|
if (step2.kind === "give-up") {
|
|
3404
|
+
if (readinessRef.current) {
|
|
3405
|
+
failReadiness(readinessRef.current.sawTransport ? "agent_timeout" : "connection_timeout");
|
|
3406
|
+
return;
|
|
3407
|
+
}
|
|
3295
3408
|
manualReconnectPendingRef.current = false;
|
|
3296
3409
|
refreshBaselineCapacityRef.current = null;
|
|
3297
3410
|
releaseRef.current("disconnected");
|
|
@@ -3300,15 +3413,16 @@ function useSessionLifecycle(input) {
|
|
|
3300
3413
|
}
|
|
3301
3414
|
timerRef.current = window.setTimeout(() => {
|
|
3302
3415
|
timerRef.current = null;
|
|
3416
|
+
if (readinessFailureRef.current || userEndedRef.current) return;
|
|
3303
3417
|
attemptRef.current = step2.attempt;
|
|
3304
3418
|
setAttempt(step2.attempt);
|
|
3305
3419
|
refreshBaselineCapacityRef.current = capacityRef.current;
|
|
3306
3420
|
releaseRef.current("superseded");
|
|
3307
|
-
setRecovery({ kind: "refreshing", attempt: step2.attempt });
|
|
3421
|
+
setRecovery({ kind: "refreshing", attempt: step2.attempt, ...recovery.reason ? { reason: recovery.reason } : {} });
|
|
3308
3422
|
refreshRef.current();
|
|
3309
3423
|
}, step2.delayMs);
|
|
3310
3424
|
return clearTimer;
|
|
3311
|
-
}, [recovery.kind, active, attempt, clearTimer]);
|
|
3425
|
+
}, [recovery.kind, active, attempt, clearTimer, failReadiness]);
|
|
3312
3426
|
const end = useCallback(() => {
|
|
3313
3427
|
if (!active || userEndedRef.current || phaseRef.current.kind === "ended") return;
|
|
3314
3428
|
userEndedRef.current = true;
|
|
@@ -3325,6 +3439,13 @@ function useSessionLifecycle(input) {
|
|
|
3325
3439
|
}, [active, clearTimer]);
|
|
3326
3440
|
const reconnect = useCallback(() => {
|
|
3327
3441
|
if (!active) return;
|
|
3442
|
+
if (readinessFailureRef.current) {
|
|
3443
|
+
readinessFailureRef.current = null;
|
|
3444
|
+
setReadinessFailed(false);
|
|
3445
|
+
reset();
|
|
3446
|
+
manualReconnectPendingRef.current = true;
|
|
3447
|
+
return;
|
|
3448
|
+
}
|
|
3328
3449
|
if (userEndedRef.current) {
|
|
3329
3450
|
userEndedRef.current = false;
|
|
3330
3451
|
setUserEnded(false);
|
|
@@ -3335,6 +3456,12 @@ function useSessionLifecycle(input) {
|
|
|
3335
3456
|
if (manualReconnectPendingRef.current || recovery.kind === "in-place-reconnecting") return;
|
|
3336
3457
|
manualReconnectPendingRef.current = true;
|
|
3337
3458
|
clearTimer();
|
|
3459
|
+
readinessRef.current = null;
|
|
3460
|
+
agentWaitRef.current = null;
|
|
3461
|
+
readyGrantRef.current = null;
|
|
3462
|
+
connectedRef.current = false;
|
|
3463
|
+
setConnected(false);
|
|
3464
|
+
setAgentPresentState(false);
|
|
3338
3465
|
attemptRef.current = 0;
|
|
3339
3466
|
setAttempt(0);
|
|
3340
3467
|
refreshBaselineCapacityRef.current = capacityRef.current;
|
|
@@ -3454,6 +3581,7 @@ function SessionLifecycleRoomBridge({
|
|
|
3454
3581
|
const assistant = useVoiceAssistant();
|
|
3455
3582
|
const transcriptions = useTranscriptions();
|
|
3456
3583
|
const room = useRoomContext();
|
|
3584
|
+
const roomMatchesGrant = lifecycle.grant === void 0 || lifecycle.grant !== null && room.name === lifecycle.grant.room_name;
|
|
3457
3585
|
useCallMedia(room, {
|
|
3458
3586
|
setMicrophoneFacts,
|
|
3459
3587
|
setAudioPlayback,
|
|
@@ -3607,11 +3735,11 @@ function SessionLifecycleRoomBridge({
|
|
|
3607
3735
|
detailsEnabled ? video?.publication : void 0
|
|
3608
3736
|
]);
|
|
3609
3737
|
useEffect(() => {
|
|
3610
|
-
onConnectionStateChange(connectionState);
|
|
3611
|
-
}, [connectionState, onConnectionStateChange]);
|
|
3738
|
+
if (roomMatchesGrant) onConnectionStateChange(connectionState);
|
|
3739
|
+
}, [connectionState, onConnectionStateChange, roomMatchesGrant]);
|
|
3612
3740
|
useEffect(() => {
|
|
3613
|
-
setAgentPresent(agentPresent);
|
|
3614
|
-
}, [agentPresent, setAgentPresent]);
|
|
3741
|
+
if (roomMatchesGrant) setAgentPresent(agentPresent);
|
|
3742
|
+
}, [agentPresent, setAgentPresent, roomMatchesGrant]);
|
|
3615
3743
|
useEffect(() => {
|
|
3616
3744
|
const active = isCallActivity({
|
|
3617
3745
|
assistantState,
|
|
@@ -4675,4 +4803,4 @@ function RecordingPlayer({ assets, className }) {
|
|
|
4675
4803
|
] });
|
|
4676
4804
|
}
|
|
4677
4805
|
|
|
4678
|
-
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, NETWORK_QUALITY_POLICY, RECONNECT_BACKOFF_MS, RealtimeAvatarCapacityError, RealtimeAvatarLiveKitRoom, RecordingPlayer, SessionLifecycleRoomBridge, capacityErrorFromBusy, capacityStateFromGrant, createProxyClient, knownBehaviorStates, mapTurnState, readInboundRtp, sessionBehaviorSchema, sessionClipSchema, shouldReplayPendingTurn, splitCallTranscript, useAvatarAdaptivePlayoutDelay, useAvatarCall, useAvatarCamera, useAvatarPlayoutDelay, useAvatarQualityGovernor, useCallTranscript, useCharacterTools, useLiveKitAvatarGrant, useMicLease, useRealtimeSession, useReleaseMicLeaseOnTrackEnded, useSessionLifecycle };
|
|
4806
|
+
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_READY_TIMEOUT_SECONDS, DEFAULT_TURN_TIMEOUT_SECONDS, MAX_RECONNECT_ATTEMPTS, MAX_SESSION_INSTRUCTIONS_CHARS, MIC_LEASE_ENDED_TIMEOUT_MS, NETWORK_QUALITY_POLICY, RECONNECT_BACKOFF_MS, RealtimeAvatarCapacityError, RealtimeAvatarLiveKitRoom, RecordingPlayer, SessionLifecycleRoomBridge, capacityErrorFromBusy, capacityStateFromGrant, createProxyClient, knownBehaviorStates, mapTurnState, readInboundRtp, sessionBehaviorSchema, sessionClipSchema, shouldReplayPendingTurn, splitCallTranscript, useAvatarAdaptivePlayoutDelay, useAvatarCall, useAvatarCamera, useAvatarPlayoutDelay, useAvatarQualityGovernor, useCallTranscript, useCharacterTools, useLiveKitAvatarGrant, useMicLease, useRealtimeSession, useReleaseMicLeaseOnTrackEnded, useSessionLifecycle };
|
package/dist/server.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, listRecordingsResponseSchema, recordingAccessResponseSchema, recordingArtifactSchema, recordingModeSchema, verifyTranscript } from './chunk-
|
|
1
|
+
export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, listRecordingsResponseSchema, recordingAccessResponseSchema, recordingArtifactSchema, recordingModeSchema, verifyTranscript } from './chunk-4IEGR75H.js';
|
|
2
2
|
import './chunk-MCIXU6XP.js';
|
package/dist/tanstack-start.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "realtime-avatar",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.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",
|