@lumavox/client 0.2.0 → 0.4.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 +3 -2
- package/dist/client.d.ts +5 -6
- package/dist/client.js +2 -2
- package/dist/conversation.d.ts +7 -6
- package/dist/conversation.js +5 -7
- package/dist/events.d.ts +3 -2
- package/dist/gen/types.gen.d.ts +8 -0
- package/dist/protocol.gen.d.ts +3 -0
- package/dist/protocol.gen.js +3 -0
- package/dist/socket.js +6 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -80,8 +80,9 @@ key, dials, authenticates, and from then on:
|
|
|
80
80
|
connection limit, 0.5 s for anything else, ±20 % jitter, final after
|
|
81
81
|
five in a row — and **rests** after the edge hangs up on a quiet
|
|
82
82
|
connection (4007), dialling again on the next `send`;
|
|
83
|
-
- **watches**:
|
|
84
|
-
and dialled
|
|
83
|
+
- **watches**: it pings after 30 s with nothing from the edge, and a
|
|
84
|
+
minute with nothing — no `pong` — is a dead socket, dropped and dialled
|
|
85
|
+
again;
|
|
85
86
|
- publishes `snapshot` — `state`, `ready`, the `turn` (its text, chunks,
|
|
86
87
|
mood, tool, knowledge), `caption`, `relation`, `queued` — with
|
|
87
88
|
`subscribe(fn)`.
|
package/dist/client.d.ts
CHANGED
|
@@ -78,14 +78,13 @@ export interface Sessions {
|
|
|
78
78
|
extract(id: string, options?: RequestOptions): Promise<CompactionInfo>;
|
|
79
79
|
}
|
|
80
80
|
export interface Facts {
|
|
81
|
+
/** Everything remembered about the person, oldest first, 200 a page. */
|
|
82
|
+
list(options?: RequestOptions): AsyncGenerator<FactInfo, void, undefined>;
|
|
81
83
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* route pages.
|
|
84
|
+
* One page of them, 100 by default; `cursor` is the `next_cursor` of the
|
|
85
|
+
* page before.
|
|
85
86
|
*/
|
|
86
|
-
|
|
87
|
-
/** One page of them, as the route answers it. */
|
|
88
|
-
page(options?: RequestOptions): Promise<FactPage>;
|
|
87
|
+
page(options?: RequestOptions, cursor?: string): Promise<FactPage>;
|
|
89
88
|
/** Forget one: `true`, or `false` when it is already gone. */
|
|
90
89
|
delete(id: string, options?: RequestOptions): Promise<boolean>;
|
|
91
90
|
/** Forget everything about the person: how many facts went. Their conversations stay. */
|
package/dist/client.js
CHANGED
|
@@ -76,7 +76,7 @@ export function createClient(options) {
|
|
|
76
76
|
/** One page of the list at `path`. */
|
|
77
77
|
const read = (path, params = {}, request) => send({ method: "GET", path, query: { ...params } }, request);
|
|
78
78
|
const page = (params = {}, request) => read("/v1/agent/sessions", params, request);
|
|
79
|
-
const factPage = (request, cursor) => read("/v1/agent/facts", { cursor }, request);
|
|
79
|
+
const factPage = (request, cursor, limit) => read("/v1/agent/facts", { limit, cursor }, request);
|
|
80
80
|
const relationPage = (params = {}, request) => read("/v1/agent/relations", params, request);
|
|
81
81
|
const get = (id, params = {}, request) => send({ method: "GET", path: sessionPath(id), query: { ...params } }, request);
|
|
82
82
|
return {
|
|
@@ -102,7 +102,7 @@ export function createClient(options) {
|
|
|
102
102
|
extract: (id, request) => send({ method: "POST", path: sessionPath(id, "/extract") }, request),
|
|
103
103
|
},
|
|
104
104
|
facts: {
|
|
105
|
-
list: (request) => pages((cursor) => factPage(request, cursor)),
|
|
105
|
+
list: (request) => pages((cursor) => factPage(request, cursor, PAGE)),
|
|
106
106
|
page: factPage,
|
|
107
107
|
delete: (id, request) => done(send({ method: "DELETE", path: `/v1/agent/facts/${segment(id)}` }, request)),
|
|
108
108
|
deleteAll: (request) => send({ method: "DELETE", path: "/v1/agent/facts" }, request),
|
package/dist/conversation.d.ts
CHANGED
|
@@ -22,10 +22,12 @@
|
|
|
22
22
|
* had it — audio sent for it would close the socket (4011).
|
|
23
23
|
* - **Speech is not paid for unheard**: with no `onAudio` handler every
|
|
24
24
|
* turn says `speech: false`, unless `connect`'s options say otherwise.
|
|
25
|
-
* - **A watchdog
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* - **A watchdog that pings**: after 30 s with nothing from the edge it
|
|
26
|
+
* sends `ping` (`KEEPALIVE_PING`, which the edge's runtime answers with
|
|
27
|
+
* its object asleep), and past a minute with nothing at all — no `pong`
|
|
28
|
+
* to two pings — the socket is dead (half-open, after a phone changed
|
|
29
|
+
* networks): it is dropped and dialled again. A ping never resets the
|
|
30
|
+
* edge's idle timer.
|
|
29
31
|
* - **The app's handlers cannot break it**: one that throws is rethrown on
|
|
30
32
|
* its own (`call`), after the state it was told about is in place.
|
|
31
33
|
*
|
|
@@ -236,7 +238,6 @@ export declare class Conversation {
|
|
|
236
238
|
private deadline;
|
|
237
239
|
private watchdog;
|
|
238
240
|
private heardAt;
|
|
239
|
-
private saidAt;
|
|
240
241
|
constructor(setup: Setup);
|
|
241
242
|
/** What is known now. A new object on every change: compare by identity. */
|
|
242
243
|
get snapshot(): Snapshot;
|
|
@@ -277,7 +278,7 @@ export declare class Conversation {
|
|
|
277
278
|
cancel(): void;
|
|
278
279
|
/** Stop speaking, and let the turn finish: the rest of the reply is never spoken, nor billed. */
|
|
279
280
|
mute(): void;
|
|
280
|
-
/** Liveness only: answered with `pong`, and never counted as activity. Not needed — the SDK
|
|
281
|
+
/** Liveness only: answered with `pong`, and never counted as activity. Not needed — the SDK pings after 30 s of silence itself. */
|
|
281
282
|
ping(): void;
|
|
282
283
|
/**
|
|
283
284
|
* Dial now: the app came to the foreground, the network came back. A
|
package/dist/conversation.js
CHANGED
|
@@ -106,7 +106,6 @@ export class Conversation {
|
|
|
106
106
|
this.deadline = null;
|
|
107
107
|
this.watchdog = null;
|
|
108
108
|
this.heardAt = 0;
|
|
109
|
-
this.saidAt = 0;
|
|
110
109
|
this.sessionId = setup.sessionId;
|
|
111
110
|
this.setup = setup;
|
|
112
111
|
this.speech =
|
|
@@ -204,7 +203,6 @@ export class Conversation {
|
|
|
204
203
|
recording.held.push(data.slice(0));
|
|
205
204
|
else if (recording.socket === this.generation && this.socket) {
|
|
206
205
|
this.socket.sendBinary(data);
|
|
207
|
-
this.saidAt = Date.now();
|
|
208
206
|
}
|
|
209
207
|
return true;
|
|
210
208
|
}
|
|
@@ -251,7 +249,7 @@ export class Conversation {
|
|
|
251
249
|
mute() {
|
|
252
250
|
this.socket?.send({ type: "mute" });
|
|
253
251
|
}
|
|
254
|
-
/** Liveness only: answered with `pong`, and never counted as activity. Not needed — the SDK
|
|
252
|
+
/** Liveness only: answered with `pong`, and never counted as activity. Not needed — the SDK pings after 30 s of silence itself. */
|
|
255
253
|
ping() {
|
|
256
254
|
this.socket?.send({ type: "ping" });
|
|
257
255
|
}
|
|
@@ -324,14 +322,16 @@ export class Conversation {
|
|
|
324
322
|
this.finish(0, "The socket could not be opened.", true, error);
|
|
325
323
|
return;
|
|
326
324
|
}
|
|
327
|
-
this.heardAt =
|
|
325
|
+
this.heardAt = Date.now();
|
|
328
326
|
this.socketReady = false;
|
|
329
327
|
this.deadline = setTimeout(() => this.drop(generation, "The connection never became ready."), READY_DEADLINE_MS);
|
|
330
328
|
this.watchdog = setInterval(() => {
|
|
331
|
-
const quiet = Date.now() -
|
|
329
|
+
const quiet = Date.now() - this.heardAt;
|
|
332
330
|
if (quiet > 2 * KEEPALIVE_SECONDS * 1000) {
|
|
333
331
|
this.drop(generation, "Nothing came from the edge for a minute.");
|
|
334
332
|
}
|
|
333
|
+
else if (quiet >= KEEPALIVE_SECONDS * 1000)
|
|
334
|
+
this.ping();
|
|
335
335
|
}, KEEPALIVE_SECONDS * 500);
|
|
336
336
|
}
|
|
337
337
|
/** A socket given up on from this side: lost as a dropped network is (1006). */
|
|
@@ -693,7 +693,6 @@ export class Conversation {
|
|
|
693
693
|
this.inFlight = next;
|
|
694
694
|
next.sent += 1;
|
|
695
695
|
this.socket.send(next.frame);
|
|
696
|
-
this.saidAt = Date.now();
|
|
697
696
|
this.emit({ turn: FRESH });
|
|
698
697
|
}
|
|
699
698
|
this.emit({ queued: this.queue.length });
|
|
@@ -719,7 +718,6 @@ export class Conversation {
|
|
|
719
718
|
}
|
|
720
719
|
said(recording) {
|
|
721
720
|
recording.socket = this.generation;
|
|
722
|
-
this.saidAt = Date.now();
|
|
723
721
|
}
|
|
724
722
|
/** A recording's words are still to come: a partial is a caption. */
|
|
725
723
|
listening() {
|
package/dist/events.d.ts
CHANGED
|
@@ -186,8 +186,9 @@ export interface ErrorFrame extends Frame<"error"> {
|
|
|
186
186
|
/** The recording it refused or failed. */
|
|
187
187
|
recording_id: string | null;
|
|
188
188
|
}
|
|
189
|
-
/** The answer to `ping`,
|
|
190
|
-
export interface PongFrame
|
|
189
|
+
/** The answer to `ping`, from the edge's runtime: the one frame with no `seq`. */
|
|
190
|
+
export interface PongFrame {
|
|
191
|
+
type: "pong";
|
|
191
192
|
}
|
|
192
193
|
/**
|
|
193
194
|
* Every event, by name. **Exhaustive**: a name in the vocabulary with no
|
package/dist/gen/types.gen.d.ts
CHANGED
|
@@ -597,6 +597,14 @@ export type ListFactsData = {
|
|
|
597
597
|
* Optional, and only ever the key's own end user — their `eu_...` id or your own id for them. Anybody else is a 403.
|
|
598
598
|
*/
|
|
599
599
|
end_user_id?: string;
|
|
600
|
+
/**
|
|
601
|
+
* How many to answer, 1 to 200.
|
|
602
|
+
*/
|
|
603
|
+
limit?: number;
|
|
604
|
+
/**
|
|
605
|
+
* The `next_cursor` of the page before.
|
|
606
|
+
*/
|
|
607
|
+
cursor?: string;
|
|
600
608
|
};
|
|
601
609
|
url: '/v1/agent/facts';
|
|
602
610
|
};
|
package/dist/protocol.gen.d.ts
CHANGED
|
@@ -8,6 +8,9 @@ export declare const SERVER_EVENTS: readonly ["ready", "turn.start", "heard", "h
|
|
|
8
8
|
export type ServerEventName = (typeof SERVER_EVENTS)[number];
|
|
9
9
|
/** The events that end a turn: exactly one of them ends every turn. */
|
|
10
10
|
export declare const TERMINAL_EVENTS: readonly ["turn.done", "interrupted"];
|
|
11
|
+
/** A `ping` of exactly this text is answered by the edge's runtime with `KEEPALIVE_PONG`, which has no `seq`. */
|
|
12
|
+
export declare const KEEPALIVE_PING = "{\"type\":\"ping\"}";
|
|
13
|
+
export declare const KEEPALIVE_PONG = "{\"type\":\"pong\"}";
|
|
11
14
|
/** The edge's close codes by name: `CLOSE.auth_timeout === 4001`. */
|
|
12
15
|
export declare const CLOSE: {
|
|
13
16
|
readonly auth_timeout: 4001;
|
package/dist/protocol.gen.js
CHANGED
|
@@ -10,6 +10,9 @@ export const CLIENT_VERBS = ["auth", "user_message", "user_audio.start", "user_a
|
|
|
10
10
|
export const SERVER_EVENTS = ["ready", "turn.start", "heard", "heard.partial", "transcript", "mood", "text.delta", "text.chunk", "thought.delta", "tool.start", "tool.end", "knowledge", "audio.start", "audio.end", "muted", "relation", "usage", "turn.done", "interrupted", "error", "pong"];
|
|
11
11
|
/** The events that end a turn: exactly one of them ends every turn. */
|
|
12
12
|
export const TERMINAL_EVENTS = ["turn.done", "interrupted"];
|
|
13
|
+
/** A `ping` of exactly this text is answered by the edge's runtime with `KEEPALIVE_PONG`, which has no `seq`. */
|
|
14
|
+
export const KEEPALIVE_PING = "{\"type\":\"ping\"}";
|
|
15
|
+
export const KEEPALIVE_PONG = "{\"type\":\"pong\"}";
|
|
13
16
|
/** The edge's close codes by name: `CLOSE.auth_timeout === 4001`. */
|
|
14
17
|
export const CLOSE = {
|
|
15
18
|
auth_timeout: 4001,
|
package/dist/socket.js
CHANGED
|
@@ -52,15 +52,13 @@ export function dial(options) {
|
|
|
52
52
|
options.onWarning({ type: "unreadable", what: "text" });
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
got: frame.seq,
|
|
61
|
-
});
|
|
55
|
+
// Every frame is numbered but `pong`, the edge's runtime's.
|
|
56
|
+
const { seq } = frame;
|
|
57
|
+
if (typeof seq === "number") {
|
|
58
|
+
if (lastSeq !== null && seq !== lastSeq + 1) {
|
|
59
|
+
options.onWarning({ type: "gap", expected: lastSeq + 1, got: seq });
|
|
62
60
|
}
|
|
63
|
-
lastSeq =
|
|
61
|
+
lastSeq = seq;
|
|
64
62
|
}
|
|
65
63
|
if (frame.type === "audio.start")
|
|
66
64
|
window = frame;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumavox/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Talk to Lumavox agents from a browser, a React Native app or Node, with an end user's ephemeral key: conversations on the agent's socket, sessions, memory and relations. Zero dependencies.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|