@pinecall/room 0.1.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.
Files changed (51) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +283 -0
  3. package/dist/index.d.ts +9 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +4 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/log/fold.d.ts +23 -0
  8. package/dist/log/fold.d.ts.map +1 -0
  9. package/dist/log/fold.js +40 -0
  10. package/dist/log/fold.js.map +1 -0
  11. package/dist/log/follow.d.ts +24 -0
  12. package/dist/log/follow.d.ts.map +1 -0
  13. package/dist/log/follow.js +96 -0
  14. package/dist/log/follow.js.map +1 -0
  15. package/dist/log/sse.d.ts +13 -0
  16. package/dist/log/sse.d.ts.map +1 -0
  17. package/dist/log/sse.js +50 -0
  18. package/dist/log/sse.js.map +1 -0
  19. package/dist/react/index.d.ts +13 -0
  20. package/dist/react/index.d.ts.map +1 -0
  21. package/dist/react/index.js +65 -0
  22. package/dist/react/index.js.map +1 -0
  23. package/dist/room.d.ts +54 -0
  24. package/dist/room.d.ts.map +1 -0
  25. package/dist/room.js +283 -0
  26. package/dist/room.js.map +1 -0
  27. package/dist/rows.d.ts +37 -0
  28. package/dist/rows.d.ts.map +1 -0
  29. package/dist/rows.js +89 -0
  30. package/dist/rows.js.map +1 -0
  31. package/dist/seat/join.d.ts +35 -0
  32. package/dist/seat/join.d.ts.map +1 -0
  33. package/dist/seat/join.js +64 -0
  34. package/dist/seat/join.js.map +1 -0
  35. package/dist/seat/livekit.d.ts +51 -0
  36. package/dist/seat/livekit.d.ts.map +1 -0
  37. package/dist/seat/livekit.js +8 -0
  38. package/dist/seat/livekit.js.map +1 -0
  39. package/dist/seat/sink.d.ts +11 -0
  40. package/dist/seat/sink.d.ts.map +1 -0
  41. package/dist/seat/sink.js +23 -0
  42. package/dist/seat/sink.js.map +1 -0
  43. package/dist/server/index.d.ts +48 -0
  44. package/dist/server/index.d.ts.map +1 -0
  45. package/dist/server/index.js +73 -0
  46. package/dist/server/index.js.map +1 -0
  47. package/dist/store.d.ts +20 -0
  48. package/dist/store.d.ts.map +1 -0
  49. package/dist/store.js +55 -0
  50. package/dist/store.js.map +1 -0
  51. package/package.json +66 -0
