realtime-avatar 0.9.1 → 0.11.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
@@ -96,12 +96,11 @@ rta.retimeAnchor(avatarId, anchorTimeMs) // same loop, differ
96
96
  rta.deleteAvatar(avatarId)
97
97
 
98
98
  // clip library — declared as JSON, never as URLs
99
- rta.setClipLibrary(avatarId, { clips, expectedRevision? }) // declare the FULL set; CAS via expectedRevision
99
+ rta.setClipLibrary(avatarId, { expectedRevision, clips, idle?, on?, actions? }) // full source record + behavior; revision required
100
100
  rta.setLoop(avatarId, { motionPrompt }) // re-direct the RESTING LOOP; clips untouched
101
101
  rta.waitForLoop(avatarId) // block until it settles; THROWS if it failed
102
102
  rta.waitForClips(avatarId) // block until nothing is still rendering
103
103
  rta.listClips(avatarId) // rows + revision, anchor, eligibility
104
- rta.syncClips(avatarId, clipUrls) // DEPRECATED — external-URL tier; use setClipLibrary
105
104
 
106
105
  // assets
107
106
  rta.createRemoteAsset({ kind, remoteUrl })
package/dist/browser.d.ts CHANGED
@@ -1,5 +1,15 @@
1
1
  export { D as DEFAULT_AVATAR_PLAYOUT_DELAY_SECONDS, P as PlayoutDelayTarget, a as applyAvatarPlayoutDelay, b as applyPlayoutDelay } from './playout-delay-DWOc8iCl.js';
2
2
 
3
+ type VideoSupport = {
4
+ supported: true;
5
+ } | {
6
+ supported: false;
7
+ reason: "webrtc_unavailable" | "codec_unavailable";
8
+ message: string;
9
+ };
10
+ /** Check before starting a billed video session; this does not prove negotiated playback. */
11
+ declare function checkVideoSupport(codec?: string): VideoSupport;
12
+
3
13
  /**
4
14
  * Turning the microphone on, and saying what went wrong when it does not.
5
15
  *
@@ -241,4 +251,4 @@ type BoomerangPlayback = {
241
251
  */
242
252
  declare function attachBoomerangPlayback(video: HTMLVideoElement, options?: BoomerangPlaybackOptions): BoomerangPlayback;
243
253
 
244
- export { type AttachRemoteAudioOptions, type AttachableTrack, type AudioCapableRoom, type BoomerangPlayback, type BoomerangPlaybackOptions, type EnableMicrophoneOptions, type MicrophoneCapableRoom, type MicrophoneFailureReason, type MicrophoneResult, type PreparableParticipant, type PreparableRoom, type PrepareAvatarRoomOptions, type PreparedAvatarRoom, type RemoteAudioAttachment, attachBoomerangPlayback, attachRemoteAudio, enableMicrophone, prepareAvatarRoom };
254
+ export { type AttachRemoteAudioOptions, type AttachableTrack, type AudioCapableRoom, type BoomerangPlayback, type BoomerangPlaybackOptions, type EnableMicrophoneOptions, type MicrophoneCapableRoom, type MicrophoneFailureReason, type MicrophoneResult, type PreparableParticipant, type PreparableRoom, type PrepareAvatarRoomOptions, type PreparedAvatarRoom, type RemoteAudioAttachment, type VideoSupport, attachBoomerangPlayback, attachRemoteAudio, checkVideoSupport, enableMicrophone, prepareAvatarRoom };
package/dist/browser.js CHANGED
@@ -1,3 +1,15 @@
1
+ // ../browser/src/video-support.ts
2
+ function checkVideoSupport(codec = "h264") {
3
+ if (typeof RTCRtpReceiver === "undefined") {
4
+ return { supported: false, reason: "webrtc_unavailable", message: "This browser does not support realtime media." };
5
+ }
6
+ const codecs = RTCRtpReceiver.getCapabilities("video")?.codecs ?? [];
7
+ if (!codecs.some((entry) => entry.mimeType.toLowerCase() === `video/${codec.toLowerCase()}`)) {
8
+ return { supported: false, reason: "codec_unavailable", message: `This browser cannot receive ${codec.toUpperCase()} video. Use a browser with that codec before starting a video call.` };
9
+ }
10
+ return { supported: true };
11
+ }
12
+
1
13
  // ../browser/src/microphone.ts
