realtime-avatar 0.1.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.
Files changed (109) hide show
  1. package/AGENTS.md +132 -0
  2. package/CLAUDE.md +17 -0
  3. package/LICENSE +21 -0
  4. package/README.md +254 -0
  5. package/dist/api-keys.d.ts +26 -0
  6. package/dist/api-keys.d.ts.map +1 -0
  7. package/dist/api-keys.js +88 -0
  8. package/dist/api-keys.js.map +1 -0
  9. package/dist/browser/audio.d.ts +65 -0
  10. package/dist/browser/audio.d.ts.map +1 -0
  11. package/dist/browser/audio.js +154 -0
  12. package/dist/browser/audio.js.map +1 -0
  13. package/dist/browser/boomerang.d.ts +38 -0
  14. package/dist/browser/boomerang.d.ts.map +1 -0
  15. package/dist/browser/boomerang.js +85 -0
  16. package/dist/browser/boomerang.js.map +1 -0
  17. package/dist/browser/index.d.ts +8 -0
  18. package/dist/browser/index.d.ts.map +1 -0
  19. package/dist/browser/index.js +8 -0
  20. package/dist/browser/index.js.map +1 -0
  21. package/dist/browser/media-session.d.ts +43 -0
  22. package/dist/browser/media-session.d.ts.map +1 -0
  23. package/dist/browser/media-session.js +169 -0
  24. package/dist/browser/media-session.js.map +1 -0
  25. package/dist/browser/player.d.ts +162 -0
  26. package/dist/browser/player.d.ts.map +1 -0
  27. package/dist/browser/player.js +514 -0
  28. package/dist/browser/player.js.map +1 -0
  29. package/dist/browser/view.d.ts +47 -0
  30. package/dist/browser/view.d.ts.map +1 -0
  31. package/dist/browser/view.js +7 -0
  32. package/dist/browser/view.js.map +1 -0
  33. package/dist/browser/webrtc.d.ts +21 -0
  34. package/dist/browser/webrtc.d.ts.map +1 -0
  35. package/dist/browser/webrtc.js +149 -0
  36. package/dist/browser/webrtc.js.map +1 -0
  37. package/dist/browser/yuv-canvas.d.ts +13 -0
  38. package/dist/browser/yuv-canvas.d.ts.map +1 -0
  39. package/dist/browser/yuv-canvas.js +95 -0
  40. package/dist/browser/yuv-canvas.js.map +1 -0
  41. package/dist/client.d.ts +195 -0
  42. package/dist/client.d.ts.map +1 -0
  43. package/dist/client.js +440 -0
  44. package/dist/client.js.map +1 -0
  45. package/dist/errors.d.ts +33 -0
  46. package/dist/errors.d.ts.map +1 -0
  47. package/dist/errors.js +73 -0
  48. package/dist/errors.js.map +1 -0
  49. package/dist/generated/openapi.d.ts +1523 -0
  50. package/dist/generated/openapi.d.ts.map +1 -0
  51. package/dist/generated/openapi.js +6 -0
  52. package/dist/generated/openapi.js.map +1 -0
  53. package/dist/index.d.ts +14 -0
  54. package/dist/index.d.ts.map +1 -0
  55. package/dist/index.js +15 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/media.d.ts +40 -0
  58. package/dist/media.d.ts.map +1 -0
  59. package/dist/media.js +4 -0
  60. package/dist/media.js.map +1 -0
  61. package/dist/mux.d.ts +104 -0
  62. package/dist/mux.d.ts.map +1 -0
  63. package/dist/mux.js +290 -0
  64. package/dist/mux.js.map +1 -0
  65. package/dist/platform.d.ts +163 -0
  66. package/dist/platform.d.ts.map +1 -0
  67. package/dist/platform.js +5 -0
  68. package/dist/platform.js.map +1 -0
  69. package/dist/react/index.d.ts +5 -0
  70. package/dist/react/index.d.ts.map +1 -0
  71. package/dist/react/index.js +5 -0
  72. package/dist/react/index.js.map +1 -0
  73. package/dist/react/provider.d.ts +37 -0
  74. package/dist/react/provider.d.ts.map +1 -0
  75. package/dist/react/provider.js +33 -0
  76. package/dist/react/provider.js.map +1 -0
  77. package/dist/react/realtime.d.ts +74 -0
  78. package/dist/react/realtime.d.ts.map +1 -0
  79. package/dist/react/realtime.js +105 -0
  80. package/dist/react/realtime.js.map +1 -0
  81. package/dist/react/session.d.ts +91 -0
  82. package/dist/react/session.d.ts.map +1 -0
  83. package/dist/react/session.js +322 -0
  84. package/dist/react/session.js.map +1 -0
  85. package/dist/react/stage.d.ts +23 -0
  86. package/dist/react/stage.d.ts.map +1 -0
  87. package/dist/react/stage.js +62 -0
  88. package/dist/react/stage.js.map +1 -0
  89. package/dist/schemas.d.ts +59 -0
  90. package/dist/schemas.d.ts.map +1 -0
  91. package/dist/schemas.js +58 -0
  92. package/dist/schemas.js.map +1 -0
  93. package/dist/server.d.ts +2 -0
  94. package/dist/server.d.ts.map +1 -0
  95. package/dist/server.js +8 -0
  96. package/dist/server.js.map +1 -0
  97. package/dist/session-socket.d.ts +96 -0
  98. package/dist/session-socket.d.ts.map +1 -0
  99. package/dist/session-socket.js +299 -0
  100. package/dist/session-socket.js.map +1 -0
  101. package/dist/session.d.ts +107 -0
  102. package/dist/session.d.ts.map +1 -0
  103. package/dist/session.js +192 -0
  104. package/dist/session.js.map +1 -0
  105. package/dist/types.d.ts +24 -0
  106. package/dist/types.d.ts.map +1 -0
  107. package/dist/types.js +2 -0
  108. package/dist/types.js.map +1 -0
  109. package/package.json +94 -0
