realtime-avatar 0.25.0 → 0.26.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/dist/server.js CHANGED
@@ -1,2 +1,2 @@
1
- export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, listRecordingsResponseSchema, recordingAccessResponseSchema, recordingArtifactSchema, recordingModeSchema, verifyTranscript } from './chunk-GMYGHDY2.js';
1
+ export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, listRecordingsResponseSchema, recordingAccessResponseSchema, recordingArtifactSchema, recordingModeSchema, verifyTranscript } from './chunk-HMESE54B.js';
2
2
  import './chunk-MCIXU6XP.js';
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-DNHHQ5GZ.js';
2
- import './chunk-GMYGHDY2.js';
1
+ import { createProxyHandler } from './chunk-XJPGQMEW.js';
2
+ import './chunk-HMESE54B.js';
3
3
  import './chunk-MCIXU6XP.js';
4
4
 
5
5
  // src/tanstack-start.ts
package/dist/tools.d.ts CHANGED
@@ -93,6 +93,22 @@ interface RoomLike {
93
93
  identity: string;
94
94
  }>;
95
95
  }
96
+ /**
97
+ * Registration did not arm the tools. `retryable` says whether trying again can help:
98
+ * - true: the agent was not there or had not armed registration by the deadline (a slow start,
99
+ * or a session minted without `client_tools` — one attempt cannot tell them apart), or the RPC
100
+ * transport failed.
101
+ * - false: the manifest is too large, the agent refused or answered something unreadable, or
102
+ * LiveKit refused the request itself. The same request will fail the same way.
103
+ */
104
+ declare class ToolRegistrationError extends Error {
105
+ readonly name = "ToolRegistrationError";
106
+ readonly retryable: boolean;
107
+ constructor(message: string, options: {
108
+ retryable: boolean;
109
+ cause?: unknown;
110
+ });
111
+ }
96
112
  /**
97
113
  * Register your tools and start answering calls to them.
98
114
  *
@@ -131,4 +147,4 @@ declare function attachAvatarTools(room: RoomLike, tools: Record<string, Registe
131
147
  timeoutMs?: number;
132
148
  }): Promise<ToolRegistration>;
133
149
 
134
- export { type AvatarTool, MAX_MANIFEST_BYTES, MAX_RESULT_CHARS, MAX_TOOLS, type RegisteredTool, TOOL_DEADLINE_MS, type ToolContext, type ToolRegistration, attachAvatarTools, defineAvatarTool };
150
+ export { type AvatarTool, MAX_MANIFEST_BYTES, MAX_RESULT_CHARS, MAX_TOOLS, type RegisteredTool, TOOL_DEADLINE_MS, type ToolContext, type ToolRegistration, ToolRegistrationError, attachAvatarTools, defineAvatarTool };
package/dist/tools.js CHANGED
@@ -35,14 +35,26 @@ function candidateIdentities(room) {
35
35
  const agents = all.filter((id) => /^agent[-_]/i.test(id));
36
36
  return [...agents, ...all.filter((id) => !agents.includes(id))];
37
37
  }
38
+ function rpcCode(error) {
39
+ return typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
40
+ }
38
41
  function methodMissing(error) {
39
- const code = error?.code;
40
- if (code === "UNSUPPORTED_METHOD" || code === "RECIPIENT_NOT_FOUND" || code === 1401 || code === 1402) {
42
+ const code = rpcCode(error);
43
+ if (code === "UNSUPPORTED_METHOD" || code === "RECIPIENT_NOT_FOUND" || code === 1400 || code === 1401) {
41
44
  return true;
42
45
  }
43
46
  const message = error instanceof Error ? error.message : String(error ?? "");
44
47
  return /not supported|unsupported_method|recipient_not_found|recipient not found/i.test(message);
45
48
  }
49
+ var TRANSIENT_RPC_CODES = /* @__PURE__ */ new Set([1501, 1502, 1503, 1505]);
50
+ var ToolRegistrationError = class extends Error {
51
+ name = "ToolRegistrationError";
52
+ retryable;
53
+ constructor(message, options) {
54
+ super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
55
+ this.retryable = options.retryable;
56
+ }
57
+ };
46
58
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
47
59
  function buildManifest(tools) {
48
60
  const descriptors = [];
@@ -72,8 +84,9 @@ async function attachAvatarTools(room, tools, options = {}) {
72
84
  const { descriptors, rejected } = buildManifest(tools);
73
85
  const payload = JSON.stringify({ tools: descriptors });
74
86
  if (payload.length > MAX_MANIFEST_BYTES) {
75
- throw new Error(
76
- `tool manifest is ${payload.length} bytes, over the ${MAX_MANIFEST_BYTES} limit \u2014 shorten descriptions or register fewer tools`
87
+ throw new ToolRegistrationError(
88
+ `tool manifest is ${payload.length} bytes, over the ${MAX_MANIFEST_BYTES} limit \u2014 shorten descriptions or register fewer tools`,
89
+ { retryable: false }
77
90
  );
78
91
  }
79
92
  options.signal?.throwIfAborted();
@@ -156,14 +169,21 @@ async function attachAvatarTools(room, tools, options = {}) {
156
169
  } catch (error) {
157
170
  activeCaller = void 0;
158
171
  lastError = error;
159
- if (!methodMissing(error)) throw error;
172
+ if (!methodMissing(error)) {
173
+ if (options.signal?.aborted) throw error;
174
+ throw new ToolRegistrationError(
175
+ `tool registration failed: ${error instanceof Error ? error.message : String(error)}`,
176
+ { retryable: TRANSIENT_RPC_CODES.has(rpcCode(error)), cause: error }
177
+ );
178
+ }
160
179
  }
161
180
  }
162
181
  if (Date.now() >= deadline) break;
163
182
  await sleep(200);
164
183
  }
165
- throw new Error(
166
- sawCandidate ? `no participant accepted the tool manifest \u2014 either this session was not minted with the \`client_tools\` capability (a server-side grant), or the agent never armed registration within ${options.timeoutMs ?? 8e3}ms` + (lastError instanceof Error ? ` (last error: ${lastError.message})` : "") : "no remote participant joined the room before the timeout \u2014 nothing to register with"
184
+ throw new ToolRegistrationError(
185
+ sawCandidate ? `no participant accepted the tool manifest \u2014 either this session was not minted with the \`client_tools\` capability (a server-side grant), or the agent never armed registration within ${options.timeoutMs ?? 8e3}ms` + (lastError instanceof Error ? ` (last error: ${lastError.message})` : "") : "no remote participant joined the room before the timeout \u2014 nothing to register with",
186
+ { retryable: true, cause: lastError ?? void 0 }
167
187
  );
168
188
  } catch (error) {
169
189
  dispose();
@@ -171,4 +191,4 @@ async function attachAvatarTools(room, tools, options = {}) {
171
191
  }
172
192
  }
173
193
 
174
- export { MAX_MANIFEST_BYTES, MAX_RESULT_CHARS, MAX_TOOLS, TOOL_DEADLINE_MS, attachAvatarTools, defineAvatarTool };
194
+ export { MAX_MANIFEST_BYTES, MAX_RESULT_CHARS, MAX_TOOLS, TOOL_DEADLINE_MS, ToolRegistrationError, attachAvatarTools, defineAvatarTool };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "realtime-avatar",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "description": "Realtime Avatar SDK. `realtime-avatar` and the route adapters hold your API key and are server-only; `realtime-avatar/react`, `/browser` and `/tools` never can — importing a server entry into a browser build throws at bundle time.",
5
5
  "license": "MIT",
6
6
  "author": "The Influence Company",
@@ -1,65 +0,0 @@
1
- /**
2
- * Receiver-side playout delay (SECONDS) for the avatar media tracks.
3
- *
4
- * The avatar is published from a GPU datacenter and reaches viewers over the
5
- * public internet, where a few percent of packet loss is normal. A shallow
6
- * receiver buffer stalls (freezes) on every loss while it waits for a
7
- * retransmit; a deeper buffer recovers the lost packets BEFORE playout, so the
8
- * viewer sees smooth 25fps instead of freezing. Measured at ~5% loss this took
9
- * the stream from ~11fps with multi-second freezes to a steady 25fps with zero
10
- * freezes.
11
- *
12
- * 0.5s is applied via the native `RemoteTrack.setPlayoutDelay`, which sets the
13
- * receiver's `playoutDelayHint` — the same Chromium jitter-buffer knob the old
14
- * hand-rolled `jitterBufferTarget` reached into (the spec renamed
15
- * playoutDelayHint→jitterBufferTarget; both influence the same buffer depth),
16
- * but typed and SDK-owned so there is no cast or feature-probe. The hint is NOT
17
- * free at the open: the receiver holds the FIRST video frame toward the target
18
- * (~250-450ms of measured TTFF), a deliberate cost paid once for the freeze-free
19
- * steady state above. The open-small-then-ramp variant that tried to dodge that
20
- * cost was shipped, failed twice (the rVFC ramp trigger is throttled on the
21
- * opacity:0-mounted element, stranding the shallow buffer), and was deliberately
22
- * removed in 2262e4c7b — do not re-propose it. No-op on browsers that don't
23
- * support the hint (the SDK warns and moves on).
24
- *
25
- * This lives in the vanilla browser package, not the React one, because the
26
- * React surface applies it for you and a plain-DOM page has to be given it.
27
- * Until this file existed the helper was only reachable through
28
- * `realtime-avatar/react`, so every plain-DOM adopter — every one of this repo's
29
- * own demos included — ran with the browser's default shallow buffer and froze on
30
- * every lost packet. Nobody should have to know this knob exists: `prepareAvatarRoom`
31
- * (and `attachRemoteAudio`, which calls it) applies it to every track the room
32
- * subscribes, and `RealtimeAvatarLiveKitRoom` does the same for React. This file is
33
- * the one place the number and its reasoning live.
34
- * Structural like its siblings: no `livekit-client` import, no version pin.
35
- */
36
- declare const DEFAULT_AVATAR_PLAYOUT_DELAY_SECONDS = 0.5;
37
- /**
38
- * A track that can take a native playout-delay hint — livekit-client's subscribed
39
- * `RemoteTrack`. Duck-typed on the method rather than `instanceof`, so a
40
- * placeholder or local track is skipped and the helper is testable with a fake.
41
- */
42
- interface PlayoutDelayTarget {
43
- setPlayoutDelay(delayInSeconds: number): void;
44
- }
45
- /**
46
- * Apply the receiver playout delay to ONE subscribed track.
47
- *
48
- * Call it for every track `RoomEvent.TrackSubscribed` hands you, audio and video
49
- * alike, before or after `attach()` — WebRTC syncs the pair to the LARGER hint,
50
- * so applying it to one stream and not the other lets the lips lag the voice.
51
- * Returns `true` when the hint was applied and `false` when the object cannot take
52
- * one (a local track, a placeholder, a not-yet-subscribed publication), so a
53
- * caller can log the miss without a try/catch. The delay is floored at 0 so a
54
- * negative input can never throw.
55
- */
56
- declare function applyPlayoutDelay(track: unknown, delaySeconds?: number): boolean;
57
- /**
58
- * Apply the SAME native playout delay to the avatar's audio + video tracks
59
- * (kept equal so the a/v sync stays lip-locked). Pure and exported so the clamp
60
- * and the both-tracks-equal invariant are unit-testable without a DOM; the React
61
- * hook `useAvatarPlayoutDelay` is a thin effect wrapper around this.
62
- */
63
- declare function applyAvatarPlayoutDelay(videoTrack: unknown, audioTrack: unknown, delaySeconds?: number): void;
64
-
65
- export { DEFAULT_AVATAR_PLAYOUT_DELAY_SECONDS as D, type PlayoutDelayTarget as P, applyAvatarPlayoutDelay as a, applyPlayoutDelay as b };