2
14
  var HINTS = {
3
15
  "insecure-origin": "The browser only exposes microphones on a secure origin. Serve the page over https, or open it on http://localhost \u2014 a LAN address like http://192.168.1.5 will not do.",
@@ -241,4 +253,4 @@ function now() {
241
253
  return typeof performance !== "undefined" ? performance.now() : Date.now();
242
254
  }
243
255
 
244
- export { DEFAULT_AVATAR_PLAYOUT_DELAY_SECONDS, applyAvatarPlayoutDelay, applyPlayoutDelay, attachBoomerangPlayback, attachRemoteAudio, enableMicrophone, prepareAvatarRoom };
256
+ export { DEFAULT_AVATAR_PLAYOUT_DELAY_SECONDS, applyAvatarPlayoutDelay, applyPlayoutDelay, attachBoomerangPlayback, attachRemoteAudio, checkVideoSupport, enableMicrophone, prepareAvatarRoom };
@@ -1,3 +1,5 @@
1
+ import { z } from 'zod';
2
+
1
3
  // ../http-client/src/errors.ts
2
4
  var RealtimeAvatarError = class extends Error {
3
5
  constructor(message, options) {
@@ -21,6 +23,179 @@ var RealtimeAvatarHttpError = class extends RealtimeAvatarError {
21
23
  return this.status === 402;
22
24
  }
23
25
  };
26
+ var motionIdSchema = z.string().regex(/^[A-Za-z0-9_-]{1,64}$/).refine((value) => !["__proto__", "constructor", "prototype"].includes(value), "Reserved identifier");
27
+ function motionRecord(valueSchema) {
28
+ const record = z.record(motionIdSchema, valueSchema);
29
+ return z.transform((value, ctx) => {
30
+ if (value !== null && typeof value === "object" && Object.hasOwn(value, "__proto__")) {
31
+ ctx.addIssue({ code: "custom", path: ["__proto__"], message: "Reserved identifier" });
32
+ }
33
+ return value;
34
+ }).pipe(record);
35
+ }
36
+ var clipSourceSchema = z.union([
37
+ z.strictObject({ assetId: z.string().trim().min(1).max(160) }),
38
+ z.strictObject({
39
+ motionPrompt: z.string().trim().min(1).max(1200),
40
+ durationSeconds: z.int().min(4).max(8).optional()
41
+ })
42
+ ]);
43
+ var clipDeclarationSchema = z.strictObject({ source: clipSourceSchema });
44
+ var clipListSchema = z.array(motionIdSchema).min(1).max(32).superRefine((clips, ctx) => {
45
+ const seen = /* @__PURE__ */ new Set();
46
+ clips.forEach((clip, index) => {
47
+ if (seen.has(clip)) {
48
+ ctx.addIssue({ code: "custom", path: [index], message: `"${clip}" is already a candidate here` });
49
+ }
50
+ seen.add(clip);
51
+ });
52
+ });
53
+ var idleSchema = z.strictObject({
54
+ clips: clipListSchema,
55
+ weight: z.number().min(0).max(100).optional()
56
+ });
57
+ var clipActionSchema = z.strictObject({
58
+ description: z.string().trim().min(1).max(512),
59
+ clips: clipListSchema
60
+ });
61
+ var clipBehaviorFields = z.strictObject({
62
+ idle: idleSchema.optional(),
63
+ /** AUTOMATIC — one reaction per speech episode, then back to idle. */
64
+ on: z.strictObject({
65
+ userSpeechStarted: z.strictObject({ clips: clipListSchema }).optional()
66
+ }).optional(),
67
+ /** REQUESTABLE, not automatic: declaring an action does not play it. */
68
+ actions: motionRecord(clipActionSchema).optional()
69
+ });
70
+ var clipBehaviorSchema = clipBehaviorFields;
71
+ function* clipReferences(behavior) {
72
+ if (behavior.idle) yield { clips: behavior.idle.clips, path: ["idle", "clips"] };
73
+ const listening = behavior.on?.userSpeechStarted;
74
+ if (listening) yield { clips: listening.clips, path: ["on", "userSpeechStarted", "clips"] };
75
+ for (const [id, action] of Object.entries(behavior.actions ?? {})) {
76
+ yield { clips: action.clips, path: ["actions", id, "clips"] };
77
+ }
78
+ }
79
+ var clipLibraryDeclarationSchema = z.strictObject({
80
+ /** CAS against the served revision: two editors cannot silently overwrite each other. */
81
+ expectedRevision: z.int().nonnegative(),
82
+ /** The complete desired media set. Anything omitted is retired. */
83
+ clips: motionRecord(clipDeclarationSchema).nonoptional(),
84
+ ...clipBehaviorFields.shape
85
+ }).superRefine((library, ctx) => {
86
+ if (Object.hasOwn(library.clips, "primary")) {
87
+ ctx.addIssue({
88
+ code: "custom",
89
+ path: ["clips", "primary"],
90
+ message: "The avatar's stored source is the rest state and is not declared \u2014 pick another id"
91
+ });
92
+ }
93
+ for (const { clips, path } of clipReferences(library)) {
94
+ clips.forEach((clip, index) => {
95
+ if (Object.hasOwn(library.clips, clip)) return;
96
+ ctx.addIssue({ code: "custom", path: [...path, index], message: `Unknown clip "${clip}"` });
97
+ });
98
+ }
99
+ });
100
+ z.strictObject({
101
+ avatar_id: z.string().min(1).max(160),
102
+ queue_ticket_id: z.string().min(1).max(160).optional(),
103
+ max_session_seconds: z.int().positive().max(180)
104
+ });
105
+ z.object({
106
+ avatarId: z.string(),
107
+ name: z.string(),
108
+ poster: z.string().url().nullable(),
109
+ idleUrl: z.string().url(),
110
+ revision: z.int().nonnegative(),
111
+ behavior: clipBehaviorSchema,
112
+ clips: z.array(z.strictObject({ id: motionIdSchema, url: z.url() }))
113
+ }).strict();
114
+ var schema0 = z.string();
115
+ var schema1 = z.enum(["queued", "generating", "ready", "failed"]);
116
+ var schema2 = z.url();
117
+ var schema3 = z.null();
118
+ var schema4 = z.union([schema2, schema3]);
119
+ var schema5 = z.enum(["generated", "uploaded"]);
120
+ var schema6 = z.union([schema0, schema3]);
121
+ var schema7 = z.int().min(-9007199254740991).max(9007199254740991);
122
+ var schema8 = z.union([schema7, schema3]);
123
+ var schema9 = z.boolean();
124
+ var schema10 = z.number().min(0).max(1);
125
+ var schema11 = z.strictObject({
126
+ "sameSubject": schema9,
127
+ "firstFrameMatchesAnchor": schema9,
128
+ "lastFrameMatchesAnchor": schema9,
129
+ "framingComparable": schema9,
130
+ "confidence": schema10
131
+ });
132
+ var schema12 = z.array(schema0);
133
+ var schema13 = z.strictObject({
134
+ "verdict": schema11,
135
+ "issues": schema12,
136
+ "firstFrameUrl": schema4,
137
+ "lastFrameUrl": schema4,
138
+ "trimStartMs": schema8,
139
+ "trimEndMs": schema8
140
+ });
141
+ var schema14 = z.union([schema13, schema3]);
142
+ var schema15 = z.strictObject({
143
+ "code": schema0,
144
+ "message": schema0
145
+ });
146
+ var schema16 = z.union([schema15, schema3]);
147
+ var schema17 = z.strictObject({
148
+ "clipId": schema0,
149
+ "status": schema1,
150
+ "url": schema4,
151
+ "source": schema5,
152
+ "uploadAssetId": schema6,
153
+ "motionPrompt": schema6,
154
+ "durationSeconds": schema8,
155
+ "anchorVersion": schema7,
156
+ "poseCheck": schema14,
157
+ "error": schema16,
158
+ "createdAt": schema0,
159
+ "updatedAt": schema0
160
+ });
161
+ var schema18 = z.array(schema17);
162
+ var schema19 = z.int().min(0).max(9007199254740991);
163
+ var schema20 = z.int().min(1).max(9007199254740991);
164
+ var schema21 = z.enum(["portrait", "source_frame"]);
165
+ var schema22 = z.strictObject({
166
+ "url": schema2,
167
+ "source": schema21,
168
+ "timeMs": schema8
169
+ });
170
+ var schema23 = z.union([schema22, schema3]);
171
+ var schema24 = z.strictObject({
172
+ "data": schema18,
173
+ "avatarId": schema0,
174
+ "revision": schema19,
175
+ "anchorVersion": schema20,
176
+ "anchor": schema23,
177
+ "clipLibraryEligible": schema9,
178
+ "defaultSourceAssetId": schema6,
179
+ "behavior": clipBehaviorSchema
180
+ });
181
+ var schema25 = z.strictObject({
182
+ "kept": schema12,
183
+ "queued": schema12,
184
+ "retired": schema12
185
+ });
186
+ var schema26 = z.strictObject({
187
+ "data": schema18,
188
+ "avatarId": schema0,
189
+ "revision": schema19,
190
+ "anchorVersion": schema20,
191
+ "anchor": schema23,
192
+ "clipLibraryEligible": schema9,
193
+ "defaultSourceAssetId": schema6,
194
+ "behavior": clipBehaviorSchema,
195
+ "plan": schema25
196
+ });
197
+ var clipLibraryResponseSchema = schema24;
198
+ var clipLibraryUpdateSchema = schema26;
24
199
 
25
200
  // ../http-client/src/retry.ts
26
201
  var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 500, 502, 503, 504]);
