@nuancelabs/js-sdk 0.11.0 → 0.13.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/index.d.ts +324 -0
- package/index.js +158 -14
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -7,6 +7,85 @@ export declare class ActiveSessionError extends Error {
|
|
|
7
7
|
constructor();
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* The turn's first audio. `atMs` is when it plays, which can be after the
|
|
12
|
+
* event arrives.
|
|
13
|
+
*/
|
|
14
|
+
export declare interface AgentSpeechStartedEvent extends SessionEventBase {
|
|
15
|
+
type: "agent.speech.started";
|
|
16
|
+
/** The turn, as {@link AgentTurnStartedEvent.turnId} names it. */
|
|
17
|
+
turnId: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The agent's words, in whole words, as they are committed to the
|
|
22
|
+
* conversation. They arrive ahead of their audio: `atMs` is when the first
|
|
23
|
+
* of them plays. When the agent's turn is interrupted, words whose `atMs` is
|
|
24
|
+
* later than the {@link AgentTurnEndedEvent}'s were never heard.
|
|
25
|
+
*/
|
|
26
|
+
export declare interface AgentTextEvent extends SessionEventBase {
|
|
27
|
+
type: "agent.text";
|
|
28
|
+
/**
|
|
29
|
+
* The turn the words belong to, as {@link AgentTurnStartedEvent.turnId}
|
|
30
|
+
* names it; null for words outside any turn.
|
|
31
|
+
*/
|
|
32
|
+
turnId: string | null;
|
|
33
|
+
/** The words, with the spacing to join them to the turn's other words. */
|
|
34
|
+
text: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The agent's turn is over. `atMs` is when the turn's audio ends: for an
|
|
39
|
+
* interrupted turn, where the agent was cut off.
|
|
40
|
+
*/
|
|
41
|
+
export declare interface AgentTurnEndedEvent extends SessionEventBase {
|
|
42
|
+
type: "agent.turn.ended";
|
|
43
|
+
/** The turn, as {@link AgentTurnStartedEvent.turnId} names it. */
|
|
44
|
+
turnId: string;
|
|
45
|
+
/**
|
|
46
|
+
* `"completed"`: the agent said all of it. `"interrupted"`: the user cut
|
|
47
|
+
* in. `"stopped"`: a command stopped the agent. `"abandoned"`: the turn
|
|
48
|
+
* gave up, as when nothing of it was heard for a while or it had nothing
|
|
49
|
+
* to say; `spoken` is then the runtime's estimate of what played. A newer
|
|
50
|
+
* runtime may send other values.
|
|
51
|
+
*/
|
|
52
|
+
reason: "completed" | "interrupted" | "stopped" | "abandoned" | (string & {});
|
|
53
|
+
/**
|
|
54
|
+
* Every word the turn committed, as its {@link AgentTextEvent}s carried
|
|
55
|
+
* them. After an interruption it includes words that were cut before they
|
|
56
|
+
* played; their `agent.text` events have an `atMs` later than this
|
|
57
|
+
* event's.
|
|
58
|
+
*/
|
|
59
|
+
spoken: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The agent took the floor, before its first audio. Its words follow as
|
|
64
|
+
* {@link AgentTextEvent}s and the turn closes with an
|
|
65
|
+
* {@link AgentTurnEndedEvent}, both with the same `turnId`. `atMs` is where
|
|
66
|
+
* on the timeline the agent took it.
|
|
67
|
+
*/
|
|
68
|
+
export declare interface AgentTurnStartedEvent extends SessionEventBase {
|
|
69
|
+
type: "agent.turn.started";
|
|
70
|
+
/** The turn's id, unique within the session. */
|
|
71
|
+
turnId: string;
|
|
72
|
+
/**
|
|
73
|
+
* What opened the turn. `"user_done"`: the agent judged the user had
|
|
74
|
+
* finished. `"silence"`: the user went quiet long enough. `"check_in"`:
|
|
75
|
+
* both were quiet and the agent spoke first, as in its opening greeting.
|
|
76
|
+
* `"command"`: a command asked for the turn. `"tool"`: a tool's result
|
|
77
|
+
* reached the agent while both were quiet. A newer runtime may send other
|
|
78
|
+
* values.
|
|
79
|
+
*/
|
|
80
|
+
openedBy: "user_done" | "silence" | "check_in" | "command" | "tool" | (string & {});
|
|
81
|
+
/**
|
|
82
|
+
* `"reply"`: the agent wrote the turn's words itself. `"say"`: the agent
|
|
83
|
+
* says a line it was given, word for word. A newer runtime may send other
|
|
84
|
+
* values.
|
|
85
|
+
*/
|
|
86
|
+
source: "reply" | "say" | (string & {});
|
|
87
|
+
}
|
|
88
|
+
|
|
10
89
|
/**
|
|
11
90
|
* An audio track that can be attached to or detached from an audio element.
|
|
12
91
|
*/
|
|
@@ -119,6 +198,56 @@ export declare interface Conversation {
|
|
|
119
198
|
* @returns A function that removes the event handler.
|
|
120
199
|
*/
|
|
121
200
|
onChange(handler: (event: ConversationEvent) => void): () => void;
|
|
201
|
+
/**
|
|
202
|
+
* Subscribes to one type of the session's events: what the user and the
|
|
203
|
+
* agent say, their turns, and who has the floor.
|
|
204
|
+
*
|
|
205
|
+
* Each event arrives once, in the session's order (its `seq`). Events the
|
|
206
|
+
* connection missed while it was down are asked of the runtime again and
|
|
207
|
+
* delivered in their place. Subscribe before `connect()` to see the
|
|
208
|
+
* session's first events.
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```ts
|
|
212
|
+
* const stop = conversation.on("agent.text", (event) => {
|
|
213
|
+
* transcript.textContent += event.text;
|
|
214
|
+
* });
|
|
215
|
+
* ```
|
|
216
|
+
*
|
|
217
|
+
* @returns A function that removes the handler.
|
|
218
|
+
*/
|
|
219
|
+
on<K extends keyof SessionEventMap>(type: K, handler: (event: SessionEventMap[K]) => void): () => void;
|
|
220
|
+
/**
|
|
221
|
+
* Subscribes to the agent's calls of the conversation's tools, named when
|
|
222
|
+
* your server mints the session token. Run the tool in your page and answer each call with
|
|
223
|
+
* {@link Conversation.sendToolResult}. The agent keeps talking while a call
|
|
224
|
+
* is open, and uses the result when it arrives.
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```ts
|
|
228
|
+
* conversation.onToolCall(async (call) => {
|
|
229
|
+
* if (call.name === "show_confetti") {
|
|
230
|
+
* launchConfetti();
|
|
231
|
+
* await conversation.sendToolResult(call.callId, "Confetti is falling.");
|
|
232
|
+
* }
|
|
233
|
+
* });
|
|
234
|
+
* ```
|
|
235
|
+
*
|
|
236
|
+
* @returns A function that removes the handler.
|
|
237
|
+
*/
|
|
238
|
+
onToolCall(handler: (call: ToolCall) => void): () => void;
|
|
239
|
+
/**
|
|
240
|
+
* Answers a tool call: what it produced, or with `isError` what went wrong.
|
|
241
|
+
* The agent keeps the first 2,000 characters of an output, so return what
|
|
242
|
+
* it needs, not everything you have.
|
|
243
|
+
*
|
|
244
|
+
* @param callId - The call's {@link ToolCall.callId}.
|
|
245
|
+
* @returns Whether the runtime took the result: `status` is `"rejected"`,
|
|
246
|
+
* with the reason in `error`, for an unknown or closed call.
|
|
247
|
+
* @throws If the conversation is not connected, or the runtime does not
|
|
248
|
+
* answer in time.
|
|
249
|
+
*/
|
|
250
|
+
sendToolResult(callId: string, output: ToolOutput, options?: ToolResultOptions): Promise<CommandResult>;
|
|
122
251
|
}
|
|
123
252
|
|
|
124
253
|
|
|
@@ -376,6 +505,46 @@ export declare interface CreateNuanceConversationOptions {
|
|
|
376
505
|
}
|
|
377
506
|
|
|
378
507
|
|
|
508
|
+
/**
|
|
509
|
+
* Who has the floor changed. `atMs` is when the change plays.
|
|
510
|
+
*/
|
|
511
|
+
export declare interface FloorEvent extends SessionEventBase {
|
|
512
|
+
type: "floor";
|
|
513
|
+
/**
|
|
514
|
+
* `"speaking"`: the agent has the floor. `"listening"`: the user has it
|
|
515
|
+
* and the agent listens. `"waiting"`: nobody has it; the agent waits for
|
|
516
|
+
* the user. A newer runtime may send other values.
|
|
517
|
+
*/
|
|
518
|
+
state: "waiting" | "listening" | "speaking" | (string & {});
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* A tool the agent may call, which your page runs: show something on the
|
|
523
|
+
* screen, read what the user has open, look something up in your app. The
|
|
524
|
+
* agent decides when to call it from its `description`, and fills its
|
|
525
|
+
* arguments to match its `parameters`. A conversation has up to 16 tools.
|
|
526
|
+
*/
|
|
527
|
+
export declare interface FunctionTool {
|
|
528
|
+
/** Always `"function"`, the default. */
|
|
529
|
+
type?: "function";
|
|
530
|
+
/**
|
|
531
|
+
* The tool's name: letters, digits, `_` and `-`, up to 64 characters,
|
|
532
|
+
* unique in the conversation. `ask_user`, `get_current_time`, `web_search`
|
|
533
|
+
* and the agent's other own tools' names are taken.
|
|
534
|
+
*/
|
|
535
|
+
name: string;
|
|
536
|
+
/**
|
|
537
|
+
* What the tool does and when to use it, written for the agent, up to
|
|
538
|
+
* 2,000 characters. Say when to call it, not only what it does.
|
|
539
|
+
*/
|
|
540
|
+
description: string;
|
|
541
|
+
/**
|
|
542
|
+
* The arguments, as a JSON Schema whose `type` is `"object"`, up to 16,000
|
|
543
|
+
* characters as compact JSON. `{ type: "object", properties: {} }` takes none.
|
|
544
|
+
*/
|
|
545
|
+
parameters: Record<string, unknown>;
|
|
546
|
+
}
|
|
547
|
+
|
|
379
548
|
/**
|
|
380
549
|
* Raised by {@link Conversation.connect} when the server refuses a guest's new
|
|
381
550
|
* session because a guest limit is used up. `reason` says which: `used` (this
|
|
@@ -473,6 +642,47 @@ export declare class PolicyCurfewError extends Error {
|
|
|
473
642
|
*/
|
|
474
643
|
export declare type SessionEndReason = "time-limit" | "ended" | "policy-violation" | "refused" | "connection-lost";
|
|
475
644
|
|
|
645
|
+
/** Any one of the session's events. */
|
|
646
|
+
export declare type SessionEvent = SessionEventMap[keyof SessionEventMap];
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* What every session event carries: its place in the session's order and on
|
|
650
|
+
* the session's timeline.
|
|
651
|
+
*/
|
|
652
|
+
export declare interface SessionEventBase {
|
|
653
|
+
/**
|
|
654
|
+
* The event's number in the session's sequence: from 1, one more for each
|
|
655
|
+
* event the runtime sends, of every kind, so the events one handler sees
|
|
656
|
+
* skip numbers. The SDK delivers events in this order, each once. 0 for an
|
|
657
|
+
* event the runtime did not number.
|
|
658
|
+
*/
|
|
659
|
+
seq: number;
|
|
660
|
+
/**
|
|
661
|
+
* Where the event falls on the session's timeline, in milliseconds. The
|
|
662
|
+
* timeline counts the user's audio from the start of the session. A user
|
|
663
|
+
* event is stamped on the user's audio, as where the user said it; an
|
|
664
|
+
* agent event where the agent's audio for it plays, which can be ahead of
|
|
665
|
+
* when the event arrives. Every event of a session is on this one timeline, so
|
|
666
|
+
* their `atMs` compare. Null when the runtime gives no time.
|
|
667
|
+
*/
|
|
668
|
+
atMs: number | null;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* The session's events by their `type`, for {@link Conversation.on}.
|
|
673
|
+
*/
|
|
674
|
+
export declare interface SessionEventMap {
|
|
675
|
+
"user.transcript": UserTranscriptEvent;
|
|
676
|
+
"user.speech.started": UserSpeechStartedEvent;
|
|
677
|
+
"user.speech.ended": UserSpeechEndedEvent;
|
|
678
|
+
"user.turn.ended": UserTurnEndedEvent;
|
|
679
|
+
"agent.turn.started": AgentTurnStartedEvent;
|
|
680
|
+
"agent.speech.started": AgentSpeechStartedEvent;
|
|
681
|
+
"agent.text": AgentTextEvent;
|
|
682
|
+
"agent.turn.ended": AgentTurnEndedEvent;
|
|
683
|
+
floor: FloorEvent;
|
|
684
|
+
}
|
|
685
|
+
|
|
476
686
|
|
|
477
687
|
|
|
478
688
|
/** Who the avatar is in a conversation. */
|
|
@@ -499,6 +709,23 @@ export declare interface SessionPersonality {
|
|
|
499
709
|
export declare interface SessionTokenRequest {
|
|
500
710
|
/** Who the avatar is in the conversation. Leave it out for the default personality. */
|
|
501
711
|
personality?: SessionPersonality;
|
|
712
|
+
/**
|
|
713
|
+
* Tools the agent may call, which your page runs and answers with
|
|
714
|
+
* `onToolCall` and `sendToolResult`;
|
|
715
|
+
* up to 16. Fixed for the conversation: the page can't change them.
|
|
716
|
+
*
|
|
717
|
+
* @example
|
|
718
|
+
* ```ts
|
|
719
|
+
* tools: [
|
|
720
|
+
* {
|
|
721
|
+
* name: "show_confetti",
|
|
722
|
+
* description: "Plays confetti on the user's screen. Use it to celebrate with them.",
|
|
723
|
+
* parameters: { type: "object", properties: {} },
|
|
724
|
+
* },
|
|
725
|
+
* ]
|
|
726
|
+
* ```
|
|
727
|
+
*/
|
|
728
|
+
tools?: FunctionTool[];
|
|
502
729
|
/**
|
|
503
730
|
* Your own id for the person this conversation is for, such as a hash of
|
|
504
731
|
* your user id. After repeated policy violations, new conversations pause
|
|
@@ -521,9 +748,53 @@ export declare interface SessionTokenResponse {
|
|
|
521
748
|
/** When the avatar starts speaking: see {@link CreateNuanceConversationOptions.startOn}. */
|
|
522
749
|
export declare type StartOn = "first-frame" | "full-quality";
|
|
523
750
|
|
|
751
|
+
/**
|
|
752
|
+
* The agent's call of one of the conversation's tools: run it, then answer
|
|
753
|
+
* with {@link Conversation.sendToolResult} and its `callId`.
|
|
754
|
+
*/
|
|
755
|
+
export declare interface ToolCall {
|
|
756
|
+
/** Identifies the call; answer with it. */
|
|
757
|
+
callId: string;
|
|
758
|
+
/** The tool, by its `name`. */
|
|
759
|
+
name: string;
|
|
760
|
+
/** The agent's own short name for this use of the tool, such as `"confetti"`. */
|
|
761
|
+
label: string;
|
|
762
|
+
/** The call's arguments, held to the tool's `parameters` schema. */
|
|
763
|
+
arguments: Record<string, unknown>;
|
|
764
|
+
}
|
|
524
765
|
|
|
766
|
+
/**
|
|
767
|
+
* An image a tool produced, for the agent to look at: a screenshot, say, or
|
|
768
|
+
* a page of a document. Up to 8,000,000 base64 characters (about 6 MB); the
|
|
769
|
+
* runtime fits it to 1920 px on its long side.
|
|
770
|
+
*/
|
|
771
|
+
export declare interface ToolImage {
|
|
772
|
+
mediaType: "image/jpeg" | "image/png";
|
|
773
|
+
/** The encoded image, base64. */
|
|
774
|
+
data: string;
|
|
775
|
+
}
|
|
525
776
|
|
|
777
|
+
/**
|
|
778
|
+
* What a tool call produced: text, or JSON the agent reads as it is. Up to
|
|
779
|
+
* 24,000 characters (its JSON, for an object); the agent keeps the first 2,000.
|
|
780
|
+
*/
|
|
781
|
+
export declare type ToolOutput = string | Record<string, unknown> | unknown[];
|
|
526
782
|
|
|
783
|
+
/**
|
|
784
|
+
* How {@link Conversation.sendToolResult} reports a result.
|
|
785
|
+
*/
|
|
786
|
+
export declare interface ToolResultOptions {
|
|
787
|
+
/** Images the tool produced, up to 4; the agent looks at them, in order,
|
|
788
|
+
* where the result enters the conversation. */
|
|
789
|
+
images?: ToolImage[];
|
|
790
|
+
/** The output says what went wrong rather than what the tool produced. */
|
|
791
|
+
isError?: boolean;
|
|
792
|
+
/** Whether this is the call's last part; a part with `final: false` adds to
|
|
793
|
+
* what the agent knows and keeps the call open.
|
|
794
|
+
*
|
|
795
|
+
* @defaultValue `true` */
|
|
796
|
+
final?: boolean;
|
|
797
|
+
}
|
|
527
798
|
|
|
528
799
|
/**
|
|
529
800
|
* The local user participant in a conversation.
|
|
@@ -561,6 +832,59 @@ export declare interface UserParticipant {
|
|
|
561
832
|
readonly isSpeaking: boolean;
|
|
562
833
|
}
|
|
563
834
|
|
|
835
|
+
/**
|
|
836
|
+
* The user's voice went off, as the same detection reads it. `atMs` is where
|
|
837
|
+
* on the timeline it went off.
|
|
838
|
+
*/
|
|
839
|
+
export declare interface UserSpeechEndedEvent extends SessionEventBase {
|
|
840
|
+
type: "user.speech.ended";
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* The user's voice came on, as the runtime's voice-activity detection reads
|
|
845
|
+
* the microphone. `atMs` is where on the timeline it came on.
|
|
846
|
+
*/
|
|
847
|
+
export declare interface UserSpeechStartedEvent extends SessionEventBase {
|
|
848
|
+
type: "user.speech.started";
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* The user's words, live: one recognized segment at a time, as the runtime
|
|
853
|
+
* hears them. A later {@link UserTurnEndedEvent} may read the same speech
|
|
854
|
+
* differently; its `text` is the record of what the agent answered.
|
|
855
|
+
*/
|
|
856
|
+
export declare interface UserTranscriptEvent extends SessionEventBase {
|
|
857
|
+
type: "user.transcript";
|
|
858
|
+
/** The segment's words. */
|
|
859
|
+
text: string;
|
|
860
|
+
/**
|
|
861
|
+
* `"asr"`: recognized from the user's microphone. `"text"`: a line given
|
|
862
|
+
* as the user's turn in text rather than spoken. `"forced"`: a transcript
|
|
863
|
+
* supplied for the user's audio in place of recognition. A newer runtime
|
|
864
|
+
* may send other values.
|
|
865
|
+
*/
|
|
866
|
+
source: "asr" | "forced" | "text" | (string & {});
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* The agent is taking the turn to answer the user; sent each time it does.
|
|
871
|
+
* `atMs` is the point in the user's audio where the agent took the turn: the
|
|
872
|
+
* same moment as the turn's {@link AgentTurnStartedEvent}, stamped on the
|
|
873
|
+
* user's side of the timeline, so it is earlier than that event's `atMs` by
|
|
874
|
+
* the delay before the agent's audio plays.
|
|
875
|
+
*/
|
|
876
|
+
export declare interface UserTurnEndedEvent extends SessionEventBase {
|
|
877
|
+
type: "user.turn.ended";
|
|
878
|
+
/**
|
|
879
|
+
* The user's words since the agent last spoke, as the agent read them: a
|
|
880
|
+
* second, more careful hearing's words in place of the live
|
|
881
|
+
* {@link UserTranscriptEvent}s' where the runtime has one. When an agent
|
|
882
|
+
* turn ends before it said a word, the next `user.turn.ended` covers the
|
|
883
|
+
* same words again.
|
|
884
|
+
*/
|
|
885
|
+
text: string;
|
|
886
|
+
}
|
|
887
|
+
|
|
564
888
|
/**
|
|
565
889
|
* Capture constraints for the user's published camera track.
|
|
566
890
|
*
|
package/index.js
CHANGED
|
@@ -4,7 +4,7 @@ function isRecord(value) {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
// src/core/sdk-version.ts
|
|
7
|
-
var SDK_HEADER = `js/${"0.
|
|
7
|
+
var SDK_HEADER = `js/${"0.13.0"}`;
|
|
8
8
|
|
|
9
9
|
// src/utils.ts
|
|
10
10
|
function normalizeHost(host) {
|
|
@@ -185,6 +185,15 @@ class PolicyCurfewError extends Error {
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
// src/core/session-events.ts
|
|
189
|
+
function sessionEventsOf(type, handler) {
|
|
190
|
+
return (change) => {
|
|
191
|
+
if (change.type === "session-event" && change.event.type === type) {
|
|
192
|
+
handler(change.event);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
188
197
|
// src/core/ticker.ts
|
|
189
198
|
var WORKER_SOURCE = "onmessage = (e) => { setInterval(() => postMessage(0), e.data); };";
|
|
190
199
|
function backgroundTicker(host) {
|
|
@@ -1059,6 +1068,69 @@ async function avatarFramesDecoded(room) {
|
|
|
1059
1068
|
return null;
|
|
1060
1069
|
}
|
|
1061
1070
|
|
|
1071
|
+
// src/providers/livekit/sequence.ts
|
|
1072
|
+
class EventSequence {
|
|
1073
|
+
deliver;
|
|
1074
|
+
replay;
|
|
1075
|
+
last = 0;
|
|
1076
|
+
held = new Map;
|
|
1077
|
+
replaying = false;
|
|
1078
|
+
stopped = false;
|
|
1079
|
+
constructor(deliver, replay) {
|
|
1080
|
+
this.deliver = deliver;
|
|
1081
|
+
this.replay = replay;
|
|
1082
|
+
}
|
|
1083
|
+
repeats(seq) {
|
|
1084
|
+
return seq !== 0 && (seq <= this.last || this.held.has(seq));
|
|
1085
|
+
}
|
|
1086
|
+
accept(seq, item) {
|
|
1087
|
+
if (this.stopped || this.repeats(seq))
|
|
1088
|
+
return;
|
|
1089
|
+
if (seq === 0) {
|
|
1090
|
+
this.deliver(item);
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
if (seq !== this.last + 1) {
|
|
1094
|
+
this.held.set(seq, item);
|
|
1095
|
+
this.requestReplay();
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
this.last = seq;
|
|
1099
|
+
this.deliver(item);
|
|
1100
|
+
this.release(false);
|
|
1101
|
+
}
|
|
1102
|
+
catchUp() {
|
|
1103
|
+
if (!this.stopped)
|
|
1104
|
+
this.requestReplay();
|
|
1105
|
+
}
|
|
1106
|
+
stop() {
|
|
1107
|
+
this.stopped = true;
|
|
1108
|
+
this.held.clear();
|
|
1109
|
+
}
|
|
1110
|
+
requestReplay() {
|
|
1111
|
+
if (this.replaying)
|
|
1112
|
+
return;
|
|
1113
|
+
this.replaying = true;
|
|
1114
|
+
this.replay(this.last).catch(() => {
|
|
1115
|
+
return;
|
|
1116
|
+
}).finally(() => {
|
|
1117
|
+
this.replaying = false;
|
|
1118
|
+
this.release(true);
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
release(all) {
|
|
1122
|
+
const pending = [...this.held.keys()].sort((a, b) => a - b);
|
|
1123
|
+
for (const seq of pending) {
|
|
1124
|
+
if (this.stopped || !all && seq !== this.last + 1)
|
|
1125
|
+
return;
|
|
1126
|
+
const item = this.held.get(seq);
|
|
1127
|
+
this.held.delete(seq);
|
|
1128
|
+
this.last = seq;
|
|
1129
|
+
this.deliver(item);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1062
1134
|
// src/providers/livekit/signals.ts
|
|
1063
1135
|
function decode(payload) {
|
|
1064
1136
|
try {
|
|
@@ -1082,6 +1154,10 @@ function parseSessionEvent(payload) {
|
|
|
1082
1154
|
const decoded = decode(payload);
|
|
1083
1155
|
if (!decoded)
|
|
1084
1156
|
return null;
|
|
1157
|
+
const seq = Number.isSafeInteger(decoded.seq) && decoded.seq > 0 ? decoded.seq : 0;
|
|
1158
|
+
return { seq, event: protocolEvent(decoded, seq) };
|
|
1159
|
+
}
|
|
1160
|
+
function protocolEvent(decoded, seq) {
|
|
1085
1161
|
if (decoded.type === "session.output_ready") {
|
|
1086
1162
|
return { type: "output-ready" };
|
|
1087
1163
|
}
|
|
@@ -1095,9 +1171,6 @@ function parseSessionEvent(payload) {
|
|
|
1095
1171
|
}
|
|
1096
1172
|
};
|
|
1097
1173
|
}
|
|
1098
|
-
if (decoded.type === "floor") {
|
|
1099
|
-
return { type: "floor", speaking: decoded.state === "speaking" };
|
|
1100
|
-
}
|
|
1101
1174
|
if (decoded.type === "tool.call" && typeof decoded.call_id === "string" && typeof decoded.name === "string" && isRecord(decoded.arguments)) {
|
|
1102
1175
|
return {
|
|
1103
1176
|
type: "tool-call",
|
|
@@ -1130,7 +1203,44 @@ function parseSessionEvent(payload) {
|
|
|
1130
1203
|
if (decoded.type === "error" && decoded.code === "bad_command") {
|
|
1131
1204
|
return { type: "command-unreadable" };
|
|
1132
1205
|
}
|
|
1133
|
-
|
|
1206
|
+
const event = sessionEvent(decoded, seq);
|
|
1207
|
+
return event ? { type: "session-event", event } : { type: "other" };
|
|
1208
|
+
}
|
|
1209
|
+
var isString = (value) => typeof value === "string";
|
|
1210
|
+
function defaulted(value, absent) {
|
|
1211
|
+
if (value === undefined)
|
|
1212
|
+
return absent;
|
|
1213
|
+
return isString(value) ? value : null;
|
|
1214
|
+
}
|
|
1215
|
+
var READERS = {
|
|
1216
|
+
"user.transcript": (wire) => {
|
|
1217
|
+
const source = defaulted(wire.source, "asr");
|
|
1218
|
+
return isString(wire.text) && source !== null ? { text: wire.text, source } : null;
|
|
1219
|
+
},
|
|
1220
|
+
"user.speech.started": () => ({}),
|
|
1221
|
+
"user.speech.ended": () => ({}),
|
|
1222
|
+
"user.turn.ended": (wire) => isString(wire.text) ? { text: wire.text } : null,
|
|
1223
|
+
"agent.turn.started": (wire) => {
|
|
1224
|
+
const source = defaulted(wire.source, "reply");
|
|
1225
|
+
return isString(wire.turn_id) && isString(wire.opened_by) && source !== null ? { turnId: wire.turn_id, openedBy: wire.opened_by, source } : null;
|
|
1226
|
+
},
|
|
1227
|
+
"agent.speech.started": (wire) => isString(wire.turn_id) ? { turnId: wire.turn_id } : null,
|
|
1228
|
+
"agent.text": (wire) => {
|
|
1229
|
+
const turnId = wire.turn_id ?? null;
|
|
1230
|
+
return (turnId === null || isString(turnId)) && isString(wire.text) ? { turnId, text: wire.text } : null;
|
|
1231
|
+
},
|
|
1232
|
+
"agent.turn.ended": (wire) => isString(wire.turn_id) && isString(wire.reason) && isString(wire.spoken) ? { turnId: wire.turn_id, reason: wire.reason, spoken: wire.spoken } : null,
|
|
1233
|
+
floor: (wire) => isString(wire.state) ? { state: wire.state } : null
|
|
1234
|
+
};
|
|
1235
|
+
function sessionEvent(decoded, seq) {
|
|
1236
|
+
const type = decoded.type;
|
|
1237
|
+
if (!isString(type) || !Object.hasOwn(READERS, type))
|
|
1238
|
+
return null;
|
|
1239
|
+
const fields = READERS[type](decoded);
|
|
1240
|
+
if (fields === null)
|
|
1241
|
+
return null;
|
|
1242
|
+
const atMs = Number.isFinite(decoded.at_ms) ? decoded.at_ms : null;
|
|
1243
|
+
return { type, seq, atMs, ...fields };
|
|
1134
1244
|
}
|
|
1135
1245
|
function parseRuntimeSignal(payload) {
|
|
1136
1246
|
const decoded = decode(payload);
|
|
@@ -1616,6 +1726,7 @@ class LiveKitConversation {
|
|
|
1616
1726
|
refusal = null;
|
|
1617
1727
|
speaksProtocol = false;
|
|
1618
1728
|
avatarSpeaking = false;
|
|
1729
|
+
sequence = this.newSequence();
|
|
1619
1730
|
answered = null;
|
|
1620
1731
|
commands = new Map;
|
|
1621
1732
|
commandCount = 0;
|
|
@@ -1666,6 +1777,8 @@ class LiveKitConversation {
|
|
|
1666
1777
|
this.setState("connecting");
|
|
1667
1778
|
this.sessionEnded = false;
|
|
1668
1779
|
this.speaksProtocol = false;
|
|
1780
|
+
this.sequence.stop();
|
|
1781
|
+
this.sequence = this.newSequence();
|
|
1669
1782
|
this.readyOwed = false;
|
|
1670
1783
|
this.everConnected = false;
|
|
1671
1784
|
if (!this.eventsRegistered) {
|
|
@@ -1739,6 +1852,7 @@ class LiveKitConversation {
|
|
|
1739
1852
|
this._user?.releaseDevices();
|
|
1740
1853
|
await this.flushStats();
|
|
1741
1854
|
await this.sendSessionEnd();
|
|
1855
|
+
this.sequence.stop();
|
|
1742
1856
|
await this.room.disconnect();
|
|
1743
1857
|
this._avatar.updateParticipant(null);
|
|
1744
1858
|
this._user = null;
|
|
@@ -1794,10 +1908,13 @@ class LiveKitConversation {
|
|
|
1794
1908
|
}
|
|
1795
1909
|
onChange(handler) {
|
|
1796
1910
|
return this.onEvent((event) => {
|
|
1797
|
-
if (event.type !== "data-event" && event.type !== "screen-share-changed" && event.type !== "tool-call")
|
|
1911
|
+
if (event.type !== "data-event" && event.type !== "screen-share-changed" && event.type !== "tool-call" && event.type !== "session-event")
|
|
1798
1912
|
handler(event);
|
|
1799
1913
|
});
|
|
1800
1914
|
}
|
|
1915
|
+
on(type, handler) {
|
|
1916
|
+
return this.onEvent(sessionEventsOf(type, handler));
|
|
1917
|
+
}
|
|
1801
1918
|
onEvent(handler) {
|
|
1802
1919
|
this.changeHandlers.add(handler);
|
|
1803
1920
|
return () => {
|
|
@@ -1899,6 +2016,7 @@ class LiveKitConversation {
|
|
|
1899
2016
|
this.awaitAvatarReturn();
|
|
1900
2017
|
}
|
|
1901
2018
|
});
|
|
2019
|
+
this.room.on(RoomEvent3.Reconnected, () => this.catchUp());
|
|
1902
2020
|
this.room.on(RoomEvent3.ParticipantConnected, (participant) => {
|
|
1903
2021
|
if (this.isAvatarParticipant(participant)) {
|
|
1904
2022
|
const tracksChanged = this._avatar.updateParticipant(participant);
|
|
@@ -1908,6 +2026,7 @@ class LiveKitConversation {
|
|
|
1908
2026
|
}
|
|
1909
2027
|
if (this.away)
|
|
1910
2028
|
this.avatarBack();
|
|
2029
|
+
this.catchUp();
|
|
1911
2030
|
}
|
|
1912
2031
|
});
|
|
1913
2032
|
this.room.on(RoomEvent3.ParticipantDisconnected, (participant) => {
|
|
@@ -1929,6 +2048,7 @@ class LiveKitConversation {
|
|
|
1929
2048
|
this.room.on(RoomEvent3.Disconnected, () => {
|
|
1930
2049
|
this.stats?.abandon();
|
|
1931
2050
|
this.stats = null;
|
|
2051
|
+
this.sequence.stop();
|
|
1932
2052
|
this.setAvatarSpeaking(false);
|
|
1933
2053
|
this.stopPresentation();
|
|
1934
2054
|
this.stopReadyFallback();
|
|
@@ -2026,13 +2146,14 @@ class LiveKitConversation {
|
|
|
2026
2146
|
this.answered = null;
|
|
2027
2147
|
}
|
|
2028
2148
|
handleSessionEvent(payload) {
|
|
2029
|
-
const
|
|
2030
|
-
if (
|
|
2149
|
+
const packet = parseSessionEvent(payload);
|
|
2150
|
+
if (packet === null)
|
|
2031
2151
|
return;
|
|
2032
2152
|
this.speaksProtocol = true;
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2153
|
+
const { seq, event } = packet;
|
|
2154
|
+
if (this.sequence.repeats(seq))
|
|
2155
|
+
return;
|
|
2156
|
+
if (event.type === "command-result") {
|
|
2036
2157
|
if (event.id === SESSION_END_ID)
|
|
2037
2158
|
this.answered?.();
|
|
2038
2159
|
else if (event.id !== null) {
|
|
@@ -2045,16 +2166,32 @@ class LiveKitConversation {
|
|
|
2045
2166
|
this.commands.delete(id);
|
|
2046
2167
|
settle({ status: "rejected", error: `unknown command ${type}` });
|
|
2047
2168
|
}
|
|
2048
|
-
}
|
|
2049
|
-
|
|
2169
|
+
}
|
|
2170
|
+
this.sequence.accept(seq, event);
|
|
2171
|
+
}
|
|
2172
|
+
deliver(event) {
|
|
2173
|
+
if (event.type === "output-ready") {
|
|
2174
|
+
this.markAvatarReady();
|
|
2050
2175
|
} else if (event.type === "tool-call") {
|
|
2051
2176
|
this.emit({ type: "tool-call", call: event.call });
|
|
2052
2177
|
} else if (event.type === "ended") {
|
|
2053
2178
|
this.answered?.();
|
|
2054
2179
|
if (!event.byClient)
|
|
2055
2180
|
this.reportEnd(event.reason, event.message);
|
|
2181
|
+
} else if (event.type === "session-event") {
|
|
2182
|
+
if (event.event.type === "floor") {
|
|
2183
|
+
this.setAvatarSpeaking(event.event.state === "speaking");
|
|
2184
|
+
}
|
|
2185
|
+
this.emit({ type: "session-event", event: event.event });
|
|
2056
2186
|
}
|
|
2057
2187
|
}
|
|
2188
|
+
newSequence() {
|
|
2189
|
+
return new EventSequence((event) => this.deliver(event), (afterSeq) => this.command({ type: "session.replay", after_seq: afterSeq }));
|
|
2190
|
+
}
|
|
2191
|
+
catchUp() {
|
|
2192
|
+
if (this.speaksProtocol)
|
|
2193
|
+
this.sequence.catchUp();
|
|
2194
|
+
}
|
|
2058
2195
|
handleRuntimeSignal(payload) {
|
|
2059
2196
|
const signal = parseRuntimeSignal(payload);
|
|
2060
2197
|
if (signal?.type === "session-ended") {
|
|
@@ -2291,6 +2428,7 @@ class NuanceConversation {
|
|
|
2291
2428
|
ticker;
|
|
2292
2429
|
sessionOverrides;
|
|
2293
2430
|
personality;
|
|
2431
|
+
tools;
|
|
2294
2432
|
variant;
|
|
2295
2433
|
maxSessionSeconds;
|
|
2296
2434
|
videoCapture;
|
|
@@ -2329,6 +2467,7 @@ class NuanceConversation {
|
|
|
2329
2467
|
this.ticker = deps.ticker ?? pageTicker;
|
|
2330
2468
|
this.sessionOverrides = options.sessionOverrides ?? {};
|
|
2331
2469
|
this.personality = options.personality;
|
|
2470
|
+
this.tools = options.tools;
|
|
2332
2471
|
this.variant = options.variant;
|
|
2333
2472
|
this.maxSessionSeconds = options.maxSessionSeconds;
|
|
2334
2473
|
this.videoCapture = options.videoCapture;
|
|
@@ -2365,6 +2504,8 @@ class NuanceConversation {
|
|
|
2365
2504
|
request.timezone = timezone;
|
|
2366
2505
|
if (this.personality)
|
|
2367
2506
|
request.personality = this.personality;
|
|
2507
|
+
if (this.tools)
|
|
2508
|
+
request.tools = this.tools;
|
|
2368
2509
|
if (this.variant)
|
|
2369
2510
|
request.variant = this.variant;
|
|
2370
2511
|
if (this.maxSessionSeconds !== undefined) {
|
|
@@ -2497,10 +2638,13 @@ class NuanceConversation {
|
|
|
2497
2638
|
}
|
|
2498
2639
|
onChange(handler) {
|
|
2499
2640
|
return this.onEvent((event) => {
|
|
2500
|
-
if (event.type !== "data-event" && event.type !== "screen-share-changed" && event.type !== "tool-call")
|
|
2641
|
+
if (event.type !== "data-event" && event.type !== "screen-share-changed" && event.type !== "tool-call" && event.type !== "session-event")
|
|
2501
2642
|
handler(event);
|
|
2502
2643
|
});
|
|
2503
2644
|
}
|
|
2645
|
+
on(type, handler) {
|
|
2646
|
+
return this.onEvent(sessionEventsOf(type, handler));
|
|
2647
|
+
}
|
|
2504
2648
|
onEvent(handler) {
|
|
2505
2649
|
this.changeHandlers.add(handler);
|
|
2506
2650
|
return () => {
|