realtime-avatar 0.16.0 → 0.17.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 CHANGED
@@ -4,7 +4,7 @@ A live character your users can talk to — voice, or voice and video. She liste
4
4
  speaks, so you can interrupt her mid-sentence and she stops, the way a person stops.
5
5
 
6
6
  ```bash
7
- npm install --save-exact realtime-avatar@0.16.0
7
+ npm install --save-exact realtime-avatar@0.17.0
8
8
  ```
9
9
 
10
10
  ```ts
@@ -57,6 +57,24 @@ default); each URL expires at `expiresAt` (up to one hour, capped by retention).
57
57
  access before replaying or seeking after expiry. An expired URL does not delete the file.
58
58
  Treat the URL as private: anyone who has it can play that file until it expires.
59
59
 
60
+ ## Optional connection history
61
+
62
+ Set `connectionHistory: true` in the server's `startCall` policy when the caller needs a
63
+ tenant-scoped LiveKit quality timeline. The SDK enables its collector only when RTA grants the
64
+ single-session capability. It receives the bridge's existing LiveKit connection snapshots,
65
+ deduplicates changes, batches at most 32 observations, and stops at 240; it does not poll WebRTC
66
+ statistics or own reconnect logic. Upload failures are bounded and never interrupt a call.
67
+
68
+ ```ts
69
+ const call = await rta.startCall({ avatarId, connectionHistory: true });
70
+ // Relay call.raw unchanged. The browser SDK uploads capability-gated observations automatically.
71
+ const history = await rta.getConnectionHistory(call.sessionId);
72
+ ```
73
+
74
+ History is an observation of native connection state and publisher quality. It is not a recording,
75
+ packet-loss report, or proof that a device rendered or played a track. The read requires
76
+ `usage:read`; store the session ID and fetch history from your authorized backend.
77
+
60
78
  Transcript delivery remains the signed `transcript` webhook configured on `startCall`. Join the
61
79
  transcript and recordings by `sessionId`; keep your own script revision with that call. Transcript
62
80
  timestamps describe conversation turns and do not by themselves establish frame-accurate media
@@ -137,13 +155,14 @@ all), and the `video` policy types are deliberately not one-to-one with the wire
137
155
 
138
156
  ```ts
139
157
  // calls
140
- rta.startCall({ avatarId, mode?, instructions?, context?, maxSeconds?, video?, recording?, transcript?, metadata? })
158
+ rta.startCall({ avatarId, mode?, instructions?, context?, maxSeconds?, video?, recording?, connectionHistory?, transcript?, metadata? })
141
159
  rta.endCall(sessionId, { reason? }) // free an abandoned call's slot; idempotent, never throws
142
160
 
143
161
  // optional recordings; server only, requires recordings:read
144
162
  rta.listRecordings({ sessionId, limit?, cursor? })
145
163
  rta.getRecording(recordingId)
146
164
  rta.getRecordingAccess(recordingId)
165
+ rta.getConnectionHistory(sessionId) // bounded LiveKit observations; requires usage:read
147
166
 
148
167
  // avatars
149
168
  rta.createAvatarFromImage({ displayName, imageUrl, motionPrompt?, voice? }) // the only lane
@@ -1,3 +1,4 @@
1
+ import { connectionHistoryGrantSchema } from './chunk-MCIXU6XP.js';
1
2
  import { z } from 'zod';
2
3
 
3
4
  // ../http-client/src/errors.ts