@@ -43,7 +218,7 @@ function isTransient(cause) {
43
218
 
44
219
  // ../http-client/src/client.ts
45
220
  var DEFAULT_BASE_URL = "https://realtimeavatar.ai/api/v1";
46
- var SDK_VERSION = "0.9.1";
221
+ var SDK_VERSION = "0.11.0";
47
222
  var RealtimeAvatar = class {
48
223
  #apiKey;
49
224
  #baseUrl;
@@ -280,49 +455,20 @@ var RealtimeAvatar = class {
280
455
  async deleteAvatar(avatarId) {
281
456
  await this.#json(await this.#request("DELETE", `/avatars/${avatarId}`));
282
457
  }
283
- // A clip envelope missing `revision` would silently drop `expectedRevision` from the
284
- // next declare — CAS degrades to unconditional with zero signal — so it throws instead.
285
- #clipEnvelope(out) {
286
- if (typeof out.revision !== "number" || !Array.isArray(out.data)) {
287
- throw new RealtimeAvatarError("clip library response did not match the contract");
288
- }
289
- return out;
290
- }
291
- /**
292
- * Declare the avatar's full desired clip library — a declaration, not a delta. The
293
- * platform reconciles it against what exists: unchanged clips are `kept` (still
294
- * serving), new or changed ones are `queued` to render, and clips you dropped are
295
- * `retired`. The 202 is acceptance, not readiness — poll `listClips` until no row is
296
- * `queued` or `generating`. A rejected upload settles `failed`, which is terminal, so
297
- * waiting for all-`ready` waits forever. While a re-render is in flight the previous
298
- * take keeps serving, so a declaration never blanks a live avatar.
299
- *
300
- * `expectedRevision` is compare-and-set: pass the `revision` you last read and a
301
- * concurrent writer surfaces as a 409 instead of a lost update. Omit it to declare
302
- * unconditionally.
303
- *
304
- * At most 20 clips: up to eight `idle`, up to two `listen`, the rest `gesture`. Several
305
- * idles are a resting ROTATION she drifts between, not alternatives to one. An uploaded
306
- * clip (`source: { assetId }`) must start AND end on the avatar's rest pose — pose
307
- * validation rejects it otherwise (`status: "failed"`, the verdict in `poseCheck`),
308
- * and the rest of the library is untouched.
309
- */
458
+ /** Reconcile the complete source registry and behavior; 202 accepts work, not media readiness. */
310
459
  async setClipLibrary(avatarId, library) {
311
- const body = { clips: library.clips };
312
- if (library.expectedRevision !== void 0) body.expectedRevision = library.expectedRevision;
313
- return this.#clipEnvelope(
314
- await this.#json(
315
- await this.#request("PUT", `/avatars/${avatarId}/clips`, { json: body })
316
- )
317
- );
460
+ const response = clipLibraryUpdateSchema.safeParse(await this.#json(
461
+ await this.#request("PUT", `/avatars/${avatarId}/clips`, { json: library })
462
+ ));
463
+ if (!response.success) throw new RealtimeAvatarError("clip library response did not match the contract");
464
+ return response.data;
318
465
  }
