realtime-avatar 0.5.0 → 0.5.2
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 +18 -4
- package/dist/express.d.ts +2 -2
- package/dist/express.js +142 -7
- package/dist/hono.d.ts +2 -2
- package/dist/hono.js +142 -7
- package/dist/index.d.ts +127 -11
- package/dist/index.js +142 -7
- package/dist/nextjs.d.ts +2 -2
- package/dist/nextjs.js +142 -7
- package/dist/{proxy-client-Dm-O0jNn.d.ts → proxy-client-BIaUo0az.d.ts} +147 -1
- package/dist/react-native.d.ts +10 -2
- package/dist/react-native.js +121 -1
- package/dist/react.d.ts +2 -2
- package/dist/react.js +132 -2
- package/dist/server.d.ts +1 -1
- package/dist/server.js +142 -7
- package/dist/tanstack-start.d.ts +2 -2
- package/dist/tanstack-start.js +142 -7
- package/dist/{types-DLxz6uFE.d.ts → types-BeGuvyra.d.ts} +19 -2
- package/dist/{types-B45GgjrV.d.ts → types-CHrhuhYb.d.ts} +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,20 +70,34 @@ all), and the `video` policy types are deliberately not one-to-one with the wire
|
|
|
70
70
|
```ts
|
|
71
71
|
// calls
|
|
72
72
|
rta.startCall({ avatarId, mode?, instructions?, context?, maxSeconds?, video?, transcript?, metadata? })
|
|
73
|
-
rta.endCall(sessionId, { reason? })
|
|
73
|
+
rta.endCall(sessionId, { reason? }) // free an abandoned call's slot; idempotent, never throws
|
|
74
74
|
|
|
75
75
|
// avatars
|
|
76
|
-
rta.
|
|
76
|
+
rta.createAvatarFromImage({ displayName, imageUrl, motionPrompt?, voice? }) // the only lane
|
|
77
|
+
rta.createAvatarFromVideo({ displayName, videoUrl, voice? }) // DEPRECATED — closed, 422
|
|
77
78
|
rta.listAvatars()
|
|
78
79
|
rta.getAvatar(avatarId)
|
|
79
|
-
rta.
|
|
80
|
+
rta.updateAvatar(avatarId, patch) // re-point displayName / defaultVoiceId
|
|
81
|
+
rta.swapSource(avatarId, { sourceAssetId, anchorTimeMs? }) // re-shoot her: new loop, library re-renders
|
|
82
|
+
rta.retimeAnchor(avatarId, anchorTimeMs) // same loop, different rest frame
|
|
83
|
+
rta.deleteAvatar(avatarId)
|
|
84
|
+
|
|
85
|
+
// clip library — declared as JSON, never as URLs
|
|
86
|
+
rta.setClipLibrary(avatarId, { clips, expectedRevision? }) // declare the FULL set; CAS via expectedRevision
|
|
87
|
+
rta.setLoop(avatarId, { motionPrompt }) // re-direct the RESTING LOOP; clips untouched
|
|
88
|
+
rta.waitForLoop(avatarId) // block until it settles; THROWS if it failed
|
|
89
|
+
rta.waitForClips(avatarId) // block until nothing is still rendering
|
|
90
|
+
rta.listClips(avatarId) // rows + revision, anchor, eligibility
|
|
91
|
+
rta.syncClips(avatarId, clipUrls) // DEPRECATED — external-URL tier; use setClipLibrary
|
|
80
92
|
|
|
81
93
|
// assets
|
|
82
94
|
rta.createRemoteAsset({ kind, remoteUrl })
|
|
83
95
|
rta.uploadAsset(file, { kind })
|
|
84
96
|
|
|
85
97
|
// billing
|
|
86
|
-
rta.creditBalance()
|
|
98
|
+
rta.creditBalance() // balance + reserved
|
|
99
|
+
rta.listSessions({ from, to, endUserId }) // per-session: when, how long, what it cost
|
|
100
|
+
rta.iterateSessions({ from, to }) // the same, paging handled
|
|
87
101
|
|
|
88
102
|
// webhooks
|
|
89
103
|
verifyTranscript(rawBytes, headers, secret)
|
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.5.
|
|
46
|
+
var SDK_VERSION = "0.5.2";
|
|
47
47
|
var RealtimeAvatar = class {
|
|
48
48
|
#apiKey;
|
|
49
49
|
#baseUrl;
|
|
@@ -157,10 +157,15 @@ var RealtimeAvatar = class {
|
|
|
157
157
|
/**
|
|
158
158
|
* Register a character from a looping clip you host.
|
|
159
159
|
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
160
|
+
* @deprecated CLOSED to new callers — this answers `422` unless your tenant was already
|
|
161
|
+
* creating from video, in which case it keeps working and your existing avatars are
|
|
162
|
+
* untouched. Use {@link createAvatarFromImage}: one still, and the platform renders the
|
|
163
|
+
* resting loop and the motion library from it.
|
|
164
|
+
*
|
|
165
|
+
* The reason it closed is not arbitrary. Every clip has to start and end on ONE rest pose
|
|
166
|
+
* or a state switch reads as a jump, and the platform can only guarantee that when it
|
|
167
|
+
* rendered the loop and the clips from the same portrait. A supplied video cannot honour
|
|
168
|
+
* it, so the lane could never be made to look right.
|
|
164
169
|
*/
|
|
165
170
|
async createAvatarFromVideo(input) {
|
|
166
171
|
const asset = await this.createRemoteAsset({ kind: "video", remoteUrl: input.videoUrl });
|
|
@@ -173,12 +178,41 @@ var RealtimeAvatar = class {
|
|
|
173
178
|
metadata: input.metadata
|
|
174
179
|
});
|
|
175
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Register a character from ONE still image. The platform generates everything moving:
|
|
183
|
+
* the resting loop she idles in, then a starter motion library rendered against her rest
|
|
184
|
+
* pose. No footage, no clip URLs, nothing to shoot.
|
|
185
|
+
*
|
|
186
|
+
* `motionPrompt` directs the RESTING LOOP — the video she plays when nothing else is
|
|
187
|
+
* happening — and it is the only chance to direct it, because there is no API today that
|
|
188
|
+
* re-generates a loop after creation (see {@link updateAvatar} for the one thing that can
|
|
189
|
+
* be re-pointed). Describe a small closed arc that returns to where it started: "settles
|
|
190
|
+
* into frame, breathes gently, a slow blink". Omit it and the house default is used.
|
|
191
|
+
*
|
|
192
|
+
* Creation returns while the avatar is still `preprocessing`; poll {@link getAvatar} until
|
|
193
|
+
* it leaves that state. The loop is load-bearing, so a failure there settles `failed` with
|
|
194
|
+
* a readable error — the motion library is not, and a library failure degrades to
|
|
195
|
+
* loop-only rather than demoting the character.
|
|
196
|
+
*/
|
|
197
|
+
async createAvatarFromImage(input) {
|
|
198
|
+
const asset = await this.createRemoteAsset({ kind: "image", remoteUrl: input.imageUrl });
|
|
199
|
+
return this.createAvatar({
|
|
200
|
+
displayName: input.displayName,
|
|
201
|
+
sourceKind: "image",
|
|
202
|
+
sourceAssetId: asset.id,
|
|
203
|
+
motionPrompt: input.motionPrompt,
|
|
204
|
+
voice: input.voice,
|
|
205
|
+
settings: input.settings,
|
|
206
|
+
metadata: input.metadata
|
|
207
|
+
});
|
|
208
|
+
}
|
|
176
209
|
async createAvatar(input) {
|
|
177
210
|
const body = {
|
|
178
211
|
displayName: input.displayName,
|
|
179
212
|
sourceKind: input.sourceKind,
|
|
180
213
|
sourceAssetId: input.sourceAssetId
|
|
181
214
|
};
|
|
215
|
+
if (input.motionPrompt !== void 0) body.motionPrompt = input.motionPrompt;
|
|
182
216
|
if (input.voice !== void 0) body.voice = input.voice;
|
|
183
217
|
if (input.settings !== void 0) body.settings = input.settings;
|
|
184
218
|
if (input.metadata !== void 0) body.metadata = input.metadata;
|
|
@@ -191,7 +225,14 @@ var RealtimeAvatar = class {
|
|
|
191
225
|
async getAvatar(avatarId) {
|
|
192
226
|
return toAvatar(await this.#json(await this.#request("GET", `/avatars/${avatarId}`)));
|
|
193
227
|
}
|
|
194
|
-
/**
|
|
228
|
+
/**
|
|
229
|
+
* Re-point what an avatar already is. `defaultVoiceId: null` clears the default voice.
|
|
230
|
+
*
|
|
231
|
+
* Cheap metadata only. The three things that cost a render have their own methods, because
|
|
232
|
+
* putting them in a patch alongside a rename would hide minutes of GPU work behind a field:
|
|
233
|
+
* {@link retimeAnchor} moves the rest frame, {@link swapSource} replaces the footage, and
|
|
234
|
+
* the resting loop is re-directed by description at `PUT /v1/avatars/{id}/loop`.
|
|
235
|
+
*/
|
|
195
236
|
async updateAvatar(avatarId, patch) {
|
|
196
237
|
return toAvatar(
|
|
197
238
|
await this.#json(await this.#request("PATCH", `/avatars/${avatarId}`, { json: patch }))
|
|
@@ -274,6 +315,94 @@ var RealtimeAvatar = class {
|
|
|
274
315
|
)
|
|
275
316
|
);
|
|
276
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* Re-direct the RESTING LOOP — the video she plays when nothing else is happening — from
|
|
320
|
+
* a new one-sentence description.
|
|
321
|
+
*
|
|
322
|
+
* Not a clip, and this is the distinction integrations get wrong: a clip with
|
|
323
|
+
* `role: "idle"` is a variant spliced OVER the loop, and declaring one never changes what
|
|
324
|
+
* she rests in. This is the only thing that does.
|
|
325
|
+
*
|
|
326
|
+
* `202`, because the render takes minutes. Three properties, all measured against a real
|
|
327
|
+
* render rather than asserted:
|
|
328
|
+
*
|
|
329
|
+
* - **She never goes dark.** She stays `ready` and keeps serving her previous loop for the
|
|
330
|
+
* entire render — returned as `servingUrl` — then the swap publishes in one step.
|
|
331
|
+
* - **Your clip library is untouched.** Clips render against the portrait, not against the
|
|
332
|
+
* loop, so a re-direct re-queues nothing and does not move `revision`.
|
|
333
|
+
* - **It bills once**, at the rendering model's rate, per re-direct.
|
|
334
|
+
*
|
|
335
|
+
* Refusals worth telling apart: `409 loop_pending` (one is already in flight — wait) and
|
|
336
|
+
* `422 loop_not_generatable` (a grandfathered video-sourced avatar has no portrait to
|
|
337
|
+
* re-animate — terminal, do not retry).
|
|
338
|
+
*/
|
|
339
|
+
async setLoop(avatarId, loop) {
|
|
340
|
+
return await this.#json(
|
|
341
|
+
await this.#request("PUT", `/avatars/${avatarId}/loop`, { json: { motionPrompt: loop.motionPrompt } })
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Block until a loop re-direct settles, and throw if it did not take.
|
|
346
|
+
*
|
|
347
|
+
* `setLoop` returns on ACCEPTANCE; the render runs for minutes afterwards. Every caller
|
|
348
|
+
* therefore writes the same polling loop, and the obvious version of it never terminates
|
|
349
|
+
* on failure — a failed re-direct leaves her `ready` (she is still serving the old loop)
|
|
350
|
+
* and writes nothing to `error`. `idleVideoStatus` is the only field that moves, which is
|
|
351
|
+
* why this exists rather than a doc line telling you to poll.
|
|
352
|
+
*
|
|
353
|
+
* Race-free without a baseline: the platform commits `queued` before `setLoop` returns, so
|
|
354
|
+
* by the time you can call this the status has already left `ready`.
|
|
355
|
+
*
|
|
356
|
+
* Resolves with the settled avatar — `sourceAssetId` is now the new loop. Throws on a
|
|
357
|
+
* failed render and on timeout; a timeout is not a failure, so re-poll or call again.
|
|
358
|
+
*/
|
|
359
|
+
async waitForLoop(avatarId, options = {}) {
|
|
360
|
+
const timeoutMs = options.timeoutMs ?? 20 * 6e4;
|
|
361
|
+
const pollMs = Math.max(1e3, options.pollMs ?? 1e4);
|
|
362
|
+
const deadline = Date.now() + timeoutMs;
|
|
363
|
+
for (; ; ) {
|
|
364
|
+
const avatar = await this.getAvatar(avatarId);
|
|
365
|
+
if (avatar.idleVideoStatus === "failed") {
|
|
366
|
+
throw new RealtimeAvatarError(
|
|
367
|
+
`Loop re-direct failed for ${avatarId}. She is still serving her previous loop \u2014 nothing was lost, and you can send another description.`
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
if (avatar.idleVideoStatus === "ready" || avatar.idleVideoStatus === "none") return avatar;
|
|
371
|
+
if (Date.now() >= deadline) {
|
|
372
|
+
throw new RealtimeAvatarError(
|
|
373
|
+
`Loop re-direct for ${avatarId} was still ${avatar.idleVideoStatus} after ${Math.round(timeoutMs / 1e3)}s. This is a timeout, not a failure \u2014 the render may still land.`
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
await sleep(pollMs);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Block until every clip in the library has stopped moving.
|
|
381
|
+
*
|
|
382
|
+
* Settled means no row is `queued` or `generating` — NOT that every row is `ready`.
|
|
383
|
+
* Waiting for all-`ready` is the intuitive version and it hangs forever: a clip rejected
|
|
384
|
+
* by pose validation settles `failed`, which is terminal. So this returns the library
|
|
385
|
+
* with the failures in it and lets you decide; a partial library is a legitimate outcome
|
|
386
|
+
* and the rest of it is already serving.
|
|
387
|
+
*
|
|
388
|
+
* Throws only on timeout.
|
|
389
|
+
*/
|
|
390
|
+
async waitForClips(avatarId, options = {}) {
|
|
391
|
+
const timeoutMs = options.timeoutMs ?? 20 * 6e4;
|
|
392
|
+
const pollMs = Math.max(1e3, options.pollMs ?? 1e4);
|
|
393
|
+
const deadline = Date.now() + timeoutMs;
|
|
394
|
+
for (; ; ) {
|
|
395
|
+
const library = await this.listClips(avatarId);
|
|
396
|
+
const moving = library.data.filter((c) => c.status === "queued" || c.status === "generating");
|
|
397
|
+
if (moving.length === 0) return library;
|
|
398
|
+
if (Date.now() >= deadline) {
|
|
399
|
+
throw new RealtimeAvatarError(
|
|
400
|
+
`${moving.length} clip(s) on ${avatarId} were still rendering after ${Math.round(timeoutMs / 1e3)}s: ${moving.map((c) => c.clipId).join(", ")}. This is a timeout, not a failure.`
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
await sleep(pollMs);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
277
406
|
/** The avatar's clip library: every non-retired clip, plus revision, anchor and eligibility. */
|
|
278
407
|
async listClips(avatarId) {
|
|
279
408
|
return this.#clipEnvelope(
|
|
@@ -475,7 +604,13 @@ function toAvatar(raw) {
|
|
|
475
604
|
sourceAssetId: a.sourceAssetId ? String(a.sourceAssetId) : null,
|
|
476
605
|
// Carried because it is the ONLY channel a failed source swap has: she stays `ready`
|
|
477
606
|
// and serving, and this says why the re-shoot did not take.
|
|
478
|
-
error: a.error ? String(a.error) : null
|
|
607
|
+
error: a.error ? String(a.error) : null,
|
|
608
|
+
// The loop lane's terminal signal, and the reason it is here: a re-direct that fails
|
|
609
|
+
// leaves `status` on `ready` (she is still serving the old loop, which is the whole
|
|
610
|
+
// design) and writes nothing to `error`. Without this field a caller polling after
|
|
611
|
+
// `setLoop` has NO way to distinguish "still rendering" from "gave up", and waits
|
|
612
|
+
// forever. queued → generating → ready | failed.
|
|
613
|
+
idleVideoStatus: a.idleVideoStatus ?? "none"
|
|
479
614
|
};
|
|
480
615
|
}
|
|
481
616
|
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.5.
|
|
46
|
+
var SDK_VERSION = "0.5.2";
|
|
47
47
|
var RealtimeAvatar = class {
|
|
48
48
|
#apiKey;
|
|
49
49
|
#baseUrl;
|
|
@@ -157,10 +157,15 @@ var RealtimeAvatar = class {
|
|
|
157
157
|
/**
|
|
158
158
|
* Register a character from a looping clip you host.
|
|
159
159
|
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
160
|
+
* @deprecated CLOSED to new callers — this answers `422` unless your tenant was already
|
|
161
|
+
* creating from video, in which case it keeps working and your existing avatars are
|
|
162
|
+
* untouched. Use {@link createAvatarFromImage}: one still, and the platform renders the
|
|
163
|
+
* resting loop and the motion library from it.
|
|
164
|
+
*
|
|
165
|
+
* The reason it closed is not arbitrary. Every clip has to start and end on ONE rest pose
|
|
166
|
+
* or a state switch reads as a jump, and the platform can only guarantee that when it
|
|
167
|
+
* rendered the loop and the clips from the same portrait. A supplied video cannot honour
|
|
168
|
+
* it, so the lane could never be made to look right.
|
|
164
169
|
*/
|
|
165
170
|
async createAvatarFromVideo(input) {
|
|
166
171
|
const asset = await this.createRemoteAsset({ kind: "video", remoteUrl: input.videoUrl });
|
|
@@ -173,12 +178,41 @@ var RealtimeAvatar = class {
|
|
|
173
178
|
metadata: input.metadata
|
|
174
179
|
});
|
|
175
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Register a character from ONE still image. The platform generates everything moving:
|
|
183
|
+
* the resting loop she idles in, then a starter motion library rendered against her rest
|
|
184
|
+
* pose. No footage, no clip URLs, nothing to shoot.
|
|
185
|
+
*
|
|
186
|
+
* `motionPrompt` directs the RESTING LOOP — the video she plays when nothing else is
|
|
187
|
+
* happening — and it is the only chance to direct it, because there is no API today that
|
|
188
|
+
* re-generates a loop after creation (see {@link updateAvatar} for the one thing that can
|
|
189
|
+
* be re-pointed). Describe a small closed arc that returns to where it started: "settles
|
|
190
|
+
* into frame, breathes gently, a slow blink". Omit it and the house default is used.
|
|
191
|
+
*
|
|
192
|
+
* Creation returns while the avatar is still `preprocessing`; poll {@link getAvatar} until
|
|
193
|
+
* it leaves that state. The loop is load-bearing, so a failure there settles `failed` with
|
|
194
|
+
* a readable error — the motion library is not, and a library failure degrades to
|
|
195
|
+
* loop-only rather than demoting the character.
|
|
196
|
+
*/
|
|
197
|
+
async createAvatarFromImage(input) {
|
|
198
|
+
const asset = await this.createRemoteAsset({ kind: "image", remoteUrl: input.imageUrl });
|
|
199
|
+
return this.createAvatar({
|
|
200
|
+
displayName: input.displayName,
|
|
201
|
+
sourceKind: "image",
|
|
202
|
+
sourceAssetId: asset.id,
|
|
203
|
+
motionPrompt: input.motionPrompt,
|
|
204
|
+
voice: input.voice,
|
|
205
|
+
settings: input.settings,
|
|
206
|
+
metadata: input.metadata
|
|
207
|
+
});
|
|
208
|
+
}
|
|
176
209
|
async createAvatar(input) {
|
|
177
210
|
const body = {
|
|
178
211
|
displayName: input.displayName,
|
|
179
212
|
sourceKind: input.sourceKind,
|
|
180
213
|
sourceAssetId: input.sourceAssetId
|
|
181
214
|
};
|
|
215
|
+
if (input.motionPrompt !== void 0) body.motionPrompt = input.motionPrompt;
|
|
182
216
|
if (input.voice !== void 0) body.voice = input.voice;
|
|
183
217
|
if (input.settings !== void 0) body.settings = input.settings;
|
|
184
218
|
if (input.metadata !== void 0) body.metadata = input.metadata;
|
|
@@ -191,7 +225,14 @@ var RealtimeAvatar = class {
|
|
|
191
225
|
async getAvatar(avatarId) {
|
|
192
226
|
return toAvatar(await this.#json(await this.#request("GET", `/avatars/${avatarId}`)));
|
|
193
227
|
}
|
|
194
|
-
/**
|
|
228
|
+
/**
|
|
229
|
+
* Re-point what an avatar already is. `defaultVoiceId: null` clears the default voice.
|
|
230
|
+
*
|
|
231
|
+
* Cheap metadata only. The three things that cost a render have their own methods, because
|
|
232
|
+
* putting them in a patch alongside a rename would hide minutes of GPU work behind a field:
|
|
233
|
+
* {@link retimeAnchor} moves the rest frame, {@link swapSource} replaces the footage, and
|
|
234
|
+
* the resting loop is re-directed by description at `PUT /v1/avatars/{id}/loop`.
|
|
235
|
+
*/
|
|
195
236
|
async updateAvatar(avatarId, patch) {
|
|
196
237
|
return toAvatar(
|
|
197
238
|
await this.#json(await this.#request("PATCH", `/avatars/${avatarId}`, { json: patch }))
|
|
@@ -274,6 +315,94 @@ var RealtimeAvatar = class {
|
|
|
274
315
|
)
|
|
275
316
|
);
|
|
276
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* Re-direct the RESTING LOOP — the video she plays when nothing else is happening — from
|
|
320
|
+
* a new one-sentence description.
|
|
321
|
+
*
|
|
322
|
+
* Not a clip, and this is the distinction integrations get wrong: a clip with
|
|
323
|
+
* `role: "idle"` is a variant spliced OVER the loop, and declaring one never changes what
|
|
324
|
+
* she rests in. This is the only thing that does.
|
|
325
|
+
*
|
|
326
|
+
* `202`, because the render takes minutes. Three properties, all measured against a real
|
|
327
|
+
* render rather than asserted:
|
|
328
|
+
*
|
|
329
|
+
* - **She never goes dark.** She stays `ready` and keeps serving her previous loop for the
|
|
330
|
+
* entire render — returned as `servingUrl` — then the swap publishes in one step.
|
|
331
|
+
* - **Your clip library is untouched.** Clips render against the portrait, not against the
|
|
332
|
+
* loop, so a re-direct re-queues nothing and does not move `revision`.
|
|
333
|
+
* - **It bills once**, at the rendering model's rate, per re-direct.
|
|
334
|
+
*
|
|
335
|
+
* Refusals worth telling apart: `409 loop_pending` (one is already in flight — wait) and
|
|
336
|
+
* `422 loop_not_generatable` (a grandfathered video-sourced avatar has no portrait to
|
|
337
|
+
* re-animate — terminal, do not retry).
|
|
338
|
+
*/
|
|
339
|
+
async setLoop(avatarId, loop) {
|
|
340
|
+
return await this.#json(
|
|
341
|
+
await this.#request("PUT", `/avatars/${avatarId}/loop`, { json: { motionPrompt: loop.motionPrompt } })
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Block until a loop re-direct settles, and throw if it did not take.
|
|
346
|
+
*
|
|
347
|
+
* `setLoop` returns on ACCEPTANCE; the render runs for minutes afterwards. Every caller
|
|
348
|
+
* therefore writes the same polling loop, and the obvious version of it never terminates
|
|
349
|
+
* on failure — a failed re-direct leaves her `ready` (she is still serving the old loop)
|
|
350
|
+
* and writes nothing to `error`. `idleVideoStatus` is the only field that moves, which is
|
|
351
|
+
* why this exists rather than a doc line telling you to poll.
|
|
352
|
+
*
|
|
353
|
+
* Race-free without a baseline: the platform commits `queued` before `setLoop` returns, so
|
|
354
|
+
* by the time you can call this the status has already left `ready`.
|
|
355
|
+
*
|
|
356
|
+
* Resolves with the settled avatar — `sourceAssetId` is now the new loop. Throws on a
|
|
357
|
+
* failed render and on timeout; a timeout is not a failure, so re-poll or call again.
|
|
358
|
+
*/
|
|
359
|
+
async waitForLoop(avatarId, options = {}) {
|
|
360
|
+
const timeoutMs = options.timeoutMs ?? 20 * 6e4;
|
|
361
|
+
const pollMs = Math.max(1e3, options.pollMs ?? 1e4);
|
|
362
|
+
const deadline = Date.now() + timeoutMs;
|
|
363
|
+
for (; ; ) {
|
|
364
|
+
const avatar = await this.getAvatar(avatarId);
|
|
365
|
+
if (avatar.idleVideoStatus === "failed") {
|
|
366
|
+
throw new RealtimeAvatarError(
|
|
367
|
+
`Loop re-direct failed for ${avatarId}. She is still serving her previous loop \u2014 nothing was lost, and you can send another description.`
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
if (avatar.idleVideoStatus === "ready" || avatar.idleVideoStatus === "none") return avatar;
|
|
371
|
+
if (Date.now() >= deadline) {
|
|
372
|
+
throw new RealtimeAvatarError(
|
|
373
|
+
`Loop re-direct for ${avatarId} was still ${avatar.idleVideoStatus} after ${Math.round(timeoutMs / 1e3)}s. This is a timeout, not a failure \u2014 the render may still land.`
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
await sleep(pollMs);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Block until every clip in the library has stopped moving.
|
|
381
|
+
*
|
|
382
|
+
* Settled means no row is `queued` or `generating` — NOT that every row is `ready`.
|
|
383
|
+
* Waiting for all-`ready` is the intuitive version and it hangs forever: a clip rejected
|
|
384
|
+
* by pose validation settles `failed`, which is terminal. So this returns the library
|
|
385
|
+
* with the failures in it and lets you decide; a partial library is a legitimate outcome
|
|
386
|
+
* and the rest of it is already serving.
|
|
387
|
+
*
|
|
388
|
+
* Throws only on timeout.
|
|
389
|
+
*/
|
|
390
|
+
async waitForClips(avatarId, options = {}) {
|
|
391
|
+
const timeoutMs = options.timeoutMs ?? 20 * 6e4;
|
|
392
|
+
const pollMs = Math.max(1e3, options.pollMs ?? 1e4);
|
|
393
|
+
const deadline = Date.now() + timeoutMs;
|
|
394
|
+
for (; ; ) {
|
|
395
|
+
const library = await this.listClips(avatarId);
|
|
396
|
+
const moving = library.data.filter((c) => c.status === "queued" || c.status === "generating");
|
|
397
|
+
if (moving.length === 0) return library;
|
|
398
|
+
if (Date.now() >= deadline) {
|
|
399
|
+
throw new RealtimeAvatarError(
|
|
400
|
+
`${moving.length} clip(s) on ${avatarId} were still rendering after ${Math.round(timeoutMs / 1e3)}s: ${moving.map((c) => c.clipId).join(", ")}. This is a timeout, not a failure.`
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
await sleep(pollMs);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
277
406
|
/** The avatar's clip library: every non-retired clip, plus revision, anchor and eligibility. */
|
|
278
407
|
async listClips(avatarId) {
|
|
279
408
|
return this.#clipEnvelope(
|
|
@@ -475,7 +604,13 @@ function toAvatar(raw) {
|
|
|
475
604
|
sourceAssetId: a.sourceAssetId ? String(a.sourceAssetId) : null,
|
|
476
605
|
// Carried because it is the ONLY channel a failed source swap has: she stays `ready`
|
|
477
606
|
// and serving, and this says why the re-shoot did not take.
|
|
478
|
-
error: a.error ? String(a.error) : null
|
|
607
|
+
error: a.error ? String(a.error) : null,
|
|
608
|
+
// The loop lane's terminal signal, and the reason it is here: a re-direct that fails
|
|
609
|
+
// leaves `status` on `ready` (she is still serving the old loop, which is the whole
|
|
610
|
+
// design) and writes nothing to `error`. Without this field a caller polling after
|
|
611
|
+
// `setLoop` has NO way to distinguish "still rendering" from "gave up", and waits
|
|
612
|
+
// forever. queued → generating → ready | failed.
|
|
613
|
+
idleVideoStatus: a.idleVideoStatus ?? "none"
|
|
479
614
|
};
|
|
480
615
|
}
|
|
481
616
|
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 AvatarSourceSwap, d as ClipDeclaration, e as ClipLibraryUpdate, f as ClipLibrary, g as ClipSyncResult, h as AssetKind, i as Asset,
|
|
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, L as LoopRedirect, f as ClipLibrary, g as ClipSyncResult, h as AssetKind, i as Asset, j as ListSessionsOptions, U as UsageSessionPage, k as UsageSession, l as CreditBalance, T as TranscriptPayload } from './types-BeGuvyra.js';
|
|
2
|
+
export { m as AvatarClip, n as CallConnection, o as CallQueued, p as ClipLibraryPlan, q as ClipSource, r as ContextMessage, s as EndCallReason, V as VideoPolicy, t as VideoState, u as isQueued } from './types-BeGuvyra.js';
|
|
3
3
|
|
|
4
4
|
interface RealtimeAvatarOptions {
|
|
5
5
|
/** `tic_live_…` or `tic_test_…`. Server-side only — never ship this to a browser. */
|
|
@@ -74,10 +74,15 @@ declare class RealtimeAvatar {
|
|
|
74
74
|
/**
|
|
75
75
|
* Register a character from a looping clip you host.
|
|
76
76
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
77
|
+
* @deprecated CLOSED to new callers — this answers `422` unless your tenant was already
|
|
78
|
+
* creating from video, in which case it keeps working and your existing avatars are
|
|
79
|
+
* untouched. Use {@link createAvatarFromImage}: one still, and the platform renders the
|
|
80
|
+
* resting loop and the motion library from it.
|
|
81
|
+
*
|
|
82
|
+
* The reason it closed is not arbitrary. Every clip has to start and end on ONE rest pose
|
|
83
|
+
* or a state switch reads as a jump, and the platform can only guarantee that when it
|
|
84
|
+
* rendered the loop and the clips from the same portrait. A supplied video cannot honour
|
|
85
|
+
* it, so the lane could never be made to look right.
|
|
81
86
|
*/
|
|
82
87
|
createAvatarFromVideo(input: {
|
|
83
88
|
displayName: string;
|
|
@@ -86,17 +91,51 @@ declare class RealtimeAvatar {
|
|
|
86
91
|
settings?: Record<string, unknown>;
|
|
87
92
|
metadata?: Record<string, unknown>;
|
|
88
93
|
}): Promise<Avatar>;
|
|
94
|
+
/**
|
|
95
|
+
* Register a character from ONE still image. The platform generates everything moving:
|
|
96
|
+
* the resting loop she idles in, then a starter motion library rendered against her rest
|
|
97
|
+
* pose. No footage, no clip URLs, nothing to shoot.
|
|
98
|
+
*
|
|
99
|
+
* `motionPrompt` directs the RESTING LOOP — the video she plays when nothing else is
|
|
100
|
+
* happening — and it is the only chance to direct it, because there is no API today that
|
|
101
|
+
* re-generates a loop after creation (see {@link updateAvatar} for the one thing that can
|
|
102
|
+
* be re-pointed). Describe a small closed arc that returns to where it started: "settles
|
|
103
|
+
* into frame, breathes gently, a slow blink". Omit it and the house default is used.
|
|
104
|
+
*
|
|
105
|
+
* Creation returns while the avatar is still `preprocessing`; poll {@link getAvatar} until
|
|
106
|
+
* it leaves that state. The loop is load-bearing, so a failure there settles `failed` with
|
|
107
|
+
* a readable error — the motion library is not, and a library failure degrades to
|
|
108
|
+
* loop-only rather than demoting the character.
|
|
109
|
+
*/
|
|
110
|
+
createAvatarFromImage(input: {
|
|
111
|
+
displayName: string;
|
|
112
|
+
imageUrl: string;
|
|
113
|
+
motionPrompt?: string;
|
|
114
|
+
voice?: unknown;
|
|
115
|
+
settings?: Record<string, unknown>;
|
|
116
|
+
metadata?: Record<string, unknown>;
|
|
117
|
+
}): Promise<Avatar>;
|
|
89
118
|
createAvatar(input: {
|
|
90
119
|
displayName: string;
|
|
91
120
|
sourceKind: "image" | "video";
|
|
92
121
|
sourceAssetId: string;
|
|
122
|
+
/** Art direction for the generated resting loop. Image sources only — a video source
|
|
123
|
+
* already IS the loop, and the platform ignores it there. */
|
|
124
|
+
motionPrompt?: string;
|
|
93
125
|
voice?: unknown;
|
|
94
126
|
settings?: Record<string, unknown>;
|
|
95
127
|
metadata?: Record<string, unknown>;
|
|
96
128
|
}): Promise<Avatar>;
|
|
97
129
|
listAvatars(): Promise<Avatar[]>;
|
|
98
130
|
getAvatar(avatarId: string): Promise<Avatar>;
|
|
99
|
-
/**
|
|
131
|
+
/**
|
|
132
|
+
* Re-point what an avatar already is. `defaultVoiceId: null` clears the default voice.
|
|
133
|
+
*
|
|
134
|
+
* Cheap metadata only. The three things that cost a render have their own methods, because
|
|
135
|
+
* putting them in a patch alongside a rename would hide minutes of GPU work behind a field:
|
|
136
|
+
* {@link retimeAnchor} moves the rest frame, {@link swapSource} replaces the footage, and
|
|
137
|
+
* the resting loop is re-directed by description at `PUT /v1/avatars/{id}/loop`.
|
|
138
|
+
*/
|
|
100
139
|
updateAvatar(avatarId: string, patch: AvatarUpdate): Promise<Avatar>;
|
|
101
140
|
/**
|
|
102
141
|
* Re-shoot the character — swap in new footage as her resting loop.
|
|
@@ -152,6 +191,64 @@ declare class RealtimeAvatar {
|
|
|
152
191
|
clips: readonly ClipDeclaration[];
|
|
153
192
|
expectedRevision?: number;
|
|
154
193
|
}): Promise<ClipLibraryUpdate>;
|
|
194
|
+
/**
|
|
195
|
+
* Re-direct the RESTING LOOP — the video she plays when nothing else is happening — from
|
|
196
|
+
* a new one-sentence description.
|
|
197
|
+
*
|
|
198
|
+
* Not a clip, and this is the distinction integrations get wrong: a clip with
|
|
199
|
+
* `role: "idle"` is a variant spliced OVER the loop, and declaring one never changes what
|
|
200
|
+
* she rests in. This is the only thing that does.
|
|
201
|
+
*
|
|
202
|
+
* `202`, because the render takes minutes. Three properties, all measured against a real
|
|
203
|
+
* render rather than asserted:
|
|
204
|
+
*
|
|
205
|
+
* - **She never goes dark.** She stays `ready` and keeps serving her previous loop for the
|
|
206
|
+
* entire render — returned as `servingUrl` — then the swap publishes in one step.
|
|
207
|
+
* - **Your clip library is untouched.** Clips render against the portrait, not against the
|
|
208
|
+
* loop, so a re-direct re-queues nothing and does not move `revision`.
|
|
209
|
+
* - **It bills once**, at the rendering model's rate, per re-direct.
|
|
210
|
+
*
|
|
211
|
+
* Refusals worth telling apart: `409 loop_pending` (one is already in flight — wait) and
|
|
212
|
+
* `422 loop_not_generatable` (a grandfathered video-sourced avatar has no portrait to
|
|
213
|
+
* re-animate — terminal, do not retry).
|
|
214
|
+
*/
|
|
215
|
+
setLoop(avatarId: string, loop: {
|
|
216
|
+
motionPrompt: string;
|
|
217
|
+
}): Promise<LoopRedirect>;
|
|
218
|
+
/**
|
|
219
|
+
* Block until a loop re-direct settles, and throw if it did not take.
|
|
220
|
+
*
|
|
221
|
+
* `setLoop` returns on ACCEPTANCE; the render runs for minutes afterwards. Every caller
|
|
222
|
+
* therefore writes the same polling loop, and the obvious version of it never terminates
|
|
223
|
+
* on failure — a failed re-direct leaves her `ready` (she is still serving the old loop)
|
|
224
|
+
* and writes nothing to `error`. `idleVideoStatus` is the only field that moves, which is
|
|
225
|
+
* why this exists rather than a doc line telling you to poll.
|
|
226
|
+
*
|
|
227
|
+
* Race-free without a baseline: the platform commits `queued` before `setLoop` returns, so
|
|
228
|
+
* by the time you can call this the status has already left `ready`.
|
|
229
|
+
*
|
|
230
|
+
* Resolves with the settled avatar — `sourceAssetId` is now the new loop. Throws on a
|
|
231
|
+
* failed render and on timeout; a timeout is not a failure, so re-poll or call again.
|
|
232
|
+
*/
|
|
233
|
+
waitForLoop(avatarId: string, options?: {
|
|
234
|
+
timeoutMs?: number;
|
|
235
|
+
pollMs?: number;
|
|
236
|
+
}): Promise<Avatar>;
|
|
237
|
+
/**
|
|
238
|
+
* Block until every clip in the library has stopped moving.
|
|
239
|
+
*
|
|
240
|
+
* Settled means no row is `queued` or `generating` — NOT that every row is `ready`.
|
|
241
|
+
* Waiting for all-`ready` is the intuitive version and it hangs forever: a clip rejected
|
|
242
|
+
* by pose validation settles `failed`, which is terminal. So this returns the library
|
|
243
|
+
* with the failures in it and lets you decide; a partial library is a legitimate outcome
|
|
244
|
+
* and the rest of it is already serving.
|
|
245
|
+
*
|
|
246
|
+
* Throws only on timeout.
|
|
247
|
+
*/
|
|
248
|
+
waitForClips(avatarId: string, options?: {
|
|
249
|
+
timeoutMs?: number;
|
|
250
|
+
pollMs?: number;
|
|
251
|
+
}): Promise<ClipLibrary>;
|
|
155
252
|
/** The avatar's clip library: every non-retired clip, plus revision, anchor and eligibility. */
|
|
156
253
|
listClips(avatarId: string): Promise<ClipLibrary>;
|
|
157
254
|
/**
|
|
@@ -216,9 +313,28 @@ declare class RealtimeAvatarError extends Error {
|
|
|
216
313
|
/**
|
|
217
314
|
* A non-2xx from the API.
|
|
218
315
|
*
|
|
219
|
-
* `code` is the machine-readable reason when the body was JSON
|
|
220
|
-
*
|
|
221
|
-
*
|
|
316
|
+
* `code` is the machine-readable reason when the body was JSON. An HTML body (no `code`)
|
|
317
|
+
* almost always means the route is not served at all rather than that your request was
|
|
318
|
+
* wrong.
|
|
319
|
+
*
|
|
320
|
+
* The ones worth branching on, and what each one asks you to DO — the distinction that
|
|
321
|
+
* matters is retry-vs-don't, because a code you cannot classify becomes either a retry loop
|
|
322
|
+
* against a permanent refusal or a give-up on a transient one:
|
|
323
|
+
*
|
|
324
|
+
* | `code` | | |
|
|
325
|
+
* | --- | --- | --- |
|
|
326
|
+
* | `insufficient_credits` · `spend_limit_exceeded` | 402 | top up; do not retry |
|
|
327
|
+
* | `clip_library_not_enabled` | 403 | per-tenant rollout, not a bad request. Nothing about the body will help |
|
|
328
|
+
* | `loop_not_generatable` | 422 | a grandfathered video-sourced avatar has no portrait to re-animate. **Terminal** |
|
|
329
|
+
* | `clip_declaration_rejected` · `loop_prompt_rejected` | 422 | the prose was refused; rewrite it |
|
|
330
|
+
* | `loop_pending` · `anchor_pending` | 409 | one is already in flight — wait, then retry |
|
|
331
|
+
* | `revision_conflict` | 409 | someone declared first. Re-read `listClips`, re-decide, re-declare |
|
|
332
|
+
* | `clip_render_limit` | 429 | a true rate limit; nothing was applied. Retry later |
|
|
333
|
+
* | `clip_screen_unavailable` | 503 | the prose screen could not run. Retry |
|
|
334
|
+
*
|
|
335
|
+
* Pose validation does NOT appear here: a rejected upload is not an error response at all.
|
|
336
|
+
* The declaration is accepted, and that clip settles `status: "failed"` with the verdict in
|
|
337
|
+
* `poseCheck`.
|
|
222
338
|
*
|
|
223
339
|
* Fields are declared and assigned rather than written as TypeScript parameter properties:
|
|
224
340
|
* that syntax cannot be type-stripped, and this package is meant to run straight from source
|
|
@@ -248,4 +364,4 @@ declare function verifyTranscript(rawBody: Uint8Array | string, headers: Headers
|
|
|
248
364
|
toleranceSeconds?: number;
|
|
249
365
|
}): Promise<TranscriptPayload>;
|
|
250
366
|
|
|
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 };
|
|
367
|
+
export { Asset, AssetKind, Avatar, AvatarSourceSwap, AvatarUpdate, CallMode, CallPolicy, ClipDeclaration, ClipLibrary, ClipLibraryUpdate, ClipSyncResult, CreditBalance, EndCallOptions, ListSessionsOptions, LoopRedirect, RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, type RealtimeAvatarOptions, type StartCallOptions, StartCallResult, TranscriptPayload, UsageSession, UsageSessionPage, verifyTranscript };
|