realtime-avatar 0.3.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.
@@ -0,0 +1,831 @@
1
+ interface components {
2
+ schemas: {
3
+ LiveKitSessionRequest: {
4
+ /** @default maria */
5
+ avatar_id: string;
6
+ /** @default plain_white */
7
+ background_id: string;
8
+ /** @default primary */
9
+ capacity_pool: string;
10
+ /**
11
+ * @default avatar
12
+ * @enum {string}
13
+ */
14
+ mode: "avatar" | "voice";
15
+ /** @default true */
16
+ create_room: boolean;
17
+ /** @default true */
18
+ dispatch_agent: boolean;
19
+ instructions?: string;
20
+ /** @default [] */
21
+ initial_context: {
22
+ /** @enum {string} */
23
+ role: "system" | "user" | "assistant";
24
+ content: string;
25
+ }[];
26
+ initial_say?: string;
27
+ llm?: {
28
+ /** @enum {string} */
29
+ backend?: "local" | "gemini" | "openai";
30
+ model?: string | null;
31
+ } | null;
32
+ max_session_seconds?: number;
33
+ participant_identity?: string;
34
+ participant_name?: string;
35
+ character_name?: string;
36
+ queue_ticket_id?: string;
37
+ room_name?: string;
38
+ /**
39
+ * @description The realtime source. Creation is image-only — every avatar grows its own PLATFORM-GENERATED looping source video from the uploaded portrait — so this is always "source_video".
40
+ * @default source_video
41
+ * @enum {string}
42
+ */
43
+ source_kind: "source_video";
44
+ /** @description Normally omitted: the avatar's platform-generated idle loop is resolved server-side. Overriding it with a custom video URL is a legacy channel for existing integrations — custom video is not part of the public contract. */
45
+ source_video_url?: string | null;
46
+ /**
47
+ * @default server
48
+ * @enum {string}
49
+ */
50
+ stt_mode: "server" | "off";
51
+ video_cache_id?: string | null;
52
+ voice?: ({
53
+ /** @constant */
54
+ provider: "cartesia";
55
+ /**
56
+ * @default cartesia/sonic-3
57
+ * @enum {string}
58
+ */
59
+ model: "cartesia/sonic-2" | "cartesia/sonic-2-latest" | "cartesia/sonic-3" | "cartesia/sonic-3-latest" | "cartesia/sonic-turbo" | "cartesia/sonic-turbo-latest";
60
+ voice_id: string;
61
+ speed?: number | null;
62
+ emotion?: string | null;
63
+ language?: string | null;
64
+ } | {
65
+ /** @constant */
66
+ provider: "breezeblue";
67
+ /** @default bluebell-v1-en */
68
+ model: string;
69
+ voice_id: string;
70
+ guidance_scale?: number | null;
71
+ instructions?: string | null;
72
+ language?: string | null;
73
+ } | {
74
+ /** @constant */
75
+ provider: "fish";
76
+ /**
77
+ * @default speech-1.6
78
+ * @enum {string}
79
+ */
80
+ model: "speech-1.6" | "s1" | "s2-pro" | "speech-1.5" | "s1-mini";
81
+ voice_id: string;
82
+ speed?: number | null;
83
+ emotion?: string | null;
84
+ language?: string | null;
85
+ }) | null;
86
+ voice_id?: string | null;
87
+ /** @description The session's motion-state map. Normally omitted: the avatar's PLATFORM-GENERATED library (idle / listen / gesture clips rendered from the portrait at creation) is injected automatically. An explicit [] opts the call out of clips. Supplying custom clip URLs is a legacy channel for existing integrations — custom video is not part of the public contract. */
88
+ clip_library?: {
89
+ clip_id: string;
90
+ /**
91
+ * Format: uri
92
+ * @description The clip's video URL — for a platform-generated library this is the generated clip's hosted URL, filled in server-side at hydration.
93
+ */
94
+ source_video_url?: string;
95
+ video_cache_id?: string;
96
+ max_seconds?: number;
97
+ /** @enum {string} */
98
+ trigger?: "idle" | "listen" | "think" | "directive";
99
+ loop?: boolean;
100
+ weight?: number;
101
+ crossfade_ms?: number;
102
+ trim_start_ms?: number;
103
+ trim_end_ms?: number;
104
+ when?: string;
105
+ hint?: string;
106
+ }[];
107
+ choreography?: {
108
+ idle_dwell_min_seconds?: number;
109
+ idle_dwell_max_seconds?: number;
110
+ special_weight?: number;
111
+ start_grace_seconds?: number;
112
+ crossfade_ms?: number;
113
+ /** @enum {string} */
114
+ crossfade_easing?: "linear" | "smooth" | "ease_out";
115
+ wrap_crossfade_ms?: number;
116
+ };
117
+ scene_graph?: {
118
+ scenes: {
119
+ scene_id: string;
120
+ hub_clip_id: string;
121
+ clips: {
122
+ clip_id: string;
123
+ /**
124
+ * Format: uri
125
+ * @description The clip's video URL — for a platform-generated library this is the generated clip's hosted URL, filled in server-side at hydration.
126
+ */
127
+ source_video_url?: string;
128
+ video_cache_id?: string;
129
+ max_seconds?: number;
130
+ /** @enum {string} */
131
+ trigger?: "idle" | "listen" | "think" | "directive";
132
+ loop?: boolean;
133
+ weight?: number;
134
+ crossfade_ms?: number;
135
+ trim_start_ms?: number;
136
+ trim_end_ms?: number;
137
+ when?: string;
138
+ hint?: string;
139
+ }[];
140
+ }[];
141
+ transitions: {
142
+ clip_id: string;
143
+ /** Format: uri */
144
+ source_video_url: string;
145
+ from_scene: string;
146
+ to_scene: string;
147
+ max_seconds?: number;
148
+ }[];
149
+ };
150
+ behavior?: {
151
+ gestures_enabled?: boolean;
152
+ /** @enum {string} */
153
+ gesture_freq?: "sparse" | "balanced" | "lively";
154
+ };
155
+ expression_profile?: string;
156
+ /** @enum {string} */
157
+ render_backend?: "warp" | "generative";
158
+ support_edits?: {
159
+ instruction: string;
160
+ /** Format: uri */
161
+ reference_url?: string;
162
+ /** @description EXPERIMENTAL ("runtime edit") — let the look follow the conversation mid-call. Less settled than the fixed `instruction` lane: the shape may still change, and the re-dress does not reach every capacity tier yet, so a live session is accepted and runs normally but may hold its opening set for the whole call. Absent means `instruction` is fixed for the session, which is the incumbent behavior. */
163
+ live_edit?: {
164
+ rules: string;
165
+ cooldown_seconds?: number;
166
+ /** @enum {string} */
167
+ renderer?: "editor" | "generative";
168
+ };
169
+ };
170
+ transcript_webhook?: {
171
+ /** Format: uri */
172
+ url: string;
173
+ secret: string;
174
+ };
175
+ client_metadata?: {
176
+ [key: string]: string;
177
+ };
178
+ };
179
+ LiveKitSessionGrant: {
180
+ /**
181
+ * @default ready
182
+ * @constant
183
+ */
184
+ status: "ready";
185
+ session_id: string;
186
+ room_name: string;
187
+ livekit_url: string;
188
+ participant_token: string;
189
+ participant_identity: string;
190
+ agent_name: string;
191
+ capacity_pool: string;
192
+ /** Format: date-time */
193
+ reservation_expires_at: string;
194
+ max_sessions_per_gpu: number;
195
+ /**
196
+ * @default server
197
+ * @enum {string}
198
+ */
199
+ stt_mode: "server" | "off";
200
+ /** @default false */
201
+ room_created: boolean;
202
+ /** @default false */
203
+ dispatch_created: boolean;
204
+ /** @default 0 */
205
+ join_timeout_seconds: number;
206
+ /** @default 0 */
207
+ idle_timeout_seconds: number;
208
+ /** @default 0 */
209
+ max_session_seconds: number;
210
+ };
211
+ LiveKitCapacitySnapshot: {
212
+ capacity_pool: string;
213
+ agent_name: string;
214
+ max_sessions: number;
215
+ max_sessions_per_gpu: number;
216
+ worker_count: number;
217
+ active_sessions: number;
218
+ reserved_sessions: number;
219
+ observed_worker_active_sessions: number;
220
+ available_sessions: number;
221
+ queue_size: number;
222
+ admission_open: boolean;
223
+ recommended_retry_ms: number;
224
+ load: number;
225
+ };
226
+ CapacityBusyResponse: {
227
+ message: string;
228
+ capacity: {
229
+ capacity_pool: string;
230
+ agent_name: string;
231
+ max_sessions: number;
232
+ max_sessions_per_gpu: number;
233
+ worker_count: number;
234
+ active_sessions: number;
235
+ reserved_sessions: number;
236
+ observed_worker_active_sessions: number;
237
+ available_sessions: number;
238
+ queue_size: number;
239
+ admission_open: boolean;
240
+ recommended_retry_ms: number;
241
+ load: number;
242
+ };
243
+ queue_size: number;
244
+ queue_ticket_id?: string;
245
+ queue_position?: number;
246
+ recommended_retry_ms: number;
247
+ };
248
+ LiveKitSessionReleaseRequest: {
249
+ session_id?: string;
250
+ queue_ticket_id?: string;
251
+ /** @enum {string} */
252
+ reason?: "page_hide" | "disconnected" | "superseded" | "unmount" | "manual" | "idle_timeout";
253
+ capacity_pool?: string;
254
+ };
255
+ Avatar: {
256
+ id: string;
257
+ tenantId: string;
258
+ displayName: string;
259
+ /** @enum {string} */
260
+ sourceKind: "image" | "video";
261
+ /** @enum {string} */
262
+ modelId: "realtime-avatar-live-v1" | "realtime-avatar-video-v1";
263
+ sourceAssetId: string | null;
264
+ /** @enum {string} */
265
+ status: "draft" | "preprocessing" | "ready" | "failed" | "disabled" | "deleted";
266
+ error: string | null;
267
+ /** @enum {string} */
268
+ idleVideoStatus: "none" | "queued" | "generating" | "ready" | "failed";
269
+ defaultVoiceId: string | null;
270
+ llm: {
271
+ /** @enum {string} */
272
+ provider: "local" | "gemini" | "openai";
273
+ model?: string | null;
274
+ } | null;
275
+ createdAt: string;
276
+ updatedAt: string;
277
+ };
278
+ CreateAvatarRequest: {
279
+ displayName: string;
280
+ /**
281
+ * @default image
282
+ * @enum {string}
283
+ */
284
+ sourceKind: "image" | "video";
285
+ /** @enum {string} */
286
+ modelId?: "realtime-avatar-live-v1" | "realtime-avatar-video-v1";
287
+ sourceAssetId?: string;
288
+ motionPrompt?: string;
289
+ defaultVoiceId?: string;
290
+ voice?: {
291
+ auto_description?: string;
292
+ voice?: {
293
+ /** @enum {string} */
294
+ provider: "cartesia" | "fish";
295
+ voice_id: string;
296
+ model?: string;
297
+ speed?: number | null;
298
+ emotion?: string | null;
299
+ language?: string | null;
300
+ };
301
+ };
302
+ llm?: {
303
+ /** @enum {string} */
304
+ provider: "local" | "gemini" | "openai";
305
+ model?: string | null;
306
+ };
307
+ /** @default {} */
308
+ settings: {
309
+ [key: string]: unknown;
310
+ };
311
+ /** @default {} */
312
+ metadata: {
313
+ [key: string]: unknown;
314
+ };
315
+ };
316
+ UpdateAvatarRequest: {
317
+ displayName?: string;
318
+ defaultVoiceId?: string | null;
319
+ llm?: {
320
+ /** @enum {string} */
321
+ provider: "local" | "gemini" | "openai";
322
+ model?: string | null;
323
+ } | null;
324
+ /** @enum {string} */
325
+ llmProvider?: "local" | "gemini" | "openai";
326
+ llmModel?: string | null;
327
+ settings?: {
328
+ [key: string]: unknown;
329
+ };
330
+ metadata?: {
331
+ [key: string]: unknown;
332
+ };
333
+ persona?: {
334
+ name?: string;
335
+ /** @default */
336
+ personality: string;
337
+ /** @default */
338
+ background: string;
339
+ /** @default short, warm, specific, and spoken */
340
+ replyStyle: string;
341
+ };
342
+ artDirection?: string;
343
+ /** @enum {string} */
344
+ stylePreset?: "cinematic-founder" | "editorial-companion" | "warm-anime" | "luxury-realism" | "soft-3d" | "noir-avatar";
345
+ /** Format: uri */
346
+ portraitUrl?: string;
347
+ };
348
+ Asset: {
349
+ id: string;
350
+ tenantId: string;
351
+ /** @enum {string} */
352
+ kind: "image" | "video" | "audio";
353
+ /** @enum {string} */
354
+ status: "pending_upload" | "uploaded" | "processing" | "ready" | "failed" | "deleted";
355
+ contentType: string;
356
+ sizeBytes: number;
357
+ sha256: string | null;
358
+ publicUrl: string | null;
359
+ createdAt: string;
360
+ updatedAt: string;
361
+ };
362
+ CreateRemoteAssetRequest: {
363
+ /** @enum {string} */
364
+ kind: "image" | "video" | "audio";
365
+ /** Format: uri */
366
+ remoteUrl: string;
367
+ originalFilename?: string;
368
+ /** @default {} */
369
+ metadata: {
370
+ [key: string]: unknown;
371
+ };
372
+ };
373
+ CreditBalance: {
374
+ tenantId: string;
375
+ balanceCreditMicros: number;
376
+ reservedCreditMicros: number;
377
+ availableCreditMicros: number;
378
+ lifetimeGrantedCreditMicros: number;
379
+ lifetimeUsedCreditMicros: number;
380
+ updatedAt: string;
381
+ };
382
+ ListUsageSessionsResponse: {
383
+ data: {
384
+ sessionId: string;
385
+ avatarId: string | null;
386
+ /** @enum {string} */
387
+ status: "reserved" | "started" | "released" | "failed";
388
+ startedAt: string | null;
389
+ endedAt: string | null;
390
+ activeSeconds: number | null;
391
+ billedCreditMicros: number | null;
392
+ /** @default {} */
393
+ metadata: {
394
+ [key: string]: unknown;
395
+ };
396
+ createdAt: string;
397
+ }[];
398
+ nextCursor: string | null;
399
+ /** Format: date-time */
400
+ from: string;
401
+ /** Format: date-time */
402
+ to: string;
403
+ };
404
+ ListAvatarClipsResponse: {
405
+ data: {
406
+ clipId: string;
407
+ /** @enum {string} */
408
+ role: "idle" | "listen" | "gesture";
409
+ /** @enum {string} */
410
+ status: "queued" | "generating" | "ready" | "failed";
411
+ url: string | null;
412
+ whenHint: string | null;
413
+ createdAt: string;
414
+ updatedAt: string;
415
+ }[];
416
+ };
417
+ SyncAvatarClipsRequest: {
418
+ clipUrls: string[];
419
+ };
420
+ SyncAvatarClipsResponse: {
421
+ queued: string[];
422
+ ready: string[];
423
+ retired: string[];
424
+ };
425
+ OkResponse: {
426
+ ok: boolean;
427
+ };
428
+ HealthResponse: {
429
+ /** @enum {string} */
430
+ status: "healthy";
431
+ };
432
+ };
433
+ responses: never;
434
+ parameters: never;
435
+ requestBodies: never;
436
+ headers: never;
437
+ pathItems: never;
438
+ }
439
+
440
+ /**
441
+ * The public shapes. Everything an integrator touches is here, in one file, on purpose:
442
+ * an agent reading this repo should be able to learn the whole surface without following
443
+ * imports.
444
+ *
445
+ * Naming is camelCase throughout. The HTTP wire is snake_case and strict — that translation
446
+ * happens once, inside `client.ts`, and nowhere else.
447
+ *
448
+ * ── WHERE A TYPE COMES FROM ────────────────────────────────────────────────────────────
449
+ *
450
+ * zod schemas on the platform (a different repo — the origin, not editable here)
451
+ * -> https://realtimeavatar.ai/openapi.json the published contract
452
+ * -> spec/realtime-avatar.openapi.json vendored, so a build needs no network
453
+ * -> src/generated/openapi.ts `npm run spec` regenerates; never hand-edit
454
+ * -> THIS FILE curated, camelCased, derived
455
+ *
456
+ * `npm run spec:check` reports drift between the vendored copy and the live one, and exits 0
457
+ * offline so a fork or an air-gapped runner does not go red for it.
458
+ *
459
+ * THREE types here are NOT derived, and each has a reason rather than an oversight:
460
+ *
461
+ * ListSessionsOptions the contract declares NO query parameters on GET /v1/usage/sessions.
462
+ * The route reads them; the spec does not describe them. Fixing that
463
+ * belongs upstream in the spec export, not here.
464
+ * TranscriptPayload the transcript webhook body is not in the published contract at all.
465
+ * Same upstream gap, bigger: nothing describes this shape publicly.
466
+ * VideoPolicy and its deliberately NOT one-to-one with the wire. `{ loop, states, edits }`
467
+ * VideoState/VideoEdits is an SDK-designed surface over `clip_library`, `support_edits` and
468
+ * `render_backend`. Deriving it would leak three wire concepts into one
469
+ * product decision and make the ergonomics hostage to the transport.
470
+ *
471
+ * Everything else indexes into the contract, so `npm run check` is what catches a divergence.
472
+ * Verified by mutation: renaming `participant_token` in the generated file fails the typecheck.
473
+ */
474
+
475
+ /**
476
+ * The wire shapes, from the published contract at https://realtimeavatar.ai/openapi.json.
477
+ *
478
+ * The public types below are DERIVED from these rather than declared beside them, so a field
479
+ * whose type changes upstream changes here too, and a field that disappears fails to compile
480
+ * instead of going quietly `undefined` at runtime.
481
+ *
482
+ * What stays hand-written is the CURATION — which fields surface and what they are called.
483
+ * That is a product decision the spec cannot make: the platform's `Avatar` carries a tenant
484
+ * id, a model id and an idle-video status that an integrator has no use for, and `publicUrl`
485
+ * reads better as `url` at the call site. `Pick` keeps that choice explicit and makes it fail
486
+ * loudly if the field it names ever goes away.
487
+ */
488
+ type Wire = components["schemas"];
489
+ /**
490
+ * Live video and audio (default), or audio only.
491
+ *
492
+ * `mode` picks the RENDERER, not the turn-taking. Both modes run the same full-duplex
493
+ * loop — she listens the entire time she is speaking, and she stops when you cut in.
494
+ * `voice` skips rendering video entirely (cheaper, no video track); `avatar` publishes it.
495
+ *
496
+ * There is deliberately no `duplex` option. An earlier version of this SDK had one, and
497
+ * `{ mode: "avatar", duplex: "full" }` silently rewrote `mode` to `"voice"` — so asking for
498
+ * full duplex cost you the video track. Both modes are full duplex now, so that trade-off
499
+ * is gone. Interruption is not a mode you select — it is how calls work.
500
+ */
501
+ type CallMode = NonNullable<Wire["LiveKitSessionRequest"]["mode"]>;
502
+ /**
503
+ * A prior message replayed as memory when the call opens.
504
+ *
505
+ * The contract declares this shape inline inside the request rather than as a named schema, so
506
+ * it is indexed out of the array. At most 32, and `content` is capped at 4,000 characters.
507
+ */
508
+ type ContextMessage = NonNullable<Wire["LiveKitSessionRequest"]["initial_context"]>[number];
509
+ /**
510
+ * DERIVATION: hand-written, and deliberately NOT one-to-one with the wire.
511
+ *
512
+ * The contract carries `clip_library`, `support_edits` and `render_backend` as three separate
513
+ * concerns. `video` is one product decision layered over all three, so deriving it would leak
514
+ * the transport's shape into the ergonomics and make this API hostage to how those three
515
+ * happen to be spelled upstream.
516
+ */
517
+ /** One named state the character can rest in, and when she should be in it. */
518
+ interface VideoState {
519
+ /**
520
+ * A plain sentence — "when the user is happy". Read by the CHARACTER, not by a rules
521
+ * engine, so write it the way you would brief an actor. `sentiment > 0.7` does nothing.
522
+ */
523
+ when: string;
524
+ /** A closed-loop clip: first frame and last frame on the same rest pose. */
525
+ url: string;
526
+ /** Relative likelihood against sibling states. Default 1. */
527
+ weight?: number;
528
+ }
529
+ /** DERIVATION: hand-written. Part of the `video` surface — see {@link VideoState}. */
530
+ /**
531
+ * Rewrite the clip instead of replaying it as shot: her loop, streamed through a video
532
+ * editing model under a prose instruction, then lip-synced by the same path as an unedited
533
+ * one. An OPTION on `looping`, not a third mode — the character, the voice, the lip-sync
534
+ * and the response latency are all a plain looping call. Only the pixels behind her change.
535
+ */
536
+ interface VideoEdits {
537
+ /**
538
+ * What to make it look like **when the call opens** — "turn the room into a snowy cabin
539
+ * at night", "give her a red wool scarf". Plain prose, read by a video editing model,
540
+ * NOT by the character: it changes the picture, never her behavior. Behavior goes in
541
+ * `instructions`. 1–1000 chars.
542
+ */
543
+ instruction: string;
544
+ /** Reference image for reference-guided editing ("put THIS hat on her"). Accepted; not yet honored. */
545
+ referenceUrl?: string;
546
+ /**
547
+ * Let the clip be **re-edited during the call**, following the conversation. Omit it and
548
+ * `instruction` is the look for the whole session — the object's PRESENCE is the switch;
549
+ * there is no `enabled` flag to drift from it.
550
+ */
551
+ live?: {
552
+ /**
553
+ * What may be re-edited, when, and what must not — plain prose, read by a model.
554
+ * 1–2000 chars.
555
+ *
556
+ * Three things worth being deliberate about:
557
+ *
558
+ * 1. **These are YOUR rules, not your user's.** They are the policy about what an end
559
+ * user may do to your character's appearance, so they belong on your server next to
560
+ * `instructions` — never accepted from a request body.
561
+ * 2. **Say what must NOT change.** The negative clause is the one that does the work:
562
+ * "only the room — never her face, hair or clothes".
563
+ * 3. **Passthrough is a legitimate brief.** "Re-edit the set to whatever the user
564
+ * describes" is a complete rule — say so explicitly if that is what you want.
565
+ */
566
+ rules: string;
567
+ /**
568
+ * Seconds a look must hold before the next edit may run, 5–600. Absent ⇒ the server
569
+ * default (30). A floor, not a preference: every edit re-processes the clip and lands
570
+ * as a visible cut.
571
+ */
572
+ cooldownSeconds?: number;
573
+ /**
574
+ * Which machinery runs the re-edit. Omit it and the server picks its default
575
+ * (`"editor"`). A deploy that cannot provide the requested renderer serves the editor
576
+ * lane and says so in its logs — the call still connects.
577
+ */
578
+ renderer?: "editor" | "generative";
579
+ };
580
+ }
581
+ /** DERIVATION: hand-written. The `video` surface itself — see {@link VideoState}. */
582
+ /**
583
+ * How the character is rendered.
584
+ *
585
+ * A union rather than optional fields: clips on a generative call is a contradiction, and
586
+ * this makes it unrepresentable instead of silently ignored. `edits` lives on the looping
587
+ * arm BY CONSTRUCTION — edits need a clip to edit, and a generative session has none.
588
+ */
589
+ type VideoPolicy = {
590
+ mode?: "looping";
591
+ /**
592
+ * Named states we compile into a state machine and switch between.
593
+ *
594
+ * The clip she RESTS in is not here, because it is not the call's to choose: a call
595
+ * identifies the character, and the character's stored source video is what she rests
596
+ * in. Upload it to the avatar once instead of supplying a URL per call — a call that
597
+ * carries its own media is rejected outright.
598
+ */
599
+ states?: Record<string, VideoState>;
600
+ /** Rewrite the clip under a prose instruction — one clip, many worlds. */
601
+ edits?: VideoEdits;
602
+ } | {
603
+ mode: "generative";
604
+ };
605
+ /** What YOUR SERVER decides about a call. Never accept any of this from a browser. */
606
+ interface CallPolicy {
607
+ /** Her behavior contract — who she is and how she speaks. Max 4000 chars. */
608
+ instructions?: string;
609
+ /** Up to 32 prior messages, replayed as memory. */
610
+ context?: readonly ContextMessage[];
611
+ /** Hard stop in seconds, max 1800. Compute it from the balance you just admitted. */
612
+ maxSeconds?: number;
613
+ /** Speech recognition. `server` for a spoken conversation, `off` if you drive turns. */
614
+ listen?: boolean;
615
+ /** How she is rendered. */
616
+ video?: VideoPolicy;
617
+ /** Voice override for this call; omit to use the avatar's default. */
618
+ voice?: unknown;
619
+ /**
620
+ * Let the browser register tools the model may call.
621
+ *
622
+ * This is a CAPABILITY, granted at mint time by your server — it is the gate, and the only
623
+ * off switch. Without it the worker never exposes the registration method, so no page code
624
+ * is reachable from the model at all. Never grant it from a request body.
625
+ *
626
+ * Note the manifest does NOT ride this request: it is registered over RPC after the client
627
+ * connects. Putting tools on the mint returns 422 (the request schema is strict).
628
+ */
629
+ clientTools?: boolean;
630
+ /** Receive the two-sided transcript, signed, after the call ends. */
631
+ transcript?: {
632
+ url: string;
633
+ secret: string;
634
+ };
635
+ /** Up to 16 string pairs, echoed verbatim on that transcript. */
636
+ metadata?: Record<string, string>;
637
+ }
638
+ /**
639
+ * What a client needs to join. Treat it as OPAQUE and relay it byte-for-byte — the browser
640
+ * SDK validates it strictly, and adding or wrapping a key throws.
641
+ */
642
+ type Grant = Wire["LiveKitSessionGrant"];
643
+ interface CallConnection {
644
+ status: Grant["status"];
645
+ sessionId: Grant["session_id"];
646
+ roomName: Grant["room_name"];
647
+ /** Hand these two to `livekit-client` if you are not using the browser SDK. */
648
+ livekitUrl: Grant["livekit_url"];
649
+ participantToken: Grant["participant_token"];
650
+ participantIdentity: Grant["participant_identity"];
651
+ maxSessionSeconds: Grant["max_session_seconds"];
652
+ idleTimeoutSeconds: Grant["idle_timeout_seconds"];
653
+ /** Join before this or the slot returns to the pool. */
654
+ reservationExpiresAt: Grant["reservation_expires_at"];
655
+ /**
656
+ * The untouched server payload. Relay THIS, not the parsed object above.
657
+ *
658
+ * Deliberately NOT derived. It is the whole grant including fields this SDK does not surface,
659
+ * and typing it as the contract's grant would invite reading it field-by-field — which is the
660
+ * habit rule 2 exists to stop. It is opaque on purpose.
661
+ */
662
+ raw: Record<string, unknown>;
663
+ }
664
+ /**
665
+ * Every slot is busy. Not an error — hold and retry.
666
+ *
667
+ * The field NAMES are this SDK's; the types come from the contract, so a widening upstream
668
+ * cannot silently disagree with what a caller branches on.
669
+ */
670
+ interface CallQueued {
671
+ /** Invented here, not on the wire — it is what makes {@link isQueued} a one-word check. */
672
+ queued: true;
673
+ /**
674
+ * `queue_position` is optional in the contract. `client.ts` coerces an absent one to `null`
675
+ * rather than `undefined`, so this widens deliberately: a caller renders "position unknown"
676
+ * instead of crashing on a queue that has not placed them yet.
677
+ */
678
+ position: NonNullable<Wire["CapacityBusyResponse"]["queue_position"]> | null;
679
+ size: Wire["CapacityBusyResponse"]["queue_size"];
680
+ retryAfterMs: Wire["CapacityBusyResponse"]["recommended_retry_ms"];
681
+ /**
682
+ * The queue's own handle on this attempt. A queued call holds no session id yet, so this is
683
+ * the ONLY thing that can release the place in line — without it a user who closes the tab
684
+ * while waiting keeps their slot until it times out. `null` when the platform did not issue
685
+ * one, which is why it is read rather than assumed.
686
+ */
687
+ queueTicketId: NonNullable<Wire["CapacityBusyResponse"]["queue_ticket_id"]> | null;
688
+ }
689
+ /**
690
+ * DERIVATION: transitive. A union of {@link CallConnection} and {@link CallQueued}, both of
691
+ * which index the contract — so this follows it without naming it.
692
+ */
693
+ type StartCallResult = CallConnection | CallQueued;
694
+ declare function isQueued(result: StartCallResult): result is CallQueued;
695
+ /**
696
+ * Why a call ended. Diagnostic — it shows on the session record, and every reason frees the
697
+ * slot identically. `page_hide` is the one a tab-close beacon sends; `manual` is an explicit
698
+ * server-side decision.
699
+ */
700
+ type EndCallReason = NonNullable<Wire["LiveKitSessionReleaseRequest"]["reason"]>;
701
+ interface EndCallOptions {
702
+ reason?: EndCallReason;
703
+ /**
704
+ * The grant's `capacity_pool` (`call.raw.capacity_pool`), naming where the slot is held so
705
+ * the release cannot miss it. Optional — without it the platform frees against its default
706
+ * placement, which is where calls land today — but if the grant is in hand, pass it.
707
+ */
708
+ capacityPool?: Wire["LiveKitSessionReleaseRequest"]["capacity_pool"];
709
+ }
710
+ /** The kinds the contract accepts. Derived, so adding one upstream is not a second edit here. */
711
+ type AssetKind = Wire["Asset"]["kind"];
712
+ type Asset = Pick<Wire["Asset"], "id" | "kind"> & {
713
+ /**
714
+ * The contract declares these REQUIRED and non-nullable. This half of the SDK does no
715
+ * runtime validation, and client.ts coerces a missing or wrong-typed value to `null` rather
716
+ * than handing back `undefined` — so the type says `| null` deliberately, widening what the
717
+ * contract promises rather than deriving it unchanged. A proxy that drops a field, or a
718
+ * server a version behind, is the case that coercion exists for.
719
+ */
720
+ contentType: Wire["Asset"]["contentType"] | null;
721
+ sizeBytes: Wire["Asset"]["sizeBytes"] | null;
722
+ /**
723
+ * Public, unguessable, range-capable. Feed straight into a state url.
724
+ *
725
+ * The contract calls this `publicUrl`; it is surfaced as `url` because that is what it is
726
+ * used for. Black-box testing caught the mapper reading the wrong name and handing back
727
+ * `undefined` — hence the regression test. The RENAME is the ergonomics; the type comes
728
+ * from the contract, so the two cannot drift apart.
729
+ */
730
+ url: Wire["Asset"]["publicUrl"];
731
+ /**
732
+ * Open on purpose: this is a response field, and a reader that treats a status it has
733
+ * never heard of as an error breaks the next time one is added. `(string & {})` rather
734
+ * than a bare `string`, which would absorb the literals and lose the autocomplete — so
735
+ * this widens what the contract declares rather than deriving it unchanged.
736
+ */
737
+ status: Wire["Asset"]["status"] | (string & {});
738
+ };
739
+ type Avatar = Pick<Wire["Avatar"], "id" | "displayName" | "sourceKind" | "status" | "defaultVoiceId">;
740
+ /**
741
+ * The patch `updateAvatar` sends — the two fields an integrator re-points after creation.
742
+ * The contract's `UpdateAvatarRequest` carries more (llm, persona, art direction…); that is
743
+ * dashboard machinery this surface deliberately does not model.
744
+ */
745
+ type AvatarUpdate = Pick<Wire["UpdateAvatarRequest"], "displayName" | "defaultVoiceId">;
746
+ type UsageSessionsResponse = Wire["ListUsageSessionsResponse"];
747
+ /**
748
+ * One billable session — when it ran, how long it was billable for, what it cost.
749
+ *
750
+ * `activeSeconds` is billable wall time in SECONDS and is null until the session settles;
751
+ * `metadata` is whatever you passed to `startCall`, `{}` if you passed nothing. The contract
752
+ * declares this shape inline inside the page rather than as a named schema, so it is indexed
753
+ * out of the array rather than picked off a `Wire[...]` key.
754
+ */
755
+ type UsageSession = UsageSessionsResponse["data"][number];
756
+ interface UsageSessionPage {
757
+ /** The contract calls this `data`; it is surfaced under the name of what it holds. */
758
+ sessions: UsageSession[];
759
+ /** Pass as `cursor` for the next page. Null on the last one. */
760
+ nextCursor: UsageSessionsResponse["nextCursor"];
761
+ /** The window actually served — the platform clamps wide or inverted ranges. */
762
+ from: UsageSessionsResponse["from"];
763
+ to: UsageSessionsResponse["to"];
764
+ }
765
+ /**
766
+ * DERIVATION: hand-written, because the published contract declares no query parameters on
767
+ * `GET /v1/usage/sessions` — the route reads them, the spec does not describe them. This is
768
+ * the one place in this file where a shape is asserted rather than taken from the contract,
769
+ * and the fix belongs upstream in the spec export, not here.
770
+ */
771
+ interface ListSessionsOptions {
772
+ /** ISO timestamps. Defaults to the trailing 30 days; 90 days is the widest served. */
773
+ from?: string;
774
+ to?: string;
775
+ /** Only sessions you tagged with this `metadata.user_id`. */
776
+ endUserId?: string;
777
+ /** Page size, capped at 200. */
778
+ limit?: number;
779
+ cursor?: string;
780
+ }
781
+ type CreditBalance = Pick<Wire["CreditBalance"], "balanceCreditMicros" | "reservedCreditMicros">;
782
+ /** Result of reconciling an avatar's clip set to the cache tier. */
783
+ type ClipSyncResult = Wire["SyncAvatarClipsResponse"];
784
+ /**
785
+ * DERIVATION: hand-written, because the contract does not describe it.
786
+ *
787
+ * The transcript webhook body is absent from the published document entirely — not narrowed,
788
+ * not redacted, absent. So there is nothing to derive from, and this is the only shape here
789
+ * whose source is a GAP rather than a decision. Fixing it means describing the webhook body
790
+ * upstream.
791
+ */
792
+ /** The signed payload delivered to `CallPolicy.transcript.url` after a call ends. */
793
+ interface TranscriptPayload {
794
+ type: "session.transcript";
795
+ session_id: string;
796
+ avatar_id: string;
797
+ mode: CallMode;
798
+ started_at: number;
799
+ ended_at: number;
800
+ seconds: number;
801
+ /** True when a very long call exceeded the buffer and the transcript is partial. */
802
+ truncated: boolean;
803
+ segments: Array<{
804
+ role: "user" | "assistant";
805
+ text: string;
806
+ ts: number;
807
+ /** She was cut off — this text is only what she actually said out loud. */
808
+ interrupted?: boolean;
809
+ }>;
810
+ /**
811
+ * The tool calls the model acted on, in order — absent when the session ran none. An
812
+ * entry without `ok` means the call produced nothing the model saw. `arguments` and
813
+ * `result`/`error` are truncated to 2,000 chars each: this is a history, not a replay.
814
+ */
815
+ tool_calls?: Array<{
816
+ name: string;
817
+ call_id: string;
818
+ /** The raw JSON arguments string, exactly as the model sent it. */
819
+ arguments: string;
820
+ ts: number;
821
+ ok?: boolean;
822
+ result?: string;
823
+ error?: string;
824
+ duration_ms?: number;
825
+ }>;
826
+ /** True when the session ran more tool calls than the buffer holds — the tail is missing. */
827
+ tool_calls_truncated?: boolean;
828
+ client_metadata: Record<string, string>;
829
+ }
830
+
831
+ export { type Avatar as A, type CallMode as C, type EndCallOptions as E, type ListSessionsOptions as L, type StartCallResult as S, type TranscriptPayload as T, type UsageSessionPage as U, type VideoPolicy as V, type CallPolicy as a, type AvatarUpdate as b, type ClipSyncResult as c, type AssetKind as d, type Asset as e, type UsageSession as f, type CreditBalance as g, type CallConnection as h, type CallQueued as i, type ContextMessage as j, type EndCallReason as k, type VideoState as l, isQueued as m };