319
466
  /**
320
467
  * Re-direct the RESTING LOOP — the video she plays when nothing else is happening — from
321
468
  * a new one-sentence description.
322
469
  *
323
- * Not a clip, and this is the distinction integrations get wrong: a clip with
324
- * `role: "idle"` is a variant spliced OVER the loop, and declaring one never changes what
325
- * she rests in. This is the only thing that does.
470
+ * This changes the stored source — the implicit rest state — not the declared idle
471
+ * variations or ordinary clip assets.
326
472
  *
327
473
  * `202`, because the render takes minutes. Three properties, all measured against a real
328
474
  * render rather than asserted:
@@ -406,32 +552,11 @@ var RealtimeAvatar = class {
406
552
  }
407
553
  /** The avatar's clip library: every non-retired clip, plus revision, anchor and eligibility. */
408
554
  async listClips(avatarId) {
409
- return this.#clipEnvelope(
410
- await this.#json(
411
- await this.#request("GET", `/avatars/${avatarId}/clips`)
412
- )
413
- );
414
- }
415
- /**
416
- * Reconcile an avatar's clip set after it changes.
417
- *
418
- * Required, not optional: clips are prepared once and cached by URL hash, and the serve
419
- * path only LOADS that cache. A clip that has never been prepared silently does nothing on
420
- * the first call after you add it. Idempotent, so calling it on every write is cheap.
421
- *
422
- * **At most 32 URLs per call.** This is the whole set for the avatar, not a delta, and the
423
- * endpoint rejects an oversize list rather than truncating it — so a library that outgrows
424
- * 32 needs the set trimmed, not split across two calls.
425
- *
426
- * @deprecated The externally-hosted clip tier this serves is sunsetting. Declare the
427
- * library with {@link setClipLibrary} instead — the platform renders and hosts the
428
- * clips, and pose-validates uploads against the avatar's rest pose.
429
- */
430
- async syncClips(avatarId, clipUrls) {
431
- const out = await this.#json(
432
- await this.#request("POST", `/avatars/${avatarId}/clips`, { json: { clipUrls } })
433
- );
434
- return { queued: out.queued ?? [], ready: out.ready ?? [], retired: out.retired ?? [] };
555
+ const response = clipLibraryResponseSchema.safeParse(await this.#json(
556
+ await this.#request("GET", `/avatars/${avatarId}/clips`)
557
+ ));
558
+ if (!response.success) throw new RealtimeAvatarError("clip library response did not match the contract");
559
+ return response.data;
435
560
  }
436
561
  // ── assets ───────────────────────────────────────────────────────────────
437
562
  /** Hand us a URL and we stream it into storage. Prefer this for anything large. */
@@ -582,20 +707,6 @@ function videoToWire(video) {
582
707
  }
583
708
  out.support_edits = edits;
584
709
  }
585
- if (video.states) {
586
- out.clip_library = Object.entries(video.states).map(([id, state]) => {
587
- const clip = {
588
- clip_id: id,
589
- source_video_url: state.url,
590
- trigger: "directive",
591
- // `when` is the public name for this cue. The wire also still accepts the older
592
- // `hint`; send one name only, and prefer the one the docs and types use.
593
- when: state.when
594
- };
595
- if (state.weight !== void 0) clip.weight = state.weight;
596
- return clip;
597
- });
598
- }
599
710
  return out;
600
711
  }
601
712
  function toAvatar(raw) {
@@ -680,4 +791,4 @@ function isQueued(result) {
680
791
  return "queued" in result;
681
792
  }
682
793
 
683
- export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, isQueued, verifyTranscript };
794
+ export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, verifyTranscript };
@@ -1,4 +1,4 @@
1
- import { RealtimeAvatar, isQueued, RealtimeAvatarHttpError } from './chunk-F7E4HMBW.js';
1
+ import { RealtimeAvatar, isQueued, RealtimeAvatarHttpError } from './chunk-MBQ2VKLC.js';
2
2
 
3
3
  // ../proxy/src/config.ts