@@ -432,7 +433,8 @@ z.object({
432
433
  render_backend: renderBackendSchema.optional(),
433
434
  support_edits: sessionSupportEditsSchema.optional(),
434
435
  transcript_webhook: transcriptWebhookSchema.optional(),
435
- client_metadata: clientMetadataSchema.optional()
436
+ client_metadata: clientMetadataSchema.optional(),
437
+ connection_history: z.boolean().optional()
436
438
  }).strict().superRefine((value, ctx) => {
437
439
  if (value.support_edits && value.render_backend === "generative") {
438
440
  ctx.addIssue({
@@ -497,10 +499,12 @@ z.object({
497
499
  renderBackend: renderBackendSchema.optional(),
498
500
  supportEdits: sessionSupportEditsSchema.optional(),
499
501
  transcriptWebhook: transcriptWebhookSchema.optional(),
500
- clientMetadata: clientMetadataSchema.optional()
502
+ clientMetadata: clientMetadataSchema.optional(),
503
+ connectionHistory: z.boolean().optional()
501
504
  }).strict();
502
505
  var liveKitSessionGrantSchema = z.object({
503
506
  recording: recordingArtifactSchema.optional(),
507
+ connection_history: connectionHistoryGrantSchema.optional(),
504
508
  status: z.literal("ready").default("ready"),
505
509
  session_id: z.string().min(1),
506
510
  room_name: z.string().min(1),
@@ -596,7 +600,7 @@ z.enum([
596
600
 
597
601
  // ../http-client/src/client.ts
598
602
  var DEFAULT_BASE_URL = "https://realtimeavatar.ai/api/v1";
599
- var SDK_VERSION = "0.16.0";
603
+ var SDK_VERSION = "0.17.1";
600
604
  var RealtimeAvatar = class {
601
605
  #apiKey;
602
606
  #baseUrl;
@@ -642,6 +646,7 @@ var RealtimeAvatar = class {
642
646
  if (options.voice !== void 0) body.voice = options.voice;
643
647
  if (options.metadata !== void 0) body.client_metadata = options.metadata;
644
648
  if (options.recording !== void 0) body.recording = recordingModeSchema.parse(options.recording);
649
+ if (options.connectionHistory !== void 0) body.connection_history = options.connectionHistory;
645
650
  if (options.clientTools) body.capabilities = ["client_tools"];
646
651
  if (options.transcript !== void 0) {
647
652
  body.transcript_webhook = { url: options.transcript.url, secret: options.transcript.secret };
@@ -675,7 +680,8 @@ var RealtimeAvatar = class {
675
680
  idleTimeoutSeconds: grant.idle_timeout_seconds,
676
681
  reservationExpiresAt: grant.reservation_expires_at,
677
682
  // The parsed fields above are for YOUR logic. Relay `raw` to the client untouched:
678
- // the browser SDK validates the grant strictly and rejects an added or renamed key.
683
+ // the browser SDK accepts provider-issued capability fields through its passthrough
684
+ // grant schema; forwarding verbatim preserves capabilities this wrapper does not model.
679
685
  raw
680
686
  };
681
687
  }
@@ -1014,6 +1020,12 @@ var RealtimeAvatar = class {
1014
1020
  async getRecordingAccess(recordingId) {
1015
1021
  return recordingAccessResponseSchema.parse(await this.#json(await this.#request("GET", `/recordings/${encodeURIComponent(recordingId)}/access`)));
1016
1022
  }
1023
+ /** Read one session's bounded LiveKit connection history. Requires `usage:read`. */
1024
+ async getConnectionHistory(sessionId) {
1025
+ if (!sessionId) throw new RealtimeAvatarError("sessionId is required");
1026
+ const { connectionHistoryResponseSchema } = await import('./connection-history-CXL5QE56.js');
1027
+ return connectionHistoryResponseSchema.parse(await this.#json(await this.#request("GET", `/sessions/${encodeURIComponent(sessionId)}/connection-history`)));
1028
+ }
1017
1029
  async creditBalance() {
1018
1030
  return await this.#json(await this.#request("GET", "/credits/balance"));
1019
1031
  }
@@ -0,0 +1,33 @@
1
+ import { z } from 'zod';
2
+
3
+ // ../http-client/src/generated/connection-history.ts
4
+ var ConnectionQuality = { Excellent: "excellent", Good: "good", Poor: "poor", Lost: "lost", Unknown: "unknown" };
5
+ var ConnectionState = { Disconnected: "disconnected", Connecting: "connecting", Connected: "connected", Reconnecting: "reconnecting", SignalReconnecting: "signalReconnecting" };
6
+ var MAX_CONNECTION_HISTORY_OBSERVATIONS = 240;
7
+ var MAX_CONNECTION_HISTORY_BATCH = 32;
8
+ var CONNECTION_HISTORY_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
9
+ var quality = z.enum(ConnectionQuality);
10
+ var connectionHistoryObservationSchema = z.object({
11
+ sequence: z.number().int().min(1).max(MAX_CONNECTION_HISTORY_OBSERVATIONS),
12
+ elapsedMs: z.number().int().min(0).max(864e5),
13
+ clientObservedAt: z.string().datetime({ offset: true }).nullable(),
14
+ connectionState: z.enum(ConnectionState),
15
+ localQuality: quality,
16
+ audioQuality: quality.nullable(),
17
+ videoQuality: quality.nullable()
18
+ }).strict();
19
+ var connectionHistoryGrantSchema = z.object({
20
+ endpoint: z.string().url().startsWith("https://"),
21
+ token: z.string().min(32).max(4096),
22
+ expiresAt: z.string().datetime({ offset: true })
23
+ }).strict();
24
+ var connectionHistoryUploadSchema = z.object({
25
+ sessionId: z.string().min(1).max(200),
26
+ observations: z.array(connectionHistoryObservationSchema).min(1).max(MAX_CONNECTION_HISTORY_BATCH)
27
+ }).strict();
28
+ var connectionHistoryResponseSchema = z.object({
29
+ sessionId: z.string().min(1).max(200),
30
+ observations: z.array(connectionHistoryObservationSchema).max(MAX_CONNECTION_HISTORY_OBSERVATIONS)
31
+ }).strict();
32
+
33
+ export { CONNECTION_HISTORY_RETENTION_MS, MAX_CONNECTION_HISTORY_BATCH, MAX_CONNECTION_HISTORY_OBSERVATIONS, connectionHistoryGrantSchema, connectionHistoryObservationSchema, connectionHistoryResponseSchema, connectionHistoryUploadSchema };
@@ -1,4 +1,4 @@
1
- import { RealtimeAvatar, isQueued, RealtimeAvatarHttpError } from './chunk-V26PE2M3.js';
1
+ import { RealtimeAvatar, isQueued, RealtimeAvatarHttpError } from './chunk-4OLLYAJT.js';
2
2
 
3
3
  // ../proxy/src/config.ts
4
4
  var json = (body, status = 200) => new Response(JSON.stringify(body), {
@@ -0,0 +1 @@
1
+ export { CONNECTION_HISTORY_RETENTION_MS, MAX_CONNECTION_HISTORY_BATCH, MAX_CONNECTION_HISTORY_OBSERVATIONS, connectionHistoryGrantSchema, connectionHistoryObservationSchema, connectionHistoryResponseSchema, connectionHistoryUploadSchema } from './chunk-MCIXU6XP.js';
@@ -0,0 +1,121 @@
1
+ import { z } from 'zod';
2
+
3
+ declare const MAX_CONNECTION_HISTORY_OBSERVATIONS = 240;
4
+ declare const MAX_CONNECTION_HISTORY_BATCH = 32;
5
+ declare const CONNECTION_HISTORY_RETENTION_MS: number;
6
+ declare const connectionHistoryObservationSchema: z.ZodObject<{
7
+ sequence: z.ZodNumber;
8
+ elapsedMs: z.ZodNumber;
9
+ clientObservedAt: z.ZodNullable<z.ZodString>;
10
+ connectionState: z.ZodEnum<{
11
+ readonly Disconnected: "disconnected";
12
+ readonly Connecting: "connecting";
13
+ readonly Connected: "connected";
14
+ readonly Reconnecting: "reconnecting";
15
+ readonly SignalReconnecting: "signalReconnecting";
16
+ }>;
17
+ localQuality: z.ZodEnum<{
18
+ readonly Excellent: "excellent";
19
+ readonly Good: "good";
20
+ readonly Poor: "poor";
21
+ readonly Lost: "lost";
22
+ readonly Unknown: "unknown";
23
+ }>;
24
+ audioQuality: z.ZodNullable<z.ZodEnum<{
25
+ readonly Excellent: "excellent";
26
+ readonly Good: "good";
27
+ readonly Poor: "poor";
28
+ readonly Lost: "lost";
29
+ readonly Unknown: "unknown";
30
+ }>>;
31
+ videoQuality: z.ZodNullable<z.ZodEnum<{
32
+ readonly Excellent: "excellent";
33
+ readonly Good: "good";
34
+ readonly Poor: "poor";
35
+ readonly Lost: "lost";
36
+ readonly Unknown: "unknown";
37
+ }>>;
38
+ }, z.core.$strict>;
39
+ declare const connectionHistoryGrantSchema: z.ZodObject<{
40
+ endpoint: z.ZodString;
41
+ token: z.ZodString;
42
+ expiresAt: z.ZodString;
43
+ }, z.core.$strict>;
44
+ declare const connectionHistoryUploadSchema: z.ZodObject<{
45
+ sessionId: z.ZodString;
46
+ observations: z.ZodArray<z.ZodObject<{
47
+ sequence: z.ZodNumber;
48
+ elapsedMs: z.ZodNumber;
49
+ clientObservedAt: z.ZodNullable<z.ZodString>;
50
+ connectionState: z.ZodEnum<{
51
+ readonly Disconnected: "disconnected";
52
+ readonly Connecting: "connecting";
53
+ readonly Connected: "connected";
54
+ readonly Reconnecting: "reconnecting";
55
+ readonly SignalReconnecting: "signalReconnecting";
56
+ }>;
57
+ localQuality: z.ZodEnum<{
58
+ readonly Excellent: "excellent";
59
+ readonly Good: "good";
60
+ readonly Poor: "poor";
61
+ readonly Lost: "lost";
62
+ readonly Unknown: "unknown";
63
+ }>;
64
+ audioQuality: z.ZodNullable<z.ZodEnum<{
65
+ readonly Excellent: "excellent";
66
+ readonly Good: "good";
67
+ readonly Poor: "poor";
68
+ readonly Lost: "lost";
69
+ readonly Unknown: "unknown";
70
+ }>>;
71
+ videoQuality: z.ZodNullable<z.ZodEnum<{
72
+ readonly Excellent: "excellent";
73
+ readonly Good: "good";
74
+ readonly Poor: "poor";
75
+ readonly Lost: "lost";
76
+ readonly Unknown: "unknown";
77
+ }>>;
78
+ }, z.core.$strict>>;
79
+ }, z.core.$strict>;
80
+ declare const connectionHistoryResponseSchema: z.ZodObject<{
81
+ sessionId: z.ZodString;
82
+ observations: z.ZodArray<z.ZodObject<{
83
+ sequence: z.ZodNumber;
84
+ elapsedMs: z.ZodNumber;
85
+ clientObservedAt: z.ZodNullable<z.ZodString>;
86
+ connectionState: z.ZodEnum<{
87
+ readonly Disconnected: "disconnected";
88
+ readonly Connecting: "connecting";
89
+ readonly Connected: "connected";
90
+ readonly Reconnecting: "reconnecting";
91
+ readonly SignalReconnecting: "signalReconnecting";
92
+ }>;
93
+ localQuality: z.ZodEnum<{
94
+ readonly Excellent: "excellent";
95
+ readonly Good: "good";
96
+ readonly Poor: "poor";
97
+ readonly Lost: "lost";
98
+ readonly Unknown: "unknown";
99
+ }>;
100
+ audioQuality: z.ZodNullable<z.ZodEnum<{
101
+ readonly Excellent: "excellent";
102
+ readonly Good: "good";
103
+ readonly Poor: "poor";
104
+ readonly Lost: "lost";
105
+ readonly Unknown: "unknown";
106
+ }>>;
107
+ videoQuality: z.ZodNullable<z.ZodEnum<{
108
+ readonly Excellent: "excellent";
109
+ readonly Good: "good";
110
+ readonly Poor: "poor";
111
+ readonly Lost: "lost";
112
+ readonly Unknown: "unknown";
113
+ }>>;
114
+ }, z.core.$strict>>;
115
+ }, z.core.$strict>;
116
+ type ConnectionHistoryObservation = z.infer<typeof connectionHistoryObservationSchema>;
117
+ type ConnectionHistoryGrant = z.infer<typeof connectionHistoryGrantSchema>;
118
+ type ConnectionHistoryUpload = z.infer<typeof connectionHistoryUploadSchema>;
119
+ type ConnectionHistoryResponse = z.infer<typeof connectionHistoryResponseSchema>;
120
+
121
+ export { CONNECTION_HISTORY_RETENTION_MS, type ConnectionHistoryGrant, type ConnectionHistoryObservation, type ConnectionHistoryResponse, type ConnectionHistoryUpload, MAX_CONNECTION_HISTORY_BATCH, MAX_CONNECTION_HISTORY_OBSERVATIONS, connectionHistoryGrantSchema, connectionHistoryObservationSchema, connectionHistoryResponseSchema, connectionHistoryUploadSchema };
@@ -0,0 +1 @@
1
+ export { CONNECTION_HISTORY_RETENTION_MS, MAX_CONNECTION_HISTORY_BATCH, MAX_CONNECTION_HISTORY_OBSERVATIONS, connectionHistoryGrantSchema, connectionHistoryObservationSchema, connectionHistoryResponseSchema, connectionHistoryUploadSchema } from './chunk-MCIXU6XP.js';
package/dist/express.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as ProxyConfig } from './types-Bphj69NC.js';
2
- import './types-VOv0vsFs.js';
1
+ import { P as ProxyConfig } from './types-CFkiNV3f.js';
2
+ import './types-BnlnrYgN.js';
3
3
  import 'zod';
4
4
 
5
5
  type Expressish = {
package/dist/express.js CHANGED
@@ -1,5 +1,6 @@
1
- import { createProxyHandler } from './chunk-2TNFET3Q.js';
2
- import './chunk-V26PE2M3.js';
1
+ import { createProxyHandler } from './chunk-Q34WDWEA.js';
2
+ import './chunk-4OLLYAJT.js';
3
+ import './chunk-MCIXU6XP.js';
3
4
 
4
5
  // ../proxy/src/express.ts
5
6
  function realtimeAvatarExpress(config) {
package/dist/hono.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as ProxyConfig } from './types-Bphj69NC.js';
2
- import './types-VOv0vsFs.js';
1
+ import { P as ProxyConfig } from './types-CFkiNV3f.js';
2
+ import './types-BnlnrYgN.js';
3
3
  import 'zod';
4
4
 
5
5
  /**
package/dist/hono.js CHANGED
@@ -1,5 +1,6 @@
1
- import { createProxyHandler } from './chunk-2TNFET3Q.js';
2
- import './chunk-V26PE2M3.js';
1
+ import { createProxyHandler } from './chunk-Q34WDWEA.js';
2
+ import './chunk-4OLLYAJT.js';
3
+ import './chunk-MCIXU6XP.js';
3
4
 
4
5
  // ../proxy/src/hono.ts
5
6
  function realtimeAvatarHono(config) {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { a as CallPolicy, C as CallMode, S as StartCallResult, E as EndCallOptions, A as Avatar, b as AvatarUpdate, c as AvatarSourceSwap, d as ClipLibraryDeclaration, e as ClipLibraryUpdate, L as LoopRedirect, f as ClipLibrary, g as AssetKind, h as Asset, i as ListSessionsOptions, U as UsageSessionPage, j as UsageSession, k as ListRecordingsQuery, l as ListRecordingsResponse, R as RecordingArtifact, m as RecordingAccessResponse, n as CreditBalance, T as TranscriptPayload } from './types-VOv0vsFs.js';
2
- export { o as AvatarClip, p as CallConnection, q as CallQueued, r as ClipDeclaration, s as ClipLibraryPlan, t as ClipSource, u as ContextMessage, v as EndCallReason, w as RecordingArtifactStatus, x as RecordingMode, V as VideoPolicy, y as isQueued, z as listRecordingsResponseSchema, B as recordingAccessResponseSchema, D as recordingArtifactSchema, F as recordingModeSchema } from './types-VOv0vsFs.js';
1
+ import { a as CallPolicy, C as CallMode, S as StartCallResult, E as EndCallOptions, A as Avatar, b as AvatarUpdate, c as AvatarSourceSwap, d as ClipLibraryDeclaration, e as ClipLibraryUpdate, L as LoopRedirect, f as ClipLibrary, g as AssetKind, h as Asset, i as ListSessionsOptions, U as UsageSessionPage, j as UsageSession, k as ListRecordingsQuery, l as ListRecordingsResponse, R as RecordingArtifact, m as RecordingAccessResponse, n as CreditBalance, T as TranscriptPayload } from './types-BnlnrYgN.js';
2
+ export { o as AvatarClip, p as CallConnection, q as CallQueued, r as ClipDeclaration, s as ClipLibraryPlan, t as ClipSource, u as ContextMessage, v as EndCallReason, w as RecordingArtifactStatus, x as RecordingMode, V as VideoPolicy, y as isQueued, z as listRecordingsResponseSchema, B as recordingAccessResponseSchema, D as recordingArtifactSchema, F as recordingModeSchema } from './types-BnlnrYgN.js';
3
+ import { ConnectionHistoryResponse } from './connection-history.js';
3
4
  import { z } from 'zod';
4
5
 
5
6
  interface RealtimeAvatarOptions {
@@ -271,6 +272,8 @@ declare class RealtimeAvatar {
271
272
  getRecording(recordingId: string): Promise<RecordingArtifact>;
272
273
  /** Renewable playback access. Keep recordingId in storage; URLs expire at expiresAt. */
273
274
  getRecordingAccess(recordingId: string): Promise<RecordingAccessResponse>;
275
+ /** Read one session's bounded LiveKit connection history. Requires `usage:read`. */
276
+ getConnectionHistory(sessionId: string): Promise<ConnectionHistoryResponse>;
274
277
  creditBalance(): Promise<CreditBalance>;
275
278
  }
276
279
 
package/dist/index.js CHANGED
@@ -1 +1,2 @@
1
- export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, listRecordingsResponseSchema, recordingAccessResponseSchema, recordingArtifactSchema, recordingModeSchema, verifyTranscript } from './chunk-V26PE2M3.js';
1
+ export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, listRecordingsResponseSchema, recordingAccessResponseSchema, recordingArtifactSchema, recordingModeSchema, verifyTranscript } from './chunk-4OLLYAJT.js';
2
+ import './chunk-MCIXU6XP.js';
package/dist/nextjs.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as ProxyConfig } from './types-Bphj69NC.js';
2
- import './types-VOv0vsFs.js';
1
+ import { P as ProxyConfig } from './types-CFkiNV3f.js';
2
+ import './types-BnlnrYgN.js';
3
3
  import 'zod';
4
4
 
5
5
  /**
package/dist/nextjs.js CHANGED
@@ -1,5 +1,6 @@
1
- import { createProxyHandler } from './chunk-2TNFET3Q.js';
2
- import './chunk-V26PE2M3.js';
1
+ import { createProxyHandler } from './chunk-Q34WDWEA.js';
2
+ import './chunk-4OLLYAJT.js';
3
+ import './chunk-MCIXU6XP.js';
3
4
 
4
5
  // ../proxy/src/nextjs.ts
5
6
  function createRealtimeAvatarRoute(config) {
@@ -220,6 +220,7 @@ declare const liveKitSessionRequestSchema: z.ZodObject<{
220
220
  secret: z.ZodString;
221
221
  }, z.core.$strict>>;
222
222
  clientMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
223
+ connectionHistory: z.ZodOptional<z.ZodBoolean>;
223
224
  }, z.core.$strict>;
224
225
  declare const liveKitSessionGrantSchema: z.ZodObject<{
225
226
  recording: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -271,6 +272,11 @@ declare const liveKitSessionGrantSchema: z.ZodObject<{
271
272
  recording_unavailable: "recording_unavailable";
272
273
  }>;
273
274
  }, z.core.$strict>], "status">>;
275
+ connection_history: z.ZodOptional<z.ZodObject<{
276
+ endpoint: z.ZodString;
277
+ token: z.ZodString;
278
+ expiresAt: z.ZodString;
279
+ }, z.core.$strict>>;
274
280
  status: z.ZodDefault<z.ZodLiteral<"ready">>;
275
281
  session_id: z.ZodString;
276
282
  room_name: z.ZodString;
@@ -291,9 +297,9 @@ declare const liveKitSessionGrantSchema: z.ZodObject<{
291
297
  declare const sessionEndReasonSchema: z.ZodEnum<{
292
298
  idle: "idle";
293
299
  failed: "failed";
300
+ disconnected: "disconnected";
294
301
  user_ended: "user_ended";
295
302
  session_cap: "session_cap";
296
- disconnected: "disconnected";
297
303
  out_of_credits: "out_of_credits";
298
304
  agent_ended: "agent_ended";
299
305
  }>;
@@ -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-Bu-5QFhA.js';
5
- export { a as AdaptivePlayoutController, b as AdaptivePlayoutDecision, c as AdaptivePlayoutOptions, d as AdaptivePlayoutSample, e as ApproachingEndEvent, f as ApproachingEndReason, g as AvatarConnectionDetails, h as AvatarSessionClient, B as BehaviorSnapshot, C as CallTranscript, i as CallTranscriptSegment, j as CartesiaTtsModel, k as ClipResult, l as ClosingTurnResult, m as CreditsLowEvent, D as DEFAULT_APPROACHING_END_LEAD_SECONDS, n as DEFAULT_CREDITS_LOW_LEAD_SECONDS, o as DEFAULT_GOVERNOR_CONFIG, p as DEFAULT_GRACE_CEILING_SECONDS, q as DEFAULT_GRACE_WINDOW_LEAD_SECONDS, r as DEFAULT_IDLE_SECONDS, s as DEFAULT_IDLE_WARN_LEAD_SECONDS, t as DEFAULT_TURN_TIMEOUT_SECONDS, E as EndReason, u as EndedEvent, v as ExtendResult, F as FishTtsModel, w as FreezeReadingFn, G as Governor, x as GovernorAction, y as GovernorConfig, z as GovernorSignal, H as GovernorState, I as GraceWindowClosedEvent, J as GraceWindowOpenEvent, K as GraceWindowState, M as IdleWarningEvent, N as InboundRtpCursor, O as InboundRtpReading, P as KnownBehaviorState, Q as LLMProvider, R as LLMSelection, S as LiveKitAvatarGrantState, T as LiveKitAvatarGrantStatus, U as LiveKitCapacityState, V as LiveKitConnectionStatus, W as LiveKitSessionRequest, X as LiveKitSessionStartResult, Y as MAX_RECONNECT_ATTEMPTS, Z as MAX_SESSION_INSTRUCTIONS_CHARS, _ as ProxyClientOptions, $ as QualityCap, a0 as RECONNECT_BACKOFF_MS, a1 as RealtimeAvatarRequestOptions, a2 as RealtimeSessionApi, a3 as RealtimeSessionMedia, a4 as RealtimeSessionRoomSinks, a5 as ReconnectPolicy, a6 as ReconnectingEvent, a7 as RecoveryState, a8 as RetryStep, a9 as SessionBehavior, aa as SessionClip, ab as SessionClocks, ac as SessionEndReason, ad as SessionLifecycleApi, ae as SessionLifecyclePhase, af as SessionLifecyclePhaseKind, ag as SessionLifecycleRoomBridge, ah as SessionLifecycleRoomBridgeProps, ai as SurfaceLayers, aj as TurnState, ak as TurnTimeoutEvent, al as UseAvatarQualityGovernorInput, am as UseLiveKitAvatarGrantInput, an as UseRealtimeSessionInput, ao as UseSessionLifecycleInput, ap as VoiceSpec, aq as VoiceSpecInput, ar as capacityErrorFromBusy, as as capacityStateFromGrant, at as createProxyClient, au as isNativeLiveTrackSubscribed, av as knownBehaviorStates, aw as mapTurnState, ax as readInboundRtp, ay as sessionBehaviorSchema, az as sessionClipSchema, aA as splitCallTranscript, aB as useAvatarAdaptivePlayoutDelay, aC as useAvatarPlayoutDelay, aD as useAvatarQualityGovernor, aE as useCallTranscript, aF as useLiveKitAvatarGrant, aG as useLiveTrackProducing, aH as useMicLease, aI as useRealtimeSession, aJ as useReleaseMicLeaseOnTrackEnded, aK as useSessionLifecycle } from './proxy-client-Bu-5QFhA.js';
4
+ import { L as LiveKitSessionGrant, A as AvatarVideoFit } from './proxy-client-DmJVj18Z.js';
5
+ export { a as AdaptivePlayoutController, b as AdaptivePlayoutDecision, c as AdaptivePlayoutOptions, d as AdaptivePlayoutSample, e as ApproachingEndEvent, f as ApproachingEndReason, g as AvatarConnectionDetails, h as AvatarSessionClient, B as BehaviorSnapshot, C as CallTranscript, i as CallTranscriptSegment, j as CartesiaTtsModel, k as ClipResult, l as ClosingTurnResult, m as CreditsLowEvent, D as DEFAULT_APPROACHING_END_LEAD_SECONDS, n as DEFAULT_CREDITS_LOW_LEAD_SECONDS, o as DEFAULT_GOVERNOR_CONFIG, p as DEFAULT_GRACE_CEILING_SECONDS, q as DEFAULT_GRACE_WINDOW_LEAD_SECONDS, r as DEFAULT_IDLE_SECONDS, s as DEFAULT_IDLE_WARN_LEAD_SECONDS, t as DEFAULT_TURN_TIMEOUT_SECONDS, E as EndReason, u as EndedEvent, v as ExtendResult, F as FishTtsModel, w as FreezeReadingFn, G as Governor, x as GovernorAction, y as GovernorConfig, z as GovernorSignal, H as GovernorState, I as GraceWindowClosedEvent, J as GraceWindowOpenEvent, K as GraceWindowState, M as IdleWarningEvent, N as InboundRtpCursor, O as InboundRtpReading, P as KnownBehaviorState, Q as LLMProvider, R as LLMSelection, S as LiveKitAvatarGrantState, T as LiveKitAvatarGrantStatus, U as LiveKitCapacityState, V as LiveKitConnectionStatus, W as LiveKitSessionRequest, X as LiveKitSessionStartResult, Y as MAX_RECONNECT_ATTEMPTS, Z as MAX_SESSION_INSTRUCTIONS_CHARS, _ as ProxyClientOptions, $ as QualityCap, a0 as RECONNECT_BACKOFF_MS, a1 as RealtimeAvatarRequestOptions, a2 as RealtimeSessionApi, a3 as RealtimeSessionMedia, a4 as RealtimeSessionRoomSinks, a5 as ReconnectPolicy, a6 as ReconnectingEvent, a7 as RecoveryState, a8 as RetryStep, a9 as SessionBehavior, aa as SessionClip, ab as SessionClocks, ac as SessionEndReason, ad as SessionLifecycleApi, ae as SessionLifecyclePhase, af as SessionLifecyclePhaseKind, ag as SessionLifecycleRoomBridge, ah as SessionLifecycleRoomBridgeProps, ai as SurfaceLayers, aj as TurnState, ak as TurnTimeoutEvent, al as UseAvatarQualityGovernorInput, am as UseLiveKitAvatarGrantInput, an as UseRealtimeSessionInput, ao as UseSessionLifecycleInput, ap as VoiceSpec, aq as VoiceSpecInput, ar as capacityErrorFromBusy, as as capacityStateFromGrant, at as createProxyClient, au as isNativeLiveTrackSubscribed, av as knownBehaviorStates, aw as mapTurnState, ax as readInboundRtp, ay as sessionBehaviorSchema, az as sessionClipSchema, aA as splitCallTranscript, aB as useAvatarAdaptivePlayoutDelay, aC as useAvatarPlayoutDelay, aD as useAvatarQualityGovernor, aE as useCallTranscript, aF as useLiveKitAvatarGrant, aG as useLiveTrackProducing, aH as useMicLease, aI as useRealtimeSession, aJ as useReleaseMicLeaseOnTrackEnded, aK as useSessionLifecycle } from './proxy-client-DmJVj18Z.js';
6
6
  import { StyleProp, ViewStyle } from 'react-native';
7
7
  export { useChat, useConnectionState, useLocalParticipant, useRoomContext, useTrackToggle, useTranscriptions, useVoiceAssistant } from '@livekit/components-react';
8
8
  export { D as DEFAULT_AVATAR_PLAYOUT_DELAY_SECONDS } from './playout-delay-DWOc8iCl.js';
@@ -1426,6 +1426,33 @@ z.object({
1426
1426
  url: z.string().url(),
1427
1427
  expiresAt: z.string().datetime({ offset: true })
1428
1428
  }).strict();
1429
+ var ConnectionQuality2 = { Excellent: "excellent", Good: "good", Poor: "poor", Lost: "lost", Unknown: "unknown" };
1430
+ var ConnectionState4 = { Disconnected: "disconnected", Connecting: "connecting", Connected: "connected", Reconnecting: "reconnecting", SignalReconnecting: "signalReconnecting" };
1431
+ var MAX_CONNECTION_HISTORY_OBSERVATIONS = 240;
1432
+ var MAX_CONNECTION_HISTORY_BATCH = 32;
1433
+ var quality = z.enum(ConnectionQuality2);
1434
+ var connectionHistoryObservationSchema = z.object({
1435
+ sequence: z.number().int().min(1).max(MAX_CONNECTION_HISTORY_OBSERVATIONS),
1436
+ elapsedMs: z.number().int().min(0).max(864e5),
1437
+ clientObservedAt: z.string().datetime({ offset: true }).nullable(),
1438
+ connectionState: z.enum(ConnectionState4),
1439
+ localQuality: quality,
1440
+ audioQuality: quality.nullable(),
1441
+ videoQuality: quality.nullable()
1442
+ }).strict();
1443
+ var connectionHistoryGrantSchema = z.object({
1444
+ endpoint: z.string().url().startsWith("https://"),
1445
+ token: z.string().min(32).max(4096),
1446
+ expiresAt: z.string().datetime({ offset: true })
1447
+ }).strict();
1448
+ z.object({
1449
+ sessionId: z.string().min(1).max(200),
1450
+ observations: z.array(connectionHistoryObservationSchema).min(1).max(MAX_CONNECTION_HISTORY_BATCH)
1451
+ }).strict();
1452
+ z.object({
1453
+ sessionId: z.string().min(1).max(200),
1454
+ observations: z.array(connectionHistoryObservationSchema).max(MAX_CONNECTION_HISTORY_OBSERVATIONS)
1455
+ }).strict();
1429
1456
 
1430
1457
  // ../client/src/wire.ts
1431
1458
  var DEFAULT_AVATAR_ID = "maria";
@@ -1589,7 +1616,8 @@ z.object({
1589
1616
  render_backend: renderBackendSchema.optional(),
1590
1617
  support_edits: sessionSupportEditsSchema.optional(),
1591
1618
  transcript_webhook: transcriptWebhookSchema.optional(),
1592
- client_metadata: clientMetadataSchema.optional()
1619
+ client_metadata: clientMetadataSchema.optional(),
1620
+ connection_history: z.boolean().optional()
1593
1621
  }).strict().superRefine((value, ctx) => {
1594
1622
  if (value.support_edits && value.render_backend === "generative") {
1595
1623
  ctx.addIssue({
@@ -1654,10 +1682,12 @@ z.object({
1654
1682
  renderBackend: renderBackendSchema.optional(),
1655
1683
  supportEdits: sessionSupportEditsSchema.optional(),
1656
1684
  transcriptWebhook: transcriptWebhookSchema.optional(),
1657
- clientMetadata: clientMetadataSchema.optional()
1685
+ clientMetadata: clientMetadataSchema.optional(),
1686
+ connectionHistory: z.boolean().optional()
1658
1687
  }).strict();
1659
1688
  z.object({
1660
1689
  recording: recordingArtifactSchema.optional(),
1690
+ connection_history: connectionHistoryGrantSchema.optional(),
1661
1691
  status: z.literal("ready").default("ready"),
1662
1692
  session_id: z.string().min(1),
1663
1693
  room_name: z.string().min(1),
@@ -1770,6 +1800,95 @@ function nextBehaviorSnapshot(prev, frame) {
1770
1800
  return next;
1771
1801
  }
1772
1802
 
1803
+ // ../client/src/react/connection-history.ts
1804
+ function createConnectionHistoryCollector(input) {
1805
+ const fetcher = input.fetch ?? ((request, init) => globalThis.fetch(request, init));
1806
+ const wallMs = input.clock?.wallMs ?? Date.now;
1807
+ const monotonicMs = input.clock?.monotonicMs ?? (() => globalThis.performance?.now() ?? Date.now());
1808
+ const startedAt = monotonicMs();
1809
+ const wallStartedAt = wallMs();
1810
+ const queue2 = [];
1811
+ let sequence = 0;
1812
+ let scheduled = false;
1813
+ let flushing = null;
1814
+ let disposed = false;
1815
+ let lastSnapshot = null;
1816
+ function enqueue(details) {
1817
+ if (disposed || details === null || sequence >= MAX_CONNECTION_HISTORY_OBSERVATIONS) return;
1818
+ const elapsedMs = Math.max(0, Math.min(864e5, Math.round(monotonicMs() - startedAt)));
1819
+ const nextSnapshot = {
1820
+ connectionState: details.connectionState,
1821
+ localQuality: details.localQuality,
1822
+ audioQuality: details.audio?.publisherQuality ?? null,
1823
+ videoQuality: details.video?.publisherQuality ?? null
1824
+ };
1825
+ const prior = lastSnapshot;
1826
+ if (prior && prior.connectionState === nextSnapshot.connectionState && prior.localQuality === nextSnapshot.localQuality && prior.audioQuality === nextSnapshot.audioQuality && prior.videoQuality === nextSnapshot.videoQuality) return;
1827
+ lastSnapshot = nextSnapshot;
1828
+ const value = {
1829
+ sequence: sequence + 1,
1830
+ elapsedMs,
1831
+ clientObservedAt: new Date(wallStartedAt + elapsedMs).toISOString(),
1832
+ ...nextSnapshot
1833
+ };
1834
+ sequence = value.sequence;
1835
+ queue2.push(value);
1836
+ if (queue2.length >= MAX_CONNECTION_HISTORY_BATCH) void flush();
1837
+ else scheduleFlush();
1838
+ }
1839
+ function scheduleFlush() {
1840
+ if (scheduled || disposed) return;
1841
+ scheduled = true;
1842
+ queueMicrotask(() => {
1843
+ scheduled = false;
1844
+ void flush();
1845
+ });
1846
+ }
1847
+ async function send(batch) {
1848
+ if (Date.parse(input.grant.expiresAt) <= wallMs()) return;
1849
+ const payload = { sessionId: input.sessionId, observations: batch };
1850
+ const retries = Math.max(0, Math.min(2, input.maxRetries ?? 2));
1851
+ for (let attempt = 0; attempt <= retries; attempt++) {
1852
+ try {
1853
+ if (Date.parse(input.grant.expiresAt) <= wallMs()) return;
1854
+ const response = await fetcher(input.grant.endpoint, {
1855
+ method: "POST",
1856
+ headers: { authorization: `Bearer ${input.grant.token}`, "content-type": "application/json" },
1857
+ body: JSON.stringify(payload),
1858
+ signal: AbortSignal.timeout(5e3)
1859
+ });
1860
+ if (response.ok) return;
1861
+ if (response.status < 500 && response.status !== 429) return;
1862
+ await response.body?.cancel().catch(() => {
1863
+ });
1864
+ } catch {
1865
+ }
1866
+ if (attempt < retries) await new Promise((resolve) => setTimeout(resolve, 100 * 2 ** attempt));
1867
+ }
1868
+ }
1869
+ async function flush() {
1870
+ if (queue2.length === 0) return;
1871
+ if (flushing) return flushing;
1872
+ flushing = (async () => {
1873
+ try {
1874
+ while (queue2.length) {
1875
+ const batch = queue2.splice(0, MAX_CONNECTION_HISTORY_BATCH);
1876
+ await send(batch);
1877
+ }
1878
+ } finally {
1879
+ flushing = null;
1880
+ }
1881
+ })();
1882
+ return flushing;
1883
+ }
1884
+ async function dispose() {
1885
+ disposed = true;
1886
+ await flush();
1887
+ queue2.length = 0;
1888
+ }
1889
+ return { enqueue, flush, dispose };
1890
+ }
1891
+
1773
1892
  // ../client/src/react/session-lifecycle.ts
1774
1893
  function phaseForReason(reason) {
1775
1894
  switch (reason) {
@@ -2244,11 +2363,35 @@ function SessionLifecycleRoomBridge({
2244
2363
  const prevTranscriptionCountRef = useRef(0);
2245
2364
  const audio = assistant.audioTrack;
2246
2365
  const video = assistant.videoTrack;
2247
- const detailsEnabled = onConnectionDetailsChange !== void 0 && lifecycle.grant !== null;
2366
+ const connectionHistoryGrant = lifecycle.grant?.connection_history;
2367
+ const detailsEnabled = (onConnectionDetailsChange !== void 0 || connectionHistoryGrant !== void 0) && lifecycle.grant !== null;
2248
2368
  const detailsSessionId = lifecycle.grant?.session_id;
2369
+ const connectionHistoryRef = useRef(null);
2249
2370
  const updateDetailsRef = useRef(null);
2250
2371
  useEffect(() => {
2251
- if (!detailsEnabled || !onConnectionDetailsChange) return;
2372
+ const sessionId = detailsSessionId;
2373
+ if (!sessionId || !connectionHistoryGrant) {
2374
+ connectionHistoryRef.current = null;
2375
+ return;
2376
+ }
2377
+ const collector = createConnectionHistoryCollector({ sessionId, grant: connectionHistoryGrant });
2378
+ connectionHistoryRef.current = collector;
2379
+ return () => {
2380
+ if (connectionHistoryRef.current === collector) connectionHistoryRef.current = null;
2381
+ void collector.dispose();
2382
+ };
2383
+ }, [connectionHistoryGrant, detailsSessionId]);
2384
+ useEffect(() => {
2385
+ const collector = connectionHistoryRef.current;
2386
+ if (!collector || typeof window === "undefined" || typeof window.addEventListener !== "function" || typeof window.removeEventListener !== "function") return;
2387
+ const flush = () => {
2388
+ void collector.flush();
2389
+ };
2390
+ window.addEventListener("pagehide", flush, true);
2391
+ return () => window.removeEventListener("pagehide", flush, true);
2392
+ }, [connectionHistoryGrant, detailsSessionId]);
2393
+ useEffect(() => {
2394
+ if (!detailsEnabled || !onConnectionDetailsChange && !connectionHistoryGrant) return;
2252
2395
  let callback = onConnectionDetailsChange;
2253
2396
  let audioSource = audio;
2254
2397
  let videoSource = video;
@@ -2256,8 +2399,9 @@ function SessionLifecycleRoomBridge({
2256
2399
  let pending = false;
2257
2400
  let previous;
2258
2401
  const deliver = (details) => {
2402
+ connectionHistoryRef.current?.enqueue(details);
2259
2403
  try {
2260
- void Promise.resolve(callback(details)).catch(() => {
2404
+ if (onConnectionDetailsChange) void Promise.resolve(callback?.(details)).catch(() => {
2261
2405
  });
2262
2406
  } catch {
2263
2407
  }
@@ -2315,7 +2459,7 @@ function SessionLifecycleRoomBridge({
2315
2459
  };
2316
2460
  }, [room, detailsSessionId, detailsEnabled]);
2317
2461
  useEffect(() => {
2318
- if (onConnectionDetailsChange) updateDetailsRef.current?.(onConnectionDetailsChange, audio, video);
2462
+ updateDetailsRef.current?.(onConnectionDetailsChange, audio, video);
2319
2463
  }, [
2320
2464
  onConnectionDetailsChange,
2321
2465
  detailsEnabled ? audio?.participant : void 0,
package/dist/react.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { RegisteredTool, ToolRegistration } from './tools.js';
2
2
  import { ReactNode } from 'react';
3
- import { ah as SessionLifecycleRoomBridgeProps, h as AvatarSessionClient, A as AvatarVideoFit } from './proxy-client-Bu-5QFhA.js';
4
- export { a as AdaptivePlayoutController, b as AdaptivePlayoutDecision, c as AdaptivePlayoutOptions, d as AdaptivePlayoutSample, e as ApproachingEndEvent, f as ApproachingEndReason, g as AvatarConnectionDetails, aL as AvatarVideoSurface, aM as AvatarVideoSurfaceProps, B as BehaviorSnapshot, C as CallTranscript, i as CallTranscriptSegment, j as CartesiaTtsModel, k as ClipResult, l as ClosingTurnResult, m as CreditsLowEvent, D as DEFAULT_APPROACHING_END_LEAD_SECONDS, n as DEFAULT_CREDITS_LOW_LEAD_SECONDS, o as DEFAULT_GOVERNOR_CONFIG, p as DEFAULT_GRACE_CEILING_SECONDS, q as DEFAULT_GRACE_WINDOW_LEAD_SECONDS, r as DEFAULT_IDLE_SECONDS, s as DEFAULT_IDLE_WARN_LEAD_SECONDS, t as DEFAULT_TURN_TIMEOUT_SECONDS, E as EndReason, u as EndedEvent, v as ExtendResult, F as FishTtsModel, w as FreezeReadingFn, G as Governor, x as GovernorAction, y as GovernorConfig, z as GovernorSignal, H as GovernorState, I as GraceWindowClosedEvent, J as GraceWindowOpenEvent, K as GraceWindowState, M as IdleWarningEvent, N as InboundRtpCursor, O as InboundRtpReading, P as KnownBehaviorState, Q as LLMProvider, R as LLMSelection, S as LiveKitAvatarGrantState, T as LiveKitAvatarGrantStatus, U as LiveKitCapacityState, V as LiveKitConnectionStatus, W as LiveKitSessionRequest, X as LiveKitSessionStartResult, aN as LivePlaybackKeeper, Y as MAX_RECONNECT_ATTEMPTS, Z as MAX_SESSION_INSTRUCTIONS_CHARS, aO as MIC_LEASE_ENDED_TIMEOUT_MS, aP as MicLease, aQ as NETWORK_QUALITY_POLICY, aR as NetworkQualityStatus, aS as PlayableVideoElement, _ as ProxyClientOptions, $ as QualityCap, a0 as RECONNECT_BACKOFF_MS, aT as RealtimeAvatarCapacityError, aU as RealtimeAvatarLiveKitRoom, aV as RealtimeAvatarLiveKitRoomProps, a1 as RealtimeAvatarRequestOptions, a2 as RealtimeSessionApi, a3 as RealtimeSessionMedia, a4 as RealtimeSessionRoomSinks, a5 as ReconnectPolicy, a6 as ReconnectingEvent, a7 as RecoveryState, a8 as RetryStep, a9 as SessionBehavior, aa as SessionClip, ab as SessionClocks, ac as SessionEndReason, ad as SessionLifecycleApi, ae as SessionLifecyclePhase, af as SessionLifecyclePhaseKind, ag as SessionLifecycleRoomBridge, ai as SurfaceLayers, aj as TurnState, ak as TurnTimeoutEvent, al as UseAvatarQualityGovernorInput, am as UseLiveKitAvatarGrantInput, an as UseRealtimeSessionInput, ao as UseSessionLifecycleInput, ap as VoiceSpec, aq as VoiceSpecInput, ar as capacityErrorFromBusy, as as capacityStateFromGrant, at as createProxyClient, av as knownBehaviorStates, aw as mapTurnState, ax as readInboundRtp, ay as sessionBehaviorSchema, az as sessionClipSchema, aW as shouldReplayPendingTurn, aA as splitCallTranscript, aB as useAvatarAdaptivePlayoutDelay, aC as useAvatarPlayoutDelay, aD as useAvatarQualityGovernor, aE as useCallTranscript, aF as useLiveKitAvatarGrant, aH as useMicLease, aI as useRealtimeSession, aJ as useReleaseMicLeaseOnTrackEnded, aK as useSessionLifecycle } from './proxy-client-Bu-5QFhA.js';
3
+ import { ah as SessionLifecycleRoomBridgeProps, h as AvatarSessionClient, A as AvatarVideoFit } from './proxy-client-DmJVj18Z.js';
4
+ export { a as AdaptivePlayoutController, b as AdaptivePlayoutDecision, c as AdaptivePlayoutOptions, d as AdaptivePlayoutSample, e as ApproachingEndEvent, f as ApproachingEndReason, g as AvatarConnectionDetails, aL as AvatarVideoSurface, aM as AvatarVideoSurfaceProps, B as BehaviorSnapshot, C as CallTranscript, i as CallTranscriptSegment, j as CartesiaTtsModel, k as ClipResult, l as ClosingTurnResult, m as CreditsLowEvent, D as DEFAULT_APPROACHING_END_LEAD_SECONDS, n as DEFAULT_CREDITS_LOW_LEAD_SECONDS, o as DEFAULT_GOVERNOR_CONFIG, p as DEFAULT_GRACE_CEILING_SECONDS, q as DEFAULT_GRACE_WINDOW_LEAD_SECONDS, r as DEFAULT_IDLE_SECONDS, s as DEFAULT_IDLE_WARN_LEAD_SECONDS, t as DEFAULT_TURN_TIMEOUT_SECONDS, E as EndReason, u as EndedEvent, v as ExtendResult, F as FishTtsModel, w as FreezeReadingFn, G as Governor, x as GovernorAction, y as GovernorConfig, z as GovernorSignal, H as GovernorState, I as GraceWindowClosedEvent, J as GraceWindowOpenEvent, K as GraceWindowState, M as IdleWarningEvent, N as InboundRtpCursor, O as InboundRtpReading, P as KnownBehaviorState, Q as LLMProvider, R as LLMSelection, S as LiveKitAvatarGrantState, T as LiveKitAvatarGrantStatus, U as LiveKitCapacityState, V as LiveKitConnectionStatus, W as LiveKitSessionRequest, X as LiveKitSessionStartResult, aN as LivePlaybackKeeper, Y as MAX_RECONNECT_ATTEMPTS, Z as MAX_SESSION_INSTRUCTIONS_CHARS, aO as MIC_LEASE_ENDED_TIMEOUT_MS, aP as MicLease, aQ as NETWORK_QUALITY_POLICY, aR as NetworkQualityStatus, aS as PlayableVideoElement, _ as ProxyClientOptions, $ as QualityCap, a0 as RECONNECT_BACKOFF_MS, aT as RealtimeAvatarCapacityError, aU as RealtimeAvatarLiveKitRoom, aV as RealtimeAvatarLiveKitRoomProps, a1 as RealtimeAvatarRequestOptions, a2 as RealtimeSessionApi, a3 as RealtimeSessionMedia, a4 as RealtimeSessionRoomSinks, a5 as ReconnectPolicy, a6 as ReconnectingEvent, a7 as RecoveryState, a8 as RetryStep, a9 as SessionBehavior, aa as SessionClip, ab as SessionClocks, ac as SessionEndReason, ad as SessionLifecycleApi, ae as SessionLifecyclePhase, af as SessionLifecyclePhaseKind, ag as SessionLifecycleRoomBridge, ai as SurfaceLayers, aj as TurnState, ak as TurnTimeoutEvent, al as UseAvatarQualityGovernorInput, am as UseLiveKitAvatarGrantInput, an as UseRealtimeSessionInput, ao as UseSessionLifecycleInput, ap as VoiceSpec, aq as VoiceSpecInput, ar as capacityErrorFromBusy, as as capacityStateFromGrant, at as createProxyClient, av as knownBehaviorStates, aw as mapTurnState, ax as readInboundRtp, ay as sessionBehaviorSchema, az as sessionClipSchema, aW as shouldReplayPendingTurn, aA as splitCallTranscript, aB as useAvatarAdaptivePlayoutDelay, aC as useAvatarPlayoutDelay, aD as useAvatarQualityGovernor, aE as useCallTranscript, aF as useLiveKitAvatarGrant, aH as useMicLease, aI as useRealtimeSession, aJ as useReleaseMicLeaseOnTrackEnded, aK as useSessionLifecycle } from './proxy-client-DmJVj18Z.js';
5
5
  export { D as DEFAULT_AVATAR_PLAYOUT_DELAY_SECONDS } from './playout-delay-DWOc8iCl.js';
6
6
  import 'zod';
7
7
  import '@livekit/components-react';
package/dist/react.js CHANGED
@@ -1960,6 +1960,33 @@ z.object({
1960
1960
  url: z.string().url(),
1961
1961
  expiresAt: z.string().datetime({ offset: true })
1962
1962
  }).strict();
1963
+ var ConnectionQuality2 = { Excellent: "excellent", Good: "good", Poor: "poor", Lost: "lost", Unknown: "unknown" };
1964
+ var ConnectionState4 = { Disconnected: "disconnected", Connecting: "connecting", Connected: "connected", Reconnecting: "reconnecting", SignalReconnecting: "signalReconnecting" };
1965
+ var MAX_CONNECTION_HISTORY_OBSERVATIONS = 240;
1966
+ var MAX_CONNECTION_HISTORY_BATCH = 32;
1967
+ var quality = z.enum(ConnectionQuality2);
1968
+ var connectionHistoryObservationSchema = z.object({
1969
+ sequence: z.number().int().min(1).max(MAX_CONNECTION_HISTORY_OBSERVATIONS),
1970
+ elapsedMs: z.number().int().min(0).max(864e5),
1971
+ clientObservedAt: z.string().datetime({ offset: true }).nullable(),
1972
+ connectionState: z.enum(ConnectionState4),
1973
+ localQuality: quality,
1974
+ audioQuality: quality.nullable(),
1975
+ videoQuality: quality.nullable()
1976
+ }).strict();
1977
+ var connectionHistoryGrantSchema = z.object({
1978
+ endpoint: z.string().url().startsWith("https://"),
1979
+ token: z.string().min(32).max(4096),
1980
+ expiresAt: z.string().datetime({ offset: true })
1981
+ }).strict();
1982
+ z.object({
1983
+ sessionId: z.string().min(1).max(200),
1984
+ observations: z.array(connectionHistoryObservationSchema).min(1).max(MAX_CONNECTION_HISTORY_BATCH)
1985
+ }).strict();
1986
+ z.object({
1987
+ sessionId: z.string().min(1).max(200),
1988
+ observations: z.array(connectionHistoryObservationSchema).max(MAX_CONNECTION_HISTORY_OBSERVATIONS)
1989
+ }).strict();
1963
1990
 
1964
1991
  // ../client/src/wire.ts
1965
1992
  var DEFAULT_AVATAR_ID = "maria";
@@ -2123,7 +2150,8 @@ z.object({
2123
2150
  render_backend: renderBackendSchema.optional(),
2124
2151
  support_edits: sessionSupportEditsSchema.optional(),
2125
2152
  transcript_webhook: transcriptWebhookSchema.optional(),
2126
- client_metadata: clientMetadataSchema.optional()
2153
+ client_metadata: clientMetadataSchema.optional(),
2154
+ connection_history: z.boolean().optional()
2127
2155
  }).strict().superRefine((value, ctx) => {
2128
2156
  if (value.support_edits && value.render_backend === "generative") {
2129
2157
  ctx.addIssue({
@@ -2188,10 +2216,12 @@ z.object({
2188
2216
  renderBackend: renderBackendSchema.optional(),
2189
2217
  supportEdits: sessionSupportEditsSchema.optional(),
2190
2218
  transcriptWebhook: transcriptWebhookSchema.optional(),
2191
- clientMetadata: clientMetadataSchema.optional()
2219
+ clientMetadata: clientMetadataSchema.optional(),
2220
+ connectionHistory: z.boolean().optional()
2192
2221
  }).strict();
2193
2222
  z.object({
2194
2223
  recording: recordingArtifactSchema.optional(),
2224
+ connection_history: connectionHistoryGrantSchema.optional(),
2195
2225
  status: z.literal("ready").default("ready"),
2196
2226
  session_id: z.string().min(1),
2197
2227
  room_name: z.string().min(1),
@@ -2304,6 +2334,95 @@ function nextBehaviorSnapshot(prev, frame) {
2304
2334
  return next;
2305
2335
  }
2306
2336
 
2337
+ // ../client/src/react/connection-history.ts
2338
+ function createConnectionHistoryCollector(input) {
2339
+ const fetcher = input.fetch ?? ((request, init) => globalThis.fetch(request, init));
2340
+ const wallMs = input.clock?.wallMs ?? Date.now;
2341
+ const monotonicMs = input.clock?.monotonicMs ?? (() => globalThis.performance?.now() ?? Date.now());
2342
+ const startedAt = monotonicMs();
2343
+ const wallStartedAt = wallMs();
2344
+ const queue2 = [];
2345
+ let sequence = 0;
2346
+ let scheduled = false;
2347
+ let flushing = null;
2348
+ let disposed = false;
2349
+ let lastSnapshot = null;
2350
+ function enqueue(details) {
2351
+ if (disposed || details === null || sequence >= MAX_CONNECTION_HISTORY_OBSERVATIONS) return;
2352
+ const elapsedMs = Math.max(0, Math.min(864e5, Math.round(monotonicMs() - startedAt)));
2353
+ const nextSnapshot = {
2354
+ connectionState: details.connectionState,
2355
+ localQuality: details.localQuality,
2356
+ audioQuality: details.audio?.publisherQuality ?? null,
2357
+ videoQuality: details.video?.publisherQuality ?? null
2358
+ };
2359
+ const prior = lastSnapshot;
2360
+ if (prior && prior.connectionState === nextSnapshot.connectionState && prior.localQuality === nextSnapshot.localQuality && prior.audioQuality === nextSnapshot.audioQuality && prior.videoQuality === nextSnapshot.videoQuality) return;
2361
+ lastSnapshot = nextSnapshot;
2362
+ const value = {
2363
+ sequence: sequence + 1,
2364
+ elapsedMs,
2365
+ clientObservedAt: new Date(wallStartedAt + elapsedMs).toISOString(),
2366
+ ...nextSnapshot
2367
+ };
2368
+ sequence = value.sequence;
2369
+ queue2.push(value);
2370
+ if (queue2.length >= MAX_CONNECTION_HISTORY_BATCH) void flush();
2371
+ else scheduleFlush();
2372
+ }
2373
+ function scheduleFlush() {
2374
+ if (scheduled || disposed) return;
2375
+ scheduled = true;
2376
+ queueMicrotask(() => {
2377
+ scheduled = false;
2378
+ void flush();
2379
+ });
2380
+ }
2381
+ async function send(batch) {
2382
+ if (Date.parse(input.grant.expiresAt) <= wallMs()) return;
2383
+ const payload = { sessionId: input.sessionId, observations: batch };
2384
+ const retries = Math.max(0, Math.min(2, input.maxRetries ?? 2));
2385
+ for (let attempt = 0; attempt <= retries; attempt++) {
2386
+ try {
2387
+ if (Date.parse(input.grant.expiresAt) <= wallMs()) return;
2388
+ const response = await fetcher(input.grant.endpoint, {
2389
+ method: "POST",
2390
+ headers: { authorization: `Bearer ${input.grant.token}`, "content-type": "application/json" },
2391
+ body: JSON.stringify(payload),
2392
+ signal: AbortSignal.timeout(5e3)
2393
+ });
2394
+ if (response.ok) return;
2395
+ if (response.status < 500 && response.status !== 429) return;
2396
+ await response.body?.cancel().catch(() => {
2397
+ });
2398
+ } catch {
2399
+ }
2400
+ if (attempt < retries) await new Promise((resolve) => setTimeout(resolve, 100 * 2 ** attempt));
2401
+ }
2402
+ }
2403
+ async function flush() {
2404
+ if (queue2.length === 0) return;
2405
+ if (flushing) return flushing;
2406
+ flushing = (async () => {
2407
+ try {
2408
+ while (queue2.length) {
2409
+ const batch = queue2.splice(0, MAX_CONNECTION_HISTORY_BATCH);
2410
+ await send(batch);
2411
+ }
2412
+ } finally {
2413
+ flushing = null;
2414
+ }
2415
+ })();
2416
+ return flushing;
2417
+ }
2418
+ async function dispose() {
2419
+ disposed = true;
2420
+ await flush();
2421
+ queue2.length = 0;
2422
+ }
2423
+ return { enqueue, flush, dispose };
2424
+ }
2425
+
2307
2426
  // ../client/src/react/session-lifecycle.ts
2308
2427
  function phaseForReason(reason) {
2309
2428
  switch (reason) {
@@ -2783,11 +2902,35 @@ function SessionLifecycleRoomBridge({
2783
2902
  const prevTranscriptionCountRef = useRef(0);
2784
2903
  const audio = assistant.audioTrack;
2785
2904
  const video = assistant.videoTrack;
2786
- const detailsEnabled = onConnectionDetailsChange !== void 0 && lifecycle.grant !== null;
2905
+ const connectionHistoryGrant = lifecycle.grant?.connection_history;
2906
+ const detailsEnabled = (onConnectionDetailsChange !== void 0 || connectionHistoryGrant !== void 0) && lifecycle.grant !== null;
2787
2907
  const detailsSessionId = lifecycle.grant?.session_id;
2908
+ const connectionHistoryRef = useRef(null);
2788
2909
  const updateDetailsRef = useRef(null);
2789
2910
  useEffect(() => {
2790
- if (!detailsEnabled || !onConnectionDetailsChange) return;
2911
+ const sessionId = detailsSessionId;
2912
+ if (!sessionId || !connectionHistoryGrant) {
2913
+ connectionHistoryRef.current = null;
2914
+ return;
2915
+ }
2916
+ const collector = createConnectionHistoryCollector({ sessionId, grant: connectionHistoryGrant });
2917
+ connectionHistoryRef.current = collector;
2918
+ return () => {
2919
+ if (connectionHistoryRef.current === collector) connectionHistoryRef.current = null;
2920
+ void collector.dispose();
2921
+ };
2922
+ }, [connectionHistoryGrant, detailsSessionId]);
2923
+ useEffect(() => {
2924
+ const collector = connectionHistoryRef.current;
2925
+ if (!collector || typeof window === "undefined" || typeof window.addEventListener !== "function" || typeof window.removeEventListener !== "function") return;
2926
+ const flush = () => {
2927
+ void collector.flush();
2928
+ };
2929
+ window.addEventListener("pagehide", flush, true);
2930
+ return () => window.removeEventListener("pagehide", flush, true);
2931
+ }, [connectionHistoryGrant, detailsSessionId]);
2932
+ useEffect(() => {
2933
+ if (!detailsEnabled || !onConnectionDetailsChange && !connectionHistoryGrant) return;
2791
2934
  let callback = onConnectionDetailsChange;
2792
2935
  let audioSource = audio;
2793
2936
  let videoSource = video;
@@ -2795,8 +2938,9 @@ function SessionLifecycleRoomBridge({
2795
2938
  let pending = false;
2796
2939
  let previous;
2797
2940
  const deliver = (details) => {
2941
+ connectionHistoryRef.current?.enqueue(details);
2798
2942
  try {
2799
- void Promise.resolve(callback(details)).catch(() => {
2943
+ if (onConnectionDetailsChange) void Promise.resolve(callback?.(details)).catch(() => {
2800
2944
  });
2801
2945
  } catch {
2802
2946
  }
@@ -2854,7 +2998,7 @@ function SessionLifecycleRoomBridge({
2854
2998
  };
2855
2999
  }, [room, detailsSessionId, detailsEnabled]);
2856
3000
  useEffect(() => {
2857
- if (onConnectionDetailsChange) updateDetailsRef.current?.(onConnectionDetailsChange, audio, video);
3001
+ updateDetailsRef.current?.(onConnectionDetailsChange, audio, video);
2858
3002
  }, [
2859
3003
  onConnectionDetailsChange,
2860
3004
  detailsEnabled ? audio?.participant : void 0,
package/dist/server.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, RealtimeAvatarOptions, StartCallOptions, clipLibraryDeclarationSchema, verifyTranscript } from './index.js';
2
- export { h as Asset, g as AssetKind, A as Avatar, o as AvatarClip, c as AvatarSourceSwap, b as AvatarUpdate, p as CallConnection, C as CallMode, a as CallPolicy, q as CallQueued, r as ClipDeclaration, f as ClipLibrary, d as ClipLibraryDeclaration, s as ClipLibraryPlan, e as ClipLibraryUpdate, t as ClipSource, u as ContextMessage, n as CreditBalance, E as EndCallOptions, v as EndCallReason, k as ListRecordingsQuery, l as ListRecordingsResponse, i as ListSessionsOptions, L as LoopRedirect, m as RecordingAccessResponse, R as RecordingArtifact, w as RecordingArtifactStatus, x as RecordingMode, S as StartCallResult, T as TranscriptPayload, j as UsageSession, U as UsageSessionPage, V as VideoPolicy, y as isQueued, z as listRecordingsResponseSchema, B as recordingAccessResponseSchema, D as recordingArtifactSchema, F as recordingModeSchema } from './types-VOv0vsFs.js';
2
+ export { h as Asset, g as AssetKind, A as Avatar, o as AvatarClip, c as AvatarSourceSwap, b as AvatarUpdate, p as CallConnection, C as CallMode, a as CallPolicy, q as CallQueued, r as ClipDeclaration, f as ClipLibrary, d as ClipLibraryDeclaration, s as ClipLibraryPlan, e as ClipLibraryUpdate, t as ClipSource, u as ContextMessage, n as CreditBalance, E as EndCallOptions, v as EndCallReason, k as ListRecordingsQuery, l as ListRecordingsResponse, i as ListSessionsOptions, L as LoopRedirect, m as RecordingAccessResponse, R as RecordingArtifact, w as RecordingArtifactStatus, x as RecordingMode, S as StartCallResult, T as TranscriptPayload, j as UsageSession, U as UsageSessionPage, V as VideoPolicy, y as isQueued, z as listRecordingsResponseSchema, B as recordingAccessResponseSchema, D as recordingArtifactSchema, F as recordingModeSchema } from './types-BnlnrYgN.js';
3
+ import './connection-history.js';
3
4
  import 'zod';
package/dist/server.js CHANGED
@@ -1 +1,2 @@
1
- export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, listRecordingsResponseSchema, recordingAccessResponseSchema, recordingArtifactSchema, recordingModeSchema, verifyTranscript } from './chunk-V26PE2M3.js';
1
+ export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, listRecordingsResponseSchema, recordingAccessResponseSchema, recordingArtifactSchema, recordingModeSchema, verifyTranscript } from './chunk-4OLLYAJT.js';
2
+ import './chunk-MCIXU6XP.js';
@@ -1,5 +1,5 @@
1
- import { P as ProxyConfig } from './types-Bphj69NC.js';
2
- import './types-VOv0vsFs.js';
1
+ import { P as ProxyConfig } from './types-CFkiNV3f.js';
2
+ import './types-BnlnrYgN.js';
3
3
  import 'zod';
4
4
 
5
5
  /**
@@ -1,5 +1,6 @@
1
- import { createProxyHandler } from './chunk-2TNFET3Q.js';
2
- import './chunk-V26PE2M3.js';
1
+ import { createProxyHandler } from './chunk-Q34WDWEA.js';
2
+ import './chunk-4OLLYAJT.js';
3
+ import './chunk-MCIXU6XP.js';
3
4
 
4
5
  // src/tanstack-start.ts
5
6
  function realtimeAvatarServerRoute(config) {
@@ -163,6 +163,7 @@ interface components {
163
163
  * @enum {string}
164
164
  */
165
165
  recording?: "off" | "audio" | "video" | "audio_video";
166
+ connection_history?: boolean;
166
167
  client_metadata?: {
167
168
  [key: string]: string;
168
169
  };
@@ -241,6 +242,13 @@ interface components {
241
242
  /** @enum {string} */
242
243
  errorCode: "recording_failed" | "recording_unavailable";
243
244
  };
245
+ readonly connection_history?: {
246
+ /** Format: starts_with */
247
+ endpoint: string;
248
+ token: string;
249
+ /** Format: date-time */
250
+ expiresAt: string;
251
+ };
244
252
  };
245
253
  LiveKitCapacitySnapshot: {
246
254
  capacity_pool: string;
@@ -573,6 +581,20 @@ interface components {
573
581
  /** Format: date-time */
574
582
  expiresAt: string;
575
583
  };
584
+ ConnectionHistoryResponse: {
585
+ sessionId: string;
586
+ observations: {
587
+ sequence: number;
588
+ elapsedMs: number;
589
+ clientObservedAt: string | null;
590
+ /** @enum {string} */
591
+ connectionState: "disconnected" | "connecting" | "connected" | "reconnecting" | "signalReconnecting";
592
+ /** @enum {string} */
593
+ localQuality: "excellent" | "good" | "poor" | "lost" | "unknown";
594
+ audioQuality: ("excellent" | "good" | "poor" | "lost" | "unknown") | null;
595
+ videoQuality: ("excellent" | "good" | "poor" | "lost" | "unknown") | null;
596
+ }[];
597
+ };
576
598
  ListAvatarsResponse: {
577
599
  data: {
578
600
  id: string;
@@ -1090,6 +1112,8 @@ type VideoPolicy = {
1090
1112
  };
1091
1113
  /** What YOUR SERVER decides about a call. Never accept any of this from a browser. */
1092
1114
  interface CallPolicy {
1115
+ /** Ask the platform to grant the SDK's optional LiveKit connection history collector. */
1116
+ connectionHistory?: boolean;
1093
1117
  /** Optional server recording policy. Omitted means off; obtain user consent before enabling. */
1094
1118
  recording?: RecordingMode;
1095
1119
  /** Her behavior contract — who she is and how she speaks. Max 8000 chars. */
@@ -1,4 +1,4 @@
1
- import { C as CallMode, a as CallPolicy } from './types-VOv0vsFs.js';
1
+ import { C as CallMode, a as CallPolicy } from './types-BnlnrYgN.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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "realtime-avatar",
3
- "version": "0.16.0",
3
+ "version": "0.17.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",
@@ -89,6 +89,10 @@
89
89
  "types": "./dist/recording.d.ts",
90
90
  "default": "./dist/recording.js"
91
91
  },
92
+ "./connection-history": {
93
+ "types": "./dist/connection-history.d.ts",
94
+ "default": "./dist/connection-history.js"
95
+ },
92
96
  "./tools": {
93
97
  "types": "./dist/tools.d.ts",
94
98
  "default": "./dist/tools.js"