@lumavox/client 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +119 -2
- package/dist/audio.d.ts +38 -0
- package/dist/audio.js +1 -0
- package/dist/client.d.ts +108 -0
- package/dist/client.js +122 -0
- package/dist/conversation.d.ts +324 -0
- package/dist/conversation.js +763 -0
- package/dist/errors.d.ts +86 -0
- package/dist/errors.js +168 -0
- package/dist/events.d.ts +224 -0
- package/dist/events.js +1 -0
- package/dist/gen/index.d.ts +1 -0
- package/dist/gen/index.js +2 -0
- package/dist/gen/types.gen.d.ts +776 -0
- package/dist/gen/types.gen.js +2 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +15 -0
- package/dist/pcm.d.ts +38 -0
- package/dist/pcm.js +110 -0
- package/dist/protocol.gen.d.ts +44 -0
- package/dist/protocol.gen.js +46 -0
- package/dist/socket.d.ts +167 -0
- package/dist/socket.js +185 -0
- package/dist/token.d.ts +39 -0
- package/dist/token.js +63 -0
- package/dist/transport.d.ts +83 -0
- package/dist/transport.js +236 -0
- package/package.json +39 -4
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A conversation on the agent's socket, kept alive: `client.connect()`
|
|
3
|
+
* answers one at once, and it dials, authenticates, reconnects, replaces
|
|
4
|
+
* a refused key, queues what is sent while it cannot be, and publishes
|
|
5
|
+
* what it knows as a snapshot (`subscribe`) — on `socket.ts`'s one socket
|
|
6
|
+
* at a time.
|
|
7
|
+
*
|
|
8
|
+
* - **The key before the dial**: minting does not eat the edge's 10 s to
|
|
9
|
+
* authenticate.
|
|
10
|
+
* - **One turn at a time, queued.** A message sent during the greeting, a
|
|
11
|
+
* turn, a reconnect or a quiet spell waits its turn instead of being
|
|
12
|
+
* refused; each `send` answers a promise of its outcome that **never
|
|
13
|
+
* rejects** — `done`, `interrupted`, or `dropped` (closed, cancelled,
|
|
14
|
+
* refused) — so leaving during the greeting is no unhandled rejection.
|
|
15
|
+
* A message the edge never acknowledged is sent again once after a
|
|
16
|
+
* reconnect; one it acknowledged is never sent twice (it was billed).
|
|
17
|
+
* - **Recordings carry an id** (`r1`, `r2`, …: a counter, since Hermes has
|
|
18
|
+
* no `randomUUID`), which the edge echoes, so a `transcribeOnly` end
|
|
19
|
+
* answers its own recording's words however the transcripts finish. A
|
|
20
|
+
* recording started before the socket is ready is held and sent at
|
|
21
|
+
* `ready`; one cut by a reconnect is over, since the new socket never
|
|
22
|
+
* had it — audio sent for it would close the socket (4011).
|
|
23
|
+
* - **Speech is not paid for unheard**: with no `onAudio` handler every
|
|
24
|
+
* turn says `speech: false`, unless `connect`'s options say otherwise.
|
|
25
|
+
* - **A watchdog, no pings**: the edge sends `pong` after 30 s of client
|
|
26
|
+
* silence, so 60 s with nothing from it, and nothing said for as long,
|
|
27
|
+
* is a dead socket (half-open, after a phone changed networks): it is
|
|
28
|
+
* dropped and dialled again. A ping never resets the edge's idle timer.
|
|
29
|
+
* - **The app's handlers cannot break it**: one that throws is rethrown on
|
|
30
|
+
* its own (`call`), after the state it was told about is in place.
|
|
31
|
+
*
|
|
32
|
+
* Cost per frame: one snapshot (and one turn) object per change a screen
|
|
33
|
+
* shows — a `text.delta`, say — and none for audio, which is passed
|
|
34
|
+
* through, never copied.
|
|
35
|
+
*/
|
|
36
|
+
import type { ErrorFrame, InterruptedFrame, KnowledgeFrame, ReadyFrame, ServerFrame, ServerFrames, TranscriptFrame, TurnDoneFrame } from "./events.js";
|
|
37
|
+
import type { RelationInfo } from "./gen/index.js";
|
|
38
|
+
import { type AudioFrame, type Warning, type WebSocketConstructor } from "./socket.js";
|
|
39
|
+
import type { Tokens } from "./token.js";
|
|
40
|
+
export type ConversationState =
|
|
41
|
+
/** Dialling for the first time. */
|
|
42
|
+
"connecting"
|
|
43
|
+
/** Authenticated and idle: a `send` goes at once. */
|
|
44
|
+
| "ready"
|
|
45
|
+
/** A turn is running — the greeting, or a message — and sends queue behind it. */
|
|
46
|
+
| "busy"
|
|
47
|
+
/** The connection dropped and is being made again. */
|
|
48
|
+
| "reconnecting"
|
|
49
|
+
/** The edge hung up on a quiet connection (4007): the next `send`, `startAudio` or `reconnect()` dials. */
|
|
50
|
+
| "dormant"
|
|
51
|
+
/** Over: `close()`, or a close nothing can cure (`onClose` says which). */
|
|
52
|
+
| "closed";
|
|
53
|
+
/** The turn in progress, or the last one. */
|
|
54
|
+
export interface TurnSnapshot {
|
|
55
|
+
/** From `turn.start`; `null` while a message is on its way. */
|
|
56
|
+
id: string | null;
|
|
57
|
+
/** The reply so far; the whole of it once `done`. */
|
|
58
|
+
text: string;
|
|
59
|
+
/** The reply as cut for speech, piece by piece. */
|
|
60
|
+
chunks: string[];
|
|
61
|
+
mood: {
|
|
62
|
+
mood: string;
|
|
63
|
+
intensity: number | null;
|
|
64
|
+
} | null;
|
|
65
|
+
/** The tool the model is waiting on, between `tool.start` and `tool.end`. */
|
|
66
|
+
tool: string | null;
|
|
67
|
+
knowledge: KnowledgeFrame[];
|
|
68
|
+
/** A spoken turn's transcript. */
|
|
69
|
+
heard: string | null;
|
|
70
|
+
done: boolean;
|
|
71
|
+
interrupted: boolean;
|
|
72
|
+
}
|
|
73
|
+
export interface Snapshot {
|
|
74
|
+
state: ConversationState;
|
|
75
|
+
/** The last `ready`: the agent, its voice's format, the greeting. */
|
|
76
|
+
ready: ReadyFrame | null;
|
|
77
|
+
turn: TurnSnapshot | null;
|
|
78
|
+
/** The open recording as heard so far (`heard.partial`), until its words arrive. */
|
|
79
|
+
caption: string | null;
|
|
80
|
+
/** The standing relationship, from `ready` and every `relation` since. */
|
|
81
|
+
relation: RelationInfo | null;
|
|
82
|
+
/** Sends waiting behind the running turn, a reconnect or the greeting. */
|
|
83
|
+
queued: number;
|
|
84
|
+
}
|
|
85
|
+
/** How a turn this conversation sent ended. */
|
|
86
|
+
export type TurnOutcome = {
|
|
87
|
+
status: "done";
|
|
88
|
+
frame: TurnDoneFrame;
|
|
89
|
+
}
|
|
90
|
+
/** Cut short: by `cancel()`, a failure (its `error` came first), or the connection (`frame: null`). It was taken, and billed. */
|
|
91
|
+
| {
|
|
92
|
+
status: "interrupted";
|
|
93
|
+
frame: InterruptedFrame | null;
|
|
94
|
+
} | Dropped;
|
|
95
|
+
/** How a `transcribeOnly` recording ended. */
|
|
96
|
+
export type TranscriptOutcome = {
|
|
97
|
+
status: "done";
|
|
98
|
+
text: string;
|
|
99
|
+
seconds: number;
|
|
100
|
+
frame: TranscriptFrame;
|
|
101
|
+
}
|
|
102
|
+
/** The edge refused or failed it: a rate it does not take, no audio, the provider. */
|
|
103
|
+
| {
|
|
104
|
+
status: "failed";
|
|
105
|
+
frame: ErrorFrame;
|
|
106
|
+
} | Dropped;
|
|
107
|
+
/** Never taken: the connection ended, `cancel()` dropped it, or it was refused before it was a turn. */
|
|
108
|
+
export interface Dropped {
|
|
109
|
+
status: "dropped";
|
|
110
|
+
reason: "closed" | "cancelled" | "refused";
|
|
111
|
+
detail?: string;
|
|
112
|
+
}
|
|
113
|
+
/** Why a conversation ended without reconnecting. */
|
|
114
|
+
export interface CloseInfo {
|
|
115
|
+
code: number;
|
|
116
|
+
reason: string;
|
|
117
|
+
/** `true` when reconnecting cannot help: `reconnect()` revives only a conversation that is not. */
|
|
118
|
+
final: boolean;
|
|
119
|
+
/** What `getToken` threw, when that ended it. */
|
|
120
|
+
error?: unknown;
|
|
121
|
+
}
|
|
122
|
+
type On<K extends keyof ServerFrames> = (frame: ServerFrames[K]) => void;
|
|
123
|
+
/** A socket that closed: its code and reason, and whether it had reached `ready`. */
|
|
124
|
+
export interface Disconnect {
|
|
125
|
+
code: number;
|
|
126
|
+
reason: string;
|
|
127
|
+
ready: boolean;
|
|
128
|
+
}
|
|
129
|
+
/** Every handler is optional; each is given the wire's frame as it is. */
|
|
130
|
+
export interface ConversationHandlers {
|
|
131
|
+
onReady?: On<"ready">;
|
|
132
|
+
onTurnStart?: On<"turn.start">;
|
|
133
|
+
onHeard?: On<"heard">;
|
|
134
|
+
onHeardPartial?: On<"heard.partial">;
|
|
135
|
+
onKnowledge?: On<"knowledge">;
|
|
136
|
+
onMood?: On<"mood">;
|
|
137
|
+
onThought?: On<"thought.delta">;
|
|
138
|
+
onText?: On<"text.delta">;
|
|
139
|
+
onChunk?: On<"text.chunk">;
|
|
140
|
+
onToolStart?: On<"tool.start">;
|
|
141
|
+
onToolEnd?: On<"tool.end">;
|
|
142
|
+
onUsage?: On<"usage">;
|
|
143
|
+
onMuted?: On<"muted">;
|
|
144
|
+
onAudioStart?: On<"audio.start">;
|
|
145
|
+
onAudioEnd?: On<"audio.end">;
|
|
146
|
+
/** A reply's PCM, with its window's format. Its presence turns speech on (`ConnectOptions.speech`). */
|
|
147
|
+
onAudio?: (frame: AudioFrame) => void;
|
|
148
|
+
onTranscript?: On<"transcript">;
|
|
149
|
+
onDone?: On<"turn.done">;
|
|
150
|
+
onInterrupted?: On<"interrupted">;
|
|
151
|
+
/** The relationship, from `ready` and when it moves: each change once. */
|
|
152
|
+
onRelation?: (relation: RelationInfo) => void;
|
|
153
|
+
onError?: On<"error">;
|
|
154
|
+
onState?: (state: ConversationState) => void;
|
|
155
|
+
/** Only when it will **not** reconnect. */
|
|
156
|
+
onClose?: (close: CloseInfo) => void;
|
|
157
|
+
/**
|
|
158
|
+
* Every socket the edge or the network closed — `close()` aside — before
|
|
159
|
+
* the close table decides what follows: for logging a drop the
|
|
160
|
+
* conversation recovers from as well as one it does not.
|
|
161
|
+
*/
|
|
162
|
+
onDisconnect?: (drop: Disconnect) => void;
|
|
163
|
+
onWarning?: (warning: Warning) => void;
|
|
164
|
+
/** Every frame, the ones above included: what a newer edge sends that this SDK does not know. */
|
|
165
|
+
onEvent?: (frame: ServerFrame) => void;
|
|
166
|
+
}
|
|
167
|
+
export interface ConnectOptions {
|
|
168
|
+
/**
|
|
169
|
+
* Every turn's `speech`. By default `false` when there is no `onAudio`
|
|
170
|
+
* handler — nothing would play it, and speech is billed — and otherwise
|
|
171
|
+
* the agent's own switch.
|
|
172
|
+
*/
|
|
173
|
+
speech?: boolean;
|
|
174
|
+
/** Ask every turn for thinking summaries (`onThought`). */
|
|
175
|
+
thoughts?: boolean;
|
|
176
|
+
}
|
|
177
|
+
/** Per turn: overrides `ConnectOptions`. */
|
|
178
|
+
export interface TurnOptions {
|
|
179
|
+
speech?: boolean;
|
|
180
|
+
thoughts?: boolean;
|
|
181
|
+
}
|
|
182
|
+
export interface StartAudioOptions extends TurnOptions {
|
|
183
|
+
/** The PCM's rate, Hz: one of `RECORDING_RATES`. Raw PCM has no header, so this is the only thing that says what the samples are — and what they are billed as. */
|
|
184
|
+
rate: number;
|
|
185
|
+
/** A language hint, 2 to 8 characters; detected per recording when unset. */
|
|
186
|
+
language?: string;
|
|
187
|
+
/** A transcription model (`STT_MODELS`); the agent's own when unset. */
|
|
188
|
+
model?: string;
|
|
189
|
+
}
|
|
190
|
+
/** What `client.connect` hands the conversation. */
|
|
191
|
+
export interface Setup {
|
|
192
|
+
sessionId: string;
|
|
193
|
+
socketUrl: string;
|
|
194
|
+
tokens: Tokens;
|
|
195
|
+
WebSocket: WebSocketConstructor;
|
|
196
|
+
handlers: ConversationHandlers;
|
|
197
|
+
options: ConnectOptions;
|
|
198
|
+
}
|
|
199
|
+
/** No `ready` this long after the dial — a proxy that took the upgrade and says nothing — is a dropped connection. */
|
|
200
|
+
export declare const READY_DEADLINE_MS = 15000;
|
|
201
|
+
export declare class Conversation {
|
|
202
|
+
readonly sessionId: string;
|
|
203
|
+
private readonly setup;
|
|
204
|
+
private readonly speech;
|
|
205
|
+
private snap;
|
|
206
|
+
private listeners;
|
|
207
|
+
private socket;
|
|
208
|
+
/** Moves with every dial and every end: a callback from an older one is stale. */
|
|
209
|
+
private generation;
|
|
210
|
+
private key;
|
|
211
|
+
private attempts;
|
|
212
|
+
/** A new key was taken for a refusal, and no turn has finished on it yet. */
|
|
213
|
+
private replaced;
|
|
214
|
+
private closed;
|
|
215
|
+
/** Closed by giving up, not by a final code: `reconnect()` may dial again. */
|
|
216
|
+
private revivable;
|
|
217
|
+
private dormant;
|
|
218
|
+
private everReady;
|
|
219
|
+
/** The socket now open reached `ready`. */
|
|
220
|
+
private socketReady;
|
|
221
|
+
/** A turn this conversation did not send is running: the greeting. */
|
|
222
|
+
private running;
|
|
223
|
+
private inFlight;
|
|
224
|
+
private queue;
|
|
225
|
+
private open;
|
|
226
|
+
/** `transcribeOnly` recordings waiting for their words, oldest first. */
|
|
227
|
+
private hearing;
|
|
228
|
+
/** Recordings waiting for `ready`. */
|
|
229
|
+
private held;
|
|
230
|
+
private recordings;
|
|
231
|
+
/** The turn whose terminal frame was taken: a second for it says nothing new. */
|
|
232
|
+
private finished;
|
|
233
|
+
private relationSeen;
|
|
234
|
+
private retry;
|
|
235
|
+
private deadline;
|
|
236
|
+
private watchdog;
|
|
237
|
+
private heardAt;
|
|
238
|
+
private saidAt;
|
|
239
|
+
constructor(setup: Setup);
|
|
240
|
+
/** What is known now. A new object on every change: compare by identity. */
|
|
241
|
+
get snapshot(): Snapshot;
|
|
242
|
+
get state(): ConversationState;
|
|
243
|
+
/** `fn` is called after every change of `snapshot`. Answers the unsubscribe. */
|
|
244
|
+
subscribe(fn: () => void): () => void;
|
|
245
|
+
/**
|
|
246
|
+
* Take a turn by typing. Queued behind the greeting, a running turn, a
|
|
247
|
+
* reconnect or a quiet spell; answers how it ended, and never rejects.
|
|
248
|
+
* 1 to 32,000 characters, or it is `dropped: refused` here.
|
|
249
|
+
*/
|
|
250
|
+
send(text: string, options?: TurnOptions): Promise<TurnOutcome>;
|
|
251
|
+
/**
|
|
252
|
+
* Open a recording at `rate`, which must be one of `RECORDING_RATES`.
|
|
253
|
+
* Answers its id. A recording already open is abandoned, as the edge
|
|
254
|
+
* abandons it.
|
|
255
|
+
*/
|
|
256
|
+
startAudio(options: StartAudioOptions): string;
|
|
257
|
+
/**
|
|
258
|
+
* PCM for the open recording: 16-bit samples, or their little-endian
|
|
259
|
+
* bytes. Answers whether it was taken — not past the 10 MB a recording
|
|
260
|
+
* may carry (`onWarning`), nor with no recording open.
|
|
261
|
+
*/
|
|
262
|
+
sendAudio(pcm: ArrayBuffer | Int16Array): boolean;
|
|
263
|
+
/**
|
|
264
|
+
* Close the recording. As a turn: queued like a `send`, and answered the
|
|
265
|
+
* same way. With `transcribeOnly`: no turn — the words come back for
|
|
266
|
+
* this recording alone, matched by its id, however its transcript and
|
|
267
|
+
* others' finish; `onTranscript` still fires for each.
|
|
268
|
+
*/
|
|
269
|
+
endAudio(options: {
|
|
270
|
+
transcribeOnly: true;
|
|
271
|
+
}): Promise<TranscriptOutcome>;
|
|
272
|
+
endAudio(options?: {
|
|
273
|
+
transcribeOnly?: false;
|
|
274
|
+
}): Promise<TurnOutcome>;
|
|
275
|
+
/** Stop the turn in flight, and drop what is queued and the open recording. What was said is still billed and stored. */
|
|
276
|
+
cancel(): void;
|
|
277
|
+
/** Stop speaking, and let the turn finish: the rest of the reply is never spoken, nor billed. */
|
|
278
|
+
mute(): void;
|
|
279
|
+
/** Liveness only: answered with `pong`, and never counted as activity. Not needed — the SDK's watchdog listens for the edge's own. */
|
|
280
|
+
ping(): void;
|
|
281
|
+
/**
|
|
282
|
+
* Dial now: the app came to the foreground, the network came back. A
|
|
283
|
+
* dormant conversation, one waiting to retry, and one that gave up (not
|
|
284
|
+
* a final close) dial at once; a live one is left alone.
|
|
285
|
+
*/
|
|
286
|
+
reconnect(): void;
|
|
287
|
+
/** End it, for good. Every pending promise answers `dropped: closed`. */
|
|
288
|
+
close(): void;
|
|
289
|
+
private dial;
|
|
290
|
+
/** A socket given up on from this side: lost as a dropped network is (1006). */
|
|
291
|
+
private drop;
|
|
292
|
+
/** The socket closed: settle what it carried, then reconnect, rest or end. */
|
|
293
|
+
private lost;
|
|
294
|
+
/** It will not reconnect: say why, once. */
|
|
295
|
+
private finish;
|
|
296
|
+
/** Closed: no socket, no timer, and every promise answered. */
|
|
297
|
+
private end;
|
|
298
|
+
private stopTimers;
|
|
299
|
+
private frame;
|
|
300
|
+
private ready;
|
|
301
|
+
/** The end of a turn: answers whether it was a second for the same turn, which says nothing new. */
|
|
302
|
+
private terminal;
|
|
303
|
+
private error;
|
|
304
|
+
/** An error naming a recording: it was refused, or failed. */
|
|
305
|
+
private refuse;
|
|
306
|
+
private transcript;
|
|
307
|
+
/** Each change of the relationship once: `ready` repeats it on every reconnect. */
|
|
308
|
+
private relate;
|
|
309
|
+
private enqueue;
|
|
310
|
+
/** The next queued turn goes, when nothing is running and the socket is ready. */
|
|
311
|
+
private pump;
|
|
312
|
+
/** A turn's frame with its `speech` and `thoughts`. */
|
|
313
|
+
private turnFrame;
|
|
314
|
+
/** The open recording is over before its end: the edge discards the rest. */
|
|
315
|
+
private abandon;
|
|
316
|
+
private said;
|
|
317
|
+
/** A recording's words are still to come: a partial is a caption. */
|
|
318
|
+
private listening;
|
|
319
|
+
private update;
|
|
320
|
+
private emit;
|
|
321
|
+
/** The state, from what is true now; `onState` when it moved. */
|
|
322
|
+
private sync;
|
|
323
|
+
}
|
|
324
|
+
export {};
|