realtime-avatar 0.22.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,6 +25,47 @@ return call.raw; // relay to the browser byte-for-byte
25
25
 
26
26
  That is the whole server half. The client joins with the payload and renders her.
27
27
 
28
+ ## React Native compatibility
29
+
30
+ The next release requires **LiveKit React Native 3** for `/react-native`; it does not claim
31
+ compatibility with LiveKit React Native 2. Install the tested group together:
32
+
33
+ ```bash
34
+ npm install --save-exact @livekit/react-native@3.0.0 @livekit/react-native-webrtc@144.2.0 livekit-client@2.22.3 @livekit/components-react@2.9.21
35
+ ```
36
+
37
+ `@livekit/components-react@2.9.21` retains `@livekit/components-core@0.12.13`; both satisfy the
38
+ native 3.0.0 peer contracts. Keep a single resolved components-react instance: the native room
39
+ provides its context and the SDK's shared hooks consume that same context. SDK checks use React
40
+ 19.2.7 and React Native 0.81.5. The web binding also uses client 2.22.3; no components upgrade or
41
+ stream-handling patch is required for this tuple. Peer ranges allow compatible updates, but only
42
+ this exact tuple has been checked here. Client 2.22.3 introduces `machina@7.0.1`, whose Node
43
+ engine is `>=22.22`; use Node 22.22 or newer for client/native tooling (verified on 22.23.1).
44
+ This does not change the dependency-free server entry's Node floor. Node 20 tooling is not
45
+ verified for the new LiveKit group.
46
+
47
+ Call the SDK's re-exported `registerGlobals()` once at app startup, before creating rooms. RN3
48
+ configures iOS audio natively by default; do not reintroduce the deprecated JavaScript audio
49
+ management callback. `RealtimeAvatarLiveKitRoom` retains the existing `AudioSession.startAudioSession`
50
+ and `stopAudioSession` lifecycle while a grant and `connect` intent are present. Apps that own audio
51
+ configuration or coordinate overlapping calls must pass `manageAudioSession={false}` and own that
52
+ lifecycle; the hook is not a shared audio-session lease. Camera publication still requires both the
53
+ server grant's permission and the caller's opt-in. Room callbacks/options continue to use LiveKit's
54
+ public types and implementation; no separate connection state machine is introduced.
55
+
56
+ **This is a native build upgrade, not an OTA-only change.** Rebuild iOS and Android binaries with
57
+ RN3/WebRTC 144.2.0 before shipping JavaScript that requires them. Retain the previous SDK group for
58
+ installed binaries that still use RN2, and isolate their OTA runtime/channel appropriately.
59
+
60
+ The packaged-entry regression suite mounts the built `/react-native` adapter with the installed
61
+ RN3 room and audio implementations, real React/LiveKit hooks, and shared contexts. It checks room
62
+ props, microphone/camera policy, callbacks, start/stop intent, redial, and listener cleanup. It mocks
63
+ native module calls, network connection and media capture; it does **not** prove native binary
64
+ linking, `registerGlobals`, RTCView rendering, permissions, Bluetooth/speaker routing, interruption
65
+ recovery, asynchronous OS audio teardown, or overlapping-room ownership. Test these on iOS and
66
+ Android devices, including repeated call/redial and background/foreground transitions, before a
67
+ consumer release.
68
+
28
69
  ## Optional call recordings
29
70
 
30
71
  Your server decides whether to record after your application obtains consent:
@@ -143,6 +184,32 @@ Two facts follow from that picture and drive everything else:
143
184
 
144
185
  ---
145
186
 
187
+ ## Input source on room messages
188
+
189
+ On React and React Native, `session.sendTurn(text)` automatically observes text.
190
+ For already recognized speech, bind
191
+ `session.createTranscriptSender({ inputSource: "client_stt" })` once per session.
192
+ The adapter defaults to `client_stt`; it captures its own default, and accepts a
193
+ per-send `{ inputSource: "text" }` override. `sendTurn` accepts the same optional
194
+ declaration. Only `text` and `client_stt` are valid client declarations. No speech
195
+ recognition engine is included. `instructions` still work on either sender.
196
+
197
+ `retryTurn()` preserves the resolved source and declaration scope after a timeout,
198
+ with a new `turn_id` and `retry_of_turn_id` pointing to the previous attempt.
199
+ Room consumers receive the attributes through the existing `lk.chat` text stream.
200
+ `useChat().chatMessages` exposes them as `message.attributes`; an imperative receiver
201
+ can read `reader.info.attributes` in `registerTextStreamHandler("lk.chat", handler)`.
202
+ The observation is `rta.observed_input_source`; the optional declaration and scope
203
+ are `rta.declared_input_source` and `rta.input_source_declaration_scope`. Missing
204
+ legacy attributes remain unknown. Use one handler owner per topic; a `useChat`
205
+ consumer should not register a duplicate raw handler on the same room.
206
+
207
+ This path needs no inference or platform changes. The consumer must already be
208
+ connected to the room, and text-stream messages are not durable webhook deliveries.
209
+ The legacy `lk-chat-topic` compatibility path omits attributes. Automatic RTA server
210
+ STT attribution and final transcript webhook provenance are separate work. Existing
211
+ webhook types and behavior are unchanged.
212
+
146
213
  ## API
147
214
 
148
215
  Everything is on one class. The full types are in
