realtime-avatar 0.4.0 → 0.5.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/express.d.ts +2 -2
- package/dist/express.js +93 -3
- package/dist/hono.d.ts +2 -2
- package/dist/hono.js +93 -3
- package/dist/index.d.ts +62 -3
- package/dist/index.js +93 -3
- package/dist/nextjs.d.ts +2 -2
- package/dist/nextjs.js +93 -3
- package/dist/react-native.js +2 -1
- package/dist/react.js +2 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +93 -3
- package/dist/tanstack-start.d.ts +2 -2
- package/dist/tanstack-start.js +93 -3
- package/dist/{types-9leGOgVv.d.ts → types-B45GgjrV.d.ts} +1 -1
- package/dist/{types-C5zypiVY.d.ts → types-DLxz6uFE.d.ts} +190 -7
- package/package.json +1 -1
package/dist/express.d.ts
CHANGED
package/dist/express.js
CHANGED
|
@@ -43,7 +43,7 @@ var RealtimeAvatarHttpError = class extends RealtimeAvatarError {
|
|
|
43
43
|
|
|
44
44
|
// ../http-client/src/client.ts
|
|
45
45
|
var DEFAULT_BASE_URL = "https://realtimeavatar.ai/api/v1";
|
|
46
|
-
var SDK_VERSION = "0.
|
|
46
|
+
var SDK_VERSION = "0.5.0";
|
|
47
47
|
var RealtimeAvatar = class {
|
|
48
48
|
#apiKey;
|
|
49
49
|
#baseUrl;
|
|
@@ -60,7 +60,7 @@ var RealtimeAvatar = class {
|
|
|
60
60
|
if (!options.apiKey) throw new RealtimeAvatarError("apiKey is required");
|
|
61
61
|
this.#apiKey = options.apiKey;
|
|
62
62
|
this.#baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
63
|
-
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
63
|
+
this.#fetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
64
64
|
this.#timeoutMs = options.timeoutMs ?? 6e4;
|
|
65
65
|
this.#maxRetries = Math.max(0, options.maxRetries ?? 2);
|
|
66
66
|
this.#userAgent = [`realtime-avatar-sdk/${SDK_VERSION}`, runtimeTag(), options.userAgent].filter(Boolean).join(" ");
|
|
@@ -197,9 +197,91 @@ var RealtimeAvatar = class {
|
|
|
197
197
|
await this.#json(await this.#request("PATCH", `/avatars/${avatarId}`, { json: patch }))
|
|
198
198
|
);
|
|
199
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Re-shoot the character — swap in new footage as her resting loop.
|
|
202
|
+
*
|
|
203
|
+
* ASYNCHRONOUS, and that is the whole design. This returns as soon as the swap is
|
|
204
|
+
* accepted; the avatar keeps serving its CURRENT loop, cache and clips the entire time,
|
|
205
|
+
* and cuts over to the new generation in one step once the replacement is prepared.
|
|
206
|
+
* A call minted a second after this returns is a normal call on the old footage.
|
|
207
|
+
*
|
|
208
|
+
* Two consequences worth designing for:
|
|
209
|
+
*
|
|
210
|
+
* - **The clip library empties and refills.** Old takes are footage of the old source and
|
|
211
|
+
* cannot splice against the new loop, so they are dropped and re-rendered. Between the
|
|
212
|
+
* cutover and the last re-render she rests on the new loop with less variety — never
|
|
213
|
+
* broken, just plainer. Do not gate your UI on the library being full.
|
|
214
|
+
* - **A failed swap does not fail the avatar.** She keeps serving, `status` stays `ready`,
|
|
215
|
+
* and the reason lands on `error`. So poll `getAvatar` and read `error` — a non-null
|
|
216
|
+
* `error` on a `ready` avatar is the swap that did not take, not an unhealthy character.
|
|
217
|
+
*
|
|
218
|
+
* The frame this rests on comes from the new footage: pass `anchorTimeMs` when frame 0 of
|
|
219
|
+
* the take is mid-blink. Video-sourced avatars only — a portrait-anchored one is a 422.
|
|
220
|
+
*/
|
|
221
|
+
async swapSource(avatarId, input) {
|
|
222
|
+
const json2 = { sourceAssetId: input.sourceAssetId };
|
|
223
|
+
if (input.anchorTimeMs !== void 0) json2.anchorTimeMs = input.anchorTimeMs;
|
|
224
|
+
return toAvatar(await this.#json(await this.#request("PATCH", `/avatars/${avatarId}`, { json: json2 })));
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Re-point the anchor at a different frame of the loop she ALREADY has — same footage,
|
|
228
|
+
* different rest pose. `swapSource` replaces the footage; this only moves the frame.
|
|
229
|
+
*
|
|
230
|
+
* Re-renders the clip library the same way, with the same degradation window, and is
|
|
231
|
+
* clamped server-side to the loop's last extractable frame (read the avatar back to see
|
|
232
|
+
* what was actually cut). Video-sourced avatars only.
|
|
233
|
+
*/
|
|
234
|
+
async retimeAnchor(avatarId, anchorTimeMs) {
|
|
235
|
+
return toAvatar(
|
|
236
|
+
await this.#json(await this.#request("PATCH", `/avatars/${avatarId}`, { json: { anchorTimeMs } }))
|
|
237
|
+
);
|
|
238
|
+
}
|
|
200
239
|
async deleteAvatar(avatarId) {
|
|
201
240
|
await this.#json(await this.#request("DELETE", `/avatars/${avatarId}`));
|
|
202
241
|
}
|
|
242
|
+
// A clip envelope missing `revision` would silently drop `expectedRevision` from the
|
|
243
|
+
// next declare — CAS degrades to unconditional with zero signal — so it throws instead.
|
|
244
|
+
#clipEnvelope(out) {
|
|
245
|
+
if (typeof out.revision !== "number" || !Array.isArray(out.data)) {
|
|
246
|
+
throw new RealtimeAvatarError("clip library response did not match the contract");
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Declare the avatar's full desired clip library — a declaration, not a delta. The
|
|
252
|
+
* platform reconciles it against what exists: unchanged clips are `kept` (still
|
|
253
|
+
* serving), new or changed ones are `queued` to render, and clips you dropped are
|
|
254
|
+
* `retired`. The 202 is acceptance, not readiness — poll `listClips` until no row is
|
|
255
|
+
* `queued` or `generating`. A rejected upload settles `failed`, which is terminal, so
|
|
256
|
+
* waiting for all-`ready` waits forever. While a re-render is in flight the previous
|
|
257
|
+
* take keeps serving, so a declaration never blanks a live avatar.
|
|
258
|
+
*
|
|
259
|
+
* `expectedRevision` is compare-and-set: pass the `revision` you last read and a
|
|
260
|
+
* concurrent writer surfaces as a 409 instead of a lost update. Omit it to declare
|
|
261
|
+
* unconditionally.
|
|
262
|
+
*
|
|
263
|
+
* At most 12 clips: one `idle`, up to two `listen`, the rest `gesture`. An uploaded
|
|
264
|
+
* clip (`source: { assetId }`) must start AND end on the avatar's rest pose — pose
|
|
265
|
+
* validation rejects it otherwise (`status: "failed"`, the verdict in `poseCheck`),
|
|
266
|
+
* and the rest of the library is untouched.
|
|
267
|
+
*/
|
|
268
|
+
async setClipLibrary(avatarId, library) {
|
|
269
|
+
const body = { clips: library.clips };
|
|
270
|
+
if (library.expectedRevision !== void 0) body.expectedRevision = library.expectedRevision;
|
|
271
|
+
return this.#clipEnvelope(
|
|
272
|
+
await this.#json(
|
|
273
|
+
await this.#request("PUT", `/avatars/${avatarId}/clips`, { json: body })
|
|
274
|
+
)
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
/** The avatar's clip library: every non-retired clip, plus revision, anchor and eligibility. */
|
|
278
|
+
async listClips(avatarId) {
|
|
279
|
+
return this.#clipEnvelope(
|
|
280
|
+
await this.#json(
|
|
281
|
+
await this.#request("GET", `/avatars/${avatarId}/clips`)
|
|
282
|
+
)
|
|
283
|
+
);
|
|
284
|
+
}
|
|
203
285
|
/**
|
|
204
286
|
* Reconcile an avatar's clip set after it changes.
|
|
205
287
|
*
|
|
@@ -210,6 +292,10 @@ var RealtimeAvatar = class {
|
|
|
210
292
|
* **At most 32 URLs per call.** This is the whole set for the avatar, not a delta, and the
|
|
211
293
|
* endpoint rejects an oversize list rather than truncating it — so a library that outgrows
|
|
212
294
|
* 32 needs the set trimmed, not split across two calls.
|
|
295
|
+
*
|
|
296
|
+
* @deprecated The externally-hosted clip tier this serves is sunsetting. Declare the
|
|
297
|
+
* library with {@link setClipLibrary} instead — the platform renders and hosts the
|
|
298
|
+
* clips, and pose-validates uploads against the avatar's rest pose.
|
|
213
299
|
*/
|
|
214
300
|
async syncClips(avatarId, clipUrls) {
|
|
215
301
|
const out = await this.#json(
|
|
@@ -385,7 +471,11 @@ function toAvatar(raw) {
|
|
|
385
471
|
displayName: String(a.displayName ?? ""),
|
|
386
472
|
sourceKind: a.sourceKind === "video" ? "video" : "image",
|
|
387
473
|
status: a.status ?? "draft",
|
|
388
|
-
defaultVoiceId: a.defaultVoiceId ? String(a.defaultVoiceId) : null
|
|
474
|
+
defaultVoiceId: a.defaultVoiceId ? String(a.defaultVoiceId) : null,
|
|
475
|
+
sourceAssetId: a.sourceAssetId ? String(a.sourceAssetId) : null,
|
|
476
|
+
// Carried because it is the ONLY channel a failed source swap has: she stays `ready`
|
|
477
|
+
// and serving, and this says why the re-shoot did not take.
|
|
478
|
+
error: a.error ? String(a.error) : null
|
|
389
479
|
};
|
|
390
480
|
}
|
|
391
481
|
function toAsset(raw) {
|
package/dist/hono.d.ts
CHANGED
package/dist/hono.js
CHANGED
|
@@ -43,7 +43,7 @@ var RealtimeAvatarHttpError = class extends RealtimeAvatarError {
|
|
|
43
43
|
|
|
44
44
|
// ../http-client/src/client.ts
|
|
45
45
|
var DEFAULT_BASE_URL = "https://realtimeavatar.ai/api/v1";
|
|
46
|
-
var SDK_VERSION = "0.
|
|
46
|
+
var SDK_VERSION = "0.5.0";
|
|
47
47
|
var RealtimeAvatar = class {
|
|
48
48
|
#apiKey;
|
|
49
49
|
#baseUrl;
|
|
@@ -60,7 +60,7 @@ var RealtimeAvatar = class {
|
|
|
60
60
|
if (!options.apiKey) throw new RealtimeAvatarError("apiKey is required");
|
|
61
61
|
this.#apiKey = options.apiKey;
|
|
62
62
|
this.#baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
63
|
-
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
63
|
+
this.#fetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
64
64
|
this.#timeoutMs = options.timeoutMs ?? 6e4;
|
|
65
65
|
this.#maxRetries = Math.max(0, options.maxRetries ?? 2);
|
|
66
66
|
this.#userAgent = [`realtime-avatar-sdk/${SDK_VERSION}`, runtimeTag(), options.userAgent].filter(Boolean).join(" ");
|
|
@@ -197,9 +197,91 @@ var RealtimeAvatar = class {
|
|
|
197
197
|
await this.#json(await this.#request("PATCH", `/avatars/${avatarId}`, { json: patch }))
|
|
198
198
|
);
|
|
199
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Re-shoot the character — swap in new footage as her resting loop.
|
|
202
|
+
*
|
|
203
|
+
* ASYNCHRONOUS, and that is the whole design. This returns as soon as the swap is
|
|
204
|
+
* accepted; the avatar keeps serving its CURRENT loop, cache and clips the entire time,
|
|
205
|
+
* and cuts over to the new generation in one step once the replacement is prepared.
|
|
206
|
+
* A call minted a second after this returns is a normal call on the old footage.
|
|
207
|
+
*
|
|
208
|
+
* Two consequences worth designing for:
|
|
209
|
+
*
|
|
210
|
+
* - **The clip library empties and refills.** Old takes are footage of the old source and
|
|
211
|
+
* cannot splice against the new loop, so they are dropped and re-rendered. Between the
|
|
212
|
+
* cutover and the last re-render she rests on the new loop with less variety — never
|
|
213
|
+
* broken, just plainer. Do not gate your UI on the library being full.
|
|
214
|
+
* - **A failed swap does not fail the avatar.** She keeps serving, `status` stays `ready`,
|
|
215
|
+
* and the reason lands on `error`. So poll `getAvatar` and read `error` — a non-null
|
|
216
|
+
* `error` on a `ready` avatar is the swap that did not take, not an unhealthy character.
|
|
217
|
+
*
|
|
218
|
+
* The frame this rests on comes from the new footage: pass `anchorTimeMs` when frame 0 of
|
|
219
|
+
* the take is mid-blink. Video-sourced avatars only — a portrait-anchored one is a 422.
|
|
220
|
+
*/
|
|
221
|
+
async swapSource(avatarId, input) {
|
|
222
|
+
const json2 = { sourceAssetId: input.sourceAssetId };
|
|
223
|
+
if (input.anchorTimeMs !== void 0) json2.anchorTimeMs = input.anchorTimeMs;
|
|
224
|
+
return toAvatar(await this.#json(await this.#request("PATCH", `/avatars/${avatarId}`, { json: json2 })));
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Re-point the anchor at a different frame of the loop she ALREADY has — same footage,
|
|
228
|
+
* different rest pose. `swapSource` replaces the footage; this only moves the frame.
|
|
229
|
+
*
|
|
230
|
+
* Re-renders the clip library the same way, with the same degradation window, and is
|
|
231
|
+
* clamped server-side to the loop's last extractable frame (read the avatar back to see
|
|
232
|
+
* what was actually cut). Video-sourced avatars only.
|
|
233
|
+
*/
|
|
234
|
+
async retimeAnchor(avatarId, anchorTimeMs) {
|
|
235
|
+
return toAvatar(
|
|
236
|
+
await this.#json(await this.#request("PATCH", `/avatars/${avatarId}`, { json: { anchorTimeMs } }))
|
|
237
|
+
);
|
|
238
|
+
}
|
|
200
239
|
async deleteAvatar(avatarId) {
|
|
201
240
|
await this.#json(await this.#request("DELETE", `/avatars/${avatarId}`));
|
|
202
241
|
}
|
|
242
|
+
// A clip envelope missing `revision` would silently drop `expectedRevision` from the
|
|
243
|
+
// next declare — CAS degrades to unconditional with zero signal — so it throws instead.
|
|
244
|
+
#clipEnvelope(out) {
|
|
245
|
+
if (typeof out.revision !== "number" || !Array.isArray(out.data)) {
|
|
246
|
+
throw new RealtimeAvatarError("clip library response did not match the contract");
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Declare the avatar's full desired clip library — a declaration, not a delta. The
|
|
252
|
+
* platform reconciles it against what exists: unchanged clips are `kept` (still
|
|
253
|
+
* serving), new or changed ones are `queued` to render, and clips you dropped are
|
|
254
|
+
* `retired`. The 202 is acceptance, not readiness — poll `listClips` until no row is
|
|
255
|
+
* `queued` or `generating`. A rejected upload settles `failed`, which is terminal, so
|
|
256
|
+
* waiting for all-`ready` waits forever. While a re-render is in flight the previous
|
|
257
|
+
* take keeps serving, so a declaration never blanks a live avatar.
|
|
258
|
+
*
|
|
259
|
+
* `expectedRevision` is compare-and-set: pass the `revision` you last read and a
|
|
260
|
+
* concurrent writer surfaces as a 409 instead of a lost update. Omit it to declare
|
|
261
|
+
* unconditionally.
|
|
262
|
+
*
|
|
263
|
+
* At most 12 clips: one `idle`, up to two `listen`, the rest `gesture`. An uploaded
|
|
264
|
+
* clip (`source: { assetId }`) must start AND end on the avatar's rest pose — pose
|
|
265
|
+
* validation rejects it otherwise (`status: "failed"`, the verdict in `poseCheck`),
|
|
266
|
+
* and the rest of the library is untouched.
|
|
267
|
+
*/
|
|
268
|
+
async setClipLibrary(avatarId, library) {
|
|
269
|
+
const body = { clips: library.clips };
|
|
270
|
+
if (library.expectedRevision !== void 0) body.expectedRevision = library.expectedRevision;
|
|
271
|
+
return this.#clipEnvelope(
|
|
272
|
+
await this.#json(
|
|
273
|
+
await this.#request("PUT", `/avatars/${avatarId}/clips`, { json: body })
|
|
274
|
+
)
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
/** The avatar's clip library: every non-retired clip, plus revision, anchor and eligibility. */
|
|
278
|
+
async listClips(avatarId) {
|
|
279
|
+
return this.#clipEnvelope(
|
|
280
|
+
await this.#json(
|
|
281
|
+
await this.#request("GET", `/avatars/${avatarId}/clips`)
|
|
282
|
+
)
|
|
283
|
+
);
|
|
284
|
+
}
|
|
203
285
|
/**
|
|
204
286
|
* Reconcile an avatar's clip set after it changes.
|
|
205
287
|
*
|
|
@@ -210,6 +292,10 @@ var RealtimeAvatar = class {
|
|
|
210
292
|
* **At most 32 URLs per call.** This is the whole set for the avatar, not a delta, and the
|
|
211
293
|
* endpoint rejects an oversize list rather than truncating it — so a library that outgrows
|
|
212
294
|
* 32 needs the set trimmed, not split across two calls.
|
|
295
|
+
*
|
|
296
|
+
* @deprecated The externally-hosted clip tier this serves is sunsetting. Declare the
|
|
297
|
+
* library with {@link setClipLibrary} instead — the platform renders and hosts the
|
|
298
|
+
* clips, and pose-validates uploads against the avatar's rest pose.
|
|
213
299
|
*/
|
|
214
300
|
async syncClips(avatarId, clipUrls) {
|
|
215
301
|
const out = await this.#json(
|
|
@@ -385,7 +471,11 @@ function toAvatar(raw) {
|
|
|
385
471
|
displayName: String(a.displayName ?? ""),
|
|
386
472
|
sourceKind: a.sourceKind === "video" ? "video" : "image",
|
|
387
473
|
status: a.status ?? "draft",
|
|
388
|
-
defaultVoiceId: a.defaultVoiceId ? String(a.defaultVoiceId) : null
|
|
474
|
+
defaultVoiceId: a.defaultVoiceId ? String(a.defaultVoiceId) : null,
|
|
475
|
+
sourceAssetId: a.sourceAssetId ? String(a.sourceAssetId) : null,
|
|
476
|
+
// Carried because it is the ONLY channel a failed source swap has: she stays `ready`
|
|
477
|
+
// and serving, and this says why the re-shoot did not take.
|
|
478
|
+
error: a.error ? String(a.error) : null
|
|
389
479
|
};
|
|
390
480
|
}
|
|
391
481
|
function toAsset(raw) {
|
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
|
|
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 ClipDeclaration, e as ClipLibraryUpdate, f as ClipLibrary, g as ClipSyncResult, h as AssetKind, i as Asset, L as ListSessionsOptions, U as UsageSessionPage, j as UsageSession, k as CreditBalance, T as TranscriptPayload } from './types-DLxz6uFE.js';
|
|
2
|
+
export { l as AvatarClip, m as CallConnection, n as CallQueued, o as ClipLibraryPlan, p as ClipSource, q as ContextMessage, r as EndCallReason, V as VideoPolicy, s as VideoState, t as isQueued } from './types-DLxz6uFE.js';
|
|
3
3
|
|
|
4
4
|
interface RealtimeAvatarOptions {
|
|
5
5
|
/** `tic_live_…` or `tic_test_…`. Server-side only — never ship this to a browser. */
|
|
@@ -98,7 +98,62 @@ declare class RealtimeAvatar {
|
|
|
98
98
|
getAvatar(avatarId: string): Promise<Avatar>;
|
|
99
99
|
/** Re-point what an avatar already is. `defaultVoiceId: null` clears the default voice. */
|
|
100
100
|
updateAvatar(avatarId: string, patch: AvatarUpdate): Promise<Avatar>;
|
|
101
|
+
/**
|
|
102
|
+
* Re-shoot the character — swap in new footage as her resting loop.
|
|
103
|
+
*
|
|
104
|
+
* ASYNCHRONOUS, and that is the whole design. This returns as soon as the swap is
|
|
105
|
+
* accepted; the avatar keeps serving its CURRENT loop, cache and clips the entire time,
|
|
106
|
+
* and cuts over to the new generation in one step once the replacement is prepared.
|
|
107
|
+
* A call minted a second after this returns is a normal call on the old footage.
|
|
108
|
+
*
|
|
109
|
+
* Two consequences worth designing for:
|
|
110
|
+
*
|
|
111
|
+
* - **The clip library empties and refills.** Old takes are footage of the old source and
|
|
112
|
+
* cannot splice against the new loop, so they are dropped and re-rendered. Between the
|
|
113
|
+
* cutover and the last re-render she rests on the new loop with less variety — never
|
|
114
|
+
* broken, just plainer. Do not gate your UI on the library being full.
|
|
115
|
+
* - **A failed swap does not fail the avatar.** She keeps serving, `status` stays `ready`,
|
|
116
|
+
* and the reason lands on `error`. So poll `getAvatar` and read `error` — a non-null
|
|
117
|
+
* `error` on a `ready` avatar is the swap that did not take, not an unhealthy character.
|
|
118
|
+
*
|
|
119
|
+
* The frame this rests on comes from the new footage: pass `anchorTimeMs` when frame 0 of
|
|
120
|
+
* the take is mid-blink. Video-sourced avatars only — a portrait-anchored one is a 422.
|
|
121
|
+
*/
|
|
122
|
+
swapSource(avatarId: string, input: AvatarSourceSwap): Promise<Avatar>;
|
|
123
|
+
/**
|
|
124
|
+
* Re-point the anchor at a different frame of the loop she ALREADY has — same footage,
|
|
125
|
+
* different rest pose. `swapSource` replaces the footage; this only moves the frame.
|
|
126
|
+
*
|
|
127
|
+
* Re-renders the clip library the same way, with the same degradation window, and is
|
|
128
|
+
* clamped server-side to the loop's last extractable frame (read the avatar back to see
|
|
129
|
+
* what was actually cut). Video-sourced avatars only.
|
|
130
|
+
*/
|
|
131
|
+
retimeAnchor(avatarId: string, anchorTimeMs: number): Promise<Avatar>;
|
|
101
132
|
deleteAvatar(avatarId: string): Promise<void>;
|
|
133
|
+
/**
|
|
134
|
+
* Declare the avatar's full desired clip library — a declaration, not a delta. The
|
|
135
|
+
* platform reconciles it against what exists: unchanged clips are `kept` (still
|
|
136
|
+
* serving), new or changed ones are `queued` to render, and clips you dropped are
|
|
137
|
+
* `retired`. The 202 is acceptance, not readiness — poll `listClips` until no row is
|
|
138
|
+
* `queued` or `generating`. A rejected upload settles `failed`, which is terminal, so
|
|
139
|
+
* waiting for all-`ready` waits forever. While a re-render is in flight the previous
|
|
140
|
+
* take keeps serving, so a declaration never blanks a live avatar.
|
|
141
|
+
*
|
|
142
|
+
* `expectedRevision` is compare-and-set: pass the `revision` you last read and a
|
|
143
|
+
* concurrent writer surfaces as a 409 instead of a lost update. Omit it to declare
|
|
144
|
+
* unconditionally.
|
|
145
|
+
*
|
|
146
|
+
* At most 12 clips: one `idle`, up to two `listen`, the rest `gesture`. An uploaded
|
|
147
|
+
* clip (`source: { assetId }`) must start AND end on the avatar's rest pose — pose
|
|
148
|
+
* validation rejects it otherwise (`status: "failed"`, the verdict in `poseCheck`),
|
|
149
|
+
* and the rest of the library is untouched.
|
|
150
|
+
*/
|
|
151
|
+
setClipLibrary(avatarId: string, library: {
|
|
152
|
+
clips: readonly ClipDeclaration[];
|
|
153
|
+
expectedRevision?: number;
|
|
154
|
+
}): Promise<ClipLibraryUpdate>;
|
|
155
|
+
/** The avatar's clip library: every non-retired clip, plus revision, anchor and eligibility. */
|
|
156
|
+
listClips(avatarId: string): Promise<ClipLibrary>;
|
|
102
157
|
/**
|
|
103
158
|
* Reconcile an avatar's clip set after it changes.
|
|
104
159
|
*
|
|
@@ -109,6 +164,10 @@ declare class RealtimeAvatar {
|
|
|
109
164
|
* **At most 32 URLs per call.** This is the whole set for the avatar, not a delta, and the
|
|
110
165
|
* endpoint rejects an oversize list rather than truncating it — so a library that outgrows
|
|
111
166
|
* 32 needs the set trimmed, not split across two calls.
|
|
167
|
+
*
|
|
168
|
+
* @deprecated The externally-hosted clip tier this serves is sunsetting. Declare the
|
|
169
|
+
* library with {@link setClipLibrary} instead — the platform renders and hosts the
|
|
170
|
+
* clips, and pose-validates uploads against the avatar's rest pose.
|
|
112
171
|
*/
|
|
113
172
|
syncClips(avatarId: string, clipUrls: readonly string[]): Promise<ClipSyncResult>;
|
|
114
173
|
/** Hand us a URL and we stream it into storage. Prefer this for anything large. */
|
|
@@ -189,4 +248,4 @@ declare function verifyTranscript(rawBody: Uint8Array | string, headers: Headers
|
|
|
189
248
|
toleranceSeconds?: number;
|
|
190
249
|
}): Promise<TranscriptPayload>;
|
|
191
250
|
|
|
192
|
-
export { Asset, AssetKind, Avatar, AvatarUpdate, CallMode, CallPolicy, ClipSyncResult, CreditBalance, EndCallOptions, ListSessionsOptions, RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, type RealtimeAvatarOptions, type StartCallOptions, StartCallResult, TranscriptPayload, UsageSession, UsageSessionPage, verifyTranscript };
|
|
251
|
+
export { Asset, AssetKind, Avatar, AvatarSourceSwap, AvatarUpdate, CallMode, CallPolicy, ClipDeclaration, ClipLibrary, ClipLibraryUpdate, ClipSyncResult, CreditBalance, EndCallOptions, ListSessionsOptions, RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, type RealtimeAvatarOptions, type StartCallOptions, StartCallResult, TranscriptPayload, UsageSession, UsageSessionPage, verifyTranscript };
|
package/dist/index.js
CHANGED
|
@@ -43,7 +43,7 @@ var RealtimeAvatarHttpError = class extends RealtimeAvatarError {
|
|
|
43
43
|
|
|
44
44
|
// ../http-client/src/client.ts
|
|
45
45
|
var DEFAULT_BASE_URL = "https://realtimeavatar.ai/api/v1";
|
|
46
|
-
var SDK_VERSION = "0.
|
|
46
|
+
var SDK_VERSION = "0.5.0";
|
|
47
47
|
var RealtimeAvatar = class {
|
|
48
48
|
#apiKey;
|
|
49
49
|
#baseUrl;
|
|
@@ -60,7 +60,7 @@ var RealtimeAvatar = class {
|
|
|
60
60
|
if (!options.apiKey) throw new RealtimeAvatarError("apiKey is required");
|
|
61
61
|
this.#apiKey = options.apiKey;
|
|
62
62
|
this.#baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
63
|
-
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
63
|
+
this.#fetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
64
64
|
this.#timeoutMs = options.timeoutMs ?? 6e4;
|
|
65
65
|
this.#maxRetries = Math.max(0, options.maxRetries ?? 2);
|
|
66
66
|
this.#userAgent = [`realtime-avatar-sdk/${SDK_VERSION}`, runtimeTag(), options.userAgent].filter(Boolean).join(" ");
|
|
@@ -197,9 +197,91 @@ var RealtimeAvatar = class {
|
|
|
197
197
|
await this.#json(await this.#request("PATCH", `/avatars/${avatarId}`, { json: patch }))
|
|
198
198
|
);
|
|
199
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Re-shoot the character — swap in new footage as her resting loop.
|
|
202
|
+
*
|
|
203
|
+
* ASYNCHRONOUS, and that is the whole design. This returns as soon as the swap is
|
|
204
|
+
* accepted; the avatar keeps serving its CURRENT loop, cache and clips the entire time,
|
|
205
|
+
* and cuts over to the new generation in one step once the replacement is prepared.
|
|
206
|
+
* A call minted a second after this returns is a normal call on the old footage.
|
|
207
|
+
*
|
|
208
|
+
* Two consequences worth designing for:
|
|
209
|
+
*
|
|
210
|
+
* - **The clip library empties and refills.** Old takes are footage of the old source and
|
|
211
|
+
* cannot splice against the new loop, so they are dropped and re-rendered. Between the
|
|
212
|
+
* cutover and the last re-render she rests on the new loop with less variety — never
|
|
213
|
+
* broken, just plainer. Do not gate your UI on the library being full.
|
|
214
|
+
* - **A failed swap does not fail the avatar.** She keeps serving, `status` stays `ready`,
|
|
215
|
+
* and the reason lands on `error`. So poll `getAvatar` and read `error` — a non-null
|
|
216
|
+
* `error` on a `ready` avatar is the swap that did not take, not an unhealthy character.
|
|
217
|
+
*
|
|
218
|
+
* The frame this rests on comes from the new footage: pass `anchorTimeMs` when frame 0 of
|
|
219
|
+
* the take is mid-blink. Video-sourced avatars only — a portrait-anchored one is a 422.
|
|
220
|
+
*/
|
|
221
|
+
async swapSource(avatarId, input) {
|
|
222
|
+
const json = { sourceAssetId: input.sourceAssetId };
|
|
223
|
+
if (input.anchorTimeMs !== void 0) json.anchorTimeMs = input.anchorTimeMs;
|
|
224
|
+
return toAvatar(await this.#json(await this.#request("PATCH", `/avatars/${avatarId}`, { json })));
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Re-point the anchor at a different frame of the loop she ALREADY has — same footage,
|
|
228
|
+
* different rest pose. `swapSource` replaces the footage; this only moves the frame.
|
|
229
|
+
*
|
|
230
|
+
* Re-renders the clip library the same way, with the same degradation window, and is
|
|
231
|
+
* clamped server-side to the loop's last extractable frame (read the avatar back to see
|
|
232
|
+
* what was actually cut). Video-sourced avatars only.
|
|
233
|
+
*/
|
|
234
|
+
async retimeAnchor(avatarId, anchorTimeMs) {
|
|
235
|
+
return toAvatar(
|
|
236
|
+
await this.#json(await this.#request("PATCH", `/avatars/${avatarId}`, { json: { anchorTimeMs } }))
|
|
237
|
+
);
|
|
238
|
+
}
|
|
200
239
|
async deleteAvatar(avatarId) {
|
|
201
240
|
await this.#json(await this.#request("DELETE", `/avatars/${avatarId}`));
|
|
202
241
|
}
|
|
242
|
+
// A clip envelope missing `revision` would silently drop `expectedRevision` from the
|
|
243
|
+
// next declare — CAS degrades to unconditional with zero signal — so it throws instead.
|
|
244
|
+
#clipEnvelope(out) {
|
|
245
|
+
if (typeof out.revision !== "number" || !Array.isArray(out.data)) {
|
|
246
|
+
throw new RealtimeAvatarError("clip library response did not match the contract");
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Declare the avatar's full desired clip library — a declaration, not a delta. The
|
|
252
|
+
* platform reconciles it against what exists: unchanged clips are `kept` (still
|
|
253
|
+
* serving), new or changed ones are `queued` to render, and clips you dropped are
|
|
254
|
+
* `retired`. The 202 is acceptance, not readiness — poll `listClips` until no row is
|
|
255
|
+
* `queued` or `generating`. A rejected upload settles `failed`, which is terminal, so
|
|
256
|
+
* waiting for all-`ready` waits forever. While a re-render is in flight the previous
|
|
257
|
+
* take keeps serving, so a declaration never blanks a live avatar.
|
|
258
|
+
*
|
|
259
|
+
* `expectedRevision` is compare-and-set: pass the `revision` you last read and a
|
|
260
|
+
* concurrent writer surfaces as a 409 instead of a lost update. Omit it to declare
|
|
261
|
+
* unconditionally.
|
|
262
|
+
*
|
|
263
|
+
* At most 12 clips: one `idle`, up to two `listen`, the rest `gesture`. An uploaded
|
|
264
|
+
* clip (`source: { assetId }`) must start AND end on the avatar's rest pose — pose
|
|
265
|
+
* validation rejects it otherwise (`status: "failed"`, the verdict in `poseCheck`),
|
|
266
|
+
* and the rest of the library is untouched.
|
|
267
|
+
*/
|
|
268
|
+
async setClipLibrary(avatarId, library) {
|
|
269
|
+
const body = { clips: library.clips };
|
|
270
|
+
if (library.expectedRevision !== void 0) body.expectedRevision = library.expectedRevision;
|
|
271
|
+
return this.#clipEnvelope(
|
|
272
|
+
await this.#json(
|
|
273
|
+
await this.#request("PUT", `/avatars/${avatarId}/clips`, { json: body })
|
|
274
|
+
)
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
/** The avatar's clip library: every non-retired clip, plus revision, anchor and eligibility. */
|
|
278
|
+
async listClips(avatarId) {
|
|
279
|
+
return this.#clipEnvelope(
|
|
280
|
+
await this.#json(
|
|
281
|
+
await this.#request("GET", `/avatars/${avatarId}/clips`)
|
|
282
|
+
)
|
|
283
|
+
);
|
|
284
|
+
}
|
|
203
285
|
/**
|
|
204
286
|
* Reconcile an avatar's clip set after it changes.
|
|
205
287
|
*
|
|
@@ -210,6 +292,10 @@ var RealtimeAvatar = class {
|
|
|
210
292
|
* **At most 32 URLs per call.** This is the whole set for the avatar, not a delta, and the
|
|
211
293
|
* endpoint rejects an oversize list rather than truncating it — so a library that outgrows
|
|
212
294
|
* 32 needs the set trimmed, not split across two calls.
|
|
295
|
+
*
|
|
296
|
+
* @deprecated The externally-hosted clip tier this serves is sunsetting. Declare the
|
|
297
|
+
* library with {@link setClipLibrary} instead — the platform renders and hosts the
|
|
298
|
+
* clips, and pose-validates uploads against the avatar's rest pose.
|
|
213
299
|
*/
|
|
214
300
|
async syncClips(avatarId, clipUrls) {
|
|
215
301
|
const out = await this.#json(
|
|
@@ -385,7 +471,11 @@ function toAvatar(raw) {
|
|
|
385
471
|
displayName: String(a.displayName ?? ""),
|
|
386
472
|
sourceKind: a.sourceKind === "video" ? "video" : "image",
|
|
387
473
|
status: a.status ?? "draft",
|
|
388
|
-
defaultVoiceId: a.defaultVoiceId ? String(a.defaultVoiceId) : null
|
|
474
|
+
defaultVoiceId: a.defaultVoiceId ? String(a.defaultVoiceId) : null,
|
|
475
|
+
sourceAssetId: a.sourceAssetId ? String(a.sourceAssetId) : null,
|
|
476
|
+
// Carried because it is the ONLY channel a failed source swap has: she stays `ready`
|
|
477
|
+
// and serving, and this says why the re-shoot did not take.
|
|
478
|
+
error: a.error ? String(a.error) : null
|
|
389
479
|
};
|
|
390
480
|
}
|
|
391
481
|
function toAsset(raw) {
|
package/dist/nextjs.d.ts
CHANGED
package/dist/nextjs.js
CHANGED
|
@@ -43,7 +43,7 @@ var RealtimeAvatarHttpError = class extends RealtimeAvatarError {
|
|
|
43
43
|
|
|
44
44
|
// ../http-client/src/client.ts
|
|
45
45
|
var DEFAULT_BASE_URL = "https://realtimeavatar.ai/api/v1";
|
|
46
|
-
var SDK_VERSION = "0.
|
|
46
|
+
var SDK_VERSION = "0.5.0";
|
|
47
47
|
var RealtimeAvatar = class {
|
|
48
48
|
#apiKey;
|
|
49
49
|
#baseUrl;
|
|
@@ -60,7 +60,7 @@ var RealtimeAvatar = class {
|
|
|
60
60
|
if (!options.apiKey) throw new RealtimeAvatarError("apiKey is required");
|
|
61
61
|
this.#apiKey = options.apiKey;
|
|
62
62
|
this.#baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
63
|
-
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
63
|
+
this.#fetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
64
64
|
this.#timeoutMs = options.timeoutMs ?? 6e4;
|
|
65
65
|
this.#maxRetries = Math.max(0, options.maxRetries ?? 2);
|
|
66
66
|
this.#userAgent = [`realtime-avatar-sdk/${SDK_VERSION}`, runtimeTag(), options.userAgent].filter(Boolean).join(" ");
|
|
@@ -197,9 +197,91 @@ var RealtimeAvatar = class {
|
|
|
197
197
|
await this.#json(await this.#request("PATCH", `/avatars/${avatarId}`, { json: patch }))
|
|
198
198
|
);
|
|
199
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Re-shoot the character — swap in new footage as her resting loop.
|
|
202
|
+
*
|
|
203
|
+
* ASYNCHRONOUS, and that is the whole design. This returns as soon as the swap is
|
|
204
|
+
* accepted; the avatar keeps serving its CURRENT loop, cache and clips the entire time,
|
|
205
|
+
* and cuts over to the new generation in one step once the replacement is prepared.
|
|
206
|
+
* A call minted a second after this returns is a normal call on the old footage.
|
|
207
|
+
*
|
|
208
|
+
* Two consequences worth designing for:
|
|
209
|
+
*
|
|
210
|
+
* - **The clip library empties and refills.** Old takes are footage of the old source and
|
|
211
|
+
* cannot splice against the new loop, so they are dropped and re-rendered. Between the
|
|
212
|
+
* cutover and the last re-render she rests on the new loop with less variety — never
|
|
213
|
+
* broken, just plainer. Do not gate your UI on the library being full.
|
|
214
|
+
* - **A failed swap does not fail the avatar.** She keeps serving, `status` stays `ready`,
|
|
215
|
+
* and the reason lands on `error`. So poll `getAvatar` and read `error` — a non-null
|
|
216
|
+
* `error` on a `ready` avatar is the swap that did not take, not an unhealthy character.
|
|
217
|
+
*
|
|
218
|
+
* The frame this rests on comes from the new footage: pass `anchorTimeMs` when frame 0 of
|
|
219
|
+
* the take is mid-blink. Video-sourced avatars only — a portrait-anchored one is a 422.
|
|
220
|
+
*/
|
|
221
|
+
async swapSource(avatarId, input) {
|
|
222
|
+
const json2 = { sourceAssetId: input.sourceAssetId };
|
|
223
|
+
if (input.anchorTimeMs !== void 0) json2.anchorTimeMs = input.anchorTimeMs;
|
|
224
|
+
return toAvatar(await this.#json(await this.#request("PATCH", `/avatars/${avatarId}`, { json: json2 })));
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Re-point the anchor at a different frame of the loop she ALREADY has — same footage,
|
|
228
|
+
* different rest pose. `swapSource` replaces the footage; this only moves the frame.
|
|
229
|
+
*
|
|
230
|
+
* Re-renders the clip library the same way, with the same degradation window, and is
|
|
231
|
+
* clamped server-side to the loop's last extractable frame (read the avatar back to see
|
|
232
|
+
* what was actually cut). Video-sourced avatars only.
|
|
233
|
+
*/
|
|
234
|
+
async retimeAnchor(avatarId, anchorTimeMs) {
|
|
235
|
+
return toAvatar(
|
|
236
|
+
await this.#json(await this.#request("PATCH", `/avatars/${avatarId}`, { json: { anchorTimeMs } }))
|
|
237
|
+
);
|
|
238
|
+
}
|
|
200
239
|
async deleteAvatar(avatarId) {
|
|
201
240
|
await this.#json(await this.#request("DELETE", `/avatars/${avatarId}`));
|
|
202
241
|
}
|
|
242
|
+
// A clip envelope missing `revision` would silently drop `expectedRevision` from the
|
|
243
|
+
// next declare — CAS degrades to unconditional with zero signal — so it throws instead.
|
|
244
|
+
#clipEnvelope(out) {
|
|
245
|
+
if (typeof out.revision !== "number" || !Array.isArray(out.data)) {
|
|
246
|
+
throw new RealtimeAvatarError("clip library response did not match the contract");
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Declare the avatar's full desired clip library — a declaration, not a delta. The
|
|
252
|
+
* platform reconciles it against what exists: unchanged clips are `kept` (still
|
|
253
|
+
* serving), new or changed ones are `queued` to render, and clips you dropped are
|
|
254
|
+
* `retired`. The 202 is acceptance, not readiness — poll `listClips` until no row is
|
|
255
|
+
* `queued` or `generating`. A rejected upload settles `failed`, which is terminal, so
|
|
256
|
+
* waiting for all-`ready` waits forever. While a re-render is in flight the previous
|
|
257
|
+
* take keeps serving, so a declaration never blanks a live avatar.
|
|
258
|
+
*
|
|
259
|
+
* `expectedRevision` is compare-and-set: pass the `revision` you last read and a
|
|
260
|
+
* concurrent writer surfaces as a 409 instead of a lost update. Omit it to declare
|
|
261
|
+
* unconditionally.
|
|
262
|
+
*
|
|
263
|
+
* At most 12 clips: one `idle`, up to two `listen`, the rest `gesture`. An uploaded
|
|
264
|
+
* clip (`source: { assetId }`) must start AND end on the avatar's rest pose — pose
|
|
265
|
+
* validation rejects it otherwise (`status: "failed"`, the verdict in `poseCheck`),
|
|
266
|
+
* and the rest of the library is untouched.
|
|
267
|
+
*/
|
|
268
|
+
async setClipLibrary(avatarId, library) {
|
|
269
|
+
const body = { clips: library.clips };
|
|
270
|
+
if (library.expectedRevision !== void 0) body.expectedRevision = library.expectedRevision;
|
|
271
|
+
return this.#clipEnvelope(
|
|
272
|
+
await this.#json(
|
|
273
|
+
await this.#request("PUT", `/avatars/${avatarId}/clips`, { json: body })
|
|
274
|
+
)
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
/** The avatar's clip library: every non-retired clip, plus revision, anchor and eligibility. */
|
|
278
|
+
async listClips(avatarId) {
|
|
279
|
+
return this.#clipEnvelope(
|
|
280
|
+
await this.#json(
|
|
281
|
+
await this.#request("GET", `/avatars/${avatarId}/clips`)
|
|
282
|
+
)
|
|
283
|
+
);
|
|
284
|
+
}
|
|
203
285
|
/**
|
|
204
286
|
* Reconcile an avatar's clip set after it changes.
|
|
205
287
|
*
|
|
@@ -210,6 +292,10 @@ var RealtimeAvatar = class {
|
|
|
210
292
|
* **At most 32 URLs per call.** This is the whole set for the avatar, not a delta, and the
|
|
211
293
|
* endpoint rejects an oversize list rather than truncating it — so a library that outgrows
|
|
212
294
|
* 32 needs the set trimmed, not split across two calls.
|
|
295
|
+
*
|
|
296
|
+
* @deprecated The externally-hosted clip tier this serves is sunsetting. Declare the
|
|
297
|
+
* library with {@link setClipLibrary} instead — the platform renders and hosts the
|
|
298
|
+
* clips, and pose-validates uploads against the avatar's rest pose.
|
|
213
299
|
*/
|
|
214
300
|
async syncClips(avatarId, clipUrls) {
|
|
215
301
|
const out = await this.#json(
|
|
@@ -385,7 +471,11 @@ function toAvatar(raw) {
|
|
|
385
471
|
displayName: String(a.displayName ?? ""),
|
|
386
472
|
sourceKind: a.sourceKind === "video" ? "video" : "image",
|
|
387
473
|
status: a.status ?? "draft",
|
|
388
|
-
defaultVoiceId: a.defaultVoiceId ? String(a.defaultVoiceId) : null
|
|
474
|
+
defaultVoiceId: a.defaultVoiceId ? String(a.defaultVoiceId) : null,
|
|
475
|
+
sourceAssetId: a.sourceAssetId ? String(a.sourceAssetId) : null,
|
|
476
|
+
// Carried because it is the ONLY channel a failed source swap has: she stays `ready`
|
|
477
|
+
// and serving, and this says why the re-shoot did not take.
|
|
478
|
+
error: a.error ? String(a.error) : null
|
|
389
479
|
};
|
|
390
480
|
}
|
|
391
481
|
function toAsset(raw) {
|
package/dist/react-native.js
CHANGED
|
@@ -405,7 +405,8 @@ function useLiveKitAvatarGrant(input) {
|
|
|
405
405
|
);
|
|
406
406
|
useEffect(() => {
|
|
407
407
|
if (!autoRetryBusy || !active || !tabVisible || state.status !== "busy" || !state.busy) return;
|
|
408
|
-
const
|
|
408
|
+
const hinted = state.busy.recommended_retry_ms;
|
|
409
|
+
const retryMs = Math.max(Number.isFinite(hinted) ? hinted : 5e3, 250);
|
|
409
410
|
const timer = window.setTimeout(refresh, retryMs);
|
|
410
411
|
return () => window.clearTimeout(timer);
|
|
411
412
|
}, [active, autoRetryBusy, tabVisible, refresh, state.busy, state.status]);
|
package/dist/react.js
CHANGED
|
@@ -364,7 +364,8 @@ function useLiveKitAvatarGrant(input) {
|
|
|
364
364
|
);
|
|
365
365
|
useEffect(() => {
|
|
366
366
|
if (!autoRetryBusy || !active || !tabVisible || state.status !== "busy" || !state.busy) return;
|
|
367
|
-
const
|
|
367
|
+
const hinted = state.busy.recommended_retry_ms;
|
|
368
|
+
const retryMs = Math.max(Number.isFinite(hinted) ? hinted : 5e3, 250);
|
|
368
369
|
const timer = window.setTimeout(refresh, retryMs);
|
|
369
370
|
return () => window.clearTimeout(timer);
|
|
370
371
|
}, [active, autoRetryBusy, tabVisible, refresh, state.busy, state.status]);
|
package/dist/server.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, RealtimeAvatarOptions, StartCallOptions, verifyTranscript } from './index.js';
|
|
2
|
-
export {
|
|
2
|
+
export { i as Asset, h as AssetKind, A as Avatar, l as AvatarClip, c as AvatarSourceSwap, b as AvatarUpdate, m as CallConnection, C as CallMode, a as CallPolicy, n as CallQueued, d as ClipDeclaration, f as ClipLibrary, o as ClipLibraryPlan, e as ClipLibraryUpdate, p as ClipSource, g as ClipSyncResult, q as ContextMessage, k as CreditBalance, E as EndCallOptions, r as EndCallReason, L as ListSessionsOptions, S as StartCallResult, T as TranscriptPayload, j as UsageSession, U as UsageSessionPage, V as VideoPolicy, s as VideoState, t as isQueued } from './types-DLxz6uFE.js';
|
package/dist/server.js
CHANGED
|
@@ -43,7 +43,7 @@ var RealtimeAvatarHttpError = class extends RealtimeAvatarError {
|
|
|
43
43
|
|
|
44
44
|
// ../http-client/src/client.ts
|
|
45
45
|
var DEFAULT_BASE_URL = "https://realtimeavatar.ai/api/v1";
|
|
46
|
-
var SDK_VERSION = "0.
|
|
46
|
+
var SDK_VERSION = "0.5.0";
|
|
47
47
|
var RealtimeAvatar = class {
|
|
48
48
|
#apiKey;
|
|
49
49
|
#baseUrl;
|
|
@@ -60,7 +60,7 @@ var RealtimeAvatar = class {
|
|
|
60
60
|
if (!options.apiKey) throw new RealtimeAvatarError("apiKey is required");
|
|
61
61
|
this.#apiKey = options.apiKey;
|
|
62
62
|
this.#baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
63
|
-
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
63
|
+
this.#fetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
64
64
|
this.#timeoutMs = options.timeoutMs ?? 6e4;
|
|
65
65
|
this.#maxRetries = Math.max(0, options.maxRetries ?? 2);
|
|
66
66
|
this.#userAgent = [`realtime-avatar-sdk/${SDK_VERSION}`, runtimeTag(), options.userAgent].filter(Boolean).join(" ");
|
|
@@ -197,9 +197,91 @@ var RealtimeAvatar = class {
|
|
|
197
197
|
await this.#json(await this.#request("PATCH", `/avatars/${avatarId}`, { json: patch }))
|
|
198
198
|
);
|
|
199
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Re-shoot the character — swap in new footage as her resting loop.
|
|
202
|
+
*
|
|
203
|
+
* ASYNCHRONOUS, and that is the whole design. This returns as soon as the swap is
|
|
204
|
+
* accepted; the avatar keeps serving its CURRENT loop, cache and clips the entire time,
|
|
205
|
+
* and cuts over to the new generation in one step once the replacement is prepared.
|
|
206
|
+
* A call minted a second after this returns is a normal call on the old footage.
|
|
207
|
+
*
|
|
208
|
+
* Two consequences worth designing for:
|
|
209
|
+
*
|
|
210
|
+
* - **The clip library empties and refills.** Old takes are footage of the old source and
|
|
211
|
+
* cannot splice against the new loop, so they are dropped and re-rendered. Between the
|
|
212
|
+
* cutover and the last re-render she rests on the new loop with less variety — never
|
|
213
|
+
* broken, just plainer. Do not gate your UI on the library being full.
|
|
214
|
+
* - **A failed swap does not fail the avatar.** She keeps serving, `status` stays `ready`,
|
|
215
|
+
* and the reason lands on `error`. So poll `getAvatar` and read `error` — a non-null
|
|
216
|
+
* `error` on a `ready` avatar is the swap that did not take, not an unhealthy character.
|
|
217
|
+
*
|
|
218
|
+
* The frame this rests on comes from the new footage: pass `anchorTimeMs` when frame 0 of
|
|
219
|
+
* the take is mid-blink. Video-sourced avatars only — a portrait-anchored one is a 422.
|
|
220
|
+
*/
|
|
221
|
+
async swapSource(avatarId, input) {
|
|
222
|
+
const json = { sourceAssetId: input.sourceAssetId };
|
|
223
|
+
if (input.anchorTimeMs !== void 0) json.anchorTimeMs = input.anchorTimeMs;
|
|
224
|
+
return toAvatar(await this.#json(await this.#request("PATCH", `/avatars/${avatarId}`, { json })));
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Re-point the anchor at a different frame of the loop she ALREADY has — same footage,
|
|
228
|
+
* different rest pose. `swapSource` replaces the footage; this only moves the frame.
|
|
229
|
+
*
|
|
230
|
+
* Re-renders the clip library the same way, with the same degradation window, and is
|
|
231
|
+
* clamped server-side to the loop's last extractable frame (read the avatar back to see
|
|
232
|
+
* what was actually cut). Video-sourced avatars only.
|
|
233
|
+
*/
|
|
234
|
+
async retimeAnchor(avatarId, anchorTimeMs) {
|
|
235
|
+
return toAvatar(
|
|
236
|
+
await this.#json(await this.#request("PATCH", `/avatars/${avatarId}`, { json: { anchorTimeMs } }))
|
|
237
|
+
);
|
|
238
|
+
}
|
|
200
239
|
async deleteAvatar(avatarId) {
|
|
201
240
|
await this.#json(await this.#request("DELETE", `/avatars/${avatarId}`));
|
|
202
241
|
}
|
|
242
|
+
// A clip envelope missing `revision` would silently drop `expectedRevision` from the
|
|
243
|
+
// next declare — CAS degrades to unconditional with zero signal — so it throws instead.
|
|
244
|
+
#clipEnvelope(out) {
|
|
245
|
+
if (typeof out.revision !== "number" || !Array.isArray(out.data)) {
|
|
246
|
+
throw new RealtimeAvatarError("clip library response did not match the contract");
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Declare the avatar's full desired clip library — a declaration, not a delta. The
|
|
252
|
+
* platform reconciles it against what exists: unchanged clips are `kept` (still
|
|
253
|
+
* serving), new or changed ones are `queued` to render, and clips you dropped are
|
|
254
|
+
* `retired`. The 202 is acceptance, not readiness — poll `listClips` until no row is
|
|
255
|
+
* `queued` or `generating`. A rejected upload settles `failed`, which is terminal, so
|
|
256
|
+
* waiting for all-`ready` waits forever. While a re-render is in flight the previous
|
|
257
|
+
* take keeps serving, so a declaration never blanks a live avatar.
|
|
258
|
+
*
|
|
259
|
+
* `expectedRevision` is compare-and-set: pass the `revision` you last read and a
|
|
260
|
+
* concurrent writer surfaces as a 409 instead of a lost update. Omit it to declare
|
|
261
|
+
* unconditionally.
|
|
262
|
+
*
|
|
263
|
+
* At most 12 clips: one `idle`, up to two `listen`, the rest `gesture`. An uploaded
|
|
264
|
+
* clip (`source: { assetId }`) must start AND end on the avatar's rest pose — pose
|
|
265
|
+
* validation rejects it otherwise (`status: "failed"`, the verdict in `poseCheck`),
|
|
266
|
+
* and the rest of the library is untouched.
|
|
267
|
+
*/
|
|
268
|
+
async setClipLibrary(avatarId, library) {
|
|
269
|
+
const body = { clips: library.clips };
|
|
270
|
+
if (library.expectedRevision !== void 0) body.expectedRevision = library.expectedRevision;
|
|
271
|
+
return this.#clipEnvelope(
|
|
272
|
+
await this.#json(
|
|
273
|
+
await this.#request("PUT", `/avatars/${avatarId}/clips`, { json: body })
|
|
274
|
+
)
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
/** The avatar's clip library: every non-retired clip, plus revision, anchor and eligibility. */
|
|
278
|
+
async listClips(avatarId) {
|
|
279
|
+
return this.#clipEnvelope(
|
|
280
|
+
await this.#json(
|
|
281
|
+
await this.#request("GET", `/avatars/${avatarId}/clips`)
|
|
282
|
+
)
|
|
283
|
+
);
|
|
284
|
+
}
|
|
203
285
|
/**
|
|
204
286
|
* Reconcile an avatar's clip set after it changes.
|
|
205
287
|
*
|
|
@@ -210,6 +292,10 @@ var RealtimeAvatar = class {
|
|
|
210
292
|
* **At most 32 URLs per call.** This is the whole set for the avatar, not a delta, and the
|
|
211
293
|
* endpoint rejects an oversize list rather than truncating it — so a library that outgrows
|
|
212
294
|
* 32 needs the set trimmed, not split across two calls.
|
|
295
|
+
*
|
|
296
|
+
* @deprecated The externally-hosted clip tier this serves is sunsetting. Declare the
|
|
297
|
+
* library with {@link setClipLibrary} instead — the platform renders and hosts the
|
|
298
|
+
* clips, and pose-validates uploads against the avatar's rest pose.
|
|
213
299
|
*/
|
|
214
300
|
async syncClips(avatarId, clipUrls) {
|
|
215
301
|
const out = await this.#json(
|
|
@@ -385,7 +471,11 @@ function toAvatar(raw) {
|
|
|
385
471
|
displayName: String(a.displayName ?? ""),
|
|
386
472
|
sourceKind: a.sourceKind === "video" ? "video" : "image",
|
|
387
473
|
status: a.status ?? "draft",
|
|
388
|
-
defaultVoiceId: a.defaultVoiceId ? String(a.defaultVoiceId) : null
|
|
474
|
+
defaultVoiceId: a.defaultVoiceId ? String(a.defaultVoiceId) : null,
|
|
475
|
+
sourceAssetId: a.sourceAssetId ? String(a.sourceAssetId) : null,
|
|
476
|
+
// Carried because it is the ONLY channel a failed source swap has: she stays `ready`
|
|
477
|
+
// and serving, and this says why the re-shoot did not take.
|
|
478
|
+
error: a.error ? String(a.error) : null
|
|
389
479
|
};
|
|
390
480
|
}
|
|
391
481
|
function toAsset(raw) {
|
package/dist/tanstack-start.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { P as ProxyConfig } from './types-
|
|
2
|
-
import './types-
|
|
1
|
+
import { P as ProxyConfig } from './types-B45GgjrV.js';
|
|
2
|
+
import './types-DLxz6uFE.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* TanStack Start. Mount at `routes/api/realtime-avatar/$.ts` — the trailing `$` is Start's
|
package/dist/tanstack-start.js
CHANGED
|
@@ -43,7 +43,7 @@ var RealtimeAvatarHttpError = class extends RealtimeAvatarError {
|
|
|
43
43
|
|
|
44
44
|
// ../http-client/src/client.ts
|
|
45
45
|
var DEFAULT_BASE_URL = "https://realtimeavatar.ai/api/v1";
|
|
46
|
-
var SDK_VERSION = "0.
|
|
46
|
+
var SDK_VERSION = "0.5.0";
|
|
47
47
|
var RealtimeAvatar = class {
|
|
48
48
|
#apiKey;
|
|
49
49
|
#baseUrl;
|
|
@@ -60,7 +60,7 @@ var RealtimeAvatar = class {
|
|
|
60
60
|
if (!options.apiKey) throw new RealtimeAvatarError("apiKey is required");
|
|
61
61
|
this.#apiKey = options.apiKey;
|
|
62
62
|
this.#baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
63
|
-
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
63
|
+
this.#fetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
64
64
|
this.#timeoutMs = options.timeoutMs ?? 6e4;
|
|
65
65
|
this.#maxRetries = Math.max(0, options.maxRetries ?? 2);
|
|
66
66
|
this.#userAgent = [`realtime-avatar-sdk/${SDK_VERSION}`, runtimeTag(), options.userAgent].filter(Boolean).join(" ");
|
|
@@ -197,9 +197,91 @@ var RealtimeAvatar = class {
|
|
|
197
197
|
await this.#json(await this.#request("PATCH", `/avatars/${avatarId}`, { json: patch }))
|
|
198
198
|
);
|
|
199
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Re-shoot the character — swap in new footage as her resting loop.
|
|
202
|
+
*
|
|
203
|
+
* ASYNCHRONOUS, and that is the whole design. This returns as soon as the swap is
|
|
204
|
+
* accepted; the avatar keeps serving its CURRENT loop, cache and clips the entire time,
|
|
205
|
+
* and cuts over to the new generation in one step once the replacement is prepared.
|
|
206
|
+
* A call minted a second after this returns is a normal call on the old footage.
|
|
207
|
+
*
|
|
208
|
+
* Two consequences worth designing for:
|
|
209
|
+
*
|
|
210
|
+
* - **The clip library empties and refills.** Old takes are footage of the old source and
|
|
211
|
+
* cannot splice against the new loop, so they are dropped and re-rendered. Between the
|
|
212
|
+
* cutover and the last re-render she rests on the new loop with less variety — never
|
|
213
|
+
* broken, just plainer. Do not gate your UI on the library being full.
|
|
214
|
+
* - **A failed swap does not fail the avatar.** She keeps serving, `status` stays `ready`,
|
|
215
|
+
* and the reason lands on `error`. So poll `getAvatar` and read `error` — a non-null
|
|
216
|
+
* `error` on a `ready` avatar is the swap that did not take, not an unhealthy character.
|
|
217
|
+
*
|
|
218
|
+
* The frame this rests on comes from the new footage: pass `anchorTimeMs` when frame 0 of
|
|
219
|
+
* the take is mid-blink. Video-sourced avatars only — a portrait-anchored one is a 422.
|
|
220
|
+
*/
|
|
221
|
+
async swapSource(avatarId, input) {
|
|
222
|
+
const json2 = { sourceAssetId: input.sourceAssetId };
|
|
223
|
+
if (input.anchorTimeMs !== void 0) json2.anchorTimeMs = input.anchorTimeMs;
|
|
224
|
+
return toAvatar(await this.#json(await this.#request("PATCH", `/avatars/${avatarId}`, { json: json2 })));
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Re-point the anchor at a different frame of the loop she ALREADY has — same footage,
|
|
228
|
+
* different rest pose. `swapSource` replaces the footage; this only moves the frame.
|
|
229
|
+
*
|
|
230
|
+
* Re-renders the clip library the same way, with the same degradation window, and is
|
|
231
|
+
* clamped server-side to the loop's last extractable frame (read the avatar back to see
|
|
232
|
+
* what was actually cut). Video-sourced avatars only.
|
|
233
|
+
*/
|
|
234
|
+
async retimeAnchor(avatarId, anchorTimeMs) {
|
|
235
|
+
return toAvatar(
|
|
236
|
+
await this.#json(await this.#request("PATCH", `/avatars/${avatarId}`, { json: { anchorTimeMs } }))
|
|
237
|
+
);
|
|
238
|
+
}
|
|
200
239
|
async deleteAvatar(avatarId) {
|
|
201
240
|
await this.#json(await this.#request("DELETE", `/avatars/${avatarId}`));
|
|
202
241
|
}
|
|
242
|
+
// A clip envelope missing `revision` would silently drop `expectedRevision` from the
|
|
243
|
+
// next declare — CAS degrades to unconditional with zero signal — so it throws instead.
|
|
244
|
+
#clipEnvelope(out) {
|
|
245
|
+
if (typeof out.revision !== "number" || !Array.isArray(out.data)) {
|
|
246
|
+
throw new RealtimeAvatarError("clip library response did not match the contract");
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Declare the avatar's full desired clip library — a declaration, not a delta. The
|
|
252
|
+
* platform reconciles it against what exists: unchanged clips are `kept` (still
|
|
253
|
+
* serving), new or changed ones are `queued` to render, and clips you dropped are
|
|
254
|
+
* `retired`. The 202 is acceptance, not readiness — poll `listClips` until no row is
|
|
255
|
+
* `queued` or `generating`. A rejected upload settles `failed`, which is terminal, so
|
|
256
|
+
* waiting for all-`ready` waits forever. While a re-render is in flight the previous
|
|
257
|
+
* take keeps serving, so a declaration never blanks a live avatar.
|
|
258
|
+
*
|
|
259
|
+
* `expectedRevision` is compare-and-set: pass the `revision` you last read and a
|
|
260
|
+
* concurrent writer surfaces as a 409 instead of a lost update. Omit it to declare
|
|
261
|
+
* unconditionally.
|
|
262
|
+
*
|
|
263
|
+
* At most 12 clips: one `idle`, up to two `listen`, the rest `gesture`. An uploaded
|
|
264
|
+
* clip (`source: { assetId }`) must start AND end on the avatar's rest pose — pose
|
|
265
|
+
* validation rejects it otherwise (`status: "failed"`, the verdict in `poseCheck`),
|
|
266
|
+
* and the rest of the library is untouched.
|
|
267
|
+
*/
|
|
268
|
+
async setClipLibrary(avatarId, library) {
|
|
269
|
+
const body = { clips: library.clips };
|
|
270
|
+
if (library.expectedRevision !== void 0) body.expectedRevision = library.expectedRevision;
|
|
271
|
+
return this.#clipEnvelope(
|
|
272
|
+
await this.#json(
|
|
273
|
+
await this.#request("PUT", `/avatars/${avatarId}/clips`, { json: body })
|
|
274
|
+
)
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
/** The avatar's clip library: every non-retired clip, plus revision, anchor and eligibility. */
|
|
278
|
+
async listClips(avatarId) {
|
|
279
|
+
return this.#clipEnvelope(
|
|
280
|
+
await this.#json(
|
|
281
|
+
await this.#request("GET", `/avatars/${avatarId}/clips`)
|
|
282
|
+
)
|
|
283
|
+
);
|
|
284
|
+
}
|
|
203
285
|
/**
|
|
204
286
|
* Reconcile an avatar's clip set after it changes.
|
|
205
287
|
*
|
|
@@ -210,6 +292,10 @@ var RealtimeAvatar = class {
|
|
|
210
292
|
* **At most 32 URLs per call.** This is the whole set for the avatar, not a delta, and the
|
|
211
293
|
* endpoint rejects an oversize list rather than truncating it — so a library that outgrows
|
|
212
294
|
* 32 needs the set trimmed, not split across two calls.
|
|
295
|
+
*
|
|
296
|
+
* @deprecated The externally-hosted clip tier this serves is sunsetting. Declare the
|
|
297
|
+
* library with {@link setClipLibrary} instead — the platform renders and hosts the
|
|
298
|
+
* clips, and pose-validates uploads against the avatar's rest pose.
|
|
213
299
|
*/
|
|
214
300
|
async syncClips(avatarId, clipUrls) {
|
|
215
301
|
const out = await this.#json(
|
|
@@ -385,7 +471,11 @@ function toAvatar(raw) {
|
|
|
385
471
|
displayName: String(a.displayName ?? ""),
|
|
386
472
|
sourceKind: a.sourceKind === "video" ? "video" : "image",
|
|
387
473
|
status: a.status ?? "draft",
|
|
388
|
-
defaultVoiceId: a.defaultVoiceId ? String(a.defaultVoiceId) : null
|
|
474
|
+
defaultVoiceId: a.defaultVoiceId ? String(a.defaultVoiceId) : null,
|
|
475
|
+
sourceAssetId: a.sourceAssetId ? String(a.sourceAssetId) : null,
|
|
476
|
+
// Carried because it is the ONLY channel a failed source swap has: she stays `ready`
|
|
477
|
+
// and serving, and this says why the re-shoot did not take.
|
|
478
|
+
error: a.error ? String(a.error) : null
|
|
389
479
|
};
|
|
390
480
|
}
|
|
391
481
|
function toAsset(raw) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as CallMode, a as CallPolicy } from './types-
|
|
1
|
+
import { C as CallMode, a as CallPolicy } from './types-DLxz6uFE.js';
|
|
2
2
|
|
|
3
3
|
/** The three things a caller can ask the proxy to do. Gate on these, not on URLs. */
|
|
4
4
|
type ProxyOperation = "connect" | "end" | "avatars" | "credits";
|
|
@@ -132,6 +132,8 @@ interface components {
|
|
|
132
132
|
cooldown_seconds?: number;
|
|
133
133
|
/** @enum {string} */
|
|
134
134
|
renderer?: "editor" | "generative";
|
|
135
|
+
/** @enum {string} */
|
|
136
|
+
objects?: "editor" | "generative";
|
|
135
137
|
};
|
|
136
138
|
};
|
|
137
139
|
transcript_webhook?: {
|
|
@@ -142,6 +144,7 @@ interface components {
|
|
|
142
144
|
client_metadata?: {
|
|
143
145
|
[key: string]: string;
|
|
144
146
|
};
|
|
147
|
+
fast_endpointing?: boolean;
|
|
145
148
|
};
|
|
146
149
|
LiveKitSessionGrant: {
|
|
147
150
|
/**
|
|
@@ -311,6 +314,42 @@ interface components {
|
|
|
311
314
|
stylePreset?: "cinematic-founder" | "editorial-companion" | "warm-anime" | "luxury-realism" | "soft-3d" | "noir-avatar";
|
|
312
315
|
/** Format: uri */
|
|
313
316
|
portraitUrl?: string;
|
|
317
|
+
sourceAssetId?: string;
|
|
318
|
+
anchorTimeMs?: number;
|
|
319
|
+
};
|
|
320
|
+
CreateApiKeyRequest: {
|
|
321
|
+
name: string;
|
|
322
|
+
/**
|
|
323
|
+
* @default test
|
|
324
|
+
* @enum {string}
|
|
325
|
+
*/
|
|
326
|
+
environment: "live" | "test";
|
|
327
|
+
/**
|
|
328
|
+
* @default [
|
|
329
|
+
* "realtime:write",
|
|
330
|
+
* "credits:read",
|
|
331
|
+
* "avatars:read"
|
|
332
|
+
* ]
|
|
333
|
+
*/
|
|
334
|
+
scopes: ("*" | "api_keys:write" | "credits:read" | "avatars:read" | "avatars:write" | "realtime:write" | "usage:read" | "usage:write")[];
|
|
335
|
+
spendLimitCreditMicros?: number | null;
|
|
336
|
+
expiresAt?: string | null;
|
|
337
|
+
};
|
|
338
|
+
CreateApiKeyResponse: {
|
|
339
|
+
id: string;
|
|
340
|
+
keyId: string;
|
|
341
|
+
tenantId: string;
|
|
342
|
+
name: string;
|
|
343
|
+
/** @enum {string} */
|
|
344
|
+
environment: "live" | "test";
|
|
345
|
+
redactedKey: string;
|
|
346
|
+
scopes: ("*" | "api_keys:write" | "credits:read" | "avatars:read" | "avatars:write" | "realtime:write" | "usage:read" | "usage:write")[];
|
|
347
|
+
/** @enum {string} */
|
|
348
|
+
status: "active" | "revoked" | "expired";
|
|
349
|
+
spendLimitCreditMicros: number | null;
|
|
350
|
+
createdAt: string;
|
|
351
|
+
expiresAt: string | null;
|
|
352
|
+
apiKey: string;
|
|
314
353
|
};
|
|
315
354
|
Asset: {
|
|
316
355
|
id: string;
|
|
@@ -326,6 +365,9 @@ interface components {
|
|
|
326
365
|
createdAt: string;
|
|
327
366
|
updatedAt: string;
|
|
328
367
|
};
|
|
368
|
+
AssetList: {
|
|
369
|
+
data: components["schemas"]["Asset"][];
|
|
370
|
+
};
|
|
329
371
|
CreateRemoteAssetRequest: {
|
|
330
372
|
/** @enum {string} */
|
|
331
373
|
kind: "image" | "video" | "audio";
|
|
@@ -377,9 +419,111 @@ interface components {
|
|
|
377
419
|
status: "queued" | "generating" | "ready" | "failed";
|
|
378
420
|
url: string | null;
|
|
379
421
|
whenHint: string | null;
|
|
422
|
+
/** @enum {string} */
|
|
423
|
+
source: "generated" | "uploaded";
|
|
424
|
+
motionPrompt: string | null;
|
|
425
|
+
durationSeconds: number | null;
|
|
426
|
+
anchorVersion: number;
|
|
427
|
+
poseCheck: {
|
|
428
|
+
verdict: {
|
|
429
|
+
sameSubject: boolean;
|
|
430
|
+
firstFrameMatchesAnchor: boolean;
|
|
431
|
+
lastFrameMatchesAnchor: boolean;
|
|
432
|
+
framingComparable: boolean;
|
|
433
|
+
confidence: number;
|
|
434
|
+
};
|
|
435
|
+
issues: string[];
|
|
436
|
+
firstFrameUrl: string | null;
|
|
437
|
+
lastFrameUrl: string | null;
|
|
438
|
+
trimStartMs: number | null;
|
|
439
|
+
trimEndMs: number | null;
|
|
440
|
+
} | null;
|
|
441
|
+
error: {
|
|
442
|
+
code: string;
|
|
443
|
+
message: string;
|
|
444
|
+
} | null;
|
|
445
|
+
createdAt: string;
|
|
446
|
+
updatedAt: string;
|
|
447
|
+
}[];
|
|
448
|
+
avatarId: string;
|
|
449
|
+
revision: number;
|
|
450
|
+
anchorVersion: number;
|
|
451
|
+
anchor: {
|
|
452
|
+
/** Format: uri */
|
|
453
|
+
url: string;
|
|
454
|
+
/** @enum {string} */
|
|
455
|
+
source: "portrait" | "source_frame";
|
|
456
|
+
timeMs: number | null;
|
|
457
|
+
} | null;
|
|
458
|
+
clipLibraryEligible: boolean;
|
|
459
|
+
};
|
|
460
|
+
PutAvatarClipsRequest: {
|
|
461
|
+
expectedRevision?: number;
|
|
462
|
+
clips: {
|
|
463
|
+
clipId: string;
|
|
464
|
+
/** @enum {string} */
|
|
465
|
+
role: "idle" | "listen" | "gesture";
|
|
466
|
+
whenHint?: string;
|
|
467
|
+
source: {
|
|
468
|
+
motionPrompt: string;
|
|
469
|
+
} | {
|
|
470
|
+
assetId: string;
|
|
471
|
+
};
|
|
472
|
+
durationSeconds?: number;
|
|
473
|
+
reroll?: boolean;
|
|
474
|
+
}[];
|
|
475
|
+
};
|
|
476
|
+
PutAvatarClipsResponse: {
|
|
477
|
+
data: {
|
|
478
|
+
clipId: string;
|
|
479
|
+
/** @enum {string} */
|
|
480
|
+
role: "idle" | "listen" | "gesture";
|
|
481
|
+
/** @enum {string} */
|
|
482
|
+
status: "queued" | "generating" | "ready" | "failed";
|
|
483
|
+
url: string | null;
|
|
484
|
+
whenHint: string | null;
|
|
485
|
+
/** @enum {string} */
|
|
486
|
+
source: "generated" | "uploaded";
|
|
487
|
+
motionPrompt: string | null;
|
|
488
|
+
durationSeconds: number | null;
|
|
489
|
+
anchorVersion: number;
|
|
490
|
+
poseCheck: {
|
|
491
|
+
verdict: {
|
|
492
|
+
sameSubject: boolean;
|
|
493
|
+
firstFrameMatchesAnchor: boolean;
|
|
494
|
+
lastFrameMatchesAnchor: boolean;
|
|
495
|
+
framingComparable: boolean;
|
|
496
|
+
confidence: number;
|
|
497
|
+
};
|
|
498
|
+
issues: string[];
|
|
499
|
+
firstFrameUrl: string | null;
|
|
500
|
+
lastFrameUrl: string | null;
|
|
501
|
+
trimStartMs: number | null;
|
|
502
|
+
trimEndMs: number | null;
|
|
503
|
+
} | null;
|
|
504
|
+
error: {
|
|
505
|
+
code: string;
|
|
506
|
+
message: string;
|
|
507
|
+
} | null;
|
|
380
508
|
createdAt: string;
|
|
381
509
|
updatedAt: string;
|
|
382
510
|
}[];
|
|
511
|
+
avatarId: string;
|
|
512
|
+
revision: number;
|
|
513
|
+
anchorVersion: number;
|
|
514
|
+
anchor: {
|
|
515
|
+
/** Format: uri */
|
|
516
|
+
url: string;
|
|
517
|
+
/** @enum {string} */
|
|
518
|
+
source: "portrait" | "source_frame";
|
|
519
|
+
timeMs: number | null;
|
|
520
|
+
} | null;
|
|
521
|
+
clipLibraryEligible: boolean;
|
|
522
|
+
plan: {
|
|
523
|
+
kept: string[];
|
|
524
|
+
queued: string[];
|
|
525
|
+
retired: string[];
|
|
526
|
+
};
|
|
383
527
|
};
|
|
384
528
|
SyncAvatarClipsRequest: {
|
|
385
529
|
clipUrls: string[];
|
|
@@ -392,10 +536,6 @@ interface components {
|
|
|
392
536
|
OkResponse: {
|
|
393
537
|
ok: boolean;
|
|
394
538
|
};
|
|
395
|
-
HealthResponse: {
|
|
396
|
-
/** @enum {string} */
|
|
397
|
-
status: "healthy";
|
|
398
|
-
};
|
|
399
539
|
};
|
|
400
540
|
responses: never;
|
|
401
541
|
parameters: never;
|
|
@@ -703,13 +843,25 @@ type Asset = Pick<Wire["Asset"], "id" | "kind"> & {
|
|
|
703
843
|
*/
|
|
704
844
|
status: Wire["Asset"]["status"] | (string & {});
|
|
705
845
|
};
|
|
706
|
-
type Avatar = Pick<Wire["Avatar"], "id" | "displayName" | "sourceKind" | "status" | "defaultVoiceId">;
|
|
846
|
+
type Avatar = Pick<Wire["Avatar"], "id" | "displayName" | "sourceKind" | "status" | "defaultVoiceId" | "sourceAssetId" | "error">;
|
|
707
847
|
/**
|
|
708
848
|
* The patch `updateAvatar` sends — the two fields an integrator re-points after creation.
|
|
709
849
|
* The contract's `UpdateAvatarRequest` carries more (llm, persona, art direction…); that is
|
|
710
850
|
* dashboard machinery this surface deliberately does not model.
|
|
851
|
+
*
|
|
852
|
+
* `sourceAssetId` and `anchorTimeMs` are deliberately NOT here: the platform refuses them
|
|
853
|
+
* alongside any other field, so folding them in would let this type spell a request that can
|
|
854
|
+
* only ever 422. They get their own methods — `swapSource` and `retimeAnchor`.
|
|
711
855
|
*/
|
|
712
856
|
type AvatarUpdate = Pick<Wire["UpdateAvatarRequest"], "displayName" | "defaultVoiceId">;
|
|
857
|
+
/**
|
|
858
|
+
* Re-shoot the character: `sourceAssetId` becomes the avatar's new resting loop, and the
|
|
859
|
+
* whole clip library re-renders against an anchor re-derived from that footage.
|
|
860
|
+
*
|
|
861
|
+
* `anchorTimeMs` picks the anchor frame OF THE NEW SOURCE (default 0) — frame 0 of a real
|
|
862
|
+
* take is sometimes mid-blink. It is the only field that may accompany a source swap.
|
|
863
|
+
*/
|
|
864
|
+
type AvatarSourceSwap = Required<Pick<Wire["UpdateAvatarRequest"], "sourceAssetId">> & Pick<Wire["UpdateAvatarRequest"], "anchorTimeMs">;
|
|
713
865
|
type UsageSessionsResponse = Wire["ListUsageSessionsResponse"];
|
|
714
866
|
/**
|
|
715
867
|
* One billable session — when it ran, how long it was billable for, what it cost.
|
|
@@ -746,8 +898,39 @@ interface ListSessionsOptions {
|
|
|
746
898
|
cursor?: string;
|
|
747
899
|
}
|
|
748
900
|
type CreditBalance = Pick<Wire["CreditBalance"], "balanceCreditMicros" | "reservedCreditMicros">;
|
|
749
|
-
/**
|
|
901
|
+
/**
|
|
902
|
+
* Result of reconciling an avatar's clip set to the cache tier.
|
|
903
|
+
* @deprecated The externally-hosted clip tier is sunsetting. Declare the library with
|
|
904
|
+
* `setClipLibrary` instead — the platform renders and hosts the clips for you.
|
|
905
|
+
*/
|
|
750
906
|
type ClipSyncResult = Wire["SyncAvatarClipsResponse"];
|
|
907
|
+
/**
|
|
908
|
+
* One desired clip in a library declaration: a `clipId` you choose, a role, and exactly ONE
|
|
909
|
+
* source — a `motionPrompt` the platform renders from the avatar's rest-pose anchor, or the
|
|
910
|
+
* `assetId` of a video you uploaded. `durationSeconds` (4–8, default 5) applies to generated
|
|
911
|
+
* clips only; `reroll` is a write-only re-render nudge and is never echoed back by a read.
|
|
912
|
+
*/
|
|
913
|
+
type ClipDeclaration = Wire["PutAvatarClipsRequest"]["clips"][number];
|
|
914
|
+
/** The two ways a declared clip gets its pixels. Exactly one — an object carrying both is rejected. */
|
|
915
|
+
type ClipSource = Wire["PutAvatarClipsRequest"]["clips"][number]["source"];
|
|
916
|
+
/**
|
|
917
|
+
* One clip row as the platform reports it. `status` is JOB state, not serveability: `url` is
|
|
918
|
+
* the SERVING asset and may stay non-null while a re-render is `generating` or has `failed` —
|
|
919
|
+
* the previous take keeps serving. An uploaded clip that fails pose validation carries the
|
|
920
|
+
* structured verdict in `poseCheck` and the reason in `error`.
|
|
921
|
+
*/
|
|
922
|
+
type AvatarClip = Wire["ListAvatarClipsResponse"]["data"][number];
|
|
923
|
+
/**
|
|
924
|
+
* The library as `listClips` reports it: every non-retired clip plus the avatar-level facts —
|
|
925
|
+
* the declaration `revision` (0 = still creation-owned), the anchor generation, and the
|
|
926
|
+
* rest-pose `anchor` the clips splice against (null only while a video-sourced avatar's
|
|
927
|
+
* anchor frame has not been derived yet).
|
|
928
|
+
*/
|
|
929
|
+
type ClipLibrary = Wire["ListAvatarClipsResponse"];
|
|
930
|
+
/** How a declaration reconciled against the previous library — by `clipId`. */
|
|
931
|
+
type ClipLibraryPlan = Wire["PutAvatarClipsResponse"]["plan"];
|
|
932
|
+
/** The accepted declaration's answer: the post-apply library plus how it reconciled. */
|
|
933
|
+
type ClipLibraryUpdate = Wire["PutAvatarClipsResponse"];
|
|
751
934
|
/**
|
|
752
935
|
* DERIVATION: hand-written, because the contract does not describe it.
|
|
753
936
|
*
|
|
@@ -795,4 +978,4 @@ interface TranscriptPayload {
|
|
|
795
978
|
client_metadata: Record<string, string>;
|
|
796
979
|
}
|
|
797
980
|
|
|
798
|
-
export { type Avatar as A, type CallMode as C, type EndCallOptions as E, type ListSessionsOptions as L, type StartCallResult as S, type TranscriptPayload as T, type UsageSessionPage as U, type VideoPolicy as V, type CallPolicy as a, type AvatarUpdate as b, type
|
|
981
|
+
export { type Avatar as A, type CallMode as C, type EndCallOptions as E, type ListSessionsOptions as L, type StartCallResult as S, type TranscriptPayload as T, type UsageSessionPage as U, type VideoPolicy as V, type CallPolicy as a, type AvatarUpdate as b, type AvatarSourceSwap as c, type ClipDeclaration as d, type ClipLibraryUpdate as e, type ClipLibrary as f, type ClipSyncResult as g, type AssetKind as h, type Asset as i, type UsageSession as j, type CreditBalance as k, type AvatarClip as l, type CallConnection as m, type CallQueued as n, type ClipLibraryPlan as o, type ClipSource as p, type ContextMessage as q, type EndCallReason as r, type VideoState as s, isQueued as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "realtime-avatar",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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",
|