package/dist/rows.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ /** The call's log as one column of rows, in the order things happened: turns and tool runs interleaved. */
2
+ import type { State } from "@pinecall/protocol";
3
+ export type Row = {
4
+ kind: "turn";
5
+ key: string;
6
+ role: "user" | "agent";
7
+ text: string;
8
+ /** How long the reply took, end to end, when the wire measured it. */
9
+ ms?: number | undefined;
10
+ /** The model's time to its first token: what a written call has instead of `ms`. */
11
+ ttft?: number | undefined;
12
+ interrupted?: boolean | undefined;
13
+ } | {
14
+ kind: "tool";
15
+ key: string;
16
+ name: string;
17
+ args: string;
18
+ output?: string | undefined;
19
+ error?: string | undefined;
20
+ status: string;
21
+ /** The arguments and the output as they are, for a page that draws more than one line. */
22
+ raw: {
23
+ arguments: Record<string, unknown>;
24
+ output: unknown;
25
+ };
26
+ };
27
+ /** A value as one line of JSON, cut where a reader stops caring. */
28
+ export declare function brief(value: unknown, max?: number): string;
29
+ /** Every turn and every tool run, interleaved the way they happened. */
30
+ export declare function rowsOf(state: State): Row[];
31
+ /**
32
+ * The agent's own declared fields, in the order the class writes them, minus the empty ones. A
33
+ * field that is an object is opened one level — `person.name`, `person.email` — because a page
34
+ * shows a name, not a JSON blob; anything deeper is written briefly as it is.
35
+ */
36
+ export declare function knownBy(state: State): [string, string][];
37
+ //# sourceMappingURL=rows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rows.d.ts","sourceRoot":"","sources":["../src/rows.ts"],"names":[],"mappings":"AAAA,2GAA2G;AAS3G,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAEhD,MAAM,MAAM,GAAG,GACX;IACE,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,oFAAoF;IACpF,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACnC,GACD;IACE,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,GAAG,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;CAC9D,CAAC;AAEN,oEAAoE;AACpE,wBAAgB,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,SAAM,GAAG,MAAM,CAKvD;AAED,wEAAwE;AACxE,wBAAgB,MAAM,CAAC,KAAK,EAAE,KAAK,GAAG,GAAG,EAAE,CAyC1C;AAOD;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAUxD"}
package/dist/rows.js ADDED
@@ -0,0 +1,89 @@
1
+ /** The call's log as one column of rows, in the order things happened: turns and tool runs interleaved. */
2
+ //
3
+ // The state the protocol folds keeps turns and tool runs in two lists, each in
4
+ // its own order, joined by the `speech_id` of the reply they belong to: a tool
5
+ // with speech id X ran while the agent was working on reply X, so it goes ABOVE
6
+ // the agent's turn and below the visitor's. Drawing the two lists one after the
7
+ // other instead — every word, then every tool — is a timeline that never
8
+ // happened, and the whole point of showing this is that it is the real one.
9
+ /** A value as one line of JSON, cut where a reader stops caring. */
10
+ export function brief(value, max = 160) {
11
+ if (value === undefined)
12
+ return "";
13
+ const written = typeof value === "string" ? value : JSON.stringify(value);
14
+ if (written === undefined)
15
+ return "";
16
+ return written.length > max ? `${written.slice(0, max - 1)}…` : written;
17
+ }
18
+ /** Every turn and every tool run, interleaved the way they happened. */
19
+ export function rowsOf(state) {
20
+ const rows = [];
21
+ const drawn = new Set();
22
+ const tool = (run) => ({
23
+ kind: "tool",
24
+ key: `tool-${run.call_id}`,
25
+ name: run.name,
26
+ args: brief(run.arguments),
27
+ output: run.status === "done" ? brief(run.output) : undefined,
28
+ error: run.error ?? undefined,
29
+ status: run.status,
30
+ raw: { arguments: run.arguments, output: run.output },
31
+ });
32
+ for (const [at, turn] of state.turns.entries()) {
33
+ if (turn.role === "agent") {
34
+ for (const run of state.tools) {
35
+ if (run.speech_id !== turn.speech_id || drawn.has(run.call_id))
36
+ continue;
37
+ drawn.add(run.call_id);
38
+ rows.push(tool(run));
39
+ }
40
+ }
41
+ rows.push({
42
+ kind: "turn",
43
+ key: `turn-${at}`,
44
+ role: turn.role,
45
+ text: turn.text,
46
+ ms: turn.role === "agent" ? msOf(turn.metrics?.e2e_latency) : undefined,
47
+ ttft: turn.role === "agent" ? msOf(turn.metrics?.llm_node_ttft) : undefined,
48
+ interrupted: turn.role === "agent" ? turn.interrupted : undefined,
49
+ });
50
+ }
51
+ // A tool that is still running belongs after everything said so far: the reply
52
+ // it was called for has not landed yet, which is exactly what it looks like.
53
+ for (const run of state.tools) {
54
+ if (drawn.has(run.call_id))
55
+ continue;
56
+ rows.push(tool(run));
57
+ }
58
+ return rows;
59
+ }
60
+ /** Seconds on the wire, milliseconds on the screen. */
61
+ function msOf(seconds) {
62
+ return typeof seconds === "number" ? Math.round(seconds * 1000) : undefined;
63
+ }
64
+ /**
65
+ * The agent's own declared fields, in the order the class writes them, minus the empty ones. A
66
+ * field that is an object is opened one level — `person.name`, `person.email` — because a page
67
+ * shows a name, not a JSON blob; anything deeper is written briefly as it is.
68
+ */
69
+ export function knownBy(state) {
70
+ const rows = [];
71
+ for (const [name, value] of Object.entries(state.app_state)) {
72
+ if (isRecord(value)) {
73
+ for (const [field, inner] of Object.entries(value)) {
74
+ if (!empty(inner))
75
+ rows.push([`${name}.${field}`, brief(inner, 60)]);
76
+ }
77
+ }
78
+ else if (!empty(value))
79
+ rows.push([name, brief(value, 60)]);
80
+ }
81
+ return rows;
82
+ }
83
+ function empty(value) {
84
+ return value === null || value === undefined || value === "";
85
+ }
86
+ function isRecord(value) {
87
+ return typeof value === "object" && value !== null && !Array.isArray(value);
88
+ }
89
+ //# sourceMappingURL=rows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rows.js","sourceRoot":"","sources":["../src/rows.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /** A seat in the call's room, talking or typing. The only file that names LiveKit's events. */
2
+ import type { LivekitModule } from "./livekit.js";
3
+ import type { Sink } from "./sink.js";
4
+ /** Who is making a sound right now, and how loud the agent is (0 to 1). */
5
+ export interface Speaking {
6
+ agent: boolean;
7
+ user: boolean;
8
+ level: number;
9
+ }
10
+ /** What the seat tells the room about itself after it is taken. */
11
+ export interface SeatHears {
12
+ /** The room closed from the other side: the agent hung up, or the network went. */
13
+ disconnected: () => void;
14
+ speaking: (speaking: Speaking) => void;
15
+ /** True when the browser has not been told it may make a sound. */
16
+ wantsSound: (wanted: boolean) => void;
17
+ }
18
+ export interface SeatOptions {
19
+ livekit: LivekitModule;
20
+ server_url: string;
21
+ participant_token: string;
22
+ mode: "talk" | "chat";
23
+ sink: Sink;
24
+ on: SeatHears;
25
+ }
26
+ /** A taken seat. Leaving is final; `on.disconnected` is not called for a seat that left. */
27
+ export interface Seat {
28
+ send(text: string): Promise<void>;
29
+ leave(): Promise<void>;
30
+ playSound(): void;
31
+ canPlaybackAudio(): boolean;
32
+ }
33
+ /** Join the room. Rejects with the room's own error when it cannot connect or hear the microphone. */
34
+ export declare function joinSeat(options: SeatOptions): Promise<Seat>;
35
+ //# sourceMappingURL=join.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"join.d.ts","sourceRoot":"","sources":["../../src/seat/join.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAE/F,OAAO,KAAK,EAAE,aAAa,EAAmB,MAAM,cAAc,CAAC;AACnE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC,2EAA2E;AAC3E,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,mEAAmE;AACnE,MAAM,WAAW,SAAS;IACxB,mFAAmF;IACnF,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,QAAQ,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;IACvC,mEAAmE;IACnE,UAAU,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,aAAa,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,IAAI,EAAE,IAAI,CAAC;IACX,EAAE,EAAE,SAAS,CAAC;CACf;AAED,4FAA4F;AAC5F,MAAM,WAAW,IAAI;IACnB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,SAAS,IAAI,IAAI,CAAC;IAClB,gBAAgB,IAAI,OAAO,CAAC;CAC7B;AAKD,sGAAsG;AACtG,wBAAsB,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAqDlE"}
@@ -0,0 +1,64 @@
1
+ /** A seat in the call's room, talking or typing. The only file that names LiveKit's events. */
2
+ /** The topic a typed line rides on. LiveKit's own, and the one the agent listens to. */
3
+ const CHAT = "lk.chat";
4
+ /** Join the room. Rejects with the room's own error when it cannot connect or hear the microphone. */
5
+ export async function joinSeat(options) {
6
+ const { livekit, mode, sink, on } = options;
7
+ const { RoomEvent } = livekit;
8
+ const room = new livekit.Room();
9
+ let left = false;
10
+ const leave = () => {
11
+ left = true;
12
+ sink.close();
13
+ return room.disconnect();
14
+ };
15
+ // Chat never plays the agent: a written conversation that starts talking out loud is a bug.
16
+ room.on(RoomEvent.TrackSubscribed, (track) => {
17
+ if (mode === "talk" && track.kind === livekit.Track.Kind.Audio)
18
+ sink.add(track.attach());
19
+ });
20
+ room.on(RoomEvent.TrackUnsubscribed, (track) => {
21
+ for (const element of track.detach())
22
+ sink.remove(element);
23
+ });
24
+ room.on(RoomEvent.Disconnected, () => {
25
+ if (left)
26
+ return;
27
+ left = true;
28
+ sink.close();
29
+ on.disconnected();
30
+ });
31
+ room.on(RoomEvent.AudioPlaybackStatusChanged, () => on.wantsSound(!room.canPlaybackAudio));
32
+ room.on(RoomEvent.ActiveSpeakersChanged, (speakers) => on.speaking(speakingOf(speakers, room.localParticipant.identity)));
33
+ try {
34
+ await room.connect(options.server_url, options.participant_token);
35
+ if (mode === "talk")
36
+ await room.localParticipant.setMicrophoneEnabled(true);
37
+ }
38
+ catch (refused) {
39
+ // The refusal is the news. A room that also fails to close has nothing to add to it.
40
+ await leave().catch(() => undefined);
41
+ throw refused;
42
+ }
43
+ return {
44
+ async send(text) {
45
+ await room.localParticipant.sendText(text, { topic: CHAT });
46
+ },
47
+ leave,
48
+ playSound() {
49
+ // A browser that still says no leaves the page asking, rather than silently mute.
50
+ room.startAudio().then(() => on.wantsSound(!room.canPlaybackAudio), () => on.wantsSound(!room.canPlaybackAudio));
51
+ },
52
+ canPlaybackAudio: () => room.canPlaybackAudio,
53
+ };
54
+ }
55
+ /** The agent is whoever speaks that is not this page; its level is how loud. */
56
+ function speakingOf(speakers, me) {
57
+ const remote = speakers.find((speaker) => speaker.identity !== me);
58
+ return {
59
+ agent: remote !== undefined,
60
+ user: speakers.some((speaker) => speaker.identity === me),
61
+ level: remote?.audioLevel ?? 0,
62
+ };
63
+ }
64
+ //# sourceMappingURL=join.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"join.js","sourceRoot":"","sources":["../../src/seat/join.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /** The one door to livekit-client: loaded when a seat is taken, and the little of it the seat uses. */
2
+ /** A remote track as the seat handles it: its kind, and the element that plays it. */
3
+ export interface SeatTrack {
4
+ readonly kind: string;
5
+ attach(): HTMLMediaElement;
6
+ detach(): HTMLMediaElement[];
7
+ }
8
+ /** Somebody in the room: who, and how loud right now (0 to 1). */
9
+ export interface SeatParticipant {
10
+ readonly identity: string;
11
+ readonly audioLevel: number;
12
+ }
13
+ /** The page's own participant: its microphone, and the typed lines it sends. */
14
+ export interface SeatLocal extends SeatParticipant {
15
+ setMicrophoneEnabled(enabled: boolean): Promise<unknown>;
16
+ sendText(text: string, options: {
17
+ topic: string;
18
+ }): Promise<unknown>;
19
+ }
20
+ /** The room the page joins. Five events are listened to; nothing else of LiveKit's is named. */
21
+ export interface SeatRoom {
22
+ readonly localParticipant: SeatLocal;
23
+ readonly canPlaybackAudio: boolean;
24
+ connect(url: string, token: string): Promise<void>;
25
+ disconnect(): Promise<void>;
26
+ startAudio(): Promise<void>;
27
+ on(event: "trackSubscribed", listener: (track: SeatTrack) => void): this;
28
+ on(event: "trackUnsubscribed", listener: (track: SeatTrack) => void): this;
29
+ on(event: "disconnected", listener: () => void): this;
30
+ on(event: "audioPlaybackChanged", listener: (playing: boolean) => void): this;
31
+ on(event: "activeSpeakersChanged", listener: (speakers: SeatParticipant[]) => void): this;
32
+ }
33
+ /** What `import("livekit-client")` has to offer the seat. A test hands in a fake of this shape. */
34
+ export interface LivekitModule {
35
+ Room: new () => SeatRoom;
36
+ RoomEvent: {
37
+ readonly TrackSubscribed: "trackSubscribed";
38
+ readonly TrackUnsubscribed: "trackUnsubscribed";
39
+ readonly Disconnected: "disconnected";
40
+ readonly AudioPlaybackStatusChanged: "audioPlaybackChanged";
41
+ readonly ActiveSpeakersChanged: "activeSpeakersChanged";
42
+ };
43
+ Track: {
44
+ Kind: {
45
+ readonly Audio: "audio";
46
+ };
47
+ };
48
+ }
49
+ /** livekit-client, the moment a seat is needed. */
50
+ export declare function loadLivekit(): Promise<LivekitModule>;
51
+ //# sourceMappingURL=livekit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"livekit.d.ts","sourceRoot":"","sources":["../../src/seat/livekit.ts"],"names":[],"mappings":"AAAA,uGAAuG;AAEvG,sFAAsF;AACtF,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,MAAM,IAAI,gBAAgB,CAAC;IAC3B,MAAM,IAAI,gBAAgB,EAAE,CAAC;CAC9B;AAED,kEAAkE;AAClE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,gFAAgF;AAChF,MAAM,WAAW,SAAU,SAAQ,eAAe;IAChD,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtE;AAED,gGAAgG;AAChG,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,gBAAgB,EAAE,SAAS,CAAC;IACrC,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,GAAG,IAAI,CAAC;IACzE,EAAE,CAAC,KAAK,EAAE,mBAAmB,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,GAAG,IAAI,CAAC;IAC3E,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IACtD,EAAE,CAAC,KAAK,EAAE,sBAAsB,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI,CAAC;IAC9E,EAAE,CAAC,KAAK,EAAE,uBAAuB,EAAE,QAAQ,EAAE,CAAC,QAAQ,EAAE,eAAe,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;CAC3F;AAED,mGAAmG;AACnG,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,UAAU,QAAQ,CAAC;IACzB,SAAS,EAAE;QACT,QAAQ,CAAC,eAAe,EAAE,iBAAiB,CAAC;QAC5C,QAAQ,CAAC,iBAAiB,EAAE,mBAAmB,CAAC;QAChD,QAAQ,CAAC,YAAY,EAAE,cAAc,CAAC;QACtC,QAAQ,CAAC,0BAA0B,EAAE,sBAAsB,CAAC;QAC5D,QAAQ,CAAC,qBAAqB,EAAE,uBAAuB,CAAC;KACzD,CAAC;IACF,KAAK,EAAE;QAAE,IAAI,EAAE;YAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;SAAE,CAAA;KAAE,CAAC;CAC9C;AAID,mDAAmD;AACnD,wBAAgB,WAAW,IAAI,OAAO,CAAC,aAAa,CAAC,CAEpD"}
@@ -0,0 +1,8 @@
1
+ /** The one door to livekit-client: loaded when a seat is taken, and the little of it the seat uses. */
2
+ // Loaded, not imported: a page that only follows a phone call never downloads a WebRTC stack, and
3
+ // a server that imports this package for `mint` never evaluates one.
4
+ /** livekit-client, the moment a seat is needed. */
5
+ export function loadLivekit() {
6
+ return import("livekit-client");
7
+ }
8
+ //# sourceMappingURL=livekit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"livekit.js","sourceRoot":"","sources":["../../src/seat/livekit.ts"],"names":[],"mappings":"AAAA,uGAAuG;AAgDvG,kGAAkG;AAClG,qEAAqE;AACrE,mDAAmD;AACnD,MAAM,UAAU,WAAW;IACzB,OAAO,MAAM,CAAC,gBAAgB,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,11 @@
1
+ /** Where the agent's voice plays: a hidden element in the page, made the first time it is needed. */
2
+ /** What the seat hands a voice to. A test hands in one that records instead of playing. */
3
+ export interface Sink {
4
+ add(element: HTMLMediaElement): void;
5
+ remove(element: HTMLMediaElement): void;
6
+ /** Every element out of the page. */
7
+ close(): void;
8
+ }
9
+ /** A hidden box at the end of the body. Nothing touches the document until a voice arrives. */
10
+ export declare function hiddenSink(): Sink;
11
+ //# sourceMappingURL=sink.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sink.d.ts","sourceRoot":"","sources":["../../src/seat/sink.ts"],"names":[],"mappings":"AAAA,qGAAqG;AAErG,2FAA2F;AAC3F,MAAM,WAAW,IAAI;IACnB,GAAG,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACrC,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACxC,qCAAqC;IACrC,KAAK,IAAI,IAAI,CAAC;CACf;AAED,+FAA+F;AAC/F,wBAAgB,UAAU,IAAI,IAAI,CAmBjC"}
@@ -0,0 +1,23 @@
1
+ /** Where the agent's voice plays: a hidden element in the page, made the first time it is needed. */
2
+ /** A hidden box at the end of the body. Nothing touches the document until a voice arrives. */
3
+ export function hiddenSink() {
4
+ let box = null;
5
+ return {
6
+ add(element) {
7
+ if (box === null) {
8
+ box = document.createElement("div");
9
+ box.style.display = "none";
10
+ document.body.append(box);
11
+ }
12
+ box.append(element);
13
+ },
14
+ remove(element) {
15
+ element.remove();
16
+ },
17
+ close() {
18
+ box?.remove();
19
+ box = null;
20
+ },
21
+ };
22
+ }
23
+ //# sourceMappingURL=sink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sink.js","sourceRoot":"","sources":["../../src/seat/sink.ts"],"names":[],"mappings":"AAAA,qGAAqG;AAUrG,+FAA+F;AAC/F,MAAM,UAAU,UAAU;IACxB,IAAI,GAAG,GAAuB,IAAI,CAAC;IACnC,OAAO;QACL,GAAG,CAAC,OAAO;YACT,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;gBACjB,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;gBACpC,GAAG,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;gBAC3B,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC5B,CAAC;YACD,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACtB,CAAC;QACD,MAAM,CAAC,OAAO;YACZ,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,CAAC;QACD,KAAK;YACH,GAAG,EAAE,MAAM,EAAE,CAAC;YACd,GAAG,GAAG,IAAI,CAAC;QACb,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,48 @@
1
+ /** @pinecall/room/server: what a tenant's server does with its key — a ticket for a seat, a call placed. */
2
+ /** What `POST /v1/tokens` answers: where the room is, the ticket into it, and the call's id. */
3
+ export interface Minted {
4
+ server_url: string;
5
+ participant_token: string;
6
+ call: string;
7
+ }
8
+ /** What `POST /v1/agents/{agent}/dial` answers: the call's id, before anything has rung. */
9
+ export interface Dialed {
10
+ call: string;
11
+ agent: string;
12
+ to: string;
13
+ from: string;
14
+ env: string;
15
+ }
16
+ export interface MintOptions {
17
+ /** The gateway, `https://box.pinecall.io` or your own. */
18
+ url: string;
19
+ agent: string;
20
+ scope: "talk" | "chat";
21
+ /** How long the ticket lives. The gateway's default is 60 s, and 600 s the most. */
22
+ ttl_s?: number | undefined;
23
+ /** Who is calling: memory files the call under it. */
24
+ contact?: string | undefined;
25
+ metadata?: Record<string, unknown> | undefined;
26
+ fetch?: typeof fetch | undefined;
27
+ }
28
+ export interface DialOptions {
29
+ url: string;
30
+ agent: string;
31
+ /** The number to call, E.164. */
32
+ to: string;
33
+ /** Which of the agent's own numbers the far end sees. Unsaid, the first one it answers at. */
34
+ from?: string | undefined;
35
+ fetch?: typeof fetch | undefined;
36
+ }
37
+ /** The gateway said no. `detail` is its own sentence when it sent one; it names the fix. */
38
+ export declare class GatewayRefused extends Error {
39
+ readonly name = "GatewayRefused";
40
+ readonly status: number;
41
+ readonly detail: string | null;
42
+ constructor(status: number, detail: string | null);
43
+ }
44
+ /** A ticket for one seat in one new call. The key needs the `talk` scope. */
45
+ export declare function mint(key: string, options: MintOptions): Promise<Minted>;
46
+ /** Have the agent call `to`. The key needs the `talk` scope; the number must have reached the org before. */
47
+ export declare function dial(key: string, options: DialOptions): Promise<Dialed>;
48
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,4GAA4G;AAM5G,gGAAgG;AAChG,MAAM,WAAW,MAAM;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,4FAA4F;AAC5F,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,WAAW;IAC1B,0DAA0D;IAC1D,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,oFAAoF;IACpF,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC/C,KAAK,CAAC,EAAE,OAAO,KAAK,GAAG,SAAS,CAAC;CAClC;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,8FAA8F;IAC9F,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,KAAK,GAAG,SAAS,CAAC;CAClC;AAED,4FAA4F;AAC5F,qBAAa,cAAe,SAAQ,KAAK;IACvC,SAAkB,IAAI,oBAAoB;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAE/B,YAAY,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,EAIhD;CACF;AAKD,6EAA6E;AAC7E,wBAAsB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAK7E;AAED,6GAA6G;AAC7G,wBAAsB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAM7E"}
@@ -0,0 +1,73 @@
1
+ /** @pinecall/room/server: what a tenant's server does with its key — a ticket for a seat, a call placed. */
2
+ //
3
+ // The key never reaches a page. The page asks the tenant's own server, the server asks the gateway
4
+ // with the key, and what comes back to the page is a ticket for one call. Nothing here is imported
5
+ // by the browser side, and nothing here needs more than `fetch`.
6
+ /** The gateway said no. `detail` is its own sentence when it sent one; it names the fix. */
7
+ export class GatewayRefused extends Error {
8
+ name = "GatewayRefused";
9
+ status;
10
+ detail;
11
+ constructor(status, detail) {
12
+ super(detail ?? `the gateway answered ${status}`);
13
+ this.status = status;
14
+ this.detail = detail;
15
+ }
16
+ }
17
+ // A gateway that has not answered in this long is not going to, and a visitor is waiting on it.
18
+ const TIMEOUT_MS = 10_000;
19
+ /** A ticket for one seat in one new call. The key needs the `talk` scope. */
20
+ export async function mint(key, options) {
21
+ const { url, agent, scope, ttl_s, contact, metadata } = options;
22
+ const body = await post(key, `${bare(url)}/v1/tokens`, { agent, scope, ttl_s, contact, metadata }, options.fetch);
23
+ if (!isMinted(body))
24
+ throw new Error("the gateway answered /v1/tokens with a shape this package does not know");
25
+ return body;
26
+ }
27
+ /** Have the agent call `to`. The key needs the `talk` scope; the number must have reached the org before. */
28
+ export async function dial(key, options) {
29
+ const { url, agent, to, from } = options;
30
+ const path = `${bare(url)}/v1/agents/${encodeURIComponent(agent)}/dial`;
31
+ const body = await post(key, path, { to, from }, options.fetch);
32
+ if (!isDialed(body))
33
+ throw new Error("the gateway answered the dial with a shape this package does not know");
34
+ return body;
35
+ }
36
+ async function post(key, url, body, fetcher = fetch) {
37
+ const answer = await fetcher(url, {
38
+ method: "POST",
39
+ headers: { authorization: `Bearer ${key}`, "content-type": "application/json" },
40
+ body: JSON.stringify(body),
41
+ signal: AbortSignal.timeout(TIMEOUT_MS),
42
+ });
43
+ const text = await answer.text();
44
+ if (!answer.ok)
45
+ throw new GatewayRefused(answer.status, detailOf(text));
46
+ return JSON.parse(text);
47
+ }
48
+ /** FastAPI's `{"detail": "…"}`, when the body is that. A 422's list of problems is not a sentence. */
49
+ function detailOf(text) {
50
+ let body;
51
+ try {
52
+ body = JSON.parse(text);
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ return typeof body === "object" && body !== null && "detail" in body && typeof body.detail === "string"
58
+ ? body.detail
59
+ : null;
60
+ }
61
+ function bare(url) {
62
+ return url.replace(/\/+$/, "");
63
+ }
64
+ function isMinted(body) {
65
+ return hasStrings(body, ["server_url", "participant_token", "call"]);
66
+ }
67
+ function isDialed(body) {
68
+ return hasStrings(body, ["call", "agent", "to", "from", "env"]);
69
+ }
70
+ function hasStrings(body, fields) {
71
+ return typeof body === "object" && body !== null && fields.every((field) => typeof Reflect.get(body, field) === "string");
72
+ }
73
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /** The primitive under every surface: a value, whoever watches it, and ten paints a second. */
2
+ /** A value that changes, and a way to hear it change. Every state it hands out is a new object. */
3
+ export interface Store<T> {
4
+ readonly state: T;
5
+ /** `fn` hears every new state. Returns the call that stops it hearing. */
6
+ subscribe(fn: (state: T) => void): () => void;
7
+ /** Nobody hears anything again, and nothing is left running. */
8
+ close(): void;
9
+ }
10
+ export declare const FRAME_MS = 100;
11
+ /** The writer's side of a store. */
12
+ export interface Cell<T> extends Store<T> {
13
+ /** Publish now: watchers hear it before this returns. A paint that was waiting rides along. */
14
+ publish(patch: Partial<T>): void;
15
+ /** Publish within FRAME_MS: asked for many times in one frame, watchers hear once, the latest. */
16
+ paint(latest: () => Partial<T>): void;
17
+ }
18
+ /** A store holding `initial`. A patch that changes nothing is not a new state and is not heard. */
19
+ export declare function cell<T extends object>(initial: T): Cell<T>;
20
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAE/F,mGAAmG;AACnG,MAAM,WAAW,KAAK,CAAC,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAClB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAC9C,gEAAgE;IAChE,KAAK,IAAI,IAAI,CAAC;CACf;AAID,eAAO,MAAM,QAAQ,MAAM,CAAC;AAE5B,oCAAoC;AACpC,MAAM,WAAW,IAAI,CAAC,CAAC,CAAE,SAAQ,KAAK,CAAC,CAAC,CAAC;IACvC,+FAA+F;IAC/F,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACjC,kGAAkG;IAClG,KAAK,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;CACvC;AAED,mGAAmG;AACnG,wBAAgB,IAAI,CAAC,CAAC,SAAS,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CA2C1D"}
package/dist/store.js ADDED
@@ -0,0 +1,55 @@
1
+ /** The primitive under every surface: a value, whoever watches it, and ten paints a second. */
2
+ // Ten paints a second. Nobody reads faster than that, and a burst of interim transcripts inside
3
+ // one turn would repaint the whole window dozens of times while somebody is trying to read it.
4
+ export const FRAME_MS = 100;
5
+ /** A store holding `initial`. A patch that changes nothing is not a new state and is not heard. */
6
+ export function cell(initial) {
7
+ let state = initial;
8
+ let waiting = null;
9
+ let frame = null;
10
+ let closed = false;
11
+ const watchers = new Set();
12
+ const publish = (patch) => {
13
+ if (closed)
14
+ return;
15
+ const merged = { ...(waiting === null ? {} : waiting()), ...patch };
16
+ waiting = null;
17
+ if (frame !== null)
18
+ clearTimeout(frame);
19
+ frame = null;
20
+ if (Object.entries(merged).every(([key, value]) => Object.is(Reflect.get(state, key), value)))
21
+ return;
22
+ state = { ...state, ...merged };
23
+ for (const watcher of [...watchers])
24
+ watcher(state);
25
+ };
26
+ return {
27
+ get state() {
28
+ return state;
29
+ },
30
+ subscribe(fn) {
31
+ if (closed)
32
+ return () => { };
33
+ watchers.add(fn);
34
+ return () => {
35
+ watchers.delete(fn);
36
+ };
37
+ },
38
+ close() {
39
+ closed = true;
40
+ waiting = null;
41
+ if (frame !== null)
42
+ clearTimeout(frame);
43
+ frame = null;
44
+ watchers.clear();
45
+ },
46
+ publish,
47
+ paint(latest) {
48
+ if (closed)
49
+ return;
50
+ waiting = latest;
51
+ frame ??= setTimeout(() => publish({}), FRAME_MS);
52
+ },
53
+ };
54
+ }
55
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAW/F,gGAAgG;AAChG,+FAA+F;AAC/F,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,CAAC;AAU5B,mGAAmG;AACnG,MAAM,UAAU,IAAI,CAAmB,OAAU;IAC/C,IAAI,KAAK,GAAG,OAAO,CAAC;IACpB,IAAI,OAAO,GAA8B,IAAI,CAAC;IAC9C,IAAI,KAAK,GAAyC,IAAI,CAAC;IACvD,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAsB,CAAC;IAE/C,MAAM,OAAO,GAAG,CAAC,KAAiB,EAAQ,EAAE;QAC1C,IAAI,MAAM;YAAE,OAAO;QACnB,MAAM,MAAM,GAAG,EAAE,GAAG,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC;QACpE,OAAO,GAAG,IAAI,CAAC;QACf,IAAI,KAAK,KAAK,IAAI;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QACxC,KAAK,GAAG,IAAI,CAAC;QACb,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;YAAE,OAAO;QACtG,KAAK,GAAG,EAAE,GAAG,KAAK,EAAE,GAAG,MAAM,EAAE,CAAC;QAChC,KAAK,MAAM,OAAO,IAAI,CAAC,GAAG,QAAQ,CAAC;YAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACtD,CAAC,CAAC;IAEF,OAAO;QACL,IAAI,KAAK;YACP,OAAO,KAAK,CAAC;QACf,CAAC;QACD,SAAS,CAAC,EAAE;YACV,IAAI,MAAM;gBAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;YAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACjB,OAAO,GAAG,EAAE;gBACV,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACtB,CAAC,CAAC;QACJ,CAAC;QACD,KAAK;YACH,MAAM,GAAG,IAAI,CAAC;YACd,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,KAAK,KAAK,IAAI;gBAAE,YAAY,CAAC,KAAK,CAAC,CAAC;YACxC,KAAK,GAAG,IAAI,CAAC;YACb,QAAQ,CAAC,KAAK,EAAE,CAAC;QACnB,CAAC;QACD,OAAO;QACP,KAAK,CAAC,MAAM;YACV,IAAI,MAAM;gBAAE,OAAO;YACnB,OAAO,GAAG,MAAM,CAAC;YACjB,KAAK,KAAK,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;QACpD,CAAC;KACF,CAAC;AACJ,CAAC"}