4
4
  var json = (body, status = 200) => new Response(JSON.stringify(body), {
package/dist/express.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as ProxyConfig } from './types-QoMHpR1h.js';
2
- import './types-D_aIKv4P.js';
1
+ import { P as ProxyConfig } from './types-DrgyCwLC.js';
2
+ import './types-FoJMJimV.js';
3
3
 
4
4
  type Expressish = {
5
5
  method: string;
package/dist/express.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-FTAD423K.js';
2
- import './chunk-F7E4HMBW.js';
1
+ import { createProxyHandler } from './chunk-RVBBZCVM.js';
2
+ import './chunk-MBQ2VKLC.js';
3
3
 
4
4
  // ../proxy/src/express.ts
5
5
  function realtimeAvatarExpress(config) {
package/dist/hono.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as ProxyConfig } from './types-QoMHpR1h.js';
2
- import './types-D_aIKv4P.js';
1
+ import { P as ProxyConfig } from './types-DrgyCwLC.js';
2
+ import './types-FoJMJimV.js';
3
3
 
4
4
  /**
5
5
  * Hono (and anything else built on Fetch handlers — Workers, Bun, Deno).
package/dist/hono.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-FTAD423K.js';
2
- import './chunk-F7E4HMBW.js';
1
+ import { createProxyHandler } from './chunk-RVBBZCVM.js';
2
+ import './chunk-MBQ2VKLC.js';
3
3
 
4
4
  // ../proxy/src/hono.ts
5
5
  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 ClipDeclaration, e as ClipLibraryUpdate, L as LoopRedirect, f as ClipLibrary, g as ClipSyncResult, h as AssetKind, i as Asset, j as ListSessionsOptions, U as UsageSessionPage, k as UsageSession, l as CreditBalance, T as TranscriptPayload } from './types-D_aIKv4P.js';
2
- export { m as AvatarClip, n as CallConnection, o as CallQueued, p as ClipLibraryPlan, q as ClipSource, r as ContextMessage, s as EndCallReason, V as VideoPolicy, t as VideoState, u as isQueued } from './types-D_aIKv4P.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 CreditBalance, T as TranscriptPayload } from './types-FoJMJimV.js';
2
+ export { l as AvatarClip, m as CallConnection, n as CallQueued, o as ClipDeclaration, p as ClipLibraryPlan, q as ClipSource, r as ContextMessage, s as EndCallReason, V as VideoPolicy, t as isQueued } from './types-FoJMJimV.js';
3
+ import { z } from 'zod';
3
4
 
4
5
  interface RealtimeAvatarOptions {
5
6
  /** `tic_live_…` or `tic_test_…`. Server-side only — never ship this to a browser. */
@@ -169,36 +170,14 @@ declare class RealtimeAvatar {
169
170
  */
170
171
  retimeAnchor(avatarId: string, anchorTimeMs: number): Promise<Avatar>;
171
172
  deleteAvatar(avatarId: string): Promise<void>;
172
- /**
173
- * Declare the avatar's full desired clip library — a declaration, not a delta. The
174
- * platform reconciles it against what exists: unchanged clips are `kept` (still
175
- * serving), new or changed ones are `queued` to render, and clips you dropped are
176
- * `retired`. The 202 is acceptance, not readiness — poll `listClips` until no row is
177
- * `queued` or `generating`. A rejected upload settles `failed`, which is terminal, so
178
- * waiting for all-`ready` waits forever. While a re-render is in flight the previous
179
- * take keeps serving, so a declaration never blanks a live avatar.
180
- *
181
- * `expectedRevision` is compare-and-set: pass the `revision` you last read and a
182
- * concurrent writer surfaces as a 409 instead of a lost update. Omit it to declare
183
- * unconditionally.
184
- *
185
- * At most 20 clips: up to eight `idle`, up to two `listen`, the rest `gesture`. Several
186
- * idles are a resting ROTATION she drifts between, not alternatives to one. An uploaded
187
- * clip (`source: { assetId }`) must start AND end on the avatar's rest pose — pose
188
- * validation rejects it otherwise (`status: "failed"`, the verdict in `poseCheck`),
189
- * and the rest of the library is untouched.
190
- */
191
- setClipLibrary(avatarId: string, library: {
192
- clips: readonly ClipDeclaration[];
193
- expectedRevision?: number;
194
- }): Promise<ClipLibraryUpdate>;
173
+ /** Reconcile the complete source registry and behavior; 202 accepts work, not media readiness. */
174
+ setClipLibrary(avatarId: string, library: ClipLibraryDeclaration): Promise<ClipLibraryUpdate>;
195
175
  /**
196
176
  * Re-direct the RESTING LOOP — the video she plays when nothing else is happening — from
197
177
  * a new one-sentence description.
198
178
  *
199
- * Not a clip, and this is the distinction integrations get wrong: a clip with
200
- * `role: "idle"` is a variant spliced OVER the loop, and declaring one never changes what
201
- * she rests in. This is the only thing that does.
179
+ * This changes the stored source — the implicit rest state — not the declared idle
180
+ * variations or ordinary clip assets.
202
181
  *
203
182
  * `202`, because the render takes minutes. Three properties, all measured against a real
204
183
  * render rather than asserted:
@@ -252,22 +231,6 @@ declare class RealtimeAvatar {
252
231
  }): Promise<ClipLibrary>;
253
232
  /** The avatar's clip library: every non-retired clip, plus revision, anchor and eligibility. */
254
233
  listClips(avatarId: string): Promise<ClipLibrary>;
255
- /**
256
- * Reconcile an avatar's clip set after it changes.
257
- *
258
- * Required, not optional: clips are prepared once and cached by URL hash, and the serve
259
- * path only LOADS that cache. A clip that has never been prepared silently does nothing on
260
- * the first call after you add it. Idempotent, so calling it on every write is cheap.
261
- *
262
- * **At most 32 URLs per call.** This is the whole set for the avatar, not a delta, and the
263
- * endpoint rejects an oversize list rather than truncating it — so a library that outgrows
264
- * 32 needs the set trimmed, not split across two calls.
265
- *
266
- * @deprecated The externally-hosted clip tier this serves is sunsetting. Declare the
267
- * library with {@link setClipLibrary} instead — the platform renders and hosts the
268
- * clips, and pose-validates uploads against the avatar's rest pose.
269
- */
270
- syncClips(avatarId: string, clipUrls: readonly string[]): Promise<ClipSyncResult>;
271
234
  /** Hand us a URL and we stream it into storage. Prefer this for anything large. */
272
235
  createRemoteAsset(input: {
273
236
  kind: AssetKind;
@@ -365,4 +328,49 @@ declare function verifyTranscript(rawBody: Uint8Array | string, headers: Headers
365
328
  toleranceSeconds?: number;
366
329
  }): Promise<TranscriptPayload>;
367
330
 
368
- export { Asset, AssetKind, Avatar, AvatarSourceSwap, AvatarUpdate, CallMode, CallPolicy, ClipDeclaration, ClipLibrary, ClipLibraryUpdate, ClipSyncResult, CreditBalance, EndCallOptions, ListSessionsOptions, LoopRedirect, RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, type RealtimeAvatarOptions, type StartCallOptions, StartCallResult, TranscriptPayload, UsageSession, UsageSessionPage, verifyTranscript };
331
+ declare const clipLibraryDeclarationSchema: z.ZodObject<{
332
+ idle: z.ZodOptional<z.ZodObject<{
333
+ clips: z.ZodArray<z.ZodString>;
334
+ weight: z.ZodOptional<z.ZodNumber>;
335
+ }, z.core.$strict>>;
336
+ on: z.ZodOptional<z.ZodObject<{
337
+ userSpeechStarted: z.ZodOptional<z.ZodObject<{
338
+ clips: z.ZodArray<z.ZodString>;
339
+ }, z.core.$strict>>;
340
+ }, z.core.$strict>>;
341
+ actions: z.ZodOptional<z.ZodPipe<z.ZodTransform<Record<string, {
342
+ description: string;
343
+ clips: string[];
344
+ }>, Record<string, {
345
+ description: string;
346
+ clips: string[];
347
+ }>>, z.ZodRecord<z.ZodString, z.ZodObject<{
348
+ description: z.ZodString;
349
+ clips: z.ZodArray<z.ZodString>;
350
+ }, z.core.$strict>>>>;
351
+ expectedRevision: z.ZodInt;
352
+ clips: z.ZodNonOptional<z.ZodPipe<z.ZodTransform<Record<string, {
353
+ source: {
354
+ assetId: string;
355
+ } | {
356
+ motionPrompt: string;
357
+ durationSeconds?: number | undefined;
358
+ };
359
+ }>, Record<string, {
360
+ source: {
361
+ assetId: string;
362
+ } | {
363
+ motionPrompt: string;
364
+ durationSeconds?: number | undefined;
365
+ };
366
+ }>>, z.ZodRecord<z.ZodString, z.ZodObject<{
367
+ source: z.ZodUnion<readonly [z.ZodObject<{
368
+ assetId: z.ZodString;
369
+ }, z.core.$strict>, z.ZodObject<{
370
+ motionPrompt: z.ZodString;
371
+ durationSeconds: z.ZodOptional<z.ZodInt>;
372
+ }, z.core.$strict>]>;
373
+ }, z.core.$strict>>>>;
374
+ }, z.core.$strict>;
375
+
376
+ export { Asset, AssetKind, Avatar, AvatarSourceSwap, AvatarUpdate, CallMode, CallPolicy, ClipLibrary, ClipLibraryDeclaration, ClipLibraryUpdate, CreditBalance, EndCallOptions, ListSessionsOptions, LoopRedirect, RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, type RealtimeAvatarOptions, type StartCallOptions, StartCallResult, TranscriptPayload, UsageSession, UsageSessionPage, clipLibraryDeclarationSchema, verifyTranscript };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, isQueued, verifyTranscript } from './chunk-F7E4HMBW.js';
1
+ export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, verifyTranscript } from './chunk-MBQ2VKLC.js';
package/dist/nextjs.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as ProxyConfig } from './types-QoMHpR1h.js';
2
- import './types-D_aIKv4P.js';
1
+ import { P as ProxyConfig } from './types-DrgyCwLC.js';
2
+ import './types-FoJMJimV.js';
3
3
 
4
4
  /**
5
5
  * App Router. Mount at `app/api/realtime-avatar/[...path]/route.ts`:
package/dist/nextjs.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-FTAD423K.js';
2
- import './chunk-F7E4HMBW.js';
1
+ import { createProxyHandler } from './chunk-RVBBZCVM.js';
2
+ import './chunk-MBQ2VKLC.js';
3
3
 
4
4
  // ../proxy/src/nextjs.ts
5
5
  function createRealtimeAvatarRoute(config) {
@@ -101,11 +101,6 @@ declare const sessionClipSchema: z.ZodObject<{
101
101
  type SessionClip = z.infer<typeof sessionClipSchema>;
102
102
  declare const sessionBehaviorSchema: z.ZodObject<{
103
103
  gestures_enabled: z.ZodOptional<z.ZodBoolean>;
104
- gesture_freq: z.ZodOptional<z.ZodEnum<{
105
- sparse: "sparse";
106
- balanced: "balanced";
107
- lively: "lively";
108
- }>>;
109
104
  }, z.core.$strict>;
110
105
  type SessionBehavior = z.infer<typeof sessionBehaviorSchema>;
111
106
  /**
@@ -206,11 +201,6 @@ declare const liveKitSessionRequestSchema: z.ZodObject<{
206
201
  }, z.core.$strict>>>;
207
202
  behavior: z.ZodOptional<z.ZodObject<{
208
203
  gestures_enabled: z.ZodOptional<z.ZodBoolean>;
209
- gesture_freq: z.ZodOptional<z.ZodEnum<{
210
- sparse: "sparse";
211
- balanced: "balanced";
212
- lively: "lively";
213
- }>>;
214
204
  }, z.core.$strict>>;
215
205
  renderBackend: z.ZodOptional<z.ZodString>;
216
206
  supportEdits: z.ZodOptional<z.ZodObject<{
@@ -407,6 +397,8 @@ type AvatarVideoSurfaceProps = {
407
397
  * avatars with no idle clip; the poster floor then shows at rest.
408
398
  */
409
399
  idleVideoUrl: string | null;
400
+ /** Reports presented-layer changes, not the agent's requested speaking state. */
401
+ onLiveVisibilityChange?: (visible: boolean) => void;
410
402
  /**
411
403
  * The avatar's PORTRAIT (its face) — the DEEPEST floor, rendered behind the idle
412
404
  * clip and the live video whenever provided. It paints IMMEDIATELY on connect
@@ -1442,7 +1434,7 @@ type UseRealtimeSessionInput<T extends LLMProvider = LLMProvider> = UseSessionLi
1442
1434
  onEnded?: (e: EndedEvent) => void;
1443
1435
  /** The avatar's behavior changed (listening/thinking/idle/special clips). */
1444
1436
  onBehaviorChange?: (b: BehaviorSnapshot) => void;
1445
- /** A clip request was answered (also resolved on the performClip promise). */
1437
+ /** A clip request was answered (also resolved on the performAction promise). */
1446
1438
  onClipResult?: (r: ClipResult) => void;
1447
1439
  };