@@ -359,3 +426,34 @@ for building your own queue UI, and the zod schemas `sessionBehaviorSchema` / `s
359
426
  - The API is versioned at `/api/v1`; breaking changes get a new version, not a silent edit.
360
427
 
361
428
  MIT licensed.
429
+
430
+
431
+ ### Continuous participant recordings
432
+
433
+ Use `recording: "participants"` on your server to retain the two participants
434
+ separately. A camera-enabled call has four logical media tracks in two MP4 files:
435
+ user camera/microphone and character video/voice. Each file is continuous through
436
+ camera mute, unpublish and republish; the user's microphone continues while their
437
+ camera is off. `camera: true` is still a separate publication permission, and
438
+ recording alone never opens a camera. Voice-only participants get audio-only MP4s.
439
+
440
+ The returned `call.recordings` contains both pending artifacts, while legacy
441
+ modes retain `call.recording`. Once finalized, use `listRecordings({ sessionId })`
442
+ and `getRecordingAccess(recordingId)` to retrieve authorized, renewable URLs.
443
+ `recording.participant.role` identifies the user or character; media timestamps
444
+ align the files without fixing their visual layout. Keep failed/missing files
445
+ visible instead of treating the surviving participant as a complete recording.
446
+
447
+ ```tsx
448
+ import { RecordingPlayer } from "realtime-avatar/react";
449
+
450
+ // assets comes from your authenticated application endpoint.
451
+ // Each item is { recording, url }; refresh expiring URLs through your server.
452
+ <RecordingPlayer assets={assets} />
453
+ ```
454
+
455
+ The player provides one play/pause control and one seek bar for both files. It
456
+ plays each file's audio once, preserves start offsets, waits for buffering and
457
+ refuses to guess synchronization when media timestamps are missing. Original
458
+ files remain independently playable and editable. Participant departure ends
459
+ that file; a new call is a new session, not an automatic concatenation.
@@ -1,4 +1,4 @@
1
- import { RealtimeAvatar, isQueued, RealtimeAvatarHttpError } from './chunk-N76M4MEU.js';
1
+ import { RealtimeAvatar, isQueued, RealtimeAvatarHttpError } from './chunk-ZXPLFSG7.js';
2
2
 
3
3
  // ../proxy/src/config.ts
4
4
  var json = (body, status = 200) => new Response(JSON.stringify(body), {
@@ -234,15 +234,22 @@ var schema26 = z.strictObject({
234
234
  var clipLibraryResponseSchema = schema24;
235
235
  var clipLibraryUpdateSchema = schema26;
236
236
  var RECORDED_MEDIA_MODES = ["audio", "video", "audio_video"];
237
- var recordingModeSchema = z.enum(["off", ...RECORDED_MEDIA_MODES]).describe("Server-owned recording policy; omitted means off. Audio includes user and avatar audio.");
237
+ var recordingModeSchema = z.enum(["off", ...RECORDED_MEDIA_MODES, "participants"]).describe("Server-owned recording policy; omitted means off. Legacy audio includes user and avatar audio. participants saves one continuous audio/video file per participant, without mixing their voices.");
238
238
  var RECORDING_STATUSES = ["pending", "recording", "processing", "ready", "failed", "expired"];
239
239
  z.enum(RECORDING_STATUSES);
240
+ var recordingParticipantSchema = z.object({
241
+ participantIdentity: z.string().min(1).max(160),
242
+ role: z.enum(["user", "avatar"])
243
+ }).strict();
240
244
  var recordingMetadataSchema = z.object({
241
245
  sessionId: z.string().min(1),
242
246
  recordingId: z.string().min(1),
243
- mode: recordingModeSchema.exclude(["off"]),
247
+ mode: z.enum(RECORDED_MEDIA_MODES),
244
248
  createdAt: z.string().datetime({ offset: true }),
245
- retainedUntil: z.string().datetime({ offset: true })
249
+ retainedUntil: z.string().datetime({ offset: true }),
250
+ participant: recordingParticipantSchema.optional(),
251
+ mediaStartedAt: z.string().datetime({ offset: true }).optional(),
252
+ mediaEndedAt: z.string().datetime({ offset: true }).optional()
246
253
  });
247
254
  var recordingArtifactSchema = z.discriminatedUnion("status", [
248
255
  recordingMetadataSchema.extend({ status: z.enum(["pending", "recording", "processing", "expired"]) }).strict(),
@@ -548,6 +555,8 @@ z.object({
548
555
  }).strict();
549
556
  var liveKitSessionGrantSchema = z.object({
550
557
  recording: recordingArtifactSchema.optional(),
558
+ recording_mode: recordingModeSchema.optional(),
559
+ recordings: z.array(recordingArtifactSchema).max(2).optional(),
551
560
  connection_history: connectionHistoryGrantSchema.optional(),
552
561
  status: z.literal("ready").default("ready"),
553
562
  session_id: z.string().min(1),
@@ -645,7 +654,7 @@ z.enum([
645
654
 
646
655
  // ../http-client/src/client.ts
647
656
  var DEFAULT_BASE_URL = "https://realtimeavatar.ai/api/v1";
648
- var SDK_VERSION = "0.22.0";
657
+ var SDK_VERSION = "0.24.0";
649
658
  var RealtimeAvatar = class {
650
659
  #apiKey;
651
660
  #baseUrl;
@@ -718,6 +727,8 @@ var RealtimeAvatar = class {
718
727
  return {
719
728
  status: "ready",
720
729
  ...grant.recording === void 0 ? {} : { recording: grant.recording },
730
+ ...grant.recording_mode === void 0 ? {} : { recordingMode: grant.recording_mode },
731
+ ...grant.recordings === void 0 ? {} : { recordings: grant.recordings },
721
732
  sessionId: grant.session_id,
722
733
  roomName: grant.room_name,
723
734
  livekitUrl: grant.livekit_url,
package/dist/express.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as ProxyConfig } from './types-k6pW-wKA.js';
2
- import './types-Cmto2uGs.js';
1
+ import { P as ProxyConfig } from './types-CpqMHna3.js';
2
+ import './types-D77jgSms.js';
3
3
  import 'zod';
4
4
 
5
5
  type Expressish = {
package/dist/express.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-T3RZSVRI.js';
2
- import './chunk-N76M4MEU.js';
1
+ import { createProxyHandler } from './chunk-FUJI3KIH.js';
2
+ import './chunk-ZXPLFSG7.js';
3
3
  import './chunk-MCIXU6XP.js';
4
4
 
5
5
  // ../proxy/src/express.ts
package/dist/hono.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as ProxyConfig } from './types-k6pW-wKA.js';
2
- import './types-Cmto2uGs.js';
1
+ import { P as ProxyConfig } from './types-CpqMHna3.js';
2
+ import './types-D77jgSms.js';
3
3
  import 'zod';
4
4
 
5
5
  /**
package/dist/hono.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-T3RZSVRI.js';
2
- import './chunk-N76M4MEU.js';
1
+ import { createProxyHandler } from './chunk-FUJI3KIH.js';
2
+ import './chunk-ZXPLFSG7.js';
3
3
  import './chunk-MCIXU6XP.js';
4
4
 
5
5
  // ../proxy/src/hono.ts
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as CallPolicy, C as CallMode, S as StartCallResult, E as EndCallOptions, A as Avatar, b as AvatarUpdate, c as AvatarSourceSwap, d as ClipLibraryDeclaration, e as ClipLibraryUpdate, L as LoopRedirect, f as ClipLibrary, g as AssetKind, h as Asset, i as ListSessionsOptions, U as UsageSessionPage, j as UsageSession, k as ListRecordingsQuery, l as ListRecordingsResponse, R as RecordingArtifact, m as RecordingAccessResponse, n as CreditBalance, T as TranscriptPayload } from './types-Cmto2uGs.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-Cmto2uGs.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-D77jgSms.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-D77jgSms.js';
3
3
  import { ConnectionHistoryResponse } from './connection-history.js';
4
4
  import { z } from 'zod';
5
5
 
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, listRecordingsResponseSchema, recordingAccessResponseSchema, recordingArtifactSchema, recordingModeSchema, verifyTranscript } from './chunk-N76M4MEU.js';
1
+ export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, listRecordingsResponseSchema, recordingAccessResponseSchema, recordingArtifactSchema, recordingModeSchema, verifyTranscript } from './chunk-ZXPLFSG7.js';
2
2
  import './chunk-MCIXU6XP.js';
package/dist/nextjs.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as ProxyConfig } from './types-k6pW-wKA.js';
2
- import './types-Cmto2uGs.js';
1
+ import { P as ProxyConfig } from './types-CpqMHna3.js';
2
+ import './types-D77jgSms.js';
3
3
  import 'zod';
4
4
 
5
5
  /**
package/dist/nextjs.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-T3RZSVRI.js';
2
- import './chunk-N76M4MEU.js';
1
+ import { createProxyHandler } from './chunk-FUJI3KIH.js';
2
+ import './chunk-ZXPLFSG7.js';
3
3
  import './chunk-MCIXU6XP.js';
4
4
 
5
5
  // ../proxy/src/nextjs.ts
@@ -135,8 +135,8 @@ declare const liveKitSessionRequestSchema: z.ZodObject<{
135
135
  instructions: z.ZodOptional<z.ZodString>;
136
136
  initialContext: z.ZodDefault<z.ZodArray<z.ZodObject<{
137
137
  role: z.ZodEnum<{
138
- system: "system";
139
138
  user: "user";
139
+ system: "system";
140
140
  assistant: "assistant";
141
141
  }>;
142
142
  content: z.ZodString;
@@ -253,6 +253,15 @@ declare const liveKitSessionGrantSchema: z.ZodObject<{
253
253
  }>;
254
254
  createdAt: z.ZodString;
255
255
  retainedUntil: z.ZodString;
256
+ participant: z.ZodOptional<z.ZodObject<{
257
+ participantIdentity: z.ZodString;
258
+ role: z.ZodEnum<{
259
+ user: "user";
260
+ avatar: "avatar";
261
+ }>;
262
+ }, z.core.$strict>>;
263
+ mediaStartedAt: z.ZodOptional<z.ZodString>;
264
+ mediaEndedAt: z.ZodOptional<z.ZodString>;
256
265
  status: z.ZodEnum<{
257
266
  pending: "pending";
258
267
  recording: "recording";
@@ -269,6 +278,15 @@ declare const liveKitSessionGrantSchema: z.ZodObject<{
269
278
  }>;
270
279
  createdAt: z.ZodString;
271
280
  retainedUntil: z.ZodString;
281
+ participant: z.ZodOptional<z.ZodObject<{
282
+ participantIdentity: z.ZodString;
283
+ role: z.ZodEnum<{
284
+ user: "user";
285
+ avatar: "avatar";
286
+ }>;
287
+ }, z.core.$strict>>;
288
+ mediaStartedAt: z.ZodOptional<z.ZodString>;
289
+ mediaEndedAt: z.ZodOptional<z.ZodString>;
272
290
  status: z.ZodLiteral<"ready">;
273
291
  mediaType: z.ZodEnum<{
274
292
  "audio/mp4": "audio/mp4";
@@ -286,12 +304,104 @@ declare const liveKitSessionGrantSchema: z.ZodObject<{
286
304
  }>;
287
305
  createdAt: z.ZodString;
288
306
  retainedUntil: z.ZodString;
307
+ participant: z.ZodOptional<z.ZodObject<{
308
+ participantIdentity: z.ZodString;
309
+ role: z.ZodEnum<{
310
+ user: "user";
311
+ avatar: "avatar";
312
+ }>;
313
+ }, z.core.$strict>>;
314
+ mediaStartedAt: z.ZodOptional<z.ZodString>;
315
+ mediaEndedAt: z.ZodOptional<z.ZodString>;
289
316
  status: z.ZodLiteral<"failed">;
290
317
  errorCode: z.ZodEnum<{
291
318
  recording_failed: "recording_failed";
292
319
  recording_unavailable: "recording_unavailable";
293
320
  }>;
294
321
  }, z.core.$strict>], "status">>;
322
+ recording_mode: z.ZodOptional<z.ZodEnum<{
323
+ audio: "audio";
324
+ video: "video";
325
+ audio_video: "audio_video";
326
+ off: "off";
327
+ participants: "participants";
328
+ }>>;
329
+ recordings: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
330
+ sessionId: z.ZodString;
331
+ recordingId: z.ZodString;
332
+ mode: z.ZodEnum<{
333
+ audio: "audio";
334
+ video: "video";
335
+ audio_video: "audio_video";
336
+ }>;
337
+ createdAt: z.ZodString;
338
+ retainedUntil: z.ZodString;
339
+ participant: z.ZodOptional<z.ZodObject<{
340
+ participantIdentity: z.ZodString;
341
+ role: z.ZodEnum<{
342
+ user: "user";
343
+ avatar: "avatar";
344
+ }>;
345
+ }, z.core.$strict>>;
346
+ mediaStartedAt: z.ZodOptional<z.ZodString>;
347
+ mediaEndedAt: z.ZodOptional<z.ZodString>;
348
+ status: z.ZodEnum<{
349
+ pending: "pending";
350
+ recording: "recording";
351
+ processing: "processing";
352
+ expired: "expired";
353
+ }>;
354
+ }, z.core.$strict>, z.ZodObject<{
355
+ sessionId: z.ZodString;
356
+ recordingId: z.ZodString;
357
+ mode: z.ZodEnum<{
358
+ audio: "audio";
359
+ video: "video";
360
+ audio_video: "audio_video";
361
+ }>;
362
+ createdAt: z.ZodString;
363
+ retainedUntil: z.ZodString;
364
+ participant: z.ZodOptional<z.ZodObject<{
365
+ participantIdentity: z.ZodString;
366
+ role: z.ZodEnum<{
367
+ user: "user";
368
+ avatar: "avatar";
369
+ }>;
370
+ }, z.core.$strict>>;
371
+ mediaStartedAt: z.ZodOptional<z.ZodString>;
372
+ mediaEndedAt: z.ZodOptional<z.ZodString>;
373
+ status: z.ZodLiteral<"ready">;
374
+ mediaType: z.ZodEnum<{
375
+ "audio/mp4": "audio/mp4";
376
+ "video/mp4": "video/mp4";
377
+ }>;
378
+ sizeBytes: z.ZodNumber;
379
+ durationMs: z.ZodNullable<z.ZodNumber>;
380
+ }, z.core.$strict>, z.ZodObject<{
381
+ sessionId: z.ZodString;
382
+ recordingId: z.ZodString;
383
+ mode: z.ZodEnum<{
384
+ audio: "audio";
385
+ video: "video";
386
+ audio_video: "audio_video";
387
+ }>;
388
+ createdAt: z.ZodString;
389
+ retainedUntil: z.ZodString;
390
+ participant: z.ZodOptional<z.ZodObject<{
391
+ participantIdentity: z.ZodString;
392
+ role: z.ZodEnum<{
393
+ user: "user";
394
+ avatar: "avatar";
395
+ }>;
396
+ }, z.core.$strict>>;
397
+ mediaStartedAt: z.ZodOptional<z.ZodString>;
398
+ mediaEndedAt: z.ZodOptional<z.ZodString>;
399
+ status: z.ZodLiteral<"failed">;
400
+ errorCode: z.ZodEnum<{
401
+ recording_failed: "recording_failed";
402
+ recording_unavailable: "recording_unavailable";
403
+ }>;
404
+ }, z.core.$strict>], "status">>>;
295
405
  connection_history: z.ZodOptional<z.ZodObject<{
296
406
  endpoint: z.ZodString;
297
407
  token: z.ZodString;
@@ -1468,6 +1578,9 @@ type TurnState = "listening" | "thinking" | "speaking" | "quiet";
1468
1578
  */
1469
1579
  declare function mapTurnState(assistantState: string | null | undefined): TurnState;
1470
1580
 
1581
+ /** Source an application declares for text sent to an in-room consumer. */
1582
+ type DeclaredInputSource = "text" | "client_stt";
1583
+
1471
1584
  /** How early `onApproachingEnd` fires before the hard cap (room to compose the goodbye). */
1472
1585
  declare const DEFAULT_APPROACHING_END_LEAD_SECONDS = 45;
1473
1586
  /** How early the grace window opens before the cap (fits LLM excuse + send RTT + TTS + playout). */
@@ -1486,6 +1599,17 @@ type ClosingTurnResult = {
1486
1599
  type ExtendResult = {
1487
1600
  ok: boolean;
1488
1601
  };
1602
+ type SendTurnOptions = {
1603
+ instructions?: string;
1604
+ /** Optional declaration; the SDK still observes a text transport. */
1605
+ inputSource?: DeclaredInputSource;
1606
+ };
1607
+ type TranscriptSenderOptions = {
1608
+ /** Captured once for this sender. Defaults to client_stt. */
1609
+ inputSource?: DeclaredInputSource;
1610
+ };
1611
+ /** Send an already recognized transcript. This adapter does not capture or recognize audio. */
1612
+ type TranscriptSender = (text: string, opts?: SendTurnOptions) => Promise<void>;
1489
1613
  type ApproachingEndEvent = {
1490
1614
  secondsLeft: number;
1491
1615
  reason: ApproachingEndReason;
@@ -1619,10 +1743,10 @@ type RealtimeSessionApi = SessionLifecycleApi & {
1619
1743
  proof?: string;
1620
1744
  }) => ExtendResult;
1621
1745
  /** Send a normal turn THROUGH the SDK (arms the turn-timeout watchdog + enables retryTurn). */
1622
- sendTurn: (text: string, opts?: {
1623
- instructions?: string;
1624
- }) => Promise<void>;
1625
- /** Re-send the last turn sent through the SDK (the "no response" recovery). */
1746
+ sendTurn: (text: string, opts?: SendTurnOptions) => Promise<void>;
1747
+ /** Bind a transcript source once; a send's explicit inputSource takes precedence. */
1748
+ createTranscriptSender: (opts?: TranscriptSenderOptions) => TranscriptSender;
1749
+ /** Re-send the last turn with its resolved provenance, a new ID, and retry_of_turn_id. */
1626
1750
  retryTurn: () => void;
1627
1751
  /** End gracefully now (the user tapped End). */
1628
1752
  end: (reason?: EndReason) => void;
@@ -1744,4 +1868,4 @@ interface ProxyClientOptions {
1744
1868
  }
1745
1869
  declare function createProxyClient(options: ProxyClientOptions): AvatarSessionClient;
1746
1870
 
1747
- export { type QualityCap 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 GovernorState as H, type GraceWindowClosedEvent as I, type GraceWindowOpenEvent as J, type GraceWindowState as K, type LiveKitSessionGrant as L, type IdleWarningEvent as M, type InboundRtpCursor as N, type InboundRtpReading as O, type KnownBehaviorState as P, type LLMProvider as Q, type LLMSelection as R, type LiveKitAvatarGrantState as S, type LiveKitAvatarGrantStatus as T, type LiveKitCapacityState as U, type LiveKitConnectionStatus as V, type LiveKitSessionRequest as W, type LiveKitSessionStartResult as X, MAX_RECONNECT_ATTEMPTS as Y, MAX_SESSION_INSTRUCTIONS_CHARS as Z, type ProxyClientOptions as _, AdaptivePlayoutController as a, RECONNECT_BACKOFF_MS as a0, type RealtimeAvatarRequestOptions as a1, type RealtimeSessionApi as a2, type RealtimeSessionMedia as a3, type RealtimeSessionRoomSinks as a4, type ReconnectPolicy as a5, type ReconnectingEvent as a6, type RecoveryState as a7, type RetryStep as a8, type SessionBehavior as a9, splitCallTranscript as aA, useAvatarAdaptivePlayoutDelay as aB, useAvatarCamera as aC, useAvatarPlayoutDelay as aD, useAvatarQualityGovernor as aE, useCallTranscript as aF, useLiveKitAvatarGrant as aG, useLiveTrackProducing as aH, useMicLease as aI, useRealtimeSession as aJ, useReleaseMicLeaseOnTrackEnded as aK, useSessionLifecycle as aL, AvatarVideoSurface as aM, type AvatarVideoSurfaceProps as aN, type LivePlaybackKeeper as aO, MIC_LEASE_ENDED_TIMEOUT_MS as aP, type MicLease as aQ, NETWORK_QUALITY_POLICY as aR, type NetworkQualityStatus as aS, type PlayableVideoElement as aT, RealtimeAvatarCapacityError as aU, RealtimeAvatarLiveKitRoom as aV, type RealtimeAvatarLiveKitRoomProps as aW, shouldReplayPendingTurn as aX, type SessionClip as aa, type SessionClocks as ab, type SessionEndReason as ac, type SessionLifecycleApi as ad, type SessionLifecyclePhase as ae, type SessionLifecyclePhaseKind as af, SessionLifecycleRoomBridge as ag, type SessionLifecycleRoomBridgeProps as ah, type SurfaceLayers as ai, type TurnState as aj, type TurnTimeoutEvent as ak, type UseAvatarQualityGovernorInput as al, type UseLiveKitAvatarGrantInput as am, type UseRealtimeSessionInput as an, type UseSessionLifecycleInput as ao, type VoiceSpec as ap, type VoiceSpecInput as aq, capacityErrorFromBusy as ar, capacityStateFromGrant as as, createProxyClient as at, isNativeLiveTrackSubscribed as au, knownBehaviorStates as av, mapTurnState as aw, readInboundRtp as ax, sessionBehaviorSchema as ay, sessionClipSchema as az, type AdaptivePlayoutDecision as b, 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_TURN_TIMEOUT_SECONDS as t, type EndedEvent as u, type ExtendResult as v, type FreezeReadingFn as w, type GovernorAction as x, type GovernorConfig as y, type GovernorSignal as z };
1871
+ export { type ProxyClientOptions 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 GovernorSignal as H, type GovernorState as I, type GraceWindowClosedEvent as J, type GraceWindowOpenEvent as K, type LiveKitSessionGrant as L, type GraceWindowState as M, type IdleWarningEvent as N, type InboundRtpCursor as O, type InboundRtpReading as P, type KnownBehaviorState as Q, type LLMProvider as R, type LLMSelection as S, type LiveKitAvatarGrantState as T, type LiveKitAvatarGrantStatus as U, type LiveKitCapacityState as V, type LiveKitConnectionStatus as W, type LiveKitSessionRequest as X, type LiveKitSessionStartResult as Y, MAX_RECONNECT_ATTEMPTS as Z, MAX_SESSION_INSTRUCTIONS_CHARS as _, AdaptivePlayoutController as a, shouldReplayPendingTurn as a$, type QualityCap as a0, RECONNECT_BACKOFF_MS as a1, type RealtimeAvatarRequestOptions as a2, type RealtimeSessionApi as a3, type RealtimeSessionMedia as a4, type RealtimeSessionRoomSinks as a5, type ReconnectPolicy as a6, type ReconnectingEvent as a7, type RecoveryState as a8, type RetryStep as a9, mapTurnState as aA, readInboundRtp as aB, sessionBehaviorSchema as aC, sessionClipSchema as aD, splitCallTranscript as aE, useAvatarAdaptivePlayoutDelay as aF, useAvatarCamera as aG, useAvatarPlayoutDelay as aH, useAvatarQualityGovernor as aI, useCallTranscript as aJ, useLiveKitAvatarGrant as aK, useLiveTrackProducing as aL, useMicLease as aM, useRealtimeSession as aN, useReleaseMicLeaseOnTrackEnded as aO, useSessionLifecycle as aP, AvatarVideoSurface as aQ, type AvatarVideoSurfaceProps as aR, type LivePlaybackKeeper as aS, MIC_LEASE_ENDED_TIMEOUT_MS as aT, type MicLease as aU, NETWORK_QUALITY_POLICY as aV, type NetworkQualityStatus as aW, type PlayableVideoElement as aX, RealtimeAvatarCapacityError as aY, RealtimeAvatarLiveKitRoom as aZ, type RealtimeAvatarLiveKitRoomProps as a_, type SendTurnOptions as aa, type SessionBehavior as ab, type SessionClip as ac, type SessionClocks as ad, type SessionEndReason as ae, type SessionLifecycleApi as af, type SessionLifecyclePhase as ag, type SessionLifecyclePhaseKind as ah, SessionLifecycleRoomBridge as ai, type SessionLifecycleRoomBridgeProps as aj, type SurfaceLayers as ak, type TranscriptSender as al, type TranscriptSenderOptions as am, type TurnState as an, type TurnTimeoutEvent as ao, type UseAvatarQualityGovernorInput as ap, type UseLiveKitAvatarGrantInput as aq, type UseRealtimeSessionInput as ar, type UseSessionLifecycleInput as as, type VoiceSpec as at, type VoiceSpecInput as au, capacityErrorFromBusy as av, capacityStateFromGrant as aw, createProxyClient as ax, isNativeLiveTrackSubscribed as ay, knownBehaviorStates as az, type AdaptivePlayoutDecision as b, 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_TURN_TIMEOUT_SECONDS as t, type DeclaredInputSource as u, type EndedEvent as v, type ExtendResult as w, type FreezeReadingFn as x, type GovernorAction as y, type GovernorConfig as z };
@@ -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-6ndCkiio.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_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 useAvatarCamera, aD as useAvatarPlayoutDelay, aE as useAvatarQualityGovernor, aF as useCallTranscript, aG as useLiveKitAvatarGrant, aH as useLiveTrackProducing, aI as useMicLease, aJ as useRealtimeSession, aK as useReleaseMicLeaseOnTrackEnded, aL as useSessionLifecycle } from './proxy-client-6ndCkiio.js';
3
+ import { L as LiveKitSessionGrant, A as AvatarVideoFit } from './proxy-client-DlFXaXtn.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_TURN_TIMEOUT_SECONDS, u as DeclaredInputSource, E as EndReason, v as EndedEvent, w as ExtendResult, F as FishTtsModel, x as FreezeReadingFn, G as Governor, y as GovernorAction, z as GovernorConfig, H as GovernorSignal, I as GovernorState, J as GraceWindowClosedEvent, K as GraceWindowOpenEvent, M as GraceWindowState, N as IdleWarningEvent, O as InboundRtpCursor, P as InboundRtpReading, Q as KnownBehaviorState, R as LLMProvider, S as LLMSelection, T as LiveKitAvatarGrantState, U as LiveKitAvatarGrantStatus, V as LiveKitCapacityState, W as LiveKitConnectionStatus, X as LiveKitSessionRequest, Y as LiveKitSessionStartResult, Z as MAX_RECONNECT_ATTEMPTS, _ as MAX_SESSION_INSTRUCTIONS_CHARS, $ as ProxyClientOptions, a0 as QualityCap, a1 as RECONNECT_BACKOFF_MS, a2 as RealtimeAvatarRequestOptions, a3 as RealtimeSessionApi, a4 as RealtimeSessionMedia, a5 as RealtimeSessionRoomSinks, a6 as ReconnectPolicy, a7 as ReconnectingEvent, a8 as RecoveryState, a9 as RetryStep, aa as SendTurnOptions, ab as SessionBehavior, ac as SessionClip, ad as SessionClocks, ae as SessionEndReason, af as SessionLifecycleApi, ag as SessionLifecyclePhase, ah as SessionLifecyclePhaseKind, ai as SessionLifecycleRoomBridge, aj as SessionLifecycleRoomBridgeProps, ak as SurfaceLayers, al as TranscriptSender, am as TranscriptSenderOptions, an as TurnState, ao as TurnTimeoutEvent, ap as UseAvatarQualityGovernorInput, aq as UseLiveKitAvatarGrantInput, ar as UseRealtimeSessionInput, as as UseSessionLifecycleInput, at as VoiceSpec, au as VoiceSpecInput, av as capacityErrorFromBusy, aw as capacityStateFromGrant, ax as createProxyClient, ay as isNativeLiveTrackSubscribed, az as knownBehaviorStates, aA as mapTurnState, aB as readInboundRtp, aC as sessionBehaviorSchema, aD as sessionClipSchema, aE as splitCallTranscript, aF as useAvatarAdaptivePlayoutDelay, aG as useAvatarCamera, aH as useAvatarPlayoutDelay, aI as useAvatarQualityGovernor, aJ as useCallTranscript, aK as useLiveKitAvatarGrant, aL as useLiveTrackProducing, aM as useMicLease, aN as useRealtimeSession, aO as useReleaseMicLeaseOnTrackEnded, aP as useSessionLifecycle } from './proxy-client-DlFXaXtn.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';
@@ -1585,15 +1585,22 @@ var styles = StyleSheet.create({
1585
1585
  }
1586
1586
  });
1587
1587
  var RECORDED_MEDIA_MODES = ["audio", "video", "audio_video"];
1588
- var recordingModeSchema = z.enum(["off", ...RECORDED_MEDIA_MODES]).describe("Server-owned recording policy; omitted means off. Audio includes user and avatar audio.");
1588
+ var recordingModeSchema = z.enum(["off", ...RECORDED_MEDIA_MODES, "participants"]).describe("Server-owned recording policy; omitted means off. Legacy audio includes user and avatar audio. participants saves one continuous audio/video file per participant, without mixing their voices.");
1589
1589
  var RECORDING_STATUSES = ["pending", "recording", "processing", "ready", "failed", "expired"];
1590
1590
  z.enum(RECORDING_STATUSES);
1591
+ var recordingParticipantSchema = z.object({
1592
+ participantIdentity: z.string().min(1).max(160),
1593
+ role: z.enum(["user", "avatar"])
1594
+ }).strict();
1591
1595
  var recordingMetadataSchema = z.object({
1592
1596
  sessionId: z.string().min(1),
1593
1597
  recordingId: z.string().min(1),
1594
- mode: recordingModeSchema.exclude(["off"]),
1598
+ mode: z.enum(RECORDED_MEDIA_MODES),
1595
1599
  createdAt: z.string().datetime({ offset: true }),
1596
- retainedUntil: z.string().datetime({ offset: true })
1600
+ retainedUntil: z.string().datetime({ offset: true }),
1601
+ participant: recordingParticipantSchema.optional(),
1602
+ mediaStartedAt: z.string().datetime({ offset: true }).optional(),
1603
+ mediaEndedAt: z.string().datetime({ offset: true }).optional()
1597
1604
  });
1598
1605
  var recordingArtifactSchema = z.discriminatedUnion("status", [
1599
1606
  recordingMetadataSchema.extend({ status: z.enum(["pending", "recording", "processing", "expired"]) }).strict(),
@@ -1900,6 +1907,8 @@ z.object({
1900
1907
  }).strict();
1901
1908
  z.object({
1902
1909
  recording: recordingArtifactSchema.optional(),
1910
+ recording_mode: recordingModeSchema.optional(),
1911
+ recordings: z.array(recordingArtifactSchema).max(2).optional(),
1903
1912
  connection_history: connectionHistoryGrantSchema.optional(),
1904
1913
  status: z.literal("ready").default("ready"),
1905
1914
  session_id: z.string().min(1),
@@ -1920,6 +1929,11 @@ var RTA_LIFECYCLE_TOPIC = "rta.lifecycle";
1920
1929
  var RTA_TURN_INSTRUCTIONS_ATTR = "rta.turn_instructions";
1921
1930
  var RTA_CLOSING_TURN_ATTR = "rta.closing_turn";
1922
1931
  var RTA_TURN_ID_ATTR = "rta.turn_id";
1932
+ var RTA_RETRY_OF_TURN_ID_ATTR = "rta.retry_of_turn_id";
1933
+ var RTA_INPUT_SOURCE_VERSION_ATTR = "rta.input_source_version";
1934
+ var RTA_OBSERVED_INPUT_SOURCE_ATTR = "rta.observed_input_source";
1935
+ var RTA_DECLARED_INPUT_SOURCE_ATTR = "rta.declared_input_source";
1936
+ var RTA_INPUT_SOURCE_DECLARATION_SCOPE_ATTR = "rta.input_source_declaration_scope";
1923
1937
  var sessionEndReasonSchema = z.enum([
1924
1938
  "user_ended",
1925
1939
  "session_cap",
@@ -2855,6 +2869,11 @@ var DEFAULT_CREDITS_LOW_LEAD_SECONDS = 300;
2855
2869
  function positive(value, fallbackSeconds) {
2856
2870
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallbackSeconds;
2857
2871
  }
2872
+ function validateInputSource(value) {
2873
+ if (value !== void 0 && value !== "text" && value !== "client_stt") {
2874
+ throw new TypeError('inputSource must be "text" or "client_stt"');
2875
+ }
2876
+ }
2858
2877
  function useRealtimeSession(input) {
2859
2878
  const {
2860
2879
  session,
@@ -2912,7 +2931,8 @@ function useRealtimeSession(input) {
2912
2931
  const turnSenderRef = useRef(null);
2913
2932
  const dataPublisherRef = useRef(null);
2914
2933
  const lastTurnRef = useRef(null);
2915
- const [clockTick, setClockTick] = useState(0);
2934
+ const turnSequenceRef = useRef(0);
2935
+ const [, setClockTick] = useState(0);
2916
2936
  const endsAt = endsAtFrom({
2917
2937
  serverStartedAtUnixMs: serverClock?.startedAtUnixMs ?? null,
2918
2938
  maxSessionSeconds: serverClock?.maxSessionSeconds ?? null
@@ -2934,8 +2954,8 @@ function useRealtimeSession(input) {
2934
2954
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
2935
2955
  } catch {
2936
2956
  }
2937
- return `t-${Date.now()}-${clockTick}`;
2938
- }, [clockTick]);
2957
+ return `t-${Date.now()}-${++turnSequenceRef.current}`;
2958
+ }, []);
2939
2959
  const onLifecycleData = useCallback((frame) => {
2940
2960
  const parsed = lifecycleServerFrameSchema.safeParse(frame);
2941
2961
  if (!parsed.success) return;
@@ -3035,8 +3055,8 @@ function useRealtimeSession(input) {
3035
3055
  if (gw === "open" || gw === "delivering") stayConnected();
3036
3056
  }
3037
3057
  const turnInfo = lastTurnRef.current;
3038
- if (turnInfo && now - turnInfo.sentAt >= turnTimeoutMs) {
3039
- lastTurnRef.current = null;
3058
+ if (turnInfo && turnInfo.sentAt !== null && now - turnInfo.sentAt >= turnTimeoutMs) {
3059
+ turnInfo.sentAt = null;
3040
3060
  cbRef.current.onTurnTimeout?.({ turnId: turnInfo.id });
3041
3061
  }
3042
3062
  setClockTick((value) => value + 1);
@@ -3065,6 +3085,7 @@ function useRealtimeSession(input) {
3065
3085
  }, [phaseKind, lifecycle.phase, lifecycle.attempt]);
3066
3086
  useEffect(() => {
3067
3087
  if (phaseKind === "ended") {
3088
+ lastTurnRef.current = null;
3068
3089
  if (!endedFiredRef.current) {
3069
3090
  endedFiredRef.current = true;
3070
3091
  const inner2 = lifecycle.phase.kind === "ended" ? lifecycle.phase.reason : void 0;
@@ -3101,23 +3122,47 @@ function useRealtimeSession(input) {
3101
3122
  setGraceWindow({ kind: "closed" });
3102
3123
  return { ok: true };
3103
3124
  }, []);
3104
- const sendTurn = useCallback(async (text, opts) => {
3125
+ const sendResolvedTurn = useCallback(async (text, attributes, retryOfTurnId) => {
3105
3126
  const trimmed = text.trim();
3106
3127
  const sender = turnSenderRef.current;
3107
3128
  if (!sender || !trimmed) return;
3108
3129
  const turnId = newTurnId();
3109
- lastTurnRef.current = { text: trimmed, opts, id: turnId, sentAt: Date.now() };
3130
+ const outgoing = { ...attributes, [RTA_TURN_ID_ATTR]: turnId };
3131
+ if (retryOfTurnId) outgoing[RTA_RETRY_OF_TURN_ID_ATTR] = retryOfTurnId;
3132
+ lastTurnRef.current = { text: trimmed, attributes: { ...outgoing }, id: turnId, sentAt: Date.now() };
3110
3133
  lifecycle.markActivity();
3111
- const attributes = opts?.instructions ? { [RTA_TURN_INSTRUCTIONS_ATTR]: opts.instructions } : void 0;
3112
- await sender(trimmed, attributes ? { attributes } : void 0);
3134
+ await sender(trimmed, { attributes: outgoing });
3113
3135
  }, [lifecycle, newTurnId]);
3136
+ const sendInput = useCallback(async (text, opts, adapterSource) => {
3137
+ const turnSource = opts?.inputSource;
3138
+ validateInputSource(turnSource);
3139
+ const declaredSource = turnSource ?? adapterSource;
3140
+ const attributes = {
3141
+ [RTA_INPUT_SOURCE_VERSION_ATTR]: "1",
3142
+ [RTA_OBSERVED_INPUT_SOURCE_ATTR]: "text"
3143
+ };
3144
+ if (declaredSource !== void 0) {
3145
+ attributes[RTA_DECLARED_INPUT_SOURCE_ATTR] = declaredSource;
3146
+ attributes[RTA_INPUT_SOURCE_DECLARATION_SCOPE_ATTR] = turnSource !== void 0 ? "turn" : "adapter";
3147
+ }
3148
+ if (opts?.instructions) attributes[RTA_TURN_INSTRUCTIONS_ATTR] = opts.instructions;
3149
+ await sendResolvedTurn(text, attributes);
3150
+ }, [sendResolvedTurn]);
3151
+ const sendTurn = useCallback((text, opts) => sendInput(text, opts), [sendInput]);
3152
+ const createTranscriptSender = useCallback((opts) => {
3153
+ const source = opts?.inputSource;
3154
+ validateInputSource(source);
3155
+ const boundSource = source ?? "client_stt";
3156
+ return (text, turnOpts) => sendInput(text, turnOpts, boundSource);
3157
+ }, [sendInput]);
3114
3158
  const retryTurn = useCallback(() => {
3115
3159
  const last = lastTurnRef.current;
3116
3160
  if (!last) return;
3117
- void sendTurn(last.text, last.opts);
3118
- }, [sendTurn]);
3161
+ void sendResolvedTurn(last.text, last.attributes, last.id).catch(() => void 0);
3162
+ }, [sendResolvedTurn]);
3119
3163
  const end = useCallback((reason) => {
3120
3164
  if (reason) lastLabeledEndReasonRef.current = reason;
3165
+ lastTurnRef.current = null;
3121
3166
  requestGracefulClose();
3122
3167
  lifecycle.reset();
3123
3168
  }, [lifecycle, requestGracefulClose]);
@@ -3184,6 +3229,7 @@ function useRealtimeSession(input) {
3184
3229
  requestGracefulClose,
3185
3230
  extend,
3186
3231
  sendTurn,
3232
+ createTranscriptSender,
3187
3233
  retryTurn,
3188
3234
  end,
3189
3235
  behavior,
@@ -3206,6 +3252,7 @@ function useRealtimeSession(input) {
3206
3252
  requestGracefulClose,
3207
3253
  extend,
3208
3254
  sendTurn,
3255
+ createTranscriptSender,
3209
3256
  retryTurn,
3210
3257
  end,
3211
3258
  behavior,