realtime-avatar 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/dist/browser.d.ts +11 -1
- package/dist/browser.js +13 -1
- package/dist/{chunk-IQUJLPYF.js → chunk-5UEMZN6J.js} +190 -79
- package/dist/{chunk-A2ATKWJT.js → chunk-UJQBYKPI.js} +1 -1
- package/dist/express.d.ts +2 -2
- package/dist/express.js +2 -2
- package/dist/hono.d.ts +2 -2
- package/dist/hono.js +2 -2
- package/dist/index.d.ts +53 -45
- package/dist/index.js +1 -1
- package/dist/nextjs.d.ts +2 -2
- package/dist/nextjs.js +2 -2
- package/dist/{proxy-client-DDqYRz-_.d.ts → proxy-client-B6OnXpaC.d.ts} +9 -15
- package/dist/react-native.d.ts +2 -2
- package/dist/react-native.js +7 -8
- package/dist/react.d.ts +19 -4
- package/dist/react.js +258 -23
- package/dist/server.d.ts +3 -2
- package/dist/server.js +1 -1
- package/dist/tanstack-start.d.ts +2 -2
- package/dist/tanstack-start.js +2 -2
- package/dist/tools.d.ts +24 -1
- package/dist/tools.js +79 -26
- package/dist/{types-D_aIKv4P.d.ts → types-BgYFwlSn.d.ts} +91 -74
- package/dist/{types-QoMHpR1h.d.ts → types-zQtYeR_l.d.ts} +1 -1
- package/package.json +1 -1
package/dist/react-native.js
CHANGED
|
@@ -2398,11 +2398,11 @@ function useRealtimeSession(input) {
|
|
|
2398
2398
|
requestGracefulClose();
|
|
2399
2399
|
lifecycle.reset();
|
|
2400
2400
|
}, [lifecycle, requestGracefulClose]);
|
|
2401
|
-
const
|
|
2402
|
-
(
|
|
2401
|
+
const performAction = useCallback(
|
|
2402
|
+
(actionId, opts) => {
|
|
2403
2403
|
const requestId = newTurnId();
|
|
2404
2404
|
const publish = dataPublisherRef.current;
|
|
2405
|
-
const trimmed =
|
|
2405
|
+
const trimmed = actionId.trim();
|
|
2406
2406
|
if (!publish || !trimmed) {
|
|
2407
2407
|
const result = { requestId, accepted: false, reason: "not_connected" };
|
|
2408
2408
|
cbRef.current.onClipResult?.(result);
|
|
@@ -2417,10 +2417,9 @@ function useRealtimeSession(input) {
|
|
|
2417
2417
|
}, opts?.timeoutMs ?? 5e3);
|
|
2418
2418
|
pendingClipsRef.current.set(requestId, { resolve, timer });
|
|
2419
2419
|
publish({
|
|
2420
|
-
kind: "
|
|
2420
|
+
kind: "action_request",
|
|
2421
2421
|
request_id: requestId,
|
|
2422
|
-
|
|
2423
|
-
...typeof opts?.holdSeconds === "number" ? { hold_seconds: opts.holdSeconds } : {}
|
|
2422
|
+
action_id: trimmed
|
|
2424
2423
|
});
|
|
2425
2424
|
});
|
|
2426
2425
|
},
|
|
@@ -2465,7 +2464,7 @@ function useRealtimeSession(input) {
|
|
|
2465
2464
|
retryTurn,
|
|
2466
2465
|
end,
|
|
2467
2466
|
behavior,
|
|
2468
|
-
|
|
2467
|
+
performAction,
|
|
2469
2468
|
onLifecycleData,
|
|
2470
2469
|
registerDataPublisher,
|
|
2471
2470
|
registerTurnSender,
|
|
@@ -2487,7 +2486,7 @@ function useRealtimeSession(input) {
|
|
|
2487
2486
|
retryTurn,
|
|
2488
2487
|
end,
|
|
2489
2488
|
behavior,
|
|
2490
|
-
|
|
2489
|
+
performAction,
|
|
2491
2490
|
onLifecycleData,
|
|
2492
2491
|
registerDataPublisher,
|
|
2493
2492
|
registerTurnSender,
|
package/dist/react.d.ts
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
|
+
import { RegisteredTool, ToolRegistration } from './tools.js';
|
|
1
2
|
import { ReactNode } from 'react';
|
|
2
|
-
import { g as AvatarSessionClient, A as AvatarVideoFit } from './proxy-client-
|
|
3
|
-
export { a as AdaptivePlayoutController, b as AdaptivePlayoutDecision, c as AdaptivePlayoutOptions, d as AdaptivePlayoutSample, e as ApproachingEndEvent, f as ApproachingEndReason, aK as AvatarVideoSurface, aL as AvatarVideoSurfaceProps, B as BehaviorSnapshot, C as CallTranscript, h as CallTranscriptSegment, i as CartesiaTtsModel, j as ClipResult, k as ClosingTurnResult, l as CreditsLowEvent, D as DEFAULT_APPROACHING_END_LEAD_SECONDS, m as DEFAULT_CREDITS_LOW_LEAD_SECONDS, n as DEFAULT_GOVERNOR_CONFIG, o as DEFAULT_GRACE_CEILING_SECONDS, p as DEFAULT_GRACE_WINDOW_LEAD_SECONDS, q as DEFAULT_IDLE_SECONDS, r as DEFAULT_IDLE_WARN_LEAD_SECONDS, s as DEFAULT_TURN_TIMEOUT_SECONDS, E as EndReason, t as EndedEvent, u as ExtendResult, F as FishTtsModel, v as FreezeReadingFn, G as Governor, w as GovernorAction, x as GovernorConfig, y as GovernorSignal, z as GovernorState, H as GraceWindowClosedEvent, I as GraceWindowOpenEvent, J as GraceWindowState, K as IdleWarningEvent, M as InboundRtpCursor, N as InboundRtpReading, O as KnownBehaviorState, P as LLMProvider, Q as LLMSelection, R as LiveKitAvatarGrantState, S as LiveKitAvatarGrantStatus, T as LiveKitCapacityState, U as LiveKitConnectionStatus, V as LiveKitSessionRequest, W as LiveKitSessionStartResult, aM as LivePlaybackKeeper, X as MAX_RECONNECT_ATTEMPTS, Y as MAX_SESSION_INSTRUCTIONS_CHARS, aN as MIC_LEASE_ENDED_TIMEOUT_MS, aO as MicLease, aP as PlayableVideoElement, Z as ProxyClientOptions, _ as QualityCap, $ as RECONNECT_BACKOFF_MS, aQ as RealtimeAvatarCapacityError, aR as RealtimeAvatarLiveKitRoom, aS as RealtimeAvatarLiveKitRoomProps, a0 as RealtimeAvatarRequestOptions, a1 as RealtimeSessionApi, a2 as RealtimeSessionMedia, a3 as RealtimeSessionRoomSinks, a4 as ReconnectPolicy, a5 as ReconnectingEvent, a6 as RecoveryState, a7 as RetryStep, a8 as SessionBehavior, a9 as SessionClip, aa as SessionClocks, ab as SessionEndReason, ac as SessionLifecycleApi, ad as SessionLifecyclePhase, ae as SessionLifecyclePhaseKind, af as SessionLifecycleRoomBridge, ag as SessionLifecycleRoomBridgeProps, ah as SurfaceLayers, ai as TurnState, aj as TurnTimeoutEvent, ak as UseAvatarQualityGovernorInput, al as UseLiveKitAvatarGrantInput, am as UseRealtimeSessionInput, an as UseSessionLifecycleInput, ao as VoiceSpec, ap as VoiceSpecInput, aq as capacityErrorFromBusy, ar as capacityStateFromGrant, as as createProxyClient, au as knownBehaviorStates, av as mapTurnState, aw as readInboundRtp, ax as sessionBehaviorSchema, ay as sessionClipSchema, aT as shouldReplayPendingTurn, az as splitCallTranscript, aA as useAvatarAdaptivePlayoutDelay, aB as useAvatarPlayoutDelay, aC as useAvatarQualityGovernor, aD as useCallTranscript, aE as useLiveKitAvatarGrant, aG as useMicLease, aH as useRealtimeSession, aI as useReleaseMicLeaseOnTrackEnded, aJ as useSessionLifecycle } from './proxy-client-
|
|
3
|
+
import { g as AvatarSessionClient, A as AvatarVideoFit } from './proxy-client-B6OnXpaC.js';
|
|
4
|
+
export { a as AdaptivePlayoutController, b as AdaptivePlayoutDecision, c as AdaptivePlayoutOptions, d as AdaptivePlayoutSample, e as ApproachingEndEvent, f as ApproachingEndReason, aK as AvatarVideoSurface, aL as AvatarVideoSurfaceProps, B as BehaviorSnapshot, C as CallTranscript, h as CallTranscriptSegment, i as CartesiaTtsModel, j as ClipResult, k as ClosingTurnResult, l as CreditsLowEvent, D as DEFAULT_APPROACHING_END_LEAD_SECONDS, m as DEFAULT_CREDITS_LOW_LEAD_SECONDS, n as DEFAULT_GOVERNOR_CONFIG, o as DEFAULT_GRACE_CEILING_SECONDS, p as DEFAULT_GRACE_WINDOW_LEAD_SECONDS, q as DEFAULT_IDLE_SECONDS, r as DEFAULT_IDLE_WARN_LEAD_SECONDS, s as DEFAULT_TURN_TIMEOUT_SECONDS, E as EndReason, t as EndedEvent, u as ExtendResult, F as FishTtsModel, v as FreezeReadingFn, G as Governor, w as GovernorAction, x as GovernorConfig, y as GovernorSignal, z as GovernorState, H as GraceWindowClosedEvent, I as GraceWindowOpenEvent, J as GraceWindowState, K as IdleWarningEvent, M as InboundRtpCursor, N as InboundRtpReading, O as KnownBehaviorState, P as LLMProvider, Q as LLMSelection, R as LiveKitAvatarGrantState, S as LiveKitAvatarGrantStatus, T as LiveKitCapacityState, U as LiveKitConnectionStatus, V as LiveKitSessionRequest, W as LiveKitSessionStartResult, aM as LivePlaybackKeeper, X as MAX_RECONNECT_ATTEMPTS, Y as MAX_SESSION_INSTRUCTIONS_CHARS, aN as MIC_LEASE_ENDED_TIMEOUT_MS, aO as MicLease, aP as PlayableVideoElement, Z as ProxyClientOptions, _ as QualityCap, $ as RECONNECT_BACKOFF_MS, aQ as RealtimeAvatarCapacityError, aR as RealtimeAvatarLiveKitRoom, aS as RealtimeAvatarLiveKitRoomProps, a0 as RealtimeAvatarRequestOptions, a1 as RealtimeSessionApi, a2 as RealtimeSessionMedia, a3 as RealtimeSessionRoomSinks, a4 as ReconnectPolicy, a5 as ReconnectingEvent, a6 as RecoveryState, a7 as RetryStep, a8 as SessionBehavior, a9 as SessionClip, aa as SessionClocks, ab as SessionEndReason, ac as SessionLifecycleApi, ad as SessionLifecyclePhase, ae as SessionLifecyclePhaseKind, af as SessionLifecycleRoomBridge, ag as SessionLifecycleRoomBridgeProps, ah as SurfaceLayers, ai as TurnState, aj as TurnTimeoutEvent, ak as UseAvatarQualityGovernorInput, al as UseLiveKitAvatarGrantInput, am as UseRealtimeSessionInput, an as UseSessionLifecycleInput, ao as VoiceSpec, ap as VoiceSpecInput, aq as capacityErrorFromBusy, ar as capacityStateFromGrant, as as createProxyClient, au as knownBehaviorStates, av as mapTurnState, aw as readInboundRtp, ax as sessionBehaviorSchema, ay as sessionClipSchema, aT as shouldReplayPendingTurn, az as splitCallTranscript, aA as useAvatarAdaptivePlayoutDelay, aB as useAvatarPlayoutDelay, aC as useAvatarQualityGovernor, aD as useCallTranscript, aE as useLiveKitAvatarGrant, aG as useMicLease, aH as useRealtimeSession, aI as useReleaseMicLeaseOnTrackEnded, aJ as useSessionLifecycle } from './proxy-client-B6OnXpaC.js';
|
|
4
5
|
export { D as DEFAULT_AVATAR_PLAYOUT_DELAY_SECONDS } from './playout-delay-DWOc8iCl.js';
|
|
6
|
+
import 'zod';
|
|
5
7
|
import '@livekit/components-react';
|
|
6
8
|
import 'livekit-client';
|
|
7
|
-
|
|
9
|
+
|
|
10
|
+
declare function useCharacterTools(tools: Record<string, RegisteredTool>, options?: {
|
|
11
|
+
onResult?: (event: {
|
|
12
|
+
name: string;
|
|
13
|
+
callId: string;
|
|
14
|
+
ok: boolean;
|
|
15
|
+
error?: string;
|
|
16
|
+
}) => void;
|
|
17
|
+
}): {
|
|
18
|
+
status: "idle" | "registering" | "ready" | "error";
|
|
19
|
+
registered: string[];
|
|
20
|
+
rejected: ToolRegistration["rejected"];
|
|
21
|
+
error?: string;
|
|
22
|
+
};
|
|
8
23
|
|
|
9
24
|
/**
|
|
10
25
|
* The CONNECT surface (client half) — one component that puts a live character on screen.
|
|
@@ -105,4 +120,4 @@ declare function useAvatarCall(props: AvatarCallProps): {
|
|
|
105
120
|
/** One component: a live character on screen. */
|
|
106
121
|
declare function AvatarCall(props: AvatarCallProps): ReactNode;
|
|
107
122
|
|
|
108
|
-
export { AvatarCall, type AvatarCallEndReason, type AvatarCallHandle, type AvatarCallProps, type AvatarCallStatus, AvatarSessionClient, AvatarVideoFit, useAvatarCall };
|
|
123
|
+
export { AvatarCall, type AvatarCallEndReason, type AvatarCallHandle, type AvatarCallProps, type AvatarCallStatus, AvatarSessionClient, AvatarVideoFit, useAvatarCall, useCharacterTools };
|
package/dist/react.js
CHANGED
|
@@ -1,10 +1,204 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useRef, useState, useEffect, useCallback, useMemo, createElement, Fragment } from 'react';
|
|
3
|
-
import { useTranscriptions, useLocalParticipant, RoomAudioRenderer, LiveKitRoom, useMaybeRoomContext, useVoiceAssistant,
|
|
4
|
-
import { ConnectionQuality, RoomEvent,
|
|
3
|
+
import { useRoomContext, useConnectionState, useTranscriptions, useLocalParticipant, RoomAudioRenderer, LiveKitRoom, useMaybeRoomContext, useVoiceAssistant, VideoTrack, useChat } from '@livekit/components-react';
|
|
4
|
+
import { ConnectionState, ConnectionQuality, RoomEvent, TrackEvent, Track, VideoQuality, ConnectionError, ConnectionErrorReason, DisconnectReason, Room } from 'livekit-client';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
|
|
7
|
-
// ../client/src/react/
|
|
7
|
+
// ../client/src/react/use-character-tools.ts
|
|
8
|
+
var invocationSchema = z.object({
|
|
9
|
+
name: z.string().regex(/^[a-zA-Z0-9_-]{1,64}$/),
|
|
10
|
+
args: z.string().max(8192),
|
|
11
|
+
call_id: z.string().min(1).max(128)
|
|
12
|
+
}).strict();
|
|
13
|
+
var registrationSchema = z.object({
|
|
14
|
+
accepted: z.array(z.string()),
|
|
15
|
+
rejected: z.array(z.object({ name: z.string(), reason: z.string() })).default([])
|
|
16
|
+
});
|
|
17
|
+
var MAX_TOOLS = 32;
|
|
18
|
+
var MAX_MANIFEST_BYTES = 12e3;
|
|
19
|
+
var MAX_RESULT_CHARS = 8192;
|
|
20
|
+
var TOOL_DEADLINE_MS = 2500;
|
|
21
|
+
var REGISTER_METHOD = "rta.tools.register";
|
|
22
|
+
var INVOKE_METHOD = "rta.tool.invoke";
|
|
23
|
+
var NAME_RE = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
24
|
+
function candidateIdentities(room) {
|
|
25
|
+
const all = Array.from(room.remoteParticipants.values(), (p) => p.identity).filter(Boolean);
|
|
26
|
+
const agents = all.filter((id) => /^agent[-_]/i.test(id));
|
|
27
|
+
return [...agents, ...all.filter((id) => !agents.includes(id))];
|
|
28
|
+
}
|
|
29
|
+
function methodMissing(error) {
|
|
30
|
+
const code = error?.code;
|
|
31
|
+
if (code === "UNSUPPORTED_METHOD" || code === "RECIPIENT_NOT_FOUND" || code === 1401 || code === 1402) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
35
|
+
return /not supported|unsupported_method|recipient_not_found|recipient not found/i.test(message);
|
|
36
|
+
}
|
|
37
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
38
|
+
function buildManifest(tools) {
|
|
39
|
+
const descriptors = [];
|
|
40
|
+
const rejected = [];
|
|
41
|
+
for (const [name, tool] of Object.entries(tools)) {
|
|
42
|
+
if (!NAME_RE.test(name)) {
|
|
43
|
+
rejected.push({ name, reason: "name must match [a-zA-Z0-9_-]{1,64}" });
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (!tool?.description) {
|
|
47
|
+
rejected.push({ name, reason: "description is required \u2014 it is how she knows when to call it" });
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (typeof tool.execute !== "function") {
|
|
51
|
+
rejected.push({ name, reason: "execute must be a function" });
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (descriptors.length >= MAX_TOOLS) {
|
|
55
|
+
rejected.push({ name, reason: `over the ${MAX_TOOLS}-tool limit for one session` });
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
descriptors.push({ name, description: tool.description, parameters: tool.parameters ?? {} });
|
|
59
|
+
}
|
|
60
|
+
return { descriptors, rejected };
|
|
61
|
+
}
|
|
62
|
+
async function attachAvatarTools(room, tools, options = {}) {
|
|
63
|
+
const { descriptors, rejected } = buildManifest(tools);
|
|
64
|
+
const payload = JSON.stringify({ tools: descriptors });
|
|
65
|
+
if (payload.length > MAX_MANIFEST_BYTES) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`tool manifest is ${payload.length} bytes, over the ${MAX_MANIFEST_BYTES} limit \u2014 shorten descriptions or register fewer tools`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
options.signal?.throwIfAborted();
|
|
71
|
+
let disposed = false;
|
|
72
|
+
let activeCaller;
|
|
73
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
74
|
+
const dispose = () => {
|
|
75
|
+
if (disposed) return;
|
|
76
|
+
disposed = true;
|
|
77
|
+
room.localParticipant.unregisterRpcMethod?.(INVOKE_METHOD);
|
|
78
|
+
for (const call of inFlight) call.abort();
|
|
79
|
+
options.signal?.removeEventListener("abort", dispose);
|
|
80
|
+
};
|
|
81
|
+
options.signal?.addEventListener("abort", dispose, { once: true });
|
|
82
|
+
room.localParticipant.registerRpcMethod(INVOKE_METHOD, async ({ payload: payload2, callerIdentity }) => {
|
|
83
|
+
let name = "", callId = "";
|
|
84
|
+
try {
|
|
85
|
+
if (disposed) throw new Error("Tool registration was disposed");
|
|
86
|
+
if (callerIdentity && callerIdentity !== activeCaller) throw new Error("Unregistered tool caller");
|
|
87
|
+
const req = invocationSchema.parse(JSON.parse(payload2));
|
|
88
|
+
name = req.name;
|
|
89
|
+
callId = req.call_id;
|
|
90
|
+
const tool = tools[name];
|
|
91
|
+
if (!tool) return JSON.stringify({ ok: false, call_id: callId, error: `no tool named ${name}` });
|
|
92
|
+
const args = z.record(z.string(), z.unknown()).parse(req.args ? JSON.parse(req.args) : {});
|
|
93
|
+
options.onInvoke?.(name, args);
|
|
94
|
+
const run = tool.execute;
|
|
95
|
+
const controller = new AbortController();
|
|
96
|
+
inFlight.add(controller);
|
|
97
|
+
const timer = setTimeout(() => controller.abort(), TOOL_DEADLINE_MS);
|
|
98
|
+
let result;
|
|
99
|
+
try {
|
|
100
|
+
result = await run(args, { signal: controller.signal, callId });
|
|
101
|
+
controller.signal.throwIfAborted();
|
|
102
|
+
} finally {
|
|
103
|
+
inFlight.delete(controller);
|
|
104
|
+
clearTimeout(timer);
|
|
105
|
+
}
|
|
106
|
+
const encoded = typeof result === "string" ? result : JSON.stringify(result ?? null);
|
|
107
|
+
if (encoded.length > MAX_RESULT_CHARS) {
|
|
108
|
+
return JSON.stringify({
|
|
109
|
+
ok: false,
|
|
110
|
+
call_id: callId,
|
|
111
|
+
error: `result was ${encoded.length} chars, over the ${MAX_RESULT_CHARS} limit`
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
options.onResult?.({ name, callId, ok: true });
|
|
115
|
+
return JSON.stringify({ ok: true, call_id: callId, result: encoded });
|
|
116
|
+
} catch (error) {
|
|
117
|
+
options.onResult?.({ name, callId, ok: false, error: error instanceof Error ? error.message : "tool failed" });
|
|
118
|
+
return JSON.stringify({
|
|
119
|
+
ok: false,
|
|
120
|
+
call_id: callId,
|
|
121
|
+
error: error instanceof Error ? error.message : "tool failed"
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
const deadline = Date.now() + Math.max(0, options.timeoutMs ?? 8e3);
|
|
126
|
+
let lastError = null;
|
|
127
|
+
let sawCandidate = false;
|
|
128
|
+
try {
|
|
129
|
+
for (; ; ) {
|
|
130
|
+
options.signal?.throwIfAborted();
|
|
131
|
+
for (const identity of candidateIdentities(room)) {
|
|
132
|
+
sawCandidate = true;
|
|
133
|
+
activeCaller = identity;
|
|
134
|
+
try {
|
|
135
|
+
const raw = await room.localParticipant.performRpc({
|
|
136
|
+
destinationIdentity: identity,
|
|
137
|
+
method: REGISTER_METHOD,
|
|
138
|
+
payload
|
|
139
|
+
});
|
|
140
|
+
options.signal?.throwIfAborted();
|
|
141
|
+
const result = registrationSchema.parse(JSON.parse(raw));
|
|
142
|
+
return {
|
|
143
|
+
accepted: result.accepted,
|
|
144
|
+
rejected: [...rejected, ...result.rejected],
|
|
145
|
+
dispose
|
|
146
|
+
};
|
|
147
|
+
} catch (error) {
|
|
148
|
+
activeCaller = void 0;
|
|
149
|
+
lastError = error;
|
|
150
|
+
if (!methodMissing(error)) throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (Date.now() >= deadline) break;
|
|
154
|
+
await sleep(200);
|
|
155
|
+
}
|
|
156
|
+
throw new Error(
|
|
157
|
+
sawCandidate ? `no participant accepted the tool manifest \u2014 either this session was not minted with the \`client_tools\` capability (a server-side grant), or the agent never armed registration within ${options.timeoutMs ?? 8e3}ms` + (lastError instanceof Error ? ` (last error: ${lastError.message})` : "") : "no remote participant joined the room before the timeout \u2014 nothing to register with"
|
|
158
|
+
);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
dispose();
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ../client/src/react/use-character-tools.ts
|
|
166
|
+
function useCharacterTools(tools, options = {}) {
|
|
167
|
+
const room = useRoomContext();
|
|
168
|
+
const connection = useConnectionState();
|
|
169
|
+
const callback = useRef(options.onResult);
|
|
170
|
+
callback.current = options.onResult;
|
|
171
|
+
const [state, setState] = useState({ status: "idle", registered: [], rejected: [] });
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
if (connection !== ConnectionState.Connected) {
|
|
174
|
+
setState({ status: "idle", registered: [], rejected: [] });
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const abort = new AbortController();
|
|
178
|
+
setState({ status: "registering", registered: [], rejected: [] });
|
|
179
|
+
void attachAvatarTools(room, tools, {
|
|
180
|
+
signal: abort.signal,
|
|
181
|
+
onResult: (event) => {
|
|
182
|
+
if (!abort.signal.aborted) callback.current?.(event);
|
|
183
|
+
}
|
|
184
|
+
}).then((result) => {
|
|
185
|
+
if (abort.signal.aborted) {
|
|
186
|
+
result.dispose();
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
setState({ status: "ready", registered: result.accepted, rejected: result.rejected });
|
|
190
|
+
}).catch((error) => {
|
|
191
|
+
if (!abort.signal.aborted) setState({
|
|
192
|
+
status: "error",
|
|
193
|
+
registered: [],
|
|
194
|
+
rejected: [],
|
|
195
|
+
error: error instanceof Error ? error.message : "Tool registration failed"
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
return () => abort.abort();
|
|
199
|
+
}, [room, tools, connection]);
|
|
200
|
+
return state;
|
|
201
|
+
}
|
|
8
202
|
var holder = null;
|
|
9
203
|
var queue = [];
|
|
10
204
|
var pendingEnded = null;
|
|
@@ -978,6 +1172,33 @@ function useAvatarQualityGovernor(input) {
|
|
|
978
1172
|
}, [enabled, room, videoTrack, freezeReading, config, tickMs]);
|
|
979
1173
|
}
|
|
980
1174
|
|
|
1175
|
+
// ../client/src/react/frame-recovery.ts
|
|
1176
|
+
var FrameRecovery = class {
|
|
1177
|
+
constructor(stallAfterMs) {
|
|
1178
|
+
this.stallAfterMs = stallAfterMs;
|
|
1179
|
+
}
|
|
1180
|
+
stallAfterMs;
|
|
1181
|
+
lastFrameAtMs = null;
|
|
1182
|
+
stableSinceMs = null;
|
|
1183
|
+
ready = true;
|
|
1184
|
+
stall() {
|
|
1185
|
+
this.ready = false;
|
|
1186
|
+
this.stableSinceMs = null;
|
|
1187
|
+
}
|
|
1188
|
+
frame(nowMs) {
|
|
1189
|
+
const gapMs = this.lastFrameAtMs === null ? 0 : nowMs - this.lastFrameAtMs;
|
|
1190
|
+
if (this.lastFrameAtMs !== null && gapMs > this.stallAfterMs) this.stall();
|
|
1191
|
+
if (!this.ready) {
|
|
1192
|
+
if (this.stableSinceMs === null || gapMs > Math.min(350, this.stallAfterMs)) {
|
|
1193
|
+
this.stableSinceMs = nowMs;
|
|
1194
|
+
}
|
|
1195
|
+
this.ready = nowMs - this.stableSinceMs >= 500;
|
|
1196
|
+
}
|
|
1197
|
+
this.lastFrameAtMs = nowMs;
|
|
1198
|
+
return this.ready;
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
|
|
981
1202
|
// ../client/src/react/avatar-video-surface.ts
|
|
982
1203
|
var DEFAULT_AVATAR_FRAME_STALL_MS = 800;
|
|
983
1204
|
var AVATAR_FRAME_GAP_FREEZE_FLOOR_MS = 100;
|
|
@@ -1000,6 +1221,8 @@ function AvatarVideoSurface(props) {
|
|
|
1000
1221
|
onPlayoutDelayChange
|
|
1001
1222
|
} = props;
|
|
1002
1223
|
const testId = props["data-testid"];
|
|
1224
|
+
const visibilityCallback = useRef(props.onLiveVisibilityChange);
|
|
1225
|
+
visibilityCallback.current = props.onLiveVisibilityChange;
|
|
1003
1226
|
const { videoTrack, audioTrack } = useVoiceAssistant();
|
|
1004
1227
|
useAvatarPlayoutDelay(videoTrack, audioTrack);
|
|
1005
1228
|
const playoutDelaySeconds = useAvatarAdaptivePlayoutDelay(
|
|
@@ -1042,10 +1265,18 @@ function AvatarVideoSurface(props) {
|
|
|
1042
1265
|
liveWanted,
|
|
1043
1266
|
connected && !networkStalled ? idleReturnDelayMs : 0
|
|
1044
1267
|
);
|
|
1268
|
+
useEffect(() => {
|
|
1269
|
+
visibilityCallback.current?.(showLive);
|
|
1270
|
+
}, [showLive]);
|
|
1045
1271
|
const liveDims = videoTrack?.publication?.dimensions ?? null;
|
|
1046
1272
|
const idleVideoRef = useRef(null);
|
|
1047
1273
|
const boxAspect = aspectRatio ?? null;
|
|
1048
|
-
useIdleWhileResting(
|
|
1274
|
+
useIdleWhileResting(
|
|
1275
|
+
idleVideoRef,
|
|
1276
|
+
idleVideoUrl,
|
|
1277
|
+
showLive,
|
|
1278
|
+
!live || connectionState === ConnectionState.Disconnected
|
|
1279
|
+
);
|
|
1049
1280
|
useLiveResumeOnProducing(liveWrapRef, trackProducing);
|
|
1050
1281
|
const fitClass = fit === "cover" ? AVATAR_VIDEO_FIT_COVER : AVATAR_VIDEO_FIT_CONTAIN;
|
|
1051
1282
|
const liveLabel = showLive && liveDims && liveDims.width > 0 && liveDims.height > 0 ? `${liveDims.width}\xD7${liveDims.height}` : null;
|
|
@@ -1177,6 +1408,7 @@ function isFrameFreezeInhibited(snapshot) {
|
|
|
1177
1408
|
}
|
|
1178
1409
|
function useLiveFrameFlow(wrapRef, trackProducing, trackIdentity, stallAfterMs) {
|
|
1179
1410
|
const boundedStallMs = normalizeFrameStallMs(stallAfterMs);
|
|
1411
|
+
const recovery = useMemo(() => new FrameRecovery(boundedStallMs), [trackIdentity, boundedStallMs]);
|
|
1180
1412
|
const [flowing, setFlowing] = useState(false);
|
|
1181
1413
|
const [seenFrame, setSeenFrame] = useState(false);
|
|
1182
1414
|
const flowingRef = useRef(false);
|
|
@@ -1206,6 +1438,7 @@ function useLiveFrameFlow(wrapRef, trackProducing, trackIdentity, stallAfterMs)
|
|
|
1206
1438
|
const markFrame = () => {
|
|
1207
1439
|
const previous = sampleRef.current;
|
|
1208
1440
|
const now = Date.now();
|
|
1441
|
+
lastCurrentTime = video?.currentTime ?? lastCurrentTime;
|
|
1209
1442
|
const firstFrame = !previous.seenFrame;
|
|
1210
1443
|
sampleRef.current = {
|
|
1211
1444
|
seenFrame: true,
|
|
@@ -1219,9 +1452,10 @@ function useLiveFrameFlow(wrapRef, trackProducing, trackIdentity, stallAfterMs)
|
|
|
1219
1452
|
resumePending: false
|
|
1220
1453
|
};
|
|
1221
1454
|
if (firstFrame) setSeenFrame(true);
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1455
|
+
const nextFlowing = recovery.frame(now);
|
|
1456
|
+
if (flowingRef.current !== nextFlowing) {
|
|
1457
|
+
flowingRef.current = nextFlowing;
|
|
1458
|
+
setFlowing(nextFlowing);
|
|
1225
1459
|
}
|
|
1226
1460
|
};
|
|
1227
1461
|
const cancelFrameCallback = () => {
|
|
@@ -1263,12 +1497,14 @@ function useLiveFrameFlow(wrapRef, trackProducing, trackIdentity, stallAfterMs)
|
|
|
1263
1497
|
lastCurrentTime = video.currentTime;
|
|
1264
1498
|
markFrame();
|
|
1265
1499
|
}
|
|
1266
|
-
const
|
|
1500
|
+
const frameFresh = isFrameFlowingAt({
|
|
1267
1501
|
seenFrame: sampleRef.current.seenFrame,
|
|
1268
1502
|
lastFrameAtMs: sampleRef.current.lastFrameAtMs,
|
|
1269
1503
|
nowMs: Date.now(),
|
|
1270
1504
|
stallAfterMs: boundedStallMs
|
|
1271
1505
|
});
|
|
1506
|
+
if (!frameFresh && sampleRef.current.seenFrame) recovery.stall();
|
|
1507
|
+
const nextFlowing = frameFresh && recovery.ready;
|
|
1272
1508
|
if (flowingRef.current !== nextFlowing) {
|
|
1273
1509
|
flowingRef.current = nextFlowing;
|
|
1274
1510
|
setFlowing(nextFlowing);
|
|
@@ -1281,7 +1517,7 @@ function useLiveFrameFlow(wrapRef, trackProducing, trackIdentity, stallAfterMs)
|
|
|
1281
1517
|
stopObserving();
|
|
1282
1518
|
cancelFrameCallback();
|
|
1283
1519
|
};
|
|
1284
|
-
}, [wrapRef, trackProducing, trackIdentity, boundedStallMs]);
|
|
1520
|
+
}, [wrapRef, trackProducing, trackIdentity, boundedStallMs, recovery]);
|
|
1285
1521
|
const freezeReading = useCallback(() => {
|
|
1286
1522
|
const sample = sampleRef.current;
|
|
1287
1523
|
const lastFrameAtMs = sample.lastFrameAtMs;
|
|
@@ -1320,19 +1556,19 @@ function useDebouncedHide(wanted, delayMs) {
|
|
|
1320
1556
|
}, [wanted, delayMs]);
|
|
1321
1557
|
return shown;
|
|
1322
1558
|
}
|
|
1323
|
-
function useIdleWhileResting(videoRef, idleVideoUrl, showLive) {
|
|
1559
|
+
function useIdleWhileResting(videoRef, idleVideoUrl, showLive, resetToAnchor) {
|
|
1324
1560
|
useEffect(() => {
|
|
1325
1561
|
const video = videoRef.current;
|
|
1326
1562
|
if (!video || !idleVideoUrl) return;
|
|
1327
|
-
applyIdleRest(video, showLive);
|
|
1328
|
-
}, [videoRef, idleVideoUrl, showLive]);
|
|
1563
|
+
applyIdleRest(video, showLive, resetToAnchor);
|
|
1564
|
+
}, [videoRef, idleVideoUrl, showLive, resetToAnchor]);
|
|
1329
1565
|
}
|
|
1330
|
-
function applyIdleRest(video, showLive) {
|
|
1566
|
+
function applyIdleRest(video, showLive, resetToAnchor = true) {
|
|
1331
1567
|
if (showLive) {
|
|
1332
1568
|
video.pause();
|
|
1333
1569
|
return;
|
|
1334
1570
|
}
|
|
1335
|
-
if (video.currentTime !== 0) video.currentTime = 0;
|
|
1571
|
+
if (resetToAnchor && video.currentTime !== 0) video.currentTime = 0;
|
|
1336
1572
|
if (video.paused) void Promise.resolve(video.play()).catch(() => {
|
|
1337
1573
|
});
|
|
1338
1574
|
}
|
|
@@ -2641,11 +2877,11 @@ function useRealtimeSession(input) {
|
|
|
2641
2877
|
requestGracefulClose();
|
|
2642
2878
|
lifecycle.reset();
|
|
2643
2879
|
}, [lifecycle, requestGracefulClose]);
|
|
2644
|
-
const
|
|
2645
|
-
(
|
|
2880
|
+
const performAction = useCallback(
|
|
2881
|
+
(actionId, opts) => {
|
|
2646
2882
|
const requestId = newTurnId();
|
|
2647
2883
|
const publish = dataPublisherRef.current;
|
|
2648
|
-
const trimmed =
|
|
2884
|
+
const trimmed = actionId.trim();
|
|
2649
2885
|
if (!publish || !trimmed) {
|
|
2650
2886
|
const result = { requestId, accepted: false, reason: "not_connected" };
|
|
2651
2887
|
cbRef.current.onClipResult?.(result);
|
|
@@ -2660,10 +2896,9 @@ function useRealtimeSession(input) {
|
|
|
2660
2896
|
}, opts?.timeoutMs ?? 5e3);
|
|
2661
2897
|
pendingClipsRef.current.set(requestId, { resolve, timer });
|
|
2662
2898
|
publish({
|
|
2663
|
-
kind: "
|
|
2899
|
+
kind: "action_request",
|
|
2664
2900
|
request_id: requestId,
|
|
2665
|
-
|
|
2666
|
-
...typeof opts?.holdSeconds === "number" ? { hold_seconds: opts.holdSeconds } : {}
|
|
2901
|
+
action_id: trimmed
|
|
2667
2902
|
});
|
|
2668
2903
|
});
|
|
2669
2904
|
},
|
|
@@ -2708,7 +2943,7 @@ function useRealtimeSession(input) {
|
|
|
2708
2943
|
retryTurn,
|
|
2709
2944
|
end,
|
|
2710
2945
|
behavior,
|
|
2711
|
-
|
|
2946
|
+
performAction,
|
|
2712
2947
|
onLifecycleData,
|
|
2713
2948
|
registerDataPublisher,
|
|
2714
2949
|
registerTurnSender,
|
|
@@ -2730,7 +2965,7 @@ function useRealtimeSession(input) {
|
|
|
2730
2965
|
retryTurn,
|
|
2731
2966
|
end,
|
|
2732
2967
|
behavior,
|
|
2733
|
-
|
|
2968
|
+
performAction,
|
|
2734
2969
|
onLifecycleData,
|
|
2735
2970
|
registerDataPublisher,
|
|
2736
2971
|
registerTurnSender,
|
|
@@ -2894,4 +3129,4 @@ function createProxyClient(options) {
|
|
|
2894
3129
|
};
|
|
2895
3130
|
}
|
|
2896
3131
|
|
|
2897
|
-
export { AdaptivePlayoutController, AvatarCall, AvatarVideoSurface, DEFAULT_APPROACHING_END_LEAD_SECONDS, DEFAULT_AVATAR_PLAYOUT_DELAY_SECONDS, DEFAULT_CREDITS_LOW_LEAD_SECONDS, DEFAULT_GOVERNOR_CONFIG, DEFAULT_GRACE_CEILING_SECONDS, DEFAULT_GRACE_WINDOW_LEAD_SECONDS, DEFAULT_IDLE_SECONDS, DEFAULT_IDLE_WARN_LEAD_SECONDS, DEFAULT_TURN_TIMEOUT_SECONDS, MAX_RECONNECT_ATTEMPTS, MAX_SESSION_INSTRUCTIONS_CHARS, MIC_LEASE_ENDED_TIMEOUT_MS, RECONNECT_BACKOFF_MS, RealtimeAvatarCapacityError, RealtimeAvatarLiveKitRoom, SessionLifecycleRoomBridge, capacityErrorFromBusy, capacityStateFromGrant, createProxyClient, knownBehaviorStates, mapTurnState, readInboundRtp, sessionBehaviorSchema, sessionClipSchema, shouldReplayPendingTurn, splitCallTranscript, useAvatarAdaptivePlayoutDelay, useAvatarCall, useAvatarPlayoutDelay, useAvatarQualityGovernor, useCallTranscript, useLiveKitAvatarGrant, useMicLease, useRealtimeSession, useReleaseMicLeaseOnTrackEnded, useSessionLifecycle };
|
|
3132
|
+
export { AdaptivePlayoutController, AvatarCall, AvatarVideoSurface, DEFAULT_APPROACHING_END_LEAD_SECONDS, DEFAULT_AVATAR_PLAYOUT_DELAY_SECONDS, DEFAULT_CREDITS_LOW_LEAD_SECONDS, DEFAULT_GOVERNOR_CONFIG, DEFAULT_GRACE_CEILING_SECONDS, DEFAULT_GRACE_WINDOW_LEAD_SECONDS, DEFAULT_IDLE_SECONDS, DEFAULT_IDLE_WARN_LEAD_SECONDS, DEFAULT_TURN_TIMEOUT_SECONDS, MAX_RECONNECT_ATTEMPTS, MAX_SESSION_INSTRUCTIONS_CHARS, MIC_LEASE_ENDED_TIMEOUT_MS, RECONNECT_BACKOFF_MS, RealtimeAvatarCapacityError, RealtimeAvatarLiveKitRoom, SessionLifecycleRoomBridge, capacityErrorFromBusy, capacityStateFromGrant, createProxyClient, knownBehaviorStates, mapTurnState, readInboundRtp, sessionBehaviorSchema, sessionClipSchema, shouldReplayPendingTurn, splitCallTranscript, useAvatarAdaptivePlayoutDelay, useAvatarCall, useAvatarPlayoutDelay, useAvatarQualityGovernor, useCallTranscript, useCharacterTools, useLiveKitAvatarGrant, useMicLease, useRealtimeSession, useReleaseMicLeaseOnTrackEnded, useSessionLifecycle };
|
package/dist/server.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, RealtimeAvatarOptions, StartCallOptions, verifyTranscript } from './index.js';
|
|
2
|
-
export {
|
|
1
|
+
export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, RealtimeAvatarOptions, StartCallOptions, clipLibraryDeclarationSchema, verifyTranscript } from './index.js';
|
|
2
|
+
export { h as Asset, g 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, o as ClipDeclaration, f as ClipLibrary, d as ClipLibraryDeclaration, p as ClipLibraryPlan, e as ClipLibraryUpdate, q as ClipSource, r as ContextMessage, k as CreditBalance, E as EndCallOptions, s as EndCallReason, i as ListSessionsOptions, L as LoopRedirect, S as StartCallResult, T as TranscriptPayload, j as UsageSession, U as UsageSessionPage, V as VideoPolicy, t as isQueued } from './types-BgYFwlSn.js';
|
|
3
|
+
import 'zod';
|
package/dist/server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, isQueued, verifyTranscript } from './chunk-
|
|
1
|
+
export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, verifyTranscript } from './chunk-5UEMZN6J.js';
|
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-zQtYeR_l.js';
|
|
2
|
+
import './types-BgYFwlSn.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
package/dist/tools.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* The browser tool plane, without React.
|
|
3
5
|
*
|
|
@@ -13,6 +15,17 @@
|
|
|
13
15
|
* minted with the `client_tools` capability, which your server decides. If registration
|
|
14
16
|
* reports the method is missing, the mint did not grant it — the fix is server-side.
|
|
15
17
|
*/
|
|
18
|
+
|
|
19
|
+
declare function defineAvatarTool<I extends z.ZodType, O extends z.ZodType>(definition: {
|
|
20
|
+
description: string;
|
|
21
|
+
inputSchema: I;
|
|
22
|
+
outputSchema: O;
|
|
23
|
+
execute: (input: z.output<I>, context: ToolContext) => z.input<O> | Promise<z.input<O>>;
|
|
24
|
+
}): {
|
|
25
|
+
description: string;
|
|
26
|
+
parameters: z.core.ZodStandardJSONSchemaPayload<I>;
|
|
27
|
+
execute(input: unknown, context: ToolContext): Promise<z.output<O>>;
|
|
28
|
+
};
|
|
16
29
|
/** Caps the wire enforces. Exceeding them is a rejection, not a truncation. */
|
|
17
30
|
declare const MAX_TOOLS = 32;
|
|
18
31
|
declare const MAX_MANIFEST_BYTES = 12000;
|
|
@@ -60,13 +73,16 @@ interface ToolRegistration {
|
|
|
60
73
|
name: string;
|
|
61
74
|
reason: string;
|
|
62
75
|
}>;
|
|
76
|
+
dispose: () => void;
|
|
63
77
|
}
|
|
64
78
|
/** The subset of a LiveKit Room this needs — kept structural so it is trivial to fake. */
|
|
65
79
|
interface RoomLike {
|
|
66
80
|
localParticipant: {
|
|
67
81
|
registerRpcMethod(method: string, handler: (data: {
|
|
68
82
|
payload: string;
|
|
83
|
+
callerIdentity?: string;
|
|
69
84
|
}) => Promise<string>): void;
|
|
85
|
+
unregisterRpcMethod?(method: string): void;
|
|
70
86
|
performRpc(options: {
|
|
71
87
|
destinationIdentity: string;
|
|
72
88
|
method: string;
|
|
@@ -104,8 +120,15 @@ interface RoomLike {
|
|
|
104
120
|
*/
|
|
105
121
|
declare function attachAvatarTools(room: RoomLike, tools: Record<string, RegisteredTool>, options?: {
|
|
106
122
|
onInvoke?: (name: string, args: unknown) => void;
|
|
123
|
+
onResult?: (event: {
|
|
124
|
+
name: string;
|
|
125
|
+
callId: string;
|
|
126
|
+
ok: boolean;
|
|
127
|
+
error?: string;
|
|
128
|
+
}) => void;
|
|
129
|
+
signal?: AbortSignal;
|
|
107
130
|
/** How long to keep waiting for the agent to arm its side. Default 8000ms. */
|
|
108
131
|
timeoutMs?: number;
|
|
109
132
|
}): Promise<ToolRegistration>;
|
|
110
133
|
|
|
111
|
-
export { type AvatarTool, MAX_MANIFEST_BYTES, MAX_RESULT_CHARS, MAX_TOOLS, type RegisteredTool, TOOL_DEADLINE_MS, type ToolContext, type ToolRegistration, attachAvatarTools };
|
|
134
|
+
export { type AvatarTool, MAX_MANIFEST_BYTES, MAX_RESULT_CHARS, MAX_TOOLS, type RegisteredTool, TOOL_DEADLINE_MS, type ToolContext, type ToolRegistration, attachAvatarTools, defineAvatarTool };
|