realtime-avatar 0.9.1 → 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-F7E4HMBW.js → chunk-5UEMZN6J.js} +190 -79
- package/dist/{chunk-FTAD423K.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-DlYBC6cp.d.ts → proxy-client-B6OnXpaC.d.ts} +5 -10
- 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 +210 -12
- 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;
|
|
@@ -1027,6 +1221,8 @@ function AvatarVideoSurface(props) {
|
|
|
1027
1221
|
onPlayoutDelayChange
|
|
1028
1222
|
} = props;
|
|
1029
1223
|
const testId = props["data-testid"];
|
|
1224
|
+
const visibilityCallback = useRef(props.onLiveVisibilityChange);
|
|
1225
|
+
visibilityCallback.current = props.onLiveVisibilityChange;
|
|
1030
1226
|
const { videoTrack, audioTrack } = useVoiceAssistant();
|
|
1031
1227
|
useAvatarPlayoutDelay(videoTrack, audioTrack);
|
|
1032
1228
|
const playoutDelaySeconds = useAvatarAdaptivePlayoutDelay(
|
|
@@ -1069,6 +1265,9 @@ function AvatarVideoSurface(props) {
|
|
|
1069
1265
|
liveWanted,
|
|
1070
1266
|
connected && !networkStalled ? idleReturnDelayMs : 0
|
|
1071
1267
|
);
|
|
1268
|
+
useEffect(() => {
|
|
1269
|
+
visibilityCallback.current?.(showLive);
|
|
1270
|
+
}, [showLive]);
|
|
1072
1271
|
const liveDims = videoTrack?.publication?.dimensions ?? null;
|
|
1073
1272
|
const idleVideoRef = useRef(null);
|
|
1074
1273
|
const boxAspect = aspectRatio ?? null;
|
|
@@ -2678,11 +2877,11 @@ function useRealtimeSession(input) {
|
|
|
2678
2877
|
requestGracefulClose();
|
|
2679
2878
|
lifecycle.reset();
|
|
2680
2879
|
}, [lifecycle, requestGracefulClose]);
|
|
2681
|
-
const
|
|
2682
|
-
(
|
|
2880
|
+
const performAction = useCallback(
|
|
2881
|
+
(actionId, opts) => {
|
|
2683
2882
|
const requestId = newTurnId();
|
|
2684
2883
|
const publish = dataPublisherRef.current;
|
|
2685
|
-
const trimmed =
|
|
2884
|
+
const trimmed = actionId.trim();
|
|
2686
2885
|
if (!publish || !trimmed) {
|
|
2687
2886
|
const result = { requestId, accepted: false, reason: "not_connected" };
|
|
2688
2887
|
cbRef.current.onClipResult?.(result);
|
|
@@ -2697,10 +2896,9 @@ function useRealtimeSession(input) {
|
|
|
2697
2896
|
}, opts?.timeoutMs ?? 5e3);
|
|
2698
2897
|
pendingClipsRef.current.set(requestId, { resolve, timer });
|
|
2699
2898
|
publish({
|
|
2700
|
-
kind: "
|
|
2899
|
+
kind: "action_request",
|
|
2701
2900
|
request_id: requestId,
|
|
2702
|
-
|
|
2703
|
-
...typeof opts?.holdSeconds === "number" ? { hold_seconds: opts.holdSeconds } : {}
|
|
2901
|
+
action_id: trimmed
|
|
2704
2902
|
});
|
|
2705
2903
|
});
|
|
2706
2904
|
},
|
|
@@ -2745,7 +2943,7 @@ function useRealtimeSession(input) {
|
|
|
2745
2943
|
retryTurn,
|
|
2746
2944
|
end,
|
|
2747
2945
|
behavior,
|
|
2748
|
-
|
|
2946
|
+
performAction,
|
|
2749
2947
|
onLifecycleData,
|
|
2750
2948
|
registerDataPublisher,
|
|
2751
2949
|
registerTurnSender,
|
|
@@ -2767,7 +2965,7 @@ function useRealtimeSession(input) {
|
|
|
2767
2965
|
retryTurn,
|
|
2768
2966
|
end,
|
|
2769
2967
|
behavior,
|
|
2770
|
-
|
|
2968
|
+
performAction,
|
|
2771
2969
|
onLifecycleData,
|
|
2772
2970
|
registerDataPublisher,
|
|
2773
2971
|
registerTurnSender,
|
|
@@ -2931,4 +3129,4 @@ function createProxyClient(options) {
|
|
|
2931
3129
|
};
|
|
2932
3130
|
}
|
|
2933
3131
|
|
|
2934
|
-
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 };
|
package/dist/tools.js
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
1
3
|
// ../tools/src/index.ts
|
|
4
|
+
function defineAvatarTool(definition) {
|
|
5
|
+
return {
|
|
6
|
+
description: definition.description,
|
|
7
|
+
parameters: z.toJSONSchema(definition.inputSchema, { io: "input" }),
|
|
8
|
+
async execute(input, context) {
|
|
9
|
+
context.signal.throwIfAborted();
|
|
10
|
+
const args = definition.inputSchema.parse(input);
|
|
11
|
+
const result = await definition.execute(args, context);
|
|
12
|
+
context.signal.throwIfAborted();
|
|
13
|
+
return definition.outputSchema.parse(result);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
var invocationSchema = z.object({
|
|
18
|
+
name: z.string().regex(/^[a-zA-Z0-9_-]{1,64}$/),
|
|
19
|
+
args: z.string().max(8192),
|
|
20
|
+
call_id: z.string().min(1).max(128)
|
|
21
|
+
}).strict();
|
|
22
|
+
var registrationSchema = z.object({
|
|
23
|
+
accepted: z.array(z.string()),
|
|
24
|
+
rejected: z.array(z.object({ name: z.string(), reason: z.string() })).default([])
|
|
25
|
+
});
|
|
2
26
|
var MAX_TOOLS = 32;
|
|
3
27
|
var MAX_MANIFEST_BYTES = 12e3;
|
|
4
28
|
var MAX_RESULT_CHARS = 8192;
|
|
@@ -52,23 +76,40 @@ async function attachAvatarTools(room, tools, options = {}) {
|
|
|
52
76
|
`tool manifest is ${payload.length} bytes, over the ${MAX_MANIFEST_BYTES} limit \u2014 shorten descriptions or register fewer tools`
|
|
53
77
|
);
|
|
54
78
|
}
|
|
55
|
-
|
|
79
|
+
options.signal?.throwIfAborted();
|
|
80
|
+
let disposed = false;
|
|
81
|
+
let activeCaller;
|
|
82
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
83
|
+
const dispose = () => {
|
|
84
|
+
if (disposed) return;
|
|
85
|
+
disposed = true;
|
|
86
|
+
room.localParticipant.unregisterRpcMethod?.(INVOKE_METHOD);
|
|
87
|
+
for (const call of inFlight) call.abort();
|
|
88
|
+
options.signal?.removeEventListener("abort", dispose);
|
|
89
|
+
};
|
|
90
|
+
options.signal?.addEventListener("abort", dispose, { once: true });
|
|
91
|
+
room.localParticipant.registerRpcMethod(INVOKE_METHOD, async ({ payload: payload2, callerIdentity }) => {
|
|
56
92
|
let name = "", callId = "";
|
|
57
93
|
try {
|
|
58
|
-
|
|
94
|
+
if (disposed) throw new Error("Tool registration was disposed");
|
|
95
|
+
if (callerIdentity && callerIdentity !== activeCaller) throw new Error("Unregistered tool caller");
|
|
96
|
+
const req = invocationSchema.parse(JSON.parse(payload2));
|
|
59
97
|
name = req.name;
|
|
60
98
|
callId = req.call_id;
|
|
61
99
|
const tool = tools[name];
|
|
62
100
|
if (!tool) return JSON.stringify({ ok: false, call_id: callId, error: `no tool named ${name}` });
|
|
63
|
-
const args = req.args ? JSON.parse(req.args) : {};
|
|
101
|
+
const args = z.record(z.string(), z.unknown()).parse(req.args ? JSON.parse(req.args) : {});
|
|
64
102
|
options.onInvoke?.(name, args);
|
|
65
103
|
const run = tool.execute;
|
|
66
104
|
const controller = new AbortController();
|
|
105
|
+
inFlight.add(controller);
|
|
67
106
|
const timer = setTimeout(() => controller.abort(), TOOL_DEADLINE_MS);
|
|
68
107
|
let result;
|
|
69
108
|
try {
|
|
70
109
|
result = await run(args, { signal: controller.signal, callId });
|
|
110
|
+
controller.signal.throwIfAborted();
|
|
71
111
|
} finally {
|
|
112
|
+
inFlight.delete(controller);
|
|
72
113
|
clearTimeout(timer);
|
|
73
114
|
}
|
|
74
115
|
const encoded = typeof result === "string" ? result : JSON.stringify(result ?? null);
|
|
@@ -79,8 +120,10 @@ async function attachAvatarTools(room, tools, options = {}) {
|
|
|
79
120
|
error: `result was ${encoded.length} chars, over the ${MAX_RESULT_CHARS} limit`
|
|
80
121
|
});
|
|
81
122
|
}
|
|
123
|
+
options.onResult?.({ name, callId, ok: true });
|
|
82
124
|
return JSON.stringify({ ok: true, call_id: callId, result: encoded });
|
|
83
125
|
} catch (error) {
|
|
126
|
+
options.onResult?.({ name, callId, ok: false, error: error instanceof Error ? error.message : "tool failed" });
|
|
84
127
|
return JSON.stringify({
|
|
85
128
|
ok: false,
|
|
86
129
|
call_id: callId,
|
|
@@ -91,31 +134,41 @@ async function attachAvatarTools(room, tools, options = {}) {
|
|
|
91
134
|
const deadline = Date.now() + Math.max(0, options.timeoutMs ?? 8e3);
|
|
92
135
|
let lastError = null;
|
|
93
136
|
let sawCandidate = false;
|
|
94
|
-
|
|
95
|
-
for (
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
137
|
+
try {
|
|
138
|
+
for (; ; ) {
|
|
139
|
+
options.signal?.throwIfAborted();
|
|
140
|
+
for (const identity of candidateIdentities(room)) {
|
|
141
|
+
sawCandidate = true;
|
|
142
|
+
activeCaller = identity;
|
|
143
|
+
try {
|
|
144
|
+
const raw = await room.localParticipant.performRpc({
|
|
145
|
+
destinationIdentity: identity,
|
|
146
|
+
method: REGISTER_METHOD,
|
|
147
|
+
payload
|
|
148
|
+
});
|
|
149
|
+
options.signal?.throwIfAborted();
|
|
150
|
+
const result = registrationSchema.parse(JSON.parse(raw));
|
|
151
|
+
return {
|
|
152
|
+
accepted: result.accepted,
|
|
153
|
+
rejected: [...rejected, ...result.rejected],
|
|
154
|
+
dispose
|
|
155
|
+
};
|
|
156
|
+
} catch (error) {
|
|
157
|
+
activeCaller = void 0;
|
|
158
|
+
lastError = error;
|
|
159
|
+
if (!methodMissing(error)) throw error;
|
|
160
|
+
}
|
|
111
161
|
}
|
|
162
|
+
if (Date.now() >= deadline) break;
|
|
163
|
+
await sleep(200);
|
|
112
164
|
}
|
|
113
|
-
|
|
114
|
-
|
|
165
|
+
throw new Error(
|
|
166
|
+
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"
|
|
167
|
+
);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
dispose();
|
|
170
|
+
throw error;
|
|
115
171
|
}
|
|
116
|
-
throw new Error(
|
|
117
|
-
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"
|
|
118
|
-
);
|
|
119
172
|
}
|
|
120
173
|
|
|
121
|
-
export { MAX_MANIFEST_BYTES, MAX_RESULT_CHARS, MAX_TOOLS, TOOL_DEADLINE_MS, attachAvatarTools };
|
|
174
|
+
export { MAX_MANIFEST_BYTES, MAX_RESULT_CHARS, MAX_TOOLS, TOOL_DEADLINE_MS, attachAvatarTools, defineAvatarTool };
|