realtime-avatar 0.15.0 → 0.16.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 +63 -2
- package/dist/{chunk-LZMJJX7C.js → chunk-2TNFET3Q.js} +1 -1
- package/dist/{chunk-PKKTFUA7.js → chunk-V26PE2M3.js} +394 -12
- package/dist/express.d.ts +3 -2
- package/dist/express.js +2 -2
- package/dist/hono.d.ts +3 -2
- package/dist/hono.js +2 -2
- package/dist/index.d.ts +9 -3
- package/dist/index.js +1 -1
- package/dist/nextjs.d.ts +3 -2
- package/dist/nextjs.js +2 -2
- package/dist/{proxy-client-BGPikYWc.d.ts → proxy-client-Bu-5QFhA.d.ts} +52 -3
- package/dist/react-native.d.ts +2 -2
- package/dist/react-native.js +41 -0
- package/dist/react.d.ts +2 -2
- package/dist/react.js +41 -0
- package/dist/recording.d.ts +135 -0
- package/dist/recording.js +43 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +1 -1
- package/dist/tanstack-start.d.ts +3 -2
- package/dist/tanstack-start.js +2 -2
- package/dist/{types-CJcTowDB.d.ts → types-Bphj69NC.d.ts} +1 -1
- package/dist/{types-B9GTrpx0.d.ts → types-VOv0vsFs.d.ts} +279 -5
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ A live character your users can talk to — voice, or voice and video. She liste
|
|
|
4
4
|
speaks, so you can interrupt her mid-sentence and she stops, the way a person stops.
|
|
5
5
|
|
|
6
6
|
```bash
|
|
7
|
-
npm install --save-exact realtime-avatar@0.
|
|
7
|
+
npm install --save-exact realtime-avatar@0.16.0
|
|
8
8
|
```
|
|
9
9
|
|
|
10
10
|
```ts
|
|
@@ -25,6 +25,61 @@ return call.raw; // relay to the browser byte-for-byte
|
|
|
25
25
|
|
|
26
26
|
That is the whole server half. The client joins with the payload and renders her.
|
|
27
27
|
|
|
28
|
+
## Optional call recordings
|
|
29
|
+
|
|
30
|
+
Your server decides whether to record after your application obtains consent:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
const call = await rta.startCall({ avatarId, recording: "audio_video" });
|
|
34
|
+
if (!isQueued(call) && call.recording) {
|
|
35
|
+
await saveRecordingId(call.sessionId, call.recording.recordingId);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Later, in your authenticated admin backend:
|
|
39
|
+
const recording = await rta.getRecording(recordingId);
|
|
40
|
+
if (recording.status === "ready") {
|
|
41
|
+
const { url, expiresAt } = await rta.getRecordingAccess(recordingId);
|
|
42
|
+
// Return this short-lived access to the authorized viewer.
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Omitted or `"off"` disables recording. `"audio"` records published user and avatar audio;
|
|
47
|
+
`"video"` records published video without audio; `"audio_video"` keeps both on one media timeline.
|
|
48
|
+
Recording does not enable the microphone, camera, or screen sharing. Only tracks that participants
|
|
49
|
+
authorize and publish can be recorded. Camera controls remain a future SDK feature.
|
|
50
|
+
|
|
51
|
+
Recordings may finish processing after a call ends. Use `listRecordings({ sessionId })` to find
|
|
52
|
+
them, or refresh `getRecording(recordingId)` while processing. These methods and
|
|
53
|
+
`getRecordingAccess` require a server key with `recordings:read`.
|
|
54
|
+
|
|
55
|
+
Save `recordingId`, not a playback URL. Files are retained until `retainedUntil` (30 days by
|
|
56
|
+
default); each URL expires at `expiresAt` (up to one hour, capped by retention). Obtain fresh
|
|
57
|
+
access before replaying or seeking after expiry. An expired URL does not delete the file.
|
|
58
|
+
Treat the URL as private: anyone who has it can play that file until it expires.
|
|
59
|
+
|
|
60
|
+
Transcript delivery remains the signed `transcript` webhook configured on `startCall`. Join the
|
|
61
|
+
transcript and recordings by `sessionId`; keep your own script revision with that call. Transcript
|
|
62
|
+
timestamps describe conversation turns and do not by themselves establish frame-accurate media
|
|
63
|
+
alignment for lip-sync analysis.
|
|
64
|
+
|
|
65
|
+
Client-safe Zod schemas and derived types are available from `realtime-avatar/recording`.
|
|
66
|
+
They are the same executable contract used by the service, verified against the published digest.
|
|
67
|
+
|
|
68
|
+
```mermaid
|
|
69
|
+
---
|
|
70
|
+
title: Recording ownership and private playback
|
|
71
|
+
---
|
|
72
|
+
flowchart LR
|
|
73
|
+
App[Application server: consent and recording policy] --> RTA[RTA: call and recording lifecycle]
|
|
74
|
+
RTA --> Media[Media provider: record published tracks]
|
|
75
|
+
Media --> Storage[Private media storage]
|
|
76
|
+
RTA --> Metadata[Recording ID and session ID]
|
|
77
|
+
Admin[Authorized admin backend] --> RTA
|
|
78
|
+
RTA --> Access[Temporary playback URL]
|
|
79
|
+
Access --> Player[Video or audio player]
|
|
80
|
+
Storage --> Player
|
|
81
|
+
```
|
|
82
|
+
|
|
28
83
|
New here? The [quickstart](https://realtimeavatar.ai/docs/quickstart) goes from an API key to a
|
|
29
84
|
working call, and a [sandbox key](https://realtimeavatar.ai/signup) is free with no card. Mount
|
|
30
85
|
the server half on your framework:
|
|
@@ -82,9 +137,14 @@ all), and the `video` policy types are deliberately not one-to-one with the wire
|
|
|
82
137
|
|
|
83
138
|
```ts
|
|
84
139
|
// calls
|
|
85
|
-
rta.startCall({ avatarId, mode?, instructions?, context?, maxSeconds?, video?, transcript?, metadata? })
|
|
140
|
+
rta.startCall({ avatarId, mode?, instructions?, context?, maxSeconds?, video?, recording?, transcript?, metadata? })
|
|
86
141
|
rta.endCall(sessionId, { reason? }) // free an abandoned call's slot; idempotent, never throws
|
|
87
142
|
|
|
143
|
+
// optional recordings; server only, requires recordings:read
|
|
144
|
+
rta.listRecordings({ sessionId, limit?, cursor? })
|
|
145
|
+
rta.getRecording(recordingId)
|
|
146
|
+
rta.getRecordingAccess(recordingId)
|
|
147
|
+
|
|
88
148
|
// avatars
|
|
89
149
|
rta.createAvatarFromImage({ displayName, imageUrl, motionPrompt?, voice? }) // the only lane
|
|
90
150
|
rta.createAvatarFromVideo({ displayName, videoUrl, voice? }) // DEPRECATED — closed, 422
|
|
@@ -141,6 +201,7 @@ Browser — these never can:
|
|
|
141
201
|
| `realtime-avatar/react-native` | The same surface for Expo / React Native |
|
|
142
202
|
| `realtime-avatar/browser` | `enableMicrophone`, `attachRemoteAudio`, `prepareAvatarRoom` — no React |
|
|
143
203
|
| `realtime-avatar/tools` | `attachAvatarTools` — the browser tool plane |
|
|
204
|
+
| `realtime-avatar/recording` | Client-safe Zod recording schemas and derived types |
|
|
144
205
|
|
|
145
206
|
Every adapter takes the same two hooks: `authorize` gates the request, `session` decides the
|
|
146
207
|
call. Policy — `instructions`, `maxSeconds`, `voice`, `video` — is decided in `session`, on
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RealtimeAvatar, isQueued, RealtimeAvatarHttpError } from './chunk-
|
|
1
|
+
import { RealtimeAvatar, isQueued, RealtimeAvatarHttpError } from './chunk-V26PE2M3.js';
|
|
2
2
|
|
|
3
3
|
// ../proxy/src/config.ts
|
|
4
4
|
var json = (body, status = 200) => new Response(JSON.stringify(body), {
|
|
@@ -214,6 +214,44 @@ var schema26 = z.strictObject({
|
|
|
214
214
|
});
|
|
215
215
|
var clipLibraryResponseSchema = schema24;
|
|
216
216
|
var clipLibraryUpdateSchema = schema26;
|
|
217
|
+
var RECORDED_MEDIA_MODES = ["audio", "video", "audio_video"];
|
|
218
|
+
var recordingModeSchema = z.enum(["off", ...RECORDED_MEDIA_MODES]).describe("Server-owned recording policy; omitted means off. Audio includes user and avatar audio.");
|
|
219
|
+
var RECORDING_STATUSES = ["pending", "recording", "processing", "ready", "failed", "expired"];
|
|
220
|
+
z.enum(RECORDING_STATUSES);
|
|
221
|
+
var recordingMetadataSchema = z.object({
|
|
222
|
+
sessionId: z.string().min(1),
|
|
223
|
+
recordingId: z.string().min(1),
|
|
224
|
+
mode: recordingModeSchema.exclude(["off"]),
|
|
225
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
226
|
+
retainedUntil: z.string().datetime({ offset: true })
|
|
227
|
+
});
|
|
228
|
+
var recordingArtifactSchema = z.discriminatedUnion("status", [
|
|
229
|
+
recordingMetadataSchema.extend({ status: z.enum(["pending", "recording", "processing", "expired"]) }).strict(),
|
|
230
|
+
recordingMetadataSchema.extend({
|
|
231
|
+
status: z.literal("ready"),
|
|
232
|
+
mediaType: z.enum(["audio/mp4", "video/mp4"]),
|
|
233
|
+
sizeBytes: z.number().int().positive(),
|
|
234
|
+
durationMs: z.number().int().nonnegative().nullable()
|
|
235
|
+
}).strict(),
|
|
236
|
+
recordingMetadataSchema.extend({
|
|
237
|
+
status: z.literal("failed"),
|
|
238
|
+
errorCode: z.enum(["recording_failed", "recording_unavailable"])
|
|
239
|
+
}).strict()
|
|
240
|
+
]);
|
|
241
|
+
var listRecordingsQuerySchema = z.object({
|
|
242
|
+
sessionId: z.string().min(1).optional(),
|
|
243
|
+
limit: z.coerce.number().int().min(1).max(100).default(50),
|
|
244
|
+
cursor: z.string().min(1).optional()
|
|
245
|
+
}).strict();
|
|
246
|
+
var listRecordingsResponseSchema = z.object({
|
|
247
|
+
data: z.array(recordingArtifactSchema),
|
|
248
|
+
nextCursor: z.string().nullable()
|
|
249
|
+
}).strict();
|
|
250
|
+
var recordingAccessResponseSchema = z.object({
|
|
251
|
+
recordingId: z.string().min(1),
|
|
252
|
+
url: z.string().url(),
|
|
253
|
+
expiresAt: z.string().datetime({ offset: true })
|
|
254
|
+
}).strict();
|
|
217
255
|
|
|
218
256
|
// ../http-client/src/retry.ts
|
|
219
257
|
var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 500, 502, 503, 504]);
|
|
@@ -233,10 +271,332 @@ function isTransient(cause) {
|
|
|
233
271
|
const name = cause?.name;
|
|
234
272
|
return name === "TimeoutError" || name === "TypeError" || name === "FetchError";
|
|
235
273
|
}
|
|
274
|
+
var DEFAULT_AVATAR_ID = "maria";
|
|
275
|
+
var DEFAULT_BACKGROUND_ID = "plain_white";
|
|
276
|
+
var sessionModeSchema = z.enum(["avatar", "voice"]);
|
|
277
|
+
var DEFAULT_SESSION_MODE = "avatar";
|
|
278
|
+
var DEFAULT_MAX_SESSION_SECONDS = 1800;
|
|
279
|
+
var avatarSourceKindSchema = z.enum(["portrait", "source_video"]);
|
|
280
|
+
var liveKitSttModeSchema = z.enum(["server", "off"]);
|
|
281
|
+
var renderBackendSchema = z.string();
|
|
282
|
+
var sessionLiveEditSchema = z.object({
|
|
283
|
+
rules: z.string().min(1).max(2e3),
|
|
284
|
+
cooldown_seconds: z.number().int().min(5).max(600).optional(),
|
|
285
|
+
// Which machinery runs the re-edit. Absent ⇒ the server default ("editor"). A deploy
|
|
286
|
+
// that cannot provide the requested renderer serves the editor lane and logs it,
|
|
287
|
+
// rather than failing a call that would otherwise have connected fine.
|
|
288
|
+
renderer: z.enum(["editor", "generative"]).optional()
|
|
289
|
+
}).strict();
|
|
290
|
+
var sessionSupportEditsSchema = z.object({
|
|
291
|
+
instruction: z.string().min(1).max(1e3),
|
|
292
|
+
reference_url: z.string().url().optional(),
|
|
293
|
+
live_edit: sessionLiveEditSchema.optional()
|
|
294
|
+
}).strict();
|
|
295
|
+
var LLM_PROVIDERS = ["local", "gemini", "openai"];
|
|
296
|
+
var llmProviderSchema = z.enum(LLM_PROVIDERS);
|
|
297
|
+
var llmConfigSchema = z.object({
|
|
298
|
+
backend: llmProviderSchema.optional(),
|
|
299
|
+
model: z.string().max(200).nullable().optional()
|
|
300
|
+
}).strict();
|
|
301
|
+
var llmSelectionSchema = z.object({
|
|
302
|
+
provider: llmProviderSchema,
|
|
303
|
+
model: z.string().max(200).nullable().optional()
|
|
304
|
+
}).strict();
|
|
305
|
+
var liveKitInitialContextMessageSchema = z.object({
|
|
306
|
+
role: z.enum(["system", "user", "assistant"]),
|
|
307
|
+
content: z.string().min(1).max(4e3)
|
|
308
|
+
}).strict();
|
|
309
|
+
var CARTESIA_TTS_MODELS = [
|
|
310
|
+
"cartesia/sonic-2",
|
|
311
|
+
"cartesia/sonic-2-latest",
|
|
312
|
+
"cartesia/sonic-3",
|
|
313
|
+
"cartesia/sonic-3-latest",
|
|
314
|
+
"cartesia/sonic-turbo",
|
|
315
|
+
"cartesia/sonic-turbo-latest"
|
|
316
|
+
];
|
|
317
|
+
var cartesiaTtsModelSchema = z.enum(CARTESIA_TTS_MODELS);
|
|
318
|
+
var cartesiaVoiceSpecSchema = z.object({
|
|
319
|
+
provider: z.literal("cartesia"),
|
|
320
|
+
model: cartesiaTtsModelSchema.default("cartesia/sonic-3"),
|
|
321
|
+
voice_id: z.string().min(1).max(120),
|
|
322
|
+
speed: z.number().min(0.5).max(2).nullable().optional(),
|
|
323
|
+
emotion: z.string().min(1).max(80).nullable().optional(),
|
|
324
|
+
language: z.string().min(2).max(16).nullable().optional()
|
|
325
|
+
}).strict();
|
|
326
|
+
var FISH_TTS_MODELS = ["speech-1.6", "s1", "s2-pro", "speech-1.5", "s1-mini"];
|
|
327
|
+
var fishTtsModelSchema = z.enum(FISH_TTS_MODELS);
|
|
328
|
+
var breezeVoiceSpecSchema = z.object({
|
|
329
|
+
provider: z.literal("breezeblue"),
|
|
330
|
+
model: z.string().min(1).max(80).default("bluebell-v1-en"),
|
|
331
|
+
voice_id: z.string().min(1).max(120),
|
|
332
|
+
guidance_scale: z.number().min(1).max(10).nullable().optional(),
|
|
333
|
+
instructions: z.string().min(1).max(1e3).nullable().optional(),
|
|
334
|
+
language: z.string().min(2).max(16).nullable().optional()
|
|
335
|
+
}).strict();
|
|
336
|
+
var fishVoiceSpecSchema = z.object({
|
|
337
|
+
provider: z.literal("fish"),
|
|
338
|
+
model: fishTtsModelSchema.default("speech-1.6"),
|
|
339
|
+
voice_id: z.string().min(1).max(120),
|
|
340
|
+
speed: z.number().min(0.5).max(2).nullable().optional(),
|
|
341
|
+
emotion: z.string().min(1).max(80).nullable().optional(),
|
|
342
|
+
language: z.string().min(2).max(16).nullable().optional()
|
|
343
|
+
}).strict();
|
|
344
|
+
var voiceSpecSchema = z.discriminatedUnion("provider", [
|
|
345
|
+
cartesiaVoiceSpecSchema,
|
|
346
|
+
breezeVoiceSpecSchema,
|
|
347
|
+
fishVoiceSpecSchema
|
|
348
|
+
]);
|
|
349
|
+
var nullableUrlSchema = z.string().url().nullable();
|
|
350
|
+
var clipTriggerSchema = z.enum([
|
|
351
|
+
"idle",
|
|
352
|
+
"listen",
|
|
353
|
+
"think",
|
|
354
|
+
"directive"
|
|
355
|
+
]);
|
|
356
|
+
var sessionClipSchema = z.object({
|
|
357
|
+
clip_id: z.string().regex(/^[A-Za-z0-9_-]{1,64}$/, "clip_id must be a slug").refine((id) => id !== "primary", "'primary' is reserved for the avatar's source video"),
|
|
358
|
+
source_video_url: z.string().url().optional(),
|
|
359
|
+
video_cache_id: z.string().min(8).max(160).optional(),
|
|
360
|
+
max_seconds: z.number().min(1).max(10).optional(),
|
|
361
|
+
trigger: clipTriggerSchema.optional(),
|
|
362
|
+
loop: z.boolean().optional(),
|
|
363
|
+
weight: z.number().min(0).max(100).optional(),
|
|
364
|
+
crossfade_ms: z.number().int().min(0).max(1e3).optional(),
|
|
365
|
+
trim_start_ms: z.number().int().min(0).max(2e3).optional(),
|
|
366
|
+
trim_end_ms: z.number().int().min(0).max(2e3).optional(),
|
|
367
|
+
// The cue the character reads to decide this clip. `when` is the public name and the
|
|
368
|
+
// one the docs use; `hint` is the name the wire first shipped under and still accepts.
|
|
369
|
+
// Both are listed because this object is `.strict()` — omitting `when` would make the
|
|
370
|
+
// public name a validation error. Send one, never both.
|
|
371
|
+
when: z.string().min(1).max(120).optional(),
|
|
372
|
+
hint: z.string().min(1).max(120).optional()
|
|
373
|
+
}).strict().refine((clip) => clip.source_video_url || clip.video_cache_id, {
|
|
374
|
+
message: "a clip needs source_video_url or video_cache_id"
|
|
375
|
+
}).refine((clip) => !(clip.when && clip.hint), {
|
|
376
|
+
message: "set `when` or `hint`, not both \u2014 they are the same field",
|
|
377
|
+
path: ["when"]
|
|
378
|
+
});
|
|
379
|
+
var sessionChoreographySchema = z.object({
|
|
380
|
+
idle_dwell_min_seconds: z.number().min(1).max(60).optional(),
|
|
381
|
+
idle_dwell_max_seconds: z.number().min(1).max(120).optional(),
|
|
382
|
+
special_weight: z.number().min(0).max(100).optional(),
|
|
383
|
+
start_grace_seconds: z.number().min(0).max(60).optional(),
|
|
384
|
+
crossfade_ms: z.number().int().min(0).max(1e3).optional(),
|
|
385
|
+
crossfade_easing: z.enum(["linear", "smooth", "ease_out"]).optional(),
|
|
386
|
+
wrap_crossfade_ms: z.number().int().min(0).max(1e3).optional()
|
|
387
|
+
}).strict().refine(
|
|
388
|
+
(c) => c.idle_dwell_min_seconds === void 0 || c.idle_dwell_max_seconds === void 0 || c.idle_dwell_min_seconds <= c.idle_dwell_max_seconds,
|
|
389
|
+
{ message: "idle_dwell_min_seconds must be <= idle_dwell_max_seconds" }
|
|
390
|
+
);
|
|
391
|
+
var sessionBehaviorSchema = z.object({
|
|
392
|
+
gestures_enabled: z.boolean().optional()
|
|
393
|
+
}).strict();
|
|
394
|
+
var transcriptWebhookSchema = z.object({
|
|
395
|
+
url: z.string().url().max(500),
|
|
396
|
+
secret: z.string().min(16).max(200)
|
|
397
|
+
}).strict();
|
|
398
|
+
var clientMetadataSchema = z.record(z.string().min(1).max(64), z.string().max(200)).refine((value) => Object.keys(value).length <= 16, {
|
|
399
|
+
message: "client_metadata carries at most 16 entries"
|
|
400
|
+
});
|
|
401
|
+
var MAX_SESSION_INSTRUCTIONS_CHARS = 8e3;
|
|
402
|
+
z.object({
|
|
403
|
+
avatar_id: z.string().min(1).max(160).default(DEFAULT_AVATAR_ID),
|
|
404
|
+
background_id: z.string().min(1).max(160).default(DEFAULT_BACKGROUND_ID),
|
|
405
|
+
mode: sessionModeSchema.default(DEFAULT_SESSION_MODE),
|
|
406
|
+
create_room: z.boolean().default(true),
|
|
407
|
+
dispatch_agent: z.boolean().default(true),
|
|
408
|
+
instructions: z.string().min(1).max(MAX_SESSION_INSTRUCTIONS_CHARS).optional(),
|
|
409
|
+
initial_context: z.array(liveKitInitialContextMessageSchema).max(32).default([]),
|
|
410
|
+
initial_say: z.string().min(1).max(1e3).optional(),
|
|
411
|
+
llm: llmConfigSchema.nullable().optional(),
|
|
412
|
+
max_session_seconds: z.number().int().min(1).max(DEFAULT_MAX_SESSION_SECONDS).optional(),
|
|
413
|
+
participant_identity: z.string().min(1).max(160).optional(),
|
|
414
|
+
participant_name: z.string().max(160).optional(),
|
|
415
|
+
queue_ticket_id: z.string().min(1).max(160).optional(),
|
|
416
|
+
portrait_url: nullableUrlSchema.optional(),
|
|
417
|
+
room_name: z.string().min(1).max(160).optional(),
|
|
418
|
+
source_kind: avatarSourceKindSchema.default("portrait"),
|
|
419
|
+
source_video_url: nullableUrlSchema.optional(),
|
|
420
|
+
stt_mode: liveKitSttModeSchema.default("server"),
|
|
421
|
+
video_cache_id: z.string().min(1).max(240).nullable().optional(),
|
|
422
|
+
voice: voiceSpecSchema.nullable().optional(),
|
|
423
|
+
voice_id: z.string().min(1).max(240).nullable().optional(),
|
|
424
|
+
// Deliberately UNBOUNDED. This object is `.strict()`, so it rejects rather than trims:
|
|
425
|
+
// a count cap here does not mean "use fewer clips", it means "there is no call". How
|
|
426
|
+
// many a session actually warms is decided where the clips are loaded, and loading
|
|
427
|
+
// fewer is always safe — so the wire must not hold a number too.
|
|
428
|
+
clip_library: z.array(sessionClipSchema).optional(),
|
|
429
|
+
choreography: sessionChoreographySchema.optional(),
|
|
430
|
+
behavior: sessionBehaviorSchema.optional(),
|
|
431
|
+
expression_profile: z.string().min(1).max(40).optional(),
|
|
432
|
+
render_backend: renderBackendSchema.optional(),
|
|
433
|
+
support_edits: sessionSupportEditsSchema.optional(),
|
|
434
|
+
transcript_webhook: transcriptWebhookSchema.optional(),
|
|
435
|
+
client_metadata: clientMetadataSchema.optional()
|
|
436
|
+
}).strict().superRefine((value, ctx) => {
|
|
437
|
+
if (value.support_edits && value.render_backend === "generative") {
|
|
438
|
+
ctx.addIssue({
|
|
439
|
+
code: "custom",
|
|
440
|
+
message: "support_edits needs a source video to edit; it cannot be combined with render_backend='generative'",
|
|
441
|
+
path: ["support_edits"]
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
if (value.support_edits && value.mode === "voice") {
|
|
445
|
+
ctx.addIssue({
|
|
446
|
+
code: "custom",
|
|
447
|
+
message: "support_edits needs a video session; it cannot be combined with mode='voice'",
|
|
448
|
+
path: ["support_edits"]
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
if (value.source_kind === "portrait") {
|
|
452
|
+
if (value.source_video_url || value.video_cache_id) {
|
|
453
|
+
ctx.addIssue({
|
|
454
|
+
code: "custom",
|
|
455
|
+
message: "source_video_url/video_cache_id require source_kind='source_video'",
|
|
456
|
+
path: ["source_kind"]
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
if (value.portrait_url) {
|
|
462
|
+
ctx.addIssue({
|
|
463
|
+
code: "custom",
|
|
464
|
+
message: "portrait_url cannot be combined with source_kind='source_video'",
|
|
465
|
+
path: ["portrait_url"]
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
if (!value.source_video_url && !value.video_cache_id) {
|
|
469
|
+
ctx.addIssue({
|
|
470
|
+
code: "custom",
|
|
471
|
+
message: "source_kind='source_video' requires source_video_url or video_cache_id",
|
|
472
|
+
path: ["source_video_url"]
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
z.object({
|
|
477
|
+
avatarId: z.string().min(1).max(160),
|
|
478
|
+
backgroundId: z.string().min(1).max(160).default(DEFAULT_BACKGROUND_ID),
|
|
479
|
+
mode: sessionModeSchema.default(DEFAULT_SESSION_MODE),
|
|
480
|
+
createRoom: z.boolean().default(true),
|
|
481
|
+
dispatchAgent: z.boolean().default(true),
|
|
482
|
+
instructions: z.string().min(1).max(MAX_SESSION_INSTRUCTIONS_CHARS).optional(),
|
|
483
|
+
initialContext: z.array(liveKitInitialContextMessageSchema).max(32).default([]),
|
|
484
|
+
initialSay: z.string().min(1).max(1e3).optional(),
|
|
485
|
+
llm: llmSelectionSchema.nullable().optional(),
|
|
486
|
+
maxSessionSeconds: z.number().int().min(1).max(DEFAULT_MAX_SESSION_SECONDS).optional(),
|
|
487
|
+
participantIdentity: z.string().min(1).max(160).optional(),
|
|
488
|
+
participantName: z.string().max(160).optional(),
|
|
489
|
+
queueTicketId: z.string().min(1).max(160).optional(),
|
|
490
|
+
roomName: z.string().min(1).max(160).optional(),
|
|
491
|
+
sttMode: liveKitSttModeSchema.default("server"),
|
|
492
|
+
voice: voiceSpecSchema.nullable().optional(),
|
|
493
|
+
voiceId: z.string().min(1).max(240).nullable().optional(),
|
|
494
|
+
// Unbounded, for the same reason as `clip_library` on the wire schema above.
|
|
495
|
+
clipLibrary: z.array(sessionClipSchema).optional(),
|
|
496
|
+
behavior: sessionBehaviorSchema.optional(),
|
|
497
|
+
renderBackend: renderBackendSchema.optional(),
|
|
498
|
+
supportEdits: sessionSupportEditsSchema.optional(),
|
|
499
|
+
transcriptWebhook: transcriptWebhookSchema.optional(),
|
|
500
|
+
clientMetadata: clientMetadataSchema.optional()
|
|
501
|
+
}).strict();
|
|
502
|
+
var liveKitSessionGrantSchema = z.object({
|
|
503
|
+
recording: recordingArtifactSchema.optional(),
|
|
504
|
+
status: z.literal("ready").default("ready"),
|
|
505
|
+
session_id: z.string().min(1),
|
|
506
|
+
room_name: z.string().min(1),
|
|
507
|
+
livekit_url: z.string().min(1),
|
|
508
|
+
participant_token: z.string().min(1),
|
|
509
|
+
participant_identity: z.string().min(1),
|
|
510
|
+
reservation_expires_at: z.string().datetime({ offset: true }),
|
|
511
|
+
stt_mode: liveKitSttModeSchema.default("server"),
|
|
512
|
+
room_created: z.boolean().default(false),
|
|
513
|
+
dispatch_created: z.boolean().default(false),
|
|
514
|
+
join_timeout_seconds: z.number().int().nonnegative().default(0),
|
|
515
|
+
idle_timeout_seconds: z.number().int().nonnegative().default(0),
|
|
516
|
+
max_session_seconds: z.number().int().nonnegative().default(0)
|
|
517
|
+
}).passthrough();
|
|
518
|
+
var sessionEndReasonSchema = z.enum([
|
|
519
|
+
"user_ended",
|
|
520
|
+
"session_cap",
|
|
521
|
+
"idle",
|
|
522
|
+
"disconnected",
|
|
523
|
+
"out_of_credits",
|
|
524
|
+
"agent_ended",
|
|
525
|
+
"failed"
|
|
526
|
+
]);
|
|
527
|
+
var approachingEndReasonSchema = z.enum(["session_cap", "idle"]);
|
|
528
|
+
var sessionClockFrameSchema = z.object({
|
|
529
|
+
kind: z.literal("session_clock"),
|
|
530
|
+
started_at_unix_ms: z.number().int().nonnegative(),
|
|
531
|
+
max_session_seconds: z.number().int().nonnegative(),
|
|
532
|
+
idle_timeout_seconds: z.number().int().nonnegative()
|
|
533
|
+
}).strict();
|
|
534
|
+
var endingFrameSchema = z.object({ kind: z.literal("ending"), reason: approachingEndReasonSchema }).strict();
|
|
535
|
+
var closingTurnDoneFrameSchema = z.object({ kind: z.literal("closing_turn_done"), turn_id: z.string().min(1) }).strict();
|
|
536
|
+
var endedFrameSchema = z.object({ kind: z.literal("ended"), reason: sessionEndReasonSchema }).strict();
|
|
537
|
+
var behaviorStateFrameSchema = z.object({
|
|
538
|
+
kind: z.literal("behavior_state"),
|
|
539
|
+
state: z.string().min(1).max(32),
|
|
540
|
+
clip_id: z.string().min(1).max(64).optional(),
|
|
541
|
+
trigger: clipTriggerSchema.optional(),
|
|
542
|
+
loop: z.boolean().optional(),
|
|
543
|
+
prev_clip_id: z.string().min(1).max(64).optional()
|
|
544
|
+
}).strip();
|
|
545
|
+
var clipAckFrameSchema = z.object({
|
|
546
|
+
kind: z.literal("clip_ack"),
|
|
547
|
+
request_id: z.string().max(64),
|
|
548
|
+
accepted: z.boolean(),
|
|
549
|
+
reason: z.string().max(64)
|
|
550
|
+
}).strip();
|
|
551
|
+
z.discriminatedUnion("kind", [
|
|
552
|
+
sessionClockFrameSchema,
|
|
553
|
+
endingFrameSchema,
|
|
554
|
+
closingTurnDoneFrameSchema,
|
|
555
|
+
endedFrameSchema,
|
|
556
|
+
behaviorStateFrameSchema,
|
|
557
|
+
clipAckFrameSchema
|
|
558
|
+
]);
|
|
559
|
+
var liveKitCapacitySnapshotSchema = z.object({
|
|
560
|
+
// Placement identity + per-worker session ceiling. These are always present on the
|
|
561
|
+
// wire (the platform serializes them and they come back on the grant, so a customer
|
|
562
|
+
// already sees them) but are typed OPTIONAL here on purpose: this SDK is the defensive
|
|
563
|
+
// READER of that wire, so a consumer must tolerate a response variant that omits them
|
|
564
|
+
// rather than hard-fail parse. `max_sessions_per_gpu` is how many sessions one waking
|
|
565
|
+
// worker serves — a queue-depth estimate input a consumer reads off the busy response.
|
|
566
|
+
capacity_pool: z.string().min(1).optional(),
|
|
567
|
+
agent_name: z.string().min(1).optional(),
|
|
568
|
+
max_sessions: z.number().int().nonnegative(),
|
|
569
|
+
max_sessions_per_gpu: z.number().int().positive().optional(),
|
|
570
|
+
worker_count: z.number().int().nonnegative(),
|
|
571
|
+
active_sessions: z.number().int().nonnegative(),
|
|
572
|
+
reserved_sessions: z.number().int().nonnegative(),
|
|
573
|
+
observed_worker_active_sessions: z.number().int().nonnegative(),
|
|
574
|
+
available_sessions: z.number().int().nonnegative(),
|
|
575
|
+
queue_size: z.number().int().nonnegative(),
|
|
576
|
+
admission_open: z.boolean(),
|
|
577
|
+
recommended_retry_ms: z.number().int().nonnegative(),
|
|
578
|
+
load: z.number().min(0).max(1)
|
|
579
|
+
}).passthrough();
|
|
580
|
+
z.object({
|
|
581
|
+
message: z.string().min(1),
|
|
582
|
+
capacity: liveKitCapacitySnapshotSchema,
|
|
583
|
+
queue_size: z.number().int().nonnegative(),
|
|
584
|
+
queue_ticket_id: z.string().min(1).optional(),
|
|
585
|
+
queue_position: z.number().int().positive().optional(),
|
|
586
|
+
recommended_retry_ms: z.number().int().nonnegative()
|
|
587
|
+
}).strict();
|
|
588
|
+
z.enum([
|
|
589
|
+
"page_hide",
|
|
590
|
+
"disconnected",
|
|
591
|
+
"superseded",
|
|
592
|
+
"unmount",
|
|
593
|
+
"manual",
|
|
594
|
+
"idle_timeout"
|
|
595
|
+
]);
|
|
236
596
|
|
|
237
597
|
// ../http-client/src/client.ts
|
|
238
598
|
var DEFAULT_BASE_URL = "https://realtimeavatar.ai/api/v1";
|
|
239
|
-
var SDK_VERSION = "0.
|
|
599
|
+
var SDK_VERSION = "0.16.0";
|
|
240
600
|
var RealtimeAvatar = class {
|
|
241
601
|
#apiKey;
|
|
242
602
|
#baseUrl;
|
|
@@ -281,6 +641,7 @@ var RealtimeAvatar = class {
|
|
|
281
641
|
if (options.maxSeconds !== void 0) body.max_session_seconds = Math.floor(options.maxSeconds);
|
|
282
642
|
if (options.voice !== void 0) body.voice = options.voice;
|
|
283
643
|
if (options.metadata !== void 0) body.client_metadata = options.metadata;
|
|
644
|
+
if (options.recording !== void 0) body.recording = recordingModeSchema.parse(options.recording);
|
|
284
645
|
if (options.clientTools) body.capabilities = ["client_tools"];
|
|
285
646
|
if (options.transcript !== void 0) {
|
|
286
647
|
body.transcript_webhook = { url: options.transcript.url, secret: options.transcript.secret };
|
|
@@ -299,20 +660,23 @@ var RealtimeAvatar = class {
|
|
|
299
660
|
};
|
|
300
661
|
}
|
|
301
662
|
}
|
|
302
|
-
const
|
|
663
|
+
const raw = await this.#json(response);
|
|
664
|
+
const grant = liveKitSessionGrantSchema.parse(raw);
|
|
665
|
+
if (!isRecord(raw)) throw new RealtimeAvatarError("Invalid session grant");
|
|
303
666
|
return {
|
|
304
667
|
status: "ready",
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
668
|
+
...grant.recording === void 0 ? {} : { recording: grant.recording },
|
|
669
|
+
sessionId: grant.session_id,
|
|
670
|
+
roomName: grant.room_name,
|
|
671
|
+
livekitUrl: grant.livekit_url,
|
|
672
|
+
participantToken: grant.participant_token,
|
|
673
|
+
participantIdentity: grant.participant_identity,
|
|
674
|
+
maxSessionSeconds: grant.max_session_seconds,
|
|
675
|
+
idleTimeoutSeconds: grant.idle_timeout_seconds,
|
|
676
|
+
reservationExpiresAt: grant.reservation_expires_at,
|
|
313
677
|
// The parsed fields above are for YOUR logic. Relay `raw` to the client untouched:
|
|
314
678
|
// the browser SDK validates the grant strictly and rejects an added or renamed key.
|
|
315
|
-
raw
|
|
679
|
+
raw
|
|
316
680
|
};
|
|
317
681
|
}
|
|
318
682
|
/**
|
|
@@ -632,6 +996,24 @@ var RealtimeAvatar = class {
|
|
|
632
996
|
cursor = page.nextCursor ?? void 0;
|
|
633
997
|
} while (cursor);
|
|
634
998
|
}
|
|
999
|
+
/** Recording metadata for this account, optionally limited to one call. Requires recordings:read. */
|
|
1000
|
+
async listRecordings(options = {}) {
|
|
1001
|
+
const parsed = listRecordingsQuerySchema.parse(options);
|
|
1002
|
+
const query = new URLSearchParams();
|
|
1003
|
+
if (parsed.sessionId !== void 0) query.set("sessionId", parsed.sessionId);
|
|
1004
|
+
if (options.limit !== void 0) query.set("limit", String(parsed.limit));
|
|
1005
|
+
if (parsed.cursor !== void 0) query.set("cursor", parsed.cursor);
|
|
1006
|
+
const suffix = query.size ? `?${query}` : "";
|
|
1007
|
+
return listRecordingsResponseSchema.parse(await this.#json(await this.#request("GET", `/recordings${suffix}`)));
|
|
1008
|
+
}
|
|
1009
|
+
/** Current recording status. Final media can become ready after the call has ended. */
|
|
1010
|
+
async getRecording(recordingId) {
|
|
1011
|
+
return recordingArtifactSchema.parse(await this.#json(await this.#request("GET", `/recordings/${encodeURIComponent(recordingId)}`)));
|
|
1012
|
+
}
|
|
1013
|
+
/** Renewable playback access. Keep recordingId in storage; URLs expire at expiresAt. */
|
|
1014
|
+
async getRecordingAccess(recordingId) {
|
|
1015
|
+
return recordingAccessResponseSchema.parse(await this.#json(await this.#request("GET", `/recordings/${encodeURIComponent(recordingId)}/access`)));
|
|
1016
|
+
}
|
|
635
1017
|
async creditBalance() {
|
|
636
1018
|
return await this.#json(await this.#request("GET", "/credits/balance"));
|
|
637
1019
|
}
|
|
@@ -811,4 +1193,4 @@ function isQueued(result) {
|
|
|
811
1193
|
return "queued" in result;
|
|
812
1194
|
}
|
|
813
1195
|
|
|
814
|
-
export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, verifyTranscript };
|
|
1196
|
+
export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, listRecordingsResponseSchema, recordingAccessResponseSchema, recordingArtifactSchema, recordingModeSchema, verifyTranscript };
|
package/dist/express.d.ts
CHANGED
package/dist/express.js
CHANGED
package/dist/hono.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { P as ProxyConfig } from './types-
|
|
2
|
-
import './types-
|
|
1
|
+
import { P as ProxyConfig } from './types-Bphj69NC.js';
|
|
2
|
+
import './types-VOv0vsFs.js';
|
|
3
|
+
import 'zod';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Hono (and anything else built on Fetch handlers — Workers, Bun, Deno).
|
package/dist/hono.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as CallPolicy, C as CallMode, S as StartCallResult, E as EndCallOptions, A as Avatar, b as AvatarUpdate, c as AvatarSourceSwap, d as ClipLibraryDeclaration, e as ClipLibraryUpdate, L as LoopRedirect, f as ClipLibrary, g as AssetKind, h as Asset, i as ListSessionsOptions, U as UsageSessionPage, j as UsageSession, k as CreditBalance, T as TranscriptPayload } from './types-
|
|
2
|
-
export {
|
|
1
|
+
import { a as CallPolicy, C as CallMode, S as StartCallResult, E as EndCallOptions, A as Avatar, b as AvatarUpdate, c as AvatarSourceSwap, d as ClipLibraryDeclaration, e as ClipLibraryUpdate, L as LoopRedirect, f as ClipLibrary, g as AssetKind, h as Asset, i as ListSessionsOptions, U as UsageSessionPage, j as UsageSession, k as ListRecordingsQuery, l as ListRecordingsResponse, R as RecordingArtifact, m as RecordingAccessResponse, n as CreditBalance, T as TranscriptPayload } from './types-VOv0vsFs.js';
|
|
2
|
+
export { o as AvatarClip, p as CallConnection, q as CallQueued, r as ClipDeclaration, s as ClipLibraryPlan, t as ClipSource, u as ContextMessage, v as EndCallReason, w as RecordingArtifactStatus, x as RecordingMode, V as VideoPolicy, y as isQueued, z as listRecordingsResponseSchema, B as recordingAccessResponseSchema, D as recordingArtifactSchema, F as recordingModeSchema } from './types-VOv0vsFs.js';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
|
|
5
5
|
interface RealtimeAvatarOptions {
|
|
@@ -265,6 +265,12 @@ declare class RealtimeAvatar {
|
|
|
265
265
|
* caller writing a monthly report should not have to hold all of them to start writing.
|
|
266
266
|
*/
|
|
267
267
|
iterateSessions(options?: ListSessionsOptions): AsyncGenerator<UsageSession>;
|
|
268
|
+
/** Recording metadata for this account, optionally limited to one call. Requires recordings:read. */
|
|
269
|
+
listRecordings(options?: ListRecordingsQuery): Promise<ListRecordingsResponse>;
|
|
270
|
+
/** Current recording status. Final media can become ready after the call has ended. */
|
|
271
|
+
getRecording(recordingId: string): Promise<RecordingArtifact>;
|
|
272
|
+
/** Renewable playback access. Keep recordingId in storage; URLs expire at expiresAt. */
|
|
273
|
+
getRecordingAccess(recordingId: string): Promise<RecordingAccessResponse>;
|
|
268
274
|
creditBalance(): Promise<CreditBalance>;
|
|
269
275
|
}
|
|
270
276
|
|
|
@@ -356,4 +362,4 @@ declare const clipLibraryDeclarationSchema: z.ZodObject<{
|
|
|
356
362
|
}, z.core.$strict>>>>;
|
|
357
363
|
}, z.core.$strict>;
|
|
358
364
|
|
|
359
|
-
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 };
|
|
365
|
+
export { Asset, AssetKind, Avatar, AvatarSourceSwap, AvatarUpdate, CallMode, CallPolicy, ClipLibrary, ClipLibraryDeclaration, ClipLibraryUpdate, CreditBalance, EndCallOptions, ListRecordingsQuery, ListRecordingsResponse, ListSessionsOptions, LoopRedirect, RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, type RealtimeAvatarOptions, RecordingAccessResponse, RecordingArtifact, type StartCallOptions, StartCallResult, TranscriptPayload, UsageSession, UsageSessionPage, clipLibraryDeclarationSchema, verifyTranscript };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, verifyTranscript } from './chunk-
|
|
1
|
+
export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, listRecordingsResponseSchema, recordingAccessResponseSchema, recordingArtifactSchema, recordingModeSchema, verifyTranscript } from './chunk-V26PE2M3.js';
|
package/dist/nextjs.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { P as ProxyConfig } from './types-
|
|
2
|
-
import './types-
|
|
1
|
+
import { P as ProxyConfig } from './types-Bphj69NC.js';
|
|
2
|
+
import './types-VOv0vsFs.js';
|
|
3
|
+
import 'zod';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* App Router. Mount at `app/api/realtime-avatar/[...path]/route.ts`:
|
package/dist/nextjs.js
CHANGED