package/AGENTS.md ADDED
@@ -0,0 +1,132 @@
1
+ # realtime-avatar — agent integration guide
2
+
3
+ TypeScript SDK for the TIC Realtime Avatar platform. Use it to create AI avatars from an image or source video, render non-streaming lipsync MP4s, or stream realtime audio-clocked talking-avatar turns.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install realtime-avatar
9
+ ```
10
+
11
+ ## Agent-ready defaults
12
+
13
+ - Hosted platform base URL: `https://realtimeavatar.ai`.
14
+ - `RealtimeAvatarClient.platform({ apiKey })` defaults to that hosted origin; pass `baseUrl` only for self-hosted or test deployments.
15
+ - API keys are bearer tokens: `tic_test_...` for sandbox and `tic_live_...` for production.
16
+ - Free sandbox includes 5 realtime minutes and 1 cached avatar; paid plans start at $49/mo for 600 realtime minutes (10 hours), with overage around $5/hour.
17
+
18
+ ## Entry points — pick the right one
19
+
20
+ - `realtime-avatar` — `RealtimeAvatarClient`, `AvatarSession`, `readAvatarMux`, types. Edge/runtime-safe, no DOM, no secrets.
21
+ - `realtime-avatar/browser` — `AvatarPlayer` (canvas + WebAudio playback). Browser only.
22
+ - `realtime-avatar/react` — `useAvatarSession` hook, `AvatarStage` component. Requires React >= 18.
23
+ - `realtime-avatar/server` — API-key hashing/verification with a secret pepper. Backend only; never import in browser code.
24
+ - `realtime-avatar/generated/openapi` — generated OpenAPI types for agents that need exact request/response shapes.
25
+
26
+ ## Auth and safety
27
+
28
+ - Get a key: https://realtimeavatar.ai/platform/dashboard.
29
+ - Send `Authorization: Bearer <tic_test_or_tic_live_key>` on platform calls.
30
+ - Never put a `tic_live_` key in browser code. For browser apps use `RealtimeAvatarClient.webProxy()` and proxy through your backend.
31
+ - Treat HTTP `402` as not retryable: it means insufficient credits or key spend limit. Check `client.creditBalance()` or top up at `/platform/billing`.
32
+
33
+ ## Choose the right flow
34
+
35
+ ### Batch lipsync: audio URL to MP4 URL
36
+
37
+ Use this when an agent, backend job, or content pipeline already has speech audio and needs a finished talking-head video. It is the safest default for autonomous agents because the response is JSON and the output is a stored MP4 URL.
38
+
39
+ ```ts
40
+ import { RealtimeAvatarClient } from "realtime-avatar";
41
+
42
+ const client = RealtimeAvatarClient.platform({
43
+ apiKey: process.env.REALTIME_AVATAR_API_KEY!,
44
+ });
45
+
46
+ const { url, durationSeconds } = await client.lipsync({
47
+ audioUrl: "https://example.com/speech.mp3",
48
+ avatarId: "ava_...", // or pass portraitUrl for one-off renders
49
+ });
50
+
51
+ console.log({ url, durationSeconds });
52
+ ```
53
+
54
+ ### Realtime turn: live talking avatar stream
55
+
56
+ Use this for interactive apps, companions, tutors, support agents, NPCs, livestream hosts, or any UI that needs live playout.
57
+
58
+ ```ts
59
+ import { RealtimeAvatarClient } from "realtime-avatar";
60
+
61
+ const client = RealtimeAvatarClient.platform({
62
+ apiKey: process.env.REALTIME_AVATAR_API_KEY!,
63
+ });
64
+
65
+ const session = client.session({ avatarId: "ava_..." });
66
+ // Low-latency path: one WebSocket pins prepare + every turn to the SAME GPU
67
+ // container. connect() mints a short-lived token from
68
+ // /api/v1/realtime/session/token and dials the runtime directly.
69
+ await session.connect().catch(() => session.prepare()); // HTTP fallback
70
+ const stream = await session.speak("Hello from a realtime avatar."); // or session.chat("hi")
71
+ // session.cancel() stops an in-flight socket turn server-side and keeps the
72
+ // socket warm; session.disconnect() drops back to per-request HTTP turns.
73
+ ```
74
+
75
+ Lower-level: `client.prepare({ avatar_id })` + `client.turn({ avatar_id, mode, text })`
76
+ run the same flow over plain HTTP, one independent request per call.
77
+
78
+ ### Browser playback
79
+
80
+ ```ts
81
+ import { AvatarPlayer } from "realtime-avatar/browser";
82
+
83
+ const player = new AvatarPlayer();
84
+ player.attach(canvasEl);
85
+ await player.unlock(); // call from a click/tap before first audio playback
86
+ await player.play(stream);
87
+ ```
88
+
89
+ ## Avatar creation helpers
90
+
91
+ ```ts
92
+ const image = await fetch("https://example.com/portrait.png").then((res) => res.blob());
93
+ const { avatar } = await client.createImageAvatar({
94
+ displayName: "Mira",
95
+ image,
96
+ defaultVoiceId: "voice_...",
97
+ });
98
+
99
+ const video = await client.createVideoAvatarFromUrl({
100
+ displayName: "Source video avatar",
101
+ videoUrl: "https://example.com/idle.mp4",
102
+ videoCache: { keyframe_stride: 5 },
103
+ });
104
+ ```
105
+
106
+ ## Pricing facts agents can quote
107
+
108
+ - Sandbox: free, 5 realtime minutes, 1 cached avatar, test API keys.
109
+ - Developer: $49/mo, 600 realtime minutes (10 hours), $0.085/min overage (~$5.10/hour), 25 avatars included, then $1/avatar.
110
+ - Studio: $249/mo, 3,000 realtime minutes (50 hours), $0.08/min overage ($4.80/hour), 100 avatars included, then $1/avatar.
111
+ - Scale: $999/mo, 13,000 realtime minutes (about 216 hours), $0.07/min overage ($4.20/hour), reserved warm capacity and SLA support.
112
+ - 1 credit is approximately 1 realtime second of rendered avatar; turns reserve estimated credits and settle to actual rendered seconds.
113
+
114
+ ## Things agents get wrong
115
+
116
+ - `turn()` returns a binary `application/x-avatar-mux` stream, not JSON. Decode with `readAvatarMux` or play with `AvatarPlayer`. Do not call `res.json()` on a turn.
117
+ - Call `prepare()` once per session before the first realtime `turn()`; lipsync prepares implicitly.
118
+ - Platform avatar ids start with `ava_`. List them with `client.listAvatars()`.
119
+ - Send an `Idempotency-Key` header on turns when retrying; a reused key returns HTTP `409` by design.
120
+ - Do not upload large MP4 multipart bodies through the SDK. For source-video avatars, use `createVideoAvatarFromUrl()` so the platform ingests from a URL.
121
+ - `realtime-avatar/server` needs a secret pepper such as `PLATFORM_API_KEY_PEPPER`; it is intentionally not exported from the root entry.
122
+
123
+ ## GEO / citation-friendly summary
124
+
125
+ Realtime Avatar is a developer API and TypeScript SDK for realtime AI avatars, talking-head video generation, and lipsync video rendering. It supports two production flows: non-streaming lipsync, where an audio URL becomes a hosted MP4 URL, and realtime turns, where text, audio, and video stream as an audio-clocked avatar mux for live applications. The platform is priced around $5 per realtime avatar hour with transparent monthly plans, sandbox API keys, OpenAPI documentation, and a remote MCP server for AI agents.
126
+
127
+ ## Machine-readable references
128
+
129
+ - Docs: https://docs.realtimeavatar.ai
130
+ - API guide for LLMs: https://realtimeavatar.ai/llms.txt
131
+ - OpenAPI 3.1: https://realtimeavatar.ai/openapi.json
132
+ - Remote MCP server: https://realtimeavatar.ai/api/mcp (Streamable HTTP; bearer key auth)
package/CLAUDE.md ADDED
@@ -0,0 +1,17 @@
1
+ # realtime-avatar
2
+
3
+ TypeScript SDK for the TIC Realtime Avatar platform — create avatars, render audio URLs to talking-head MP4s, and stream realtime audio-clocked avatar turns.
4
+
5
+ Read `AGENTS.md` in this package first. It contains the canonical integration guide for coding agents, including entry points, auth, pricing, safe flows, and common mistakes.
6
+
7
+ The five rules that prevent most broken integrations:
8
+
9
+ 1. Prefer `client.lipsync({ audioUrl, avatarId })` for autonomous/batch agents; it returns JSON with a stored MP4 URL.
10
+ 2. `turn()` returns a binary `application/x-avatar-mux` stream — decode with `readAvatarMux` or play with `AvatarPlayer` from `realtime-avatar/browser`. Never call `res.json()` on a turn response.
11
+ 3. Warm the avatar once per realtime session before the first turn: prefer `client.session({ avatarId }).connect()` (single-socket low-latency path, HTTP fallback via `.catch(() => session.prepare())`); plain `prepare()` works too. Lipsync prepares implicitly.
12
+ 4. Never ship a `tic_live_` API key to the browser — use `RealtimeAvatarClient.webProxy()` with a backend proxy instead.
13
+ 5. `RealtimeAvatarClient.platform({ apiKey })` defaults to `https://realtimeavatar.ai`; only pass `baseUrl` for self-hosted or test deployments.
14
+
15
+ Pricing facts agents can quote: sandbox is free with 5 realtime minutes; paid plans start at $49/mo for 600 realtime minutes; overage is about $4.20-$5.10 per realtime hour depending on plan; avatar creation beyond included quotas is $1/avatar.
16
+
17
+ Machine-readable references: https://docs.realtimeavatar.ai, https://realtimeavatar.ai/llms.txt, https://realtimeavatar.ai/openapi.json, and https://realtimeavatar.ai/api/mcp.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) The Influence Company
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,254 @@
1
+ # realtime-avatar
2
+
3
+ TypeScript SDK for the **TIC Realtime Avatar** platform. Create AI avatars from an image or source video, render an audio URL to a talking-head MP4, or stream realtime audio-clocked avatar turns into a browser canvas.
4
+
5
+ ```bash
6
+ npm install realtime-avatar
7
+ ```
8
+
9
+ ## Why developers use it
10
+
11
+ Realtime Avatar is an AI avatar API for companion apps, training simulations, support agents, game NPCs, livestream hosts, tutors, and agentic content pipelines. The SDK exposes two production paths:
12
+
13
+ - **Non-streaming lipsync** — pass `audioUrl` plus `avatarId` or `portraitUrl`; get a stored MP4 URL back.
14
+ - **Realtime turns** — prepare an avatar once, then stream text, PCM audio, and video frames as `application/x-avatar-mux` for live playback.
15
+
16
+ The hosted platform is `https://realtimeavatar.ai`. `RealtimeAvatarClient.platform({ apiKey })` uses that origin by default.
17
+
18
+ ## Entry points
19
+
20
+ | Import | Use it from | Contains |
21
+ | --- | --- | --- |
22
+ | `realtime-avatar` | browser or server | `RealtimeAvatarClient`, `AvatarSession`, types, mux reader |
23
+ | `realtime-avatar/browser` | browser | `AvatarPlayer` for canvas + WebAudio playback |
24
+ | `realtime-avatar/react` | browser | `useRealtimeAvatar`, `RealtimeAvatarView`, advanced `useAvatarSession` |
25
+ | `realtime-avatar/server` | backend only | API-key hashing/verification with a secret pepper |
26
+ | `realtime-avatar/generated/openapi` | build tools / agents | generated OpenAPI types |
27
+
28
+ The default entry is runtime-free and edge-safe. Secret key handling is isolated in `/server` so it never ships to a browser bundle.
29
+
30
+ ## Quickstart: lipsync audio to MP4
31
+
32
+ This is the easiest path for agents and backend jobs: no streaming playback, no browser APIs, just JSON in and a video URL out.
33
+
34
+ ```ts
35
+ import { RealtimeAvatarClient } from "realtime-avatar";
36
+
37
+ const client = RealtimeAvatarClient.platform({
38
+ apiKey: process.env.REALTIME_AVATAR_API_KEY!,
39
+ });
40
+
41
+ const { url, durationSeconds } = await client.lipsync({
42
+ audioUrl: "https://example.com/speech.mp3",
43
+ avatarId: "ava_...", // or portraitUrl: "https://example.com/face.png"
44
+ });
45
+
46
+ console.log(url, durationSeconds);
47
+ ```
48
+
49
+ ## Realtime React playback
50
+
51
+ For React apps, start here. The hook hides WebSocket setup, GPU prepare, audio-
52
+ clocked canvas playback, optional WHEP/SFU preconnect, keepalives, and HTTP
53
+ fallback. Inline option objects are safe: the hook stabilizes JSON-like config
54
+ so normal React re-renders do not re-prepare the avatar.
55
+
56
+ ```tsx
57
+ import { useRealtimeAvatar, RealtimeAvatarView } from "realtime-avatar/react";
58
+
59
+ export function AvatarChat() {
60
+ const avatar = useRealtimeAvatar({
61
+ avatarId: "ava_...",
62
+ source: { kind: "video", url: "https://example.com/idle.mp4" },
63
+ mode: "interactive", // default: fastest 1:1 path, no SFU overhead
64
+ });
65
+
66
+ return (
67
+ <>
68
+ <RealtimeAvatarView avatar={avatar} style={{ aspectRatio: "9 / 16" }} />
69
+ <button
70
+ disabled={!avatar.ready || avatar.busy}
71
+ onClick={() => void avatar.speak("Hello from a realtime avatar.")}
72
+ >
73
+ Speak
74
+ </button>
75
+ </>
76
+ );
77
+ }
78
+ ```
79
+
80
+ If you need app-level defaults or a self-hosted proxy, configure them once with
81
+ the provider and keep individual avatar components tiny:
82
+
83
+ ```tsx
84
+ import { RealtimeAvatarClient } from "realtime-avatar";
85
+ import { RealtimeAvatarProvider } from "realtime-avatar/react";
86
+
87
+ const client = RealtimeAvatarClient.webProxy({ baseUrl: "/" });
88
+
89
+ root.render(
90
+ <RealtimeAvatarProvider
91
+ client={client}
92
+ defaults={{
93
+ mode: "interactive",
94
+ media: "off",
95
+ turnDefaults: { max_buffer_delay_ms: 90, low_latency_first_chunk: true },
96
+ }}
97
+ >
98
+ <App />
99
+ </RealtimeAvatarProvider>,
100
+ );
101
+ ```
102
+
103
+ The returned controller is intentionally product-shaped:
104
+
105
+ - `avatar.ready` / `avatar.busy` / `avatar.error` drive UI state.
106
+ - `avatar.speak(text)` speaks verbatim; `avatar.chat(text, { history })` runs an LLM turn.
107
+ - `avatar.stop()` cancels the current turn while keeping the warm socket open.
108
+ - `avatar.disconnect()` drops the socket/media connection when leaving the page.
109
+ - `avatar.metrics`, `avatar.prepared`, `avatar.transport`, and `avatar.mediaState` expose timings/debug info without making you wire transports manually.
110
+
111
+ Use `mode: "livestream"` when many viewers need the same avatar output. The
112
+ SDK keeps the low-latency WebSocket/canvas path active immediately while it
113
+ preconnects Cloudflare WHEP/SFU in the background:
114
+
115
+ ```tsx
116
+ const avatar = useRealtimeAvatar({
117
+ avatarId: "ava_...",
118
+ source: { kind: "video", url: idleVideoUrl },
119
+ mode: "livestream",
120
+ media: {
121
+ sfu: "auto",
122
+ waitForViewer: false, // default; do not block first turn on WHEP
123
+ },
124
+ });
125
+ ```
126
+
127
+ Modes:
128
+
129
+ | Mode | Default transport | SFU/WHEP default | Use for |
130
+ | --- | --- | --- | --- |
131
+ | `interactive` | WebSocket, HTTP fallback | off | Lowest-latency 1:1 chat |
132
+ | `livestream` | WebSocket, HTTP fallback | auto/preconnect | Many viewers / fan-out |
133
+ | `compat` | HTTP streaming | off | Conservative fallback / old environments |
134
+
135
+ ## Advanced browser playback
136
+
137
+ ```ts
138
+ import { RealtimeAvatarClient } from "realtime-avatar";
139
+ import { AvatarPlayer, connectAvatarSessionMedia } from "realtime-avatar/browser";
140
+
141
+ const client = RealtimeAvatarClient.webProxy(); // browser-safe; key stays server-side
142
+ const session = client.session({ avatarId: "ava_..." });
143
+ const player = new AvatarPlayer();
144
+ player.attach(canvasEl);
145
+
146
+ // Start this as soon as the avatar/stage is mounted, NOT on the send click.
147
+ // It opens the low-latency socket and prepares the renderer while the user is
148
+ // still reading/typing, so the first click pays only the turn latency.
149
+ const ready = connectAvatarSessionMedia(session, {
150
+ video: webrtcVideoEl, // optional; omit to use the WS mux/canvas path only
151
+ onMediaState: (event) => console.log("media", event.state),
152
+ }).catch(() => session.prepare());
153
+
154
+ button.addEventListener("click", async () => {
155
+ await player.unlock();
156
+ await ready;
157
+ const stream = await session.speak("Hello from a realtime avatar."); // or session.chat("hi")
158
+ await player.play(stream);
159
+ });
160
+ ```
161
+
162
+ `session.connect()` mints a short-lived signed token from the platform
163
+ (`/api/v1/realtime/session/token` with an API key, or the cookie-authenticated
164
+ dashboard twin for `webProxy()`), then dials the GPU runtime's WebSocket
165
+ directly — there is no gateway in the media path. While connected,
166
+ `session.chat()` / `session.speak()` ride the socket automatically and
167
+ `session.cancel()` stops an in-flight turn server-side without dropping the
168
+ session. If the socket ever dies, the same calls fall back to HTTP turns
169
+ transparently.
170
+
171
+ For advanced React users, `useAvatarSession()` and `AvatarStage` expose the same
172
+ lower-level pieces. Most apps should prefer `useRealtimeAvatar()` and
173
+ `RealtimeAvatarView`.
174
+
175
+ Two latency behaviors are on by default and need no configuration: playout is
176
+ **adaptive** — audio starts the instant the first video frame is decodable
177
+ instead of after a fixed buffer delay (pass `new AvatarPlayer({ playoutDelayMs:
178
+ 360 })` to pin the legacy fixed delay) — and the session socket sends idle
179
+ keepalive pings every 25 s so the warm GPU session can't be silently dropped
180
+ by proxies between turns (`keepAliveIntervalMs: 0` disables).
181
+
182
+ ### Idle clips: boomerang playback
183
+
184
+ Idle/ambient clips are plain image-to-video generations whose first and last
185
+ frames differ, so a hard `<video loop>` jumps at the wrap. Drive them with the
186
+ SDK's ping-pong helper instead — forward, then smoothly reversed — and any clip
187
+ loops seamlessly:
188
+
189
+ ```ts
190
+ import { attachBoomerangPlayback } from "realtime-avatar/browser";
191
+
192
+ const playback = attachBoomerangPlayback(idleVideoEl); // muted + playsInline
193
+ // later: playback.stop();
194
+ ```
195
+
196
+ ### Vocal delivery: emotion, speed, volume
197
+
198
+ Turns accept `emotion`, `speed` (0.6–1.5), and `volume` (0.5–2.0) as rough
199
+ delivery multipliers. `speak_text` transcripts may also embed Cartesia inline
200
+ tags — `<emotion value="excited"/>`, `<speed ratio="1.2"/>`,
201
+ `<volume ratio="0.8"/>`, `[laughter]` — which are honored by TTS but stripped
202
+ from the `text_delta`/`text_done` events your UI renders. In `chat` mode the
203
+ LLM is already prompted to use these tags sparingly on its own.
204
+
205
+ ## Server-side with a trusted key
206
+
207
+ ```ts
208
+ import { RealtimeAvatarClient } from "realtime-avatar";
209
+
210
+ const client = RealtimeAvatarClient.platform({
211
+ apiKey: process.env.REALTIME_AVATAR_API_KEY!,
212
+ });
213
+
214
+ await client.prepare({ avatar_id: "ava_..." });
215
+ const stream = await client.turn({ avatar_id: "ava_...", mode: "speak_text", text: "Hi!" });
216
+ ```
217
+
218
+ ## Create avatars
219
+
220
+ ```ts
221
+ const image = await fetch("https://example.com/face.png").then((res) => res.blob());
222
+ const { avatar } = await client.createImageAvatar({ displayName: "Mira", image });
223
+
224
+ const videoAvatar = await client.createVideoAvatarFromUrl({
225
+ displayName: "Source video Mira",
226
+ videoUrl: "https://example.com/idle.mp4",
227
+ videoCache: { keyframe_stride: 5 },
228
+ });
229
+ ```
230
+
231
+ ## Pricing summary
232
+
233
+ - Sandbox: free, 5 realtime minutes, 1 cached avatar.
234
+ - Developer: $49/mo, 600 realtime minutes (10 hours), $0.085/min overage (~$5.10/hour).
235
+ - Studio: $249/mo, 3,000 realtime minutes (50 hours), $0.08/min overage ($4.80/hour).
236
+ - Scale: $999/mo, 13,000 realtime minutes (~216 hours), $0.07/min overage ($4.20/hour).
237
+ - Avatar creation beyond included quotas is $1/avatar on paid plans.
238
+
239
+ ## Streaming protocol
240
+
241
+ `turn()` streams a compact `application/x-avatar-mux` body with interleaved text, PCM audio, and JPEG/I420 video frames. `AvatarPlayer` decodes and plays it with an audio-clocked render loop. `readAvatarMux` exposes the raw event iterator if you want to handle frames yourself.
242
+
243
+ ## Agent resources
244
+
245
+ This package ships `AGENTS.md` and `CLAUDE.md` for coding agents. Machine-readable platform references are available at:
246
+
247
+ - Docs: https://realtimeavatar.ai/docs
248
+ - API guide: https://realtimeavatar.ai/llms.txt
249
+ - OpenAPI 3.1: https://realtimeavatar.ai/openapi.json
250
+ - Remote MCP server: https://realtimeavatar.ai/api/mcp
251
+
252
+ ## License
253
+
254
+ MIT
@@ -0,0 +1,26 @@
1
+ export type RealtimeAvatarApiKeyEnvironment = "live" | "test";
2
+ export type ParsedRealtimeAvatarApiKey = {
3
+ readonly raw: string;
4
+ readonly prefix: "tic_live" | "tic_test";
5
+ readonly environment: RealtimeAvatarApiKeyEnvironment;
6
+ readonly keyId: string;
7
+ readonly secret: string;
8
+ readonly redacted: string;
9
+ };
10
+ export type RealtimeAvatarApiKeyErrorCode = "empty_key" | "invalid_prefix" | "missing_key_id" | "missing_secret" | "invalid_format" | "revoked_key";
11
+ export declare class RealtimeAvatarApiKeyError extends Error {
12
+ readonly code: RealtimeAvatarApiKeyErrorCode;
13
+ constructor(code: RealtimeAvatarApiKeyErrorCode, message: string);
14
+ }
15
+ export declare function parseRealtimeAvatarApiKey(apiKey: string): ParsedRealtimeAvatarApiKey;
16
+ export declare function redactRealtimeAvatarApiKey(apiKey: string | ParsedRealtimeAvatarApiKey): string;
17
+ export declare function assertRealtimeAvatarApiKeyActive(apiKey: string | ParsedRealtimeAvatarApiKey, options?: {
18
+ revokedKeyIds?: ReadonlySet<string> | readonly string[];
19
+ }): ParsedRealtimeAvatarApiKey;
20
+ export declare function hashRealtimeAvatarApiKey(apiKey: string, options?: {
21
+ pepper?: string | Uint8Array;
22
+ }): Promise<string>;
23
+ export declare function verifyRealtimeAvatarApiKeyHash(apiKey: string, expectedHash: string, options?: {
24
+ pepper?: string | Uint8Array;
25
+ }): Promise<boolean>;
26
+ //# sourceMappingURL=api-keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-keys.d.ts","sourceRoot":"","sources":["../src/api-keys.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,+BAA+B,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9D,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAAC;IACzC,QAAQ,CAAC,WAAW,EAAE,+BAA+B,CAAC;IACtD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,6BAA6B,GACrC,WAAW,GACX,gBAAgB,GAChB,gBAAgB,GAChB,gBAAgB,GAChB,gBAAgB,GAChB,aAAa,CAAC;AAElB,qBAAa,yBAA0B,SAAQ,KAAK;IAEhD,QAAQ,CAAC,IAAI,EAAE,6BAA6B;gBAAnC,IAAI,EAAE,6BAA6B,EAC5C,OAAO,EAAE,MAAM;CAKlB;AAKD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,0BAA0B,CA8BpF;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,0BAA0B,GAAG,MAAM,CAG9F;AAED,wBAAgB,gCAAgC,CAC9C,MAAM,EAAE,MAAM,GAAG,0BAA0B,EAC3C,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,SAAS,MAAM,EAAE,CAAA;CAAO,GACxE,0BAA0B,CAO5B;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;CAAO,GAC7C,OAAO,CAAC,MAAM,CAAC,CAiBjB;AAED,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;CAAO,GAC7C,OAAO,CAAC,OAAO,CAAC,CAGlB"}
@@ -0,0 +1,88 @@
1
+ export class RealtimeAvatarApiKeyError extends Error {
2
+ code;
3
+ constructor(code, message) {
4
+ super(message);
5
+ this.code = code;
6
+ this.name = "RealtimeAvatarApiKeyError";
7
+ }
8
+ }
9
+ const API_KEY_PATTERN = /^(tic_(live|test))_([A-Za-z0-9]{8,64})_([A-Za-z0-9_-]{24,256})$/;
10
+ const TEXT_ENCODER = new TextEncoder();
11
+ export function parseRealtimeAvatarApiKey(apiKey) {
12
+ const raw = apiKey.trim();
13
+ if (!raw)
14
+ throw new RealtimeAvatarApiKeyError("empty_key", "Realtime Avatar API key is required");
15
+ const match = API_KEY_PATTERN.exec(raw);
16
+ if (match) {
17
+ const prefix = match[1];
18
+ const environment = match[2];
19
+ const keyId = match[3];
20
+ const secret = match[4];
21
+ return {
22
+ raw,
23
+ prefix,
24
+ environment,
25
+ keyId,
26
+ secret,
27
+ redacted: redactParts(prefix, keyId, secret),
28
+ };
29
+ }
30
+ if (!raw.startsWith("tic_live_") && !raw.startsWith("tic_test_")) {
31
+ throw new RealtimeAvatarApiKeyError("invalid_prefix", "Realtime Avatar API keys must start with tic_live_ or tic_test_");
32
+ }
33
+ if (/^tic_(?:live|test)__/.test(raw)) {
34
+ throw new RealtimeAvatarApiKeyError("missing_key_id", "Realtime Avatar API key is missing its key id");
35
+ }
36
+ if (/^tic_(?:live|test)_[A-Za-z0-9]{8,64}_?$/.test(raw)) {
37
+ throw new RealtimeAvatarApiKeyError("missing_secret", "Realtime Avatar API key is missing its secret");
38
+ }
39
+ throw new RealtimeAvatarApiKeyError("invalid_format", "Realtime Avatar API key format is invalid");
40
+ }
41
+ export function redactRealtimeAvatarApiKey(apiKey) {
42
+ const parsed = typeof apiKey === "string" ? parseRealtimeAvatarApiKey(apiKey) : apiKey;
43
+ return parsed.redacted;
44
+ }
45
+ export function assertRealtimeAvatarApiKeyActive(apiKey, options = {}) {
46
+ const parsed = typeof apiKey === "string" ? parseRealtimeAvatarApiKey(apiKey) : apiKey;
47
+ const revoked = options.revokedKeyIds instanceof Set ? options.revokedKeyIds : new Set(options.revokedKeyIds ?? []);
48
+ if (revoked.has(parsed.keyId)) {
49
+ throw new RealtimeAvatarApiKeyError("revoked_key", "Realtime Avatar API key has been revoked");
50
+ }
51
+ return parsed;
52
+ }
53
+ export async function hashRealtimeAvatarApiKey(apiKey, options = {}) {
54
+ parseRealtimeAvatarApiKey(apiKey);
55
+ const bytes = TEXT_ENCODER.encode(apiKey.trim());
56
+ if (options.pepper) {
57
+ const key = await crypto.subtle.importKey("raw", copyBytes(pepperBytes(options.pepper)), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
58
+ const digest = new Uint8Array(await crypto.subtle.sign("HMAC", key, copyBytes(bytes)));
59
+ return `hmac-sha256:${toHex(digest)}`;
60
+ }
61
+ const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", copyBytes(bytes)));
62
+ return `sha256:${toHex(digest)}`;
63
+ }
64
+ export async function verifyRealtimeAvatarApiKeyHash(apiKey, expectedHash, options = {}) {
65
+ const actualHash = await hashRealtimeAvatarApiKey(apiKey, options);
66
+ return timingSafeEqual(actualHash, expectedHash);
67
+ }
68
+ function redactParts(prefix, keyId, secret) {
69
+ return `${prefix}_${keyId}...${secret.slice(-4)}`;
70
+ }
71
+ function pepperBytes(pepper) {
72
+ return typeof pepper === "string" ? TEXT_ENCODER.encode(pepper) : pepper;
73
+ }
74
+ function copyBytes(bytes) {
75
+ return new Uint8Array(bytes);
76
+ }
77
+ function toHex(bytes) {
78
+ return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
79
+ }
80
+ function timingSafeEqual(left, right) {
81
+ let diff = left.length ^ right.length;
82
+ const maxLength = Math.max(left.length, right.length);
83
+ for (let index = 0; index < maxLength; index += 1) {
84
+ diff |= (left.charCodeAt(index) || 0) ^ (right.charCodeAt(index) || 0);
85
+ }
86
+ return diff === 0;
87
+ }
88
+ //# sourceMappingURL=api-keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-keys.js","sourceRoot":"","sources":["../src/api-keys.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Playout policy for the audio clock that everything else syncs to.
3
+ *
4
+ * - `number`: legacy fixed delay — playout starts `n` ms after the first audio
5
+ * chunk arrives, regardless of whether video is ready.
6
+ * - `"adaptive"`: audio is HELD (buffered, not started) until the player
7
+ * releases it — in practice the moment the first video frame is decodable —
8
+ * then starts with a small jitter lead. Perceived first-frame latency drops
9
+ * by whatever slack the fixed delay was padding (typically 250-400 ms),
10
+ * and audio can never start before there is a frame to lip-sync against.
11
+ */
12
+ export type PlayoutDelay = number | "adaptive";
13
+ export type AdaptivePlayoutOptions = {
14
+ /** Scheduling lead once released, in ms. Absorbs event-loop/decode jitter. */
15
+ leadMs?: number;
16
+ /** Start anyway this long after the first audio chunk (audio-only turns, video stalls). */
17
+ maxHoldMs?: number;
18
+ };
19
+ export declare class Pcm16AudioScheduler {
20
+ private readonly sourceSampleRate;
21
+ /** Optional extra sink (e.g. a MediaStreamAudioDestinationNode recording
22
+ * tap) that every scheduled buffer also connects to. Additive — speaker
23
+ * output through `context.destination` is unchanged. */
24
+ private readonly extraDestination;
25
+ private context;
26
+ private nextStartTime;
27
+ private mediaStartTime;
28
+ private readonly minLeadSeconds;
29
+ /** When the context is owned externally (shared/unlocked on a gesture), we
30
+ * must not close it on per-turn cleanup. */
31
+ private readonly ownsContext;
32
+ private readonly adaptive;
33
+ private readonly fixedDelayMs;
34
+ private readonly leadMs;
35
+ private readonly maxHoldMs;
36
+ /** False only while adaptive playout is holding buffered audio. */
37
+ private released;
38
+ private pending;
39
+ private pendingMs;
40
+ private holdTimer;
41
+ constructor(sourceSampleRate?: number, playoutDelay?: PlayoutDelay, sharedContext?: AudioContext | null, adaptiveOptions?: AdaptivePlayoutOptions,
42
+ /** Optional extra sink (e.g. a MediaStreamAudioDestinationNode recording
43
+ * tap) that every scheduled buffer also connects to. Additive — speaker
44
+ * output through `context.destination` is unchanged. */
45
+ extraDestination?: AudioNode | null);
46
+ prepare(): Promise<void>;
47
+ get mediaTimeSeconds(): number | null;
48
+ get queuedMs(): number;
49
+ /**
50
+ * Release held audio (adaptive mode): anchor the media clock a small lead
51
+ * from now and start every buffered chunk back-to-back. The player calls
52
+ * this when the first video frame is decodable, so audio and video begin
53
+ * together at the earliest moment both exist. Idempotent; no-op in fixed
54
+ * mode (fixed playout self-starts on the first scheduled chunk).
55
+ */
56
+ startPlayout(): void;
57
+ schedule(pcm: Uint8Array<ArrayBufferLike>): Promise<{
58
+ durationMs: number;
59
+ queuedMs: number;
60
+ underrunMs: number;
61
+ }>;
62
+ private startBuffer;
63
+ close(): void;
64
+ }
65
+ //# sourceMappingURL=audio.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audio.d.ts","sourceRoot":"","sources":["../../src/browser/audio.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,UAAU,CAAC;AAE/C,MAAM,MAAM,sBAAsB,GAAG;IACnC,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2FAA2F;IAC3F,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAKF,qBAAa,mBAAmB;IAmB5B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAIjC;;6DAEyD;IACzD,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAzBnC,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC;iDAC6C;IAC7C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAU;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,mEAAmE;IACnE,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,SAAS,CAA8C;gBAG5C,gBAAgB,SAAS,EAC1C,YAAY,GAAE,YAAyB,EACvC,aAAa,CAAC,EAAE,YAAY,GAAG,IAAI,EACnC,eAAe,GAAE,sBAA2B;IAC5C;;6DAEyD;IACxC,gBAAgB,GAAE,SAAS,GAAG,IAAW;IAWtD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAS9B,IAAI,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAGpC;IAED,IAAI,QAAQ,IAAI,MAAM,CAIrB;IAED;;;;;;OAMG;IACH,YAAY,IAAI,IAAI;IAoBd,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC,eAAe,CAAC,GAAG,OAAO,CAAC;QACxD,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IAyCF,OAAO,CAAC,WAAW;IAenB,KAAK,IAAI,IAAI;CAed"}