1448
1440
  /**
@@ -1495,15 +1487,8 @@ type RealtimeSessionApi = SessionLifecycleApi & {
1495
1487
  end: (reason?: EndReason) => void;
1496
1488
  /** The avatar's live nonverbal behavior, or null pre-choreo (see {@link BehaviorSnapshot}). */
1497
1489
  behavior: BehaviorSnapshot | null;
1498
- /**
1499
- * Ask the character to PERFORM a clip by id — a gesture arc plays once (e.g. the
1500
- * gift moment); a `special`-role clip pins for `holdSeconds` (3-20, default 8).
1501
- * Scheduled at the next seamless swap point, never a hard cut. Resolves with the
1502
- * worker's verdict (`accepted:false` + reason on refusal / not connected /
1503
- * timeout) — never rejects, so the app can always fall back to text-only behavior.
1504
- */
1505
- performClip: (clipId: string, opts?: {
1506
- holdSeconds?: number;
1490
+ /** Request a declared semantic action; acceptance schedules it and does not prove playback. */
1491
+ performAction: (actionId: string, opts?: {
1507
1492
  timeoutMs?: number;
1508
1493
  }) => Promise<ClipResult>;
1509
1494
  /** Inbound `rta.lifecycle` frames (the bridge decodes RoomEvent.DataReceived). */
@@ -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-DlYBC6cp.js';
5
- export { a as AdaptivePlayoutController, b as AdaptivePlayoutDecision, c as AdaptivePlayoutOptions, d as AdaptivePlayoutSample, e as ApproachingEndEvent, f as ApproachingEndReason, g as AvatarSessionClient, B as BehaviorSnapshot, C as CallTranscript, h as CallTranscriptSegment, i as CartesiaTtsModel, j as ClipResult, k as ClosingTurnResult, l as CreditsLowEvent, D as DEFAULT_APPROACHING_END_LEAD_SECONDS, m as DEFAULT_CREDITS_LOW_LEAD_SECONDS, n as DEFAULT_GOVERNOR_CONFIG, o as DEFAULT_GRACE_CEILING_SECONDS, p as DEFAULT_GRACE_WINDOW_LEAD_SECONDS, q as DEFAULT_IDLE_SECONDS, r as DEFAULT_IDLE_WARN_LEAD_SECONDS, s as DEFAULT_TURN_TIMEOUT_SECONDS, E as EndReason, t as EndedEvent, u as ExtendResult, F as FishTtsModel, v as FreezeReadingFn, G as Governor, w as GovernorAction, x as GovernorConfig, y as GovernorSignal, z as GovernorState, H as GraceWindowClosedEvent, I as GraceWindowOpenEvent, J as GraceWindowState, K as IdleWarningEvent, M as InboundRtpCursor, N as InboundRtpReading, O as KnownBehaviorState, P as LLMProvider, Q as LLMSelection, R as LiveKitAvatarGrantState, S as LiveKitAvatarGrantStatus, T as LiveKitCapacityState, U as LiveKitConnectionStatus, V as LiveKitSessionRequest, W as LiveKitSessionStartResult, X as MAX_RECONNECT_ATTEMPTS, Y as MAX_SESSION_INSTRUCTIONS_CHARS, Z as ProxyClientOptions, _ as QualityCap, $ as RECONNECT_BACKOFF_MS, a0 as RealtimeAvatarRequestOptions, a1 as RealtimeSessionApi, a2 as RealtimeSessionMedia, a3 as RealtimeSessionRoomSinks, a4 as ReconnectPolicy, a5 as ReconnectingEvent, a6 as RecoveryState, a7 as RetryStep, a8 as SessionBehavior, a9 as SessionClip, aa as SessionClocks, ab as SessionEndReason, ac as SessionLifecycleApi, ad as SessionLifecyclePhase, ae as SessionLifecyclePhaseKind, af as SessionLifecycleRoomBridge, ag as SessionLifecycleRoomBridgeProps, ah as SurfaceLayers, ai as TurnState, aj as TurnTimeoutEvent, ak as UseAvatarQualityGovernorInput, al as UseLiveKitAvatarGrantInput, am as UseRealtimeSessionInput, an as UseSessionLifecycleInput, ao as VoiceSpec, ap as VoiceSpecInput, aq as capacityErrorFromBusy, ar as capacityStateFromGrant, as as createProxyClient, at as isNativeLiveTrackSubscribed, au as knownBehaviorStates, av as mapTurnState, aw as readInboundRtp, ax as sessionBehaviorSchema, ay as sessionClipSchema, az as splitCallTranscript, aA as useAvatarAdaptivePlayoutDelay, aB as useAvatarPlayoutDelay, aC as useAvatarQualityGovernor, aD as useCallTranscript, aE as useLiveKitAvatarGrant, aF as useLiveTrackProducing, aG as useMicLease, aH as useRealtimeSession, aI as useReleaseMicLeaseOnTrackEnded, aJ as useSessionLifecycle } from './proxy-client-DlYBC6cp.js';
4
+ import { L as LiveKitSessionGrant, A as AvatarVideoFit } from './proxy-client-CWvT16wz.js';
5
+ export { a as AdaptivePlayoutController, b as AdaptivePlayoutDecision, c as AdaptivePlayoutOptions, d as AdaptivePlayoutSample, e as ApproachingEndEvent, f as ApproachingEndReason, g as AvatarSessionClient, B as BehaviorSnapshot, C as CallTranscript, h as CallTranscriptSegment, i as CartesiaTtsModel, j as ClipResult, k as ClosingTurnResult, l as CreditsLowEvent, D as DEFAULT_APPROACHING_END_LEAD_SECONDS, m as DEFAULT_CREDITS_LOW_LEAD_SECONDS, n as DEFAULT_GOVERNOR_CONFIG, o as DEFAULT_GRACE_CEILING_SECONDS, p as DEFAULT_GRACE_WINDOW_LEAD_SECONDS, q as DEFAULT_IDLE_SECONDS, r as DEFAULT_IDLE_WARN_LEAD_SECONDS, s as DEFAULT_TURN_TIMEOUT_SECONDS, E as EndReason, t as EndedEvent, u as ExtendResult, F as FishTtsModel, v as FreezeReadingFn, G as Governor, w as GovernorAction, x as GovernorConfig, y as GovernorSignal, z as GovernorState, H as GraceWindowClosedEvent, I as GraceWindowOpenEvent, J as GraceWindowState, K as IdleWarningEvent, M as InboundRtpCursor, N as InboundRtpReading, O as KnownBehaviorState, P as LLMProvider, Q as LLMSelection, R as LiveKitAvatarGrantState, S as LiveKitAvatarGrantStatus, T as LiveKitCapacityState, U as LiveKitConnectionStatus, V as LiveKitSessionRequest, W as LiveKitSessionStartResult, X as MAX_RECONNECT_ATTEMPTS, Y as MAX_SESSION_INSTRUCTIONS_CHARS, Z as ProxyClientOptions, _ as QualityCap, $ as RECONNECT_BACKOFF_MS, a0 as RealtimeAvatarRequestOptions, a1 as RealtimeSessionApi, a2 as RealtimeSessionMedia, a3 as RealtimeSessionRoomSinks, a4 as ReconnectPolicy, a5 as ReconnectingEvent, a6 as RecoveryState, a7 as RetryStep, a8 as SessionBehavior, a9 as SessionClip, aa as SessionClocks, ab as SessionEndReason, ac as SessionLifecycleApi, ad as SessionLifecyclePhase, ae as SessionLifecyclePhaseKind, af as SessionLifecycleRoomBridge, ag as SessionLifecycleRoomBridgeProps, ah as SurfaceLayers, ai as TurnState, aj as TurnTimeoutEvent, ak as UseAvatarQualityGovernorInput, al as UseLiveKitAvatarGrantInput, am as UseRealtimeSessionInput, an as UseSessionLifecycleInput, ao as VoiceSpec, ap as VoiceSpecInput, aq as capacityErrorFromBusy, ar as capacityStateFromGrant, as as createProxyClient, at as isNativeLiveTrackSubscribed, au as knownBehaviorStates, av as mapTurnState, aw as readInboundRtp, ax as sessionBehaviorSchema, ay as sessionClipSchema, az as splitCallTranscript, aA as useAvatarAdaptivePlayoutDelay, aB as useAvatarPlayoutDelay, aC as useAvatarQualityGovernor, aD as useCallTranscript, aE as useLiveKitAvatarGrant, aF as useLiveTrackProducing, aG as useMicLease, aH as useRealtimeSession, aI as useReleaseMicLeaseOnTrackEnded, aJ as useSessionLifecycle } from './proxy-client-CWvT16wz.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';