@oneinbox/web-sdk 0.1.0-beta.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OneInbox
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # @oneinbox/web-sdk
2
+
3
+ Browser SDK for talking to a [OneInbox](https://oneinbox.ai) voice agent over WebRTC. Vapi-style: create a client with a **publishable key**, call `start(agentId)`, and the call just works — the mic publishes, the agent answers, transcripts stream as typed events.
4
+
5
+ Using React? See [`@oneinbox/web-sdk-react`](https://www.npmjs.com/package/@oneinbox/web-sdk-react).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @oneinbox/web-sdk livekit-client
11
+ ```
12
+
13
+ `livekit-client` is a peer dependency.
14
+
15
+ ## Auth
16
+
17
+ The SDK runs fully client-side with a **publishable key** (`oi_pk_…`) — safe to embed in browser JS. Keys are locked to the origins you register (requests from other domains get `403 ORIGIN_NOT_ALLOWED`), rate-limited, and scoped to web calls only. Never put a secret key (`oi_sk_…`) in front-end code.
18
+
19
+ ## Quick start
20
+
21
+ ```ts
22
+ import { OneInbox } from "@oneinbox/web-sdk";
23
+
24
+ const oi = new OneInbox("oi_pk_live_…"); // optionally { baseUrl: "https://api.oneinbox.ai" }
25
+
26
+ oi.on("transcript", (t) => console.log(`[${t.role}]`, t.text, t.final ? "(final)" : "…"));
27
+ oi.on("call-start", () => console.log("connected"));
28
+ oi.on("call-end", (reason) => console.log("ended", reason));
29
+ oi.on("error", (e) => console.error(e.code, e.message));
30
+
31
+ await oi.start("agt_…", { variables: { customer_name: "Suman" } });
32
+
33
+ oi.setMuted(true); // mute mic
34
+ oi.setMuted(false);
35
+ await oi.stop(); // hang up; the backend marks the call completed
36
+ ```
37
+
38
+ ### Bring-your-own-token
39
+
40
+ Minting the token on your own backend (e.g. with a secret key via `POST /v1/calls/web`)? Pass it straight in:
41
+
42
+ ```ts
43
+ await oi.start({ token, serverUrl }); // from your backend
44
+ ```
45
+
46
+ ## API
47
+
48
+ | Member | Description |
49
+ | --- | --- |
50
+ | `new OneInbox(publishableKey, { baseUrl? })` | Create a client. |
51
+ | `start(agentId, { variables?, metadata?, autoPublishMicrophone? })` | Fetch a token and connect. |
52
+ | `start({ token, serverUrl })` | Connect with a token you minted server-side. |
53
+ | `stop()` | End the call. |
54
+ | `setMuted(muted)` / `isMuted()` | Toggle / read mic state. |
55
+ | `status` | `"idle" \| "connecting" \| "active" \| "ending"`. |
56
+ | `underlyingRoom` | Escape hatch to the raw `livekit-client` `Room`. |
57
+ | `on(event, cb)` / `once` / `off` | Typed event subscription; `on` returns an unsubscribe fn. |
58
+
59
+ ### Events
60
+
61
+ `call-start`, `call-end` (`reason?`), `status` (`CallStatus`), `connection-state` (LiveKit `ConnectionState`), `transcript` (`{ role, text, final, language? }`), `speech-start` / `speech-end` (`"user" | "agent"`), `volume-level` (`number`, 0..1), `error` (`{ code, message, cause? }`).
62
+
63
+ Error codes: `TOKEN_FETCH_FAILED`, `ORIGIN_NOT_ALLOWED`, `INVALID_PUBLISHABLE_KEY`, `CONNECT_FAILED`, `MIC_PUBLISH_FAILED`, `MIC_TOGGLE_FAILED`, `DISCONNECTED` (plus server `error_code`s passed through from the token endpoint).
64
+
65
+ ## License
66
+
67
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,263 @@
1
+ 'use strict';
2
+
3
+ var livekitClient = require('livekit-client');
4
+
5
+ // src/index.ts
6
+
7
+ // src/emitter.ts
8
+ var TypedEmitter = class {
9
+ listeners = {};
10
+ /** Subscribe to `event`. Returns an unsubscribe function. */
11
+ on(event, fn) {
12
+ (this.listeners[event] ??= /* @__PURE__ */ new Set()).add(fn);
13
+ return () => this.off(event, fn);
14
+ }
15
+ /** Subscribe once; auto-unsubscribes after the first emission. */
16
+ once(event, fn) {
17
+ const wrapper = ((...args) => {
18
+ this.off(event, wrapper);
19
+ fn(...args);
20
+ });
21
+ return this.on(event, wrapper);
22
+ }
23
+ /** Remove a specific listener. */
24
+ off(event, fn) {
25
+ this.listeners[event]?.delete(fn);
26
+ }
27
+ /** Remove all listeners (all events, or just `event`). */
28
+ removeAllListeners(event) {
29
+ if (event) this.listeners[event]?.clear();
30
+ else Object.keys(this.listeners).forEach((k) => this.listeners[k]?.clear());
31
+ }
32
+ emit(event, ...args) {
33
+ const set = this.listeners[event];
34
+ if (!set) return;
35
+ for (const fn of [...set]) fn(...args);
36
+ }
37
+ };
38
+
39
+ // src/index.ts
40
+ var VOLUME_SAMPLE_MS = 100;
41
+ var OneInbox = class extends TypedEmitter {
42
+ constructor(publishableKey, config = {}) {
43
+ super();
44
+ this.publishableKey = publishableKey;
45
+ this.config = config;
46
+ }
47
+ publishableKey;
48
+ config;
49
+ room = null;
50
+ _status = "idle";
51
+ _muted = false;
52
+ volumeTimer = null;
53
+ speaking = /* @__PURE__ */ new Set();
54
+ /** Current high-level call status. */
55
+ get status() {
56
+ return this._status;
57
+ }
58
+ /** Whether the local microphone is currently muted. */
59
+ isMuted() {
60
+ return this._muted;
61
+ }
62
+ /** Underlying LiveKit room (escape hatch for advanced use). */
63
+ get underlyingRoom() {
64
+ return this.room;
65
+ }
66
+ async start(arg, opts) {
67
+ if (this._status !== "idle") {
68
+ throw new Error("A call is already in progress \u2014 call stop() first.");
69
+ }
70
+ this.setStatus("connecting");
71
+ try {
72
+ let serverUrl;
73
+ let token;
74
+ let autoMic;
75
+ if (typeof arg === "string") {
76
+ const res = await this.fetchToken(arg, opts);
77
+ serverUrl = res.server_url;
78
+ token = res.participant_token;
79
+ autoMic = opts?.autoPublishMicrophone !== false;
80
+ } else {
81
+ serverUrl = arg.serverUrl;
82
+ token = arg.token;
83
+ autoMic = arg.autoPublishMicrophone !== false;
84
+ }
85
+ await this.connectRoom(serverUrl, token, autoMic);
86
+ } catch (err) {
87
+ this.setStatus("idle");
88
+ throw err;
89
+ }
90
+ }
91
+ /** End the call and release resources. The backend marks the call completed. */
92
+ async stop() {
93
+ if (!this.room) {
94
+ this.setStatus("idle");
95
+ return;
96
+ }
97
+ this.setStatus("ending");
98
+ await this.room.disconnect();
99
+ }
100
+ /** Mute/unmute the local microphone (fire-and-forget, like Vapi's setMuted). */
101
+ setMuted(muted) {
102
+ this._muted = muted;
103
+ const lp = this.room?.localParticipant;
104
+ if (!lp) return;
105
+ void lp.setMicrophoneEnabled(!muted).catch((err) => {
106
+ this.emit("error", {
107
+ code: "MIC_TOGGLE_FAILED",
108
+ message: err?.message ?? "Failed to toggle microphone",
109
+ cause: err
110
+ });
111
+ });
112
+ }
113
+ // ---- internals -------------------------------------------------------
114
+ async fetchToken(agentId, opts) {
115
+ const base = (this.config.baseUrl ?? "https://api.oneinbox.ai").replace(/\/+$/, "");
116
+ let res;
117
+ try {
118
+ res = await fetch(`${base}/v1/web-calls/token`, {
119
+ method: "POST",
120
+ headers: {
121
+ Authorization: `Bearer ${this.publishableKey}`,
122
+ "Content-Type": "application/json"
123
+ },
124
+ body: JSON.stringify({
125
+ agent_id: agentId,
126
+ variables: opts?.variables ?? {},
127
+ metadata: opts?.metadata ?? {}
128
+ })
129
+ });
130
+ } catch (err) {
131
+ throw this.fail("TOKEN_FETCH_FAILED", err?.message ?? "Network error", err);
132
+ }
133
+ if (!res.ok) {
134
+ let code = `HTTP_${res.status}`;
135
+ let detail = res.statusText;
136
+ try {
137
+ const body = await res.json();
138
+ code = body.error_code ?? code;
139
+ detail = body.detail ?? detail;
140
+ } catch {
141
+ }
142
+ throw this.fail(code, detail);
143
+ }
144
+ return res.json();
145
+ }
146
+ async connectRoom(serverUrl, token, autoMic) {
147
+ const room = new livekitClient.Room({ adaptiveStream: true, dynacast: true });
148
+ this.room = room;
149
+ this.bindRoomEvents(room);
150
+ try {
151
+ await room.connect(serverUrl, token);
152
+ } catch (err) {
153
+ this.room = null;
154
+ throw this.fail("CONNECT_FAILED", err?.message ?? "Connection failed", err);
155
+ }
156
+ this.setStatus("active");
157
+ this.emit("call-start");
158
+ this.startVolumeSampling();
159
+ if (autoMic) {
160
+ try {
161
+ await room.localParticipant.setMicrophoneEnabled(true);
162
+ this._muted = false;
163
+ } catch (err) {
164
+ this.emit("error", {
165
+ code: "MIC_PUBLISH_FAILED",
166
+ message: err?.message ?? "Failed to publish microphone",
167
+ cause: err
168
+ });
169
+ }
170
+ }
171
+ }
172
+ bindRoomEvents(room) {
173
+ room.on(livekitClient.RoomEvent.ConnectionStateChanged, (state) => {
174
+ this.emit("connection-state", state);
175
+ });
176
+ room.on(livekitClient.RoomEvent.TranscriptionReceived, (segments, participant) => {
177
+ const role = roleFor(participant);
178
+ for (const seg of segments) {
179
+ this.emit("transcript", {
180
+ role,
181
+ text: seg.text,
182
+ final: seg.final,
183
+ language: seg.language
184
+ });
185
+ }
186
+ });
187
+ room.on(livekitClient.RoomEvent.ActiveSpeakersChanged, (speakers) => {
188
+ const now = new Set(speakers.map(roleFor));
189
+ for (const role of now) {
190
+ if (!this.speaking.has(role)) this.emit("speech-start", role);
191
+ }
192
+ for (const role of this.speaking) {
193
+ if (!now.has(role)) this.emit("speech-end", role);
194
+ }
195
+ this.speaking = now;
196
+ });
197
+ room.on(livekitClient.RoomEvent.Disconnected, (reason) => {
198
+ const clientInitiated = reason === void 0 || reason === livekitClient.DisconnectReason.CLIENT_INITIATED;
199
+ if (!clientInitiated) {
200
+ this.emit("error", {
201
+ code: "DISCONNECTED",
202
+ message: `Room disconnected: ${livekitClient.DisconnectReason[reason] ?? reason}`
203
+ });
204
+ }
205
+ this.teardown(reason !== void 0 ? livekitClient.DisconnectReason[reason] : void 0);
206
+ });
207
+ }
208
+ startVolumeSampling() {
209
+ this.stopVolumeSampling();
210
+ this.volumeTimer = setInterval(() => {
211
+ const lp = this.room?.localParticipant;
212
+ if (lp) this.emit("volume-level", lp.audioLevel);
213
+ }, VOLUME_SAMPLE_MS);
214
+ }
215
+ stopVolumeSampling() {
216
+ if (this.volumeTimer !== null) {
217
+ clearInterval(this.volumeTimer);
218
+ this.volumeTimer = null;
219
+ }
220
+ }
221
+ teardown(reason) {
222
+ this.stopVolumeSampling();
223
+ this.speaking.clear();
224
+ this.room = null;
225
+ this.setStatus("idle");
226
+ this.emit("call-end", reason);
227
+ }
228
+ setStatus(status) {
229
+ if (this._status === status) return;
230
+ this._status = status;
231
+ this.emit("status", status);
232
+ }
233
+ fail(code, message, cause) {
234
+ const err = { code, message, cause };
235
+ this.emit("error", err);
236
+ return err;
237
+ }
238
+ };
239
+ function roleFor(participant) {
240
+ if (!participant) return "agent";
241
+ return participant.isLocal ? "user" : "agent";
242
+ }
243
+
244
+ Object.defineProperty(exports, "ConnectionState", {
245
+ enumerable: true,
246
+ get: function () { return livekitClient.ConnectionState; }
247
+ });
248
+ Object.defineProperty(exports, "DisconnectReason", {
249
+ enumerable: true,
250
+ get: function () { return livekitClient.DisconnectReason; }
251
+ });
252
+ Object.defineProperty(exports, "RoomEvent", {
253
+ enumerable: true,
254
+ get: function () { return livekitClient.RoomEvent; }
255
+ });
256
+ Object.defineProperty(exports, "Track", {
257
+ enumerable: true,
258
+ get: function () { return livekitClient.Track; }
259
+ });
260
+ exports.OneInbox = OneInbox;
261
+ exports.TypedEmitter = TypedEmitter;
262
+ //# sourceMappingURL=index.cjs.map
263
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/emitter.ts","../src/index.ts"],"names":["Room","RoomEvent","DisconnectReason"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Tiny strongly-typed event emitter. Zero runtime deps so the SDK stays\n * tree-shakeable and installs without pulling `events`/`eventemitter3`.\n *\n * `E` maps event name → listener signature, e.g.\n * { \"call-start\": () => void; \"volume-level\": (v: number) => void }\n */\nexport type EventMap = Record<string, (...args: never[]) => void>;\n\nexport class TypedEmitter<E extends EventMap> {\n private readonly listeners: { [K in keyof E]?: Set<E[K]> } = {};\n\n /** Subscribe to `event`. Returns an unsubscribe function. */\n on<K extends keyof E>(event: K, fn: E[K]): () => void {\n (this.listeners[event] ??= new Set<E[K]>()).add(fn);\n return () => this.off(event, fn);\n }\n\n /** Subscribe once; auto-unsubscribes after the first emission. */\n once<K extends keyof E>(event: K, fn: E[K]): () => void {\n const wrapper = ((...args: Parameters<E[K]>) => {\n this.off(event, wrapper);\n (fn as (...a: Parameters<E[K]>) => void)(...args);\n }) as E[K];\n return this.on(event, wrapper);\n }\n\n /** Remove a specific listener. */\n off<K extends keyof E>(event: K, fn: E[K]): void {\n this.listeners[event]?.delete(fn);\n }\n\n /** Remove all listeners (all events, or just `event`). */\n removeAllListeners<K extends keyof E>(event?: K): void {\n if (event) this.listeners[event]?.clear();\n else (Object.keys(this.listeners) as K[]).forEach((k) => this.listeners[k]?.clear());\n }\n\n protected emit<K extends keyof E>(event: K, ...args: Parameters<E[K]>): void {\n const set = this.listeners[event];\n if (!set) return;\n // Copy so a listener that unsubscribes mid-emit doesn't skip the next one.\n for (const fn of [...set]) (fn as (...a: Parameters<E[K]>) => void)(...args);\n }\n}\n","import {\n ConnectionState,\n DisconnectReason,\n type LocalParticipant,\n type Participant,\n type RemoteParticipant,\n Room,\n RoomEvent,\n Track,\n type TranscriptionSegment,\n} from \"livekit-client\";\n\nimport { TypedEmitter } from \"./emitter\";\n\nexport type TranscriptRole = \"user\" | \"agent\";\n\nexport interface TranscriptEvent {\n role: TranscriptRole;\n text: string;\n final: boolean;\n language?: string;\n}\n\nexport interface OneInboxError {\n /** Stable machine code, e.g. CONNECT_FAILED, ORIGIN_NOT_ALLOWED, MIC_PUBLISH_FAILED. */\n code: string;\n message: string;\n cause?: unknown;\n}\n\n/** High-level call lifecycle, mirrored to the `status` event. */\nexport type CallStatus = \"idle\" | \"connecting\" | \"active\" | \"ending\";\n\n/**\n * Typed event map. Listen with `oi.on(\"transcript\", cb)` — the callback type\n * is inferred per event (Vapi/LiveKit style).\n */\nexport type OneInboxEvents = {\n /** Connected to the room; the agent is joining. */\n \"call-start\": () => void;\n /** Call ended (locally or remotely). `reason` is the LiveKit disconnect reason when known. */\n \"call-end\": (reason?: string) => void;\n /** Underlying LiveKit connection state changed. */\n \"connection-state\": (state: ConnectionState) => void;\n /** High-level status changed. */\n status: (status: CallStatus) => void;\n /** Incremental transcript chunk for the caller or the agent (`final` marks end-of-utterance). */\n transcript: (ev: TranscriptEvent) => void;\n /** Someone started speaking (best-effort, from active-speaker detection). */\n \"speech-start\": (who: TranscriptRole) => void;\n /** Someone stopped speaking. */\n \"speech-end\": (who: TranscriptRole) => void;\n /** Local microphone level, 0..1, sampled ~10x/sec while active (for mic UIs). */\n \"volume-level\": (level: number) => void;\n /** Any error (connection, token fetch, mic publish, server disconnect). */\n error: (err: OneInboxError) => void;\n};\n\nexport interface OneInboxConfig {\n /** OneInbox API base URL. Default `https://api.oneinbox.ai`. */\n baseUrl?: string;\n}\n\nexport interface StartOptions {\n /** Per-call template variables interpolated into the agent prompt. */\n variables?: Record<string, unknown>;\n /** Free-form metadata stored on the call record. */\n metadata?: Record<string, unknown>;\n /** Auto-publish the user's microphone on connect. Default `true`. */\n autoPublishMicrophone?: boolean;\n}\n\n/** Bring-your-own-token escape hatch: connect with a token your backend minted. */\nexport interface TokenStartOptions {\n /** From `POST /v1/web-calls/token` (or `/v1/calls/web`) → `participant_token`. */\n token: string;\n /** → `server_url`. */\n serverUrl: string;\n autoPublishMicrophone?: boolean;\n}\n\nconst VOLUME_SAMPLE_MS = 100;\n\n/**\n * Browser client for OneInbox voice agents over WebRTC.\n *\n * ```ts\n * const oi = new OneInbox(\"oi_pk_…\");\n * oi.on(\"transcript\", (t) => console.log(t.role, t.text));\n * await oi.start(agentId); // mic publishes, the agent answers\n * // …\n * await oi.stop();\n * ```\n */\nexport class OneInbox extends TypedEmitter<OneInboxEvents> {\n private room: Room | null = null;\n private _status: CallStatus = \"idle\";\n private _muted = false;\n private volumeTimer: ReturnType<typeof setInterval> | null = null;\n private speaking = new Set<TranscriptRole>();\n\n constructor(\n private readonly publishableKey: string,\n private readonly config: OneInboxConfig = {},\n ) {\n super();\n }\n\n /** Current high-level call status. */\n get status(): CallStatus {\n return this._status;\n }\n\n /** Whether the local microphone is currently muted. */\n isMuted(): boolean {\n return this._muted;\n }\n\n /** Underlying LiveKit room (escape hatch for advanced use). */\n get underlyingRoom(): Room | null {\n return this.room;\n }\n\n /**\n * Start a call.\n *\n * - `start(agentId, opts?)` — publishable-key path: fetches a short-lived\n * token from the OneInbox API, then connects.\n * - `start({ token, serverUrl })` — connect with a token your backend minted.\n */\n async start(agentId: string, opts?: StartOptions): Promise<void>;\n async start(opts: TokenStartOptions): Promise<void>;\n async start(arg: string | TokenStartOptions, opts?: StartOptions): Promise<void> {\n if (this._status !== \"idle\") {\n throw new Error(\"A call is already in progress — call stop() first.\");\n }\n this.setStatus(\"connecting\");\n try {\n let serverUrl: string;\n let token: string;\n let autoMic: boolean;\n if (typeof arg === \"string\") {\n const res = await this.fetchToken(arg, opts);\n serverUrl = res.server_url;\n token = res.participant_token;\n autoMic = opts?.autoPublishMicrophone !== false;\n } else {\n serverUrl = arg.serverUrl;\n token = arg.token;\n autoMic = arg.autoPublishMicrophone !== false;\n }\n await this.connectRoom(serverUrl, token, autoMic);\n } catch (err) {\n this.setStatus(\"idle\");\n throw err;\n }\n }\n\n /** End the call and release resources. The backend marks the call completed. */\n async stop(): Promise<void> {\n if (!this.room) {\n this.setStatus(\"idle\");\n return;\n }\n this.setStatus(\"ending\");\n await this.room.disconnect();\n // teardown is finalized in the Disconnected handler\n }\n\n /** Mute/unmute the local microphone (fire-and-forget, like Vapi's setMuted). */\n setMuted(muted: boolean): void {\n this._muted = muted;\n const lp = this.room?.localParticipant;\n if (!lp) return;\n void lp.setMicrophoneEnabled(!muted).catch((err: unknown) => {\n this.emit(\"error\", {\n code: \"MIC_TOGGLE_FAILED\",\n message: (err as Error)?.message ?? \"Failed to toggle microphone\",\n cause: err,\n });\n });\n }\n\n // ---- internals -------------------------------------------------------\n\n private async fetchToken(\n agentId: string,\n opts?: StartOptions,\n ): Promise<{ server_url: string; participant_token: string }> {\n const base = (this.config.baseUrl ?? \"https://api.oneinbox.ai\").replace(/\\/+$/, \"\");\n let res: Response;\n try {\n res = await fetch(`${base}/v1/web-calls/token`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.publishableKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n agent_id: agentId,\n variables: opts?.variables ?? {},\n metadata: opts?.metadata ?? {},\n }),\n });\n } catch (err) {\n throw this.fail(\"TOKEN_FETCH_FAILED\", (err as Error)?.message ?? \"Network error\", err);\n }\n if (!res.ok) {\n let code = `HTTP_${res.status}`;\n let detail = res.statusText;\n try {\n const body = (await res.json()) as { error_code?: string; detail?: string };\n code = body.error_code ?? code;\n detail = body.detail ?? detail;\n } catch {\n /* non-JSON error body */\n }\n throw this.fail(code, detail);\n }\n return res.json() as Promise<{ server_url: string; participant_token: string }>;\n }\n\n private async connectRoom(serverUrl: string, token: string, autoMic: boolean): Promise<void> {\n const room = new Room({ adaptiveStream: true, dynacast: true });\n this.room = room;\n this.bindRoomEvents(room);\n\n try {\n await room.connect(serverUrl, token);\n } catch (err) {\n this.room = null;\n throw this.fail(\"CONNECT_FAILED\", (err as Error)?.message ?? \"Connection failed\", err);\n }\n\n this.setStatus(\"active\");\n this.emit(\"call-start\");\n this.startVolumeSampling();\n\n if (autoMic) {\n try {\n await room.localParticipant.setMicrophoneEnabled(true);\n this._muted = false;\n } catch (err) {\n this.emit(\"error\", {\n code: \"MIC_PUBLISH_FAILED\",\n message: (err as Error)?.message ?? \"Failed to publish microphone\",\n cause: err,\n });\n }\n }\n }\n\n private bindRoomEvents(room: Room): void {\n room.on(RoomEvent.ConnectionStateChanged, (state) => {\n this.emit(\"connection-state\", state);\n });\n\n room.on(RoomEvent.TranscriptionReceived, (segments, participant) => {\n const role = roleFor(participant);\n for (const seg of segments) {\n this.emit(\"transcript\", {\n role,\n text: seg.text,\n final: seg.final,\n language: seg.language,\n });\n }\n });\n\n room.on(RoomEvent.ActiveSpeakersChanged, (speakers) => {\n const now = new Set<TranscriptRole>(speakers.map(roleFor));\n for (const role of now) {\n if (!this.speaking.has(role)) this.emit(\"speech-start\", role);\n }\n for (const role of this.speaking) {\n if (!now.has(role)) this.emit(\"speech-end\", role);\n }\n this.speaking = now;\n });\n\n room.on(RoomEvent.Disconnected, (reason?: DisconnectReason) => {\n const clientInitiated =\n reason === undefined || reason === DisconnectReason.CLIENT_INITIATED;\n if (!clientInitiated) {\n this.emit(\"error\", {\n code: \"DISCONNECTED\",\n message: `Room disconnected: ${DisconnectReason[reason] ?? reason}`,\n });\n }\n this.teardown(reason !== undefined ? DisconnectReason[reason] : undefined);\n });\n }\n\n private startVolumeSampling(): void {\n this.stopVolumeSampling();\n this.volumeTimer = setInterval(() => {\n const lp = this.room?.localParticipant;\n if (lp) this.emit(\"volume-level\", lp.audioLevel);\n }, VOLUME_SAMPLE_MS);\n }\n\n private stopVolumeSampling(): void {\n if (this.volumeTimer !== null) {\n clearInterval(this.volumeTimer);\n this.volumeTimer = null;\n }\n }\n\n private teardown(reason?: string): void {\n this.stopVolumeSampling();\n this.speaking.clear();\n this.room = null;\n this.setStatus(\"idle\");\n this.emit(\"call-end\", reason);\n }\n\n private setStatus(status: CallStatus): void {\n if (this._status === status) return;\n this._status = status;\n this.emit(\"status\", status);\n }\n\n private fail(code: string, message: string, cause?: unknown): OneInboxError {\n const err: OneInboxError = { code, message, cause };\n this.emit(\"error\", err);\n return err;\n }\n}\n\nfunction roleFor(participant: Participant | undefined): TranscriptRole {\n // The human is the local participant; the agent joins as a remote participant.\n if (!participant) return \"agent\";\n return participant.isLocal ? \"user\" : \"agent\";\n}\n\n// Re-export upstream types callers commonly need so they don't have to add a\n// direct livekit-client import just for types.\nexport { ConnectionState, DisconnectReason, RoomEvent, Track };\nexport type { LocalParticipant, Participant, RemoteParticipant, Room, TranscriptionSegment };\nexport { TypedEmitter } from \"./emitter\";\n"]}
@@ -0,0 +1,133 @@
1
+ import { ConnectionState, Room } from 'livekit-client';
2
+ export { ConnectionState, DisconnectReason, LocalParticipant, Participant, RemoteParticipant, Room, RoomEvent, Track, TranscriptionSegment } from 'livekit-client';
3
+
4
+ /**
5
+ * Tiny strongly-typed event emitter. Zero runtime deps so the SDK stays
6
+ * tree-shakeable and installs without pulling `events`/`eventemitter3`.
7
+ *
8
+ * `E` maps event name → listener signature, e.g.
9
+ * { "call-start": () => void; "volume-level": (v: number) => void }
10
+ */
11
+ type EventMap = Record<string, (...args: never[]) => void>;
12
+ declare class TypedEmitter<E extends EventMap> {
13
+ private readonly listeners;
14
+ /** Subscribe to `event`. Returns an unsubscribe function. */
15
+ on<K extends keyof E>(event: K, fn: E[K]): () => void;
16
+ /** Subscribe once; auto-unsubscribes after the first emission. */
17
+ once<K extends keyof E>(event: K, fn: E[K]): () => void;
18
+ /** Remove a specific listener. */
19
+ off<K extends keyof E>(event: K, fn: E[K]): void;
20
+ /** Remove all listeners (all events, or just `event`). */
21
+ removeAllListeners<K extends keyof E>(event?: K): void;
22
+ protected emit<K extends keyof E>(event: K, ...args: Parameters<E[K]>): void;
23
+ }
24
+
25
+ type TranscriptRole = "user" | "agent";
26
+ interface TranscriptEvent {
27
+ role: TranscriptRole;
28
+ text: string;
29
+ final: boolean;
30
+ language?: string;
31
+ }
32
+ interface OneInboxError {
33
+ /** Stable machine code, e.g. CONNECT_FAILED, ORIGIN_NOT_ALLOWED, MIC_PUBLISH_FAILED. */
34
+ code: string;
35
+ message: string;
36
+ cause?: unknown;
37
+ }
38
+ /** High-level call lifecycle, mirrored to the `status` event. */
39
+ type CallStatus = "idle" | "connecting" | "active" | "ending";
40
+ /**
41
+ * Typed event map. Listen with `oi.on("transcript", cb)` — the callback type
42
+ * is inferred per event (Vapi/LiveKit style).
43
+ */
44
+ type OneInboxEvents = {
45
+ /** Connected to the room; the agent is joining. */
46
+ "call-start": () => void;
47
+ /** Call ended (locally or remotely). `reason` is the LiveKit disconnect reason when known. */
48
+ "call-end": (reason?: string) => void;
49
+ /** Underlying LiveKit connection state changed. */
50
+ "connection-state": (state: ConnectionState) => void;
51
+ /** High-level status changed. */
52
+ status: (status: CallStatus) => void;
53
+ /** Incremental transcript chunk for the caller or the agent (`final` marks end-of-utterance). */
54
+ transcript: (ev: TranscriptEvent) => void;
55
+ /** Someone started speaking (best-effort, from active-speaker detection). */
56
+ "speech-start": (who: TranscriptRole) => void;
57
+ /** Someone stopped speaking. */
58
+ "speech-end": (who: TranscriptRole) => void;
59
+ /** Local microphone level, 0..1, sampled ~10x/sec while active (for mic UIs). */
60
+ "volume-level": (level: number) => void;
61
+ /** Any error (connection, token fetch, mic publish, server disconnect). */
62
+ error: (err: OneInboxError) => void;
63
+ };
64
+ interface OneInboxConfig {
65
+ /** OneInbox API base URL. Default `https://api.oneinbox.ai`. */
66
+ baseUrl?: string;
67
+ }
68
+ interface StartOptions {
69
+ /** Per-call template variables interpolated into the agent prompt. */
70
+ variables?: Record<string, unknown>;
71
+ /** Free-form metadata stored on the call record. */
72
+ metadata?: Record<string, unknown>;
73
+ /** Auto-publish the user's microphone on connect. Default `true`. */
74
+ autoPublishMicrophone?: boolean;
75
+ }
76
+ /** Bring-your-own-token escape hatch: connect with a token your backend minted. */
77
+ interface TokenStartOptions {
78
+ /** From `POST /v1/web-calls/token` (or `/v1/calls/web`) → `participant_token`. */
79
+ token: string;
80
+ /** → `server_url`. */
81
+ serverUrl: string;
82
+ autoPublishMicrophone?: boolean;
83
+ }
84
+ /**
85
+ * Browser client for OneInbox voice agents over WebRTC.
86
+ *
87
+ * ```ts
88
+ * const oi = new OneInbox("oi_pk_…");
89
+ * oi.on("transcript", (t) => console.log(t.role, t.text));
90
+ * await oi.start(agentId); // mic publishes, the agent answers
91
+ * // …
92
+ * await oi.stop();
93
+ * ```
94
+ */
95
+ declare class OneInbox extends TypedEmitter<OneInboxEvents> {
96
+ private readonly publishableKey;
97
+ private readonly config;
98
+ private room;
99
+ private _status;
100
+ private _muted;
101
+ private volumeTimer;
102
+ private speaking;
103
+ constructor(publishableKey: string, config?: OneInboxConfig);
104
+ /** Current high-level call status. */
105
+ get status(): CallStatus;
106
+ /** Whether the local microphone is currently muted. */
107
+ isMuted(): boolean;
108
+ /** Underlying LiveKit room (escape hatch for advanced use). */
109
+ get underlyingRoom(): Room | null;
110
+ /**
111
+ * Start a call.
112
+ *
113
+ * - `start(agentId, opts?)` — publishable-key path: fetches a short-lived
114
+ * token from the OneInbox API, then connects.
115
+ * - `start({ token, serverUrl })` — connect with a token your backend minted.
116
+ */
117
+ start(agentId: string, opts?: StartOptions): Promise<void>;
118
+ start(opts: TokenStartOptions): Promise<void>;
119
+ /** End the call and release resources. The backend marks the call completed. */
120
+ stop(): Promise<void>;
121
+ /** Mute/unmute the local microphone (fire-and-forget, like Vapi's setMuted). */
122
+ setMuted(muted: boolean): void;
123
+ private fetchToken;
124
+ private connectRoom;
125
+ private bindRoomEvents;
126
+ private startVolumeSampling;
127
+ private stopVolumeSampling;
128
+ private teardown;
129
+ private setStatus;
130
+ private fail;
131
+ }
132
+
133
+ export { type CallStatus, OneInbox, type OneInboxConfig, type OneInboxError, type OneInboxEvents, type StartOptions, type TokenStartOptions, type TranscriptEvent, type TranscriptRole, TypedEmitter };
@@ -0,0 +1,133 @@
1
+ import { ConnectionState, Room } from 'livekit-client';
2
+ export { ConnectionState, DisconnectReason, LocalParticipant, Participant, RemoteParticipant, Room, RoomEvent, Track, TranscriptionSegment } from 'livekit-client';
3
+
4
+ /**
5
+ * Tiny strongly-typed event emitter. Zero runtime deps so the SDK stays
6
+ * tree-shakeable and installs without pulling `events`/`eventemitter3`.
7
+ *
8
+ * `E` maps event name → listener signature, e.g.
9
+ * { "call-start": () => void; "volume-level": (v: number) => void }
10
+ */
11
+ type EventMap = Record<string, (...args: never[]) => void>;
12
+ declare class TypedEmitter<E extends EventMap> {
13
+ private readonly listeners;
14
+ /** Subscribe to `event`. Returns an unsubscribe function. */
15
+ on<K extends keyof E>(event: K, fn: E[K]): () => void;
16
+ /** Subscribe once; auto-unsubscribes after the first emission. */
17
+ once<K extends keyof E>(event: K, fn: E[K]): () => void;
18
+ /** Remove a specific listener. */
19
+ off<K extends keyof E>(event: K, fn: E[K]): void;
20
+ /** Remove all listeners (all events, or just `event`). */
21
+ removeAllListeners<K extends keyof E>(event?: K): void;
22
+ protected emit<K extends keyof E>(event: K, ...args: Parameters<E[K]>): void;
23
+ }
24
+
25
+ type TranscriptRole = "user" | "agent";
26
+ interface TranscriptEvent {
27
+ role: TranscriptRole;
28
+ text: string;
29
+ final: boolean;
30
+ language?: string;
31
+ }
32
+ interface OneInboxError {
33
+ /** Stable machine code, e.g. CONNECT_FAILED, ORIGIN_NOT_ALLOWED, MIC_PUBLISH_FAILED. */
34
+ code: string;
35
+ message: string;
36
+ cause?: unknown;
37
+ }
38
+ /** High-level call lifecycle, mirrored to the `status` event. */
39
+ type CallStatus = "idle" | "connecting" | "active" | "ending";
40
+ /**
41
+ * Typed event map. Listen with `oi.on("transcript", cb)` — the callback type
42
+ * is inferred per event (Vapi/LiveKit style).
43
+ */
44
+ type OneInboxEvents = {
45
+ /** Connected to the room; the agent is joining. */
46
+ "call-start": () => void;
47
+ /** Call ended (locally or remotely). `reason` is the LiveKit disconnect reason when known. */
48
+ "call-end": (reason?: string) => void;
49
+ /** Underlying LiveKit connection state changed. */
50
+ "connection-state": (state: ConnectionState) => void;
51
+ /** High-level status changed. */
52
+ status: (status: CallStatus) => void;
53
+ /** Incremental transcript chunk for the caller or the agent (`final` marks end-of-utterance). */
54
+ transcript: (ev: TranscriptEvent) => void;
55
+ /** Someone started speaking (best-effort, from active-speaker detection). */
56
+ "speech-start": (who: TranscriptRole) => void;
57
+ /** Someone stopped speaking. */
58
+ "speech-end": (who: TranscriptRole) => void;
59
+ /** Local microphone level, 0..1, sampled ~10x/sec while active (for mic UIs). */
60
+ "volume-level": (level: number) => void;
61
+ /** Any error (connection, token fetch, mic publish, server disconnect). */
62
+ error: (err: OneInboxError) => void;
63
+ };
64
+ interface OneInboxConfig {
65
+ /** OneInbox API base URL. Default `https://api.oneinbox.ai`. */
66
+ baseUrl?: string;
67
+ }
68
+ interface StartOptions {
69
+ /** Per-call template variables interpolated into the agent prompt. */
70
+ variables?: Record<string, unknown>;
71
+ /** Free-form metadata stored on the call record. */
72
+ metadata?: Record<string, unknown>;
73
+ /** Auto-publish the user's microphone on connect. Default `true`. */
74
+ autoPublishMicrophone?: boolean;
75
+ }
76
+ /** Bring-your-own-token escape hatch: connect with a token your backend minted. */
77
+ interface TokenStartOptions {
78
+ /** From `POST /v1/web-calls/token` (or `/v1/calls/web`) → `participant_token`. */
79
+ token: string;
80
+ /** → `server_url`. */
81
+ serverUrl: string;
82
+ autoPublishMicrophone?: boolean;
83
+ }
84
+ /**
85
+ * Browser client for OneInbox voice agents over WebRTC.
86
+ *
87
+ * ```ts
88
+ * const oi = new OneInbox("oi_pk_…");
89
+ * oi.on("transcript", (t) => console.log(t.role, t.text));
90
+ * await oi.start(agentId); // mic publishes, the agent answers
91
+ * // …
92
+ * await oi.stop();
93
+ * ```
94
+ */
95
+ declare class OneInbox extends TypedEmitter<OneInboxEvents> {
96
+ private readonly publishableKey;
97
+ private readonly config;
98
+ private room;
99
+ private _status;
100
+ private _muted;
101
+ private volumeTimer;
102
+ private speaking;
103
+ constructor(publishableKey: string, config?: OneInboxConfig);
104
+ /** Current high-level call status. */
105
+ get status(): CallStatus;
106
+ /** Whether the local microphone is currently muted. */
107
+ isMuted(): boolean;
108
+ /** Underlying LiveKit room (escape hatch for advanced use). */
109
+ get underlyingRoom(): Room | null;
110
+ /**
111
+ * Start a call.
112
+ *
113
+ * - `start(agentId, opts?)` — publishable-key path: fetches a short-lived
114
+ * token from the OneInbox API, then connects.
115
+ * - `start({ token, serverUrl })` — connect with a token your backend minted.
116
+ */
117
+ start(agentId: string, opts?: StartOptions): Promise<void>;
118
+ start(opts: TokenStartOptions): Promise<void>;
119
+ /** End the call and release resources. The backend marks the call completed. */
120
+ stop(): Promise<void>;
121
+ /** Mute/unmute the local microphone (fire-and-forget, like Vapi's setMuted). */
122
+ setMuted(muted: boolean): void;
123
+ private fetchToken;
124
+ private connectRoom;
125
+ private bindRoomEvents;
126
+ private startVolumeSampling;
127
+ private stopVolumeSampling;
128
+ private teardown;
129
+ private setStatus;
130
+ private fail;
131
+ }
132
+
133
+ export { type CallStatus, OneInbox, type OneInboxConfig, type OneInboxError, type OneInboxEvents, type StartOptions, type TokenStartOptions, type TranscriptEvent, type TranscriptRole, TypedEmitter };
package/dist/index.js ADDED
@@ -0,0 +1,245 @@
1
+ import { Room, RoomEvent, DisconnectReason } from 'livekit-client';
2
+ export { ConnectionState, DisconnectReason, RoomEvent, Track } from 'livekit-client';
3
+
4
+ // src/index.ts
5
+
6
+ // src/emitter.ts
7
+ var TypedEmitter = class {
8
+ listeners = {};
9
+ /** Subscribe to `event`. Returns an unsubscribe function. */
10
+ on(event, fn) {
11
+ (this.listeners[event] ??= /* @__PURE__ */ new Set()).add(fn);
12
+ return () => this.off(event, fn);
13
+ }
14
+ /** Subscribe once; auto-unsubscribes after the first emission. */
15
+ once(event, fn) {
16
+ const wrapper = ((...args) => {
17
+ this.off(event, wrapper);
18
+ fn(...args);
19
+ });
20
+ return this.on(event, wrapper);
21
+ }
22
+ /** Remove a specific listener. */
23
+ off(event, fn) {
24
+ this.listeners[event]?.delete(fn);
25
+ }
26
+ /** Remove all listeners (all events, or just `event`). */
27
+ removeAllListeners(event) {
28
+ if (event) this.listeners[event]?.clear();
29
+ else Object.keys(this.listeners).forEach((k) => this.listeners[k]?.clear());
30
+ }
31
+ emit(event, ...args) {
32
+ const set = this.listeners[event];
33
+ if (!set) return;
34
+ for (const fn of [...set]) fn(...args);
35
+ }
36
+ };
37
+
38
+ // src/index.ts
39
+ var VOLUME_SAMPLE_MS = 100;
40
+ var OneInbox = class extends TypedEmitter {
41
+ constructor(publishableKey, config = {}) {
42
+ super();
43
+ this.publishableKey = publishableKey;
44
+ this.config = config;
45
+ }
46
+ publishableKey;
47
+ config;
48
+ room = null;
49
+ _status = "idle";
50
+ _muted = false;
51
+ volumeTimer = null;
52
+ speaking = /* @__PURE__ */ new Set();
53
+ /** Current high-level call status. */
54
+ get status() {
55
+ return this._status;
56
+ }
57
+ /** Whether the local microphone is currently muted. */
58
+ isMuted() {
59
+ return this._muted;
60
+ }
61
+ /** Underlying LiveKit room (escape hatch for advanced use). */
62
+ get underlyingRoom() {
63
+ return this.room;
64
+ }
65
+ async start(arg, opts) {
66
+ if (this._status !== "idle") {
67
+ throw new Error("A call is already in progress \u2014 call stop() first.");
68
+ }
69
+ this.setStatus("connecting");
70
+ try {
71
+ let serverUrl;
72
+ let token;
73
+ let autoMic;
74
+ if (typeof arg === "string") {
75
+ const res = await this.fetchToken(arg, opts);
76
+ serverUrl = res.server_url;
77
+ token = res.participant_token;
78
+ autoMic = opts?.autoPublishMicrophone !== false;
79
+ } else {
80
+ serverUrl = arg.serverUrl;
81
+ token = arg.token;
82
+ autoMic = arg.autoPublishMicrophone !== false;
83
+ }
84
+ await this.connectRoom(serverUrl, token, autoMic);
85
+ } catch (err) {
86
+ this.setStatus("idle");
87
+ throw err;
88
+ }
89
+ }
90
+ /** End the call and release resources. The backend marks the call completed. */
91
+ async stop() {
92
+ if (!this.room) {
93
+ this.setStatus("idle");
94
+ return;
95
+ }
96
+ this.setStatus("ending");
97
+ await this.room.disconnect();
98
+ }
99
+ /** Mute/unmute the local microphone (fire-and-forget, like Vapi's setMuted). */
100
+ setMuted(muted) {
101
+ this._muted = muted;
102
+ const lp = this.room?.localParticipant;
103
+ if (!lp) return;
104
+ void lp.setMicrophoneEnabled(!muted).catch((err) => {
105
+ this.emit("error", {
106
+ code: "MIC_TOGGLE_FAILED",
107
+ message: err?.message ?? "Failed to toggle microphone",
108
+ cause: err
109
+ });
110
+ });
111
+ }
112
+ // ---- internals -------------------------------------------------------
113
+ async fetchToken(agentId, opts) {
114
+ const base = (this.config.baseUrl ?? "https://api.oneinbox.ai").replace(/\/+$/, "");
115
+ let res;
116
+ try {
117
+ res = await fetch(`${base}/v1/web-calls/token`, {
118
+ method: "POST",
119
+ headers: {
120
+ Authorization: `Bearer ${this.publishableKey}`,
121
+ "Content-Type": "application/json"
122
+ },
123
+ body: JSON.stringify({
124
+ agent_id: agentId,
125
+ variables: opts?.variables ?? {},
126
+ metadata: opts?.metadata ?? {}
127
+ })
128
+ });
129
+ } catch (err) {
130
+ throw this.fail("TOKEN_FETCH_FAILED", err?.message ?? "Network error", err);
131
+ }
132
+ if (!res.ok) {
133
+ let code = `HTTP_${res.status}`;
134
+ let detail = res.statusText;
135
+ try {
136
+ const body = await res.json();
137
+ code = body.error_code ?? code;
138
+ detail = body.detail ?? detail;
139
+ } catch {
140
+ }
141
+ throw this.fail(code, detail);
142
+ }
143
+ return res.json();
144
+ }
145
+ async connectRoom(serverUrl, token, autoMic) {
146
+ const room = new Room({ adaptiveStream: true, dynacast: true });
147
+ this.room = room;
148
+ this.bindRoomEvents(room);
149
+ try {
150
+ await room.connect(serverUrl, token);
151
+ } catch (err) {
152
+ this.room = null;
153
+ throw this.fail("CONNECT_FAILED", err?.message ?? "Connection failed", err);
154
+ }
155
+ this.setStatus("active");
156
+ this.emit("call-start");
157
+ this.startVolumeSampling();
158
+ if (autoMic) {
159
+ try {
160
+ await room.localParticipant.setMicrophoneEnabled(true);
161
+ this._muted = false;
162
+ } catch (err) {
163
+ this.emit("error", {
164
+ code: "MIC_PUBLISH_FAILED",
165
+ message: err?.message ?? "Failed to publish microphone",
166
+ cause: err
167
+ });
168
+ }
169
+ }
170
+ }
171
+ bindRoomEvents(room) {
172
+ room.on(RoomEvent.ConnectionStateChanged, (state) => {
173
+ this.emit("connection-state", state);
174
+ });
175
+ room.on(RoomEvent.TranscriptionReceived, (segments, participant) => {
176
+ const role = roleFor(participant);
177
+ for (const seg of segments) {
178
+ this.emit("transcript", {
179
+ role,
180
+ text: seg.text,
181
+ final: seg.final,
182
+ language: seg.language
183
+ });
184
+ }
185
+ });
186
+ room.on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
187
+ const now = new Set(speakers.map(roleFor));
188
+ for (const role of now) {
189
+ if (!this.speaking.has(role)) this.emit("speech-start", role);
190
+ }
191
+ for (const role of this.speaking) {
192
+ if (!now.has(role)) this.emit("speech-end", role);
193
+ }
194
+ this.speaking = now;
195
+ });
196
+ room.on(RoomEvent.Disconnected, (reason) => {
197
+ const clientInitiated = reason === void 0 || reason === DisconnectReason.CLIENT_INITIATED;
198
+ if (!clientInitiated) {
199
+ this.emit("error", {
200
+ code: "DISCONNECTED",
201
+ message: `Room disconnected: ${DisconnectReason[reason] ?? reason}`
202
+ });
203
+ }
204
+ this.teardown(reason !== void 0 ? DisconnectReason[reason] : void 0);
205
+ });
206
+ }
207
+ startVolumeSampling() {
208
+ this.stopVolumeSampling();
209
+ this.volumeTimer = setInterval(() => {
210
+ const lp = this.room?.localParticipant;
211
+ if (lp) this.emit("volume-level", lp.audioLevel);
212
+ }, VOLUME_SAMPLE_MS);
213
+ }
214
+ stopVolumeSampling() {
215
+ if (this.volumeTimer !== null) {
216
+ clearInterval(this.volumeTimer);
217
+ this.volumeTimer = null;
218
+ }
219
+ }
220
+ teardown(reason) {
221
+ this.stopVolumeSampling();
222
+ this.speaking.clear();
223
+ this.room = null;
224
+ this.setStatus("idle");
225
+ this.emit("call-end", reason);
226
+ }
227
+ setStatus(status) {
228
+ if (this._status === status) return;
229
+ this._status = status;
230
+ this.emit("status", status);
231
+ }
232
+ fail(code, message, cause) {
233
+ const err = { code, message, cause };
234
+ this.emit("error", err);
235
+ return err;
236
+ }
237
+ };
238
+ function roleFor(participant) {
239
+ if (!participant) return "agent";
240
+ return participant.isLocal ? "user" : "agent";
241
+ }
242
+
243
+ export { OneInbox, TypedEmitter };
244
+ //# sourceMappingURL=index.js.map
245
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/emitter.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Tiny strongly-typed event emitter. Zero runtime deps so the SDK stays\n * tree-shakeable and installs without pulling `events`/`eventemitter3`.\n *\n * `E` maps event name → listener signature, e.g.\n * { \"call-start\": () => void; \"volume-level\": (v: number) => void }\n */\nexport type EventMap = Record<string, (...args: never[]) => void>;\n\nexport class TypedEmitter<E extends EventMap> {\n private readonly listeners: { [K in keyof E]?: Set<E[K]> } = {};\n\n /** Subscribe to `event`. Returns an unsubscribe function. */\n on<K extends keyof E>(event: K, fn: E[K]): () => void {\n (this.listeners[event] ??= new Set<E[K]>()).add(fn);\n return () => this.off(event, fn);\n }\n\n /** Subscribe once; auto-unsubscribes after the first emission. */\n once<K extends keyof E>(event: K, fn: E[K]): () => void {\n const wrapper = ((...args: Parameters<E[K]>) => {\n this.off(event, wrapper);\n (fn as (...a: Parameters<E[K]>) => void)(...args);\n }) as E[K];\n return this.on(event, wrapper);\n }\n\n /** Remove a specific listener. */\n off<K extends keyof E>(event: K, fn: E[K]): void {\n this.listeners[event]?.delete(fn);\n }\n\n /** Remove all listeners (all events, or just `event`). */\n removeAllListeners<K extends keyof E>(event?: K): void {\n if (event) this.listeners[event]?.clear();\n else (Object.keys(this.listeners) as K[]).forEach((k) => this.listeners[k]?.clear());\n }\n\n protected emit<K extends keyof E>(event: K, ...args: Parameters<E[K]>): void {\n const set = this.listeners[event];\n if (!set) return;\n // Copy so a listener that unsubscribes mid-emit doesn't skip the next one.\n for (const fn of [...set]) (fn as (...a: Parameters<E[K]>) => void)(...args);\n }\n}\n","import {\n ConnectionState,\n DisconnectReason,\n type LocalParticipant,\n type Participant,\n type RemoteParticipant,\n Room,\n RoomEvent,\n Track,\n type TranscriptionSegment,\n} from \"livekit-client\";\n\nimport { TypedEmitter } from \"./emitter\";\n\nexport type TranscriptRole = \"user\" | \"agent\";\n\nexport interface TranscriptEvent {\n role: TranscriptRole;\n text: string;\n final: boolean;\n language?: string;\n}\n\nexport interface OneInboxError {\n /** Stable machine code, e.g. CONNECT_FAILED, ORIGIN_NOT_ALLOWED, MIC_PUBLISH_FAILED. */\n code: string;\n message: string;\n cause?: unknown;\n}\n\n/** High-level call lifecycle, mirrored to the `status` event. */\nexport type CallStatus = \"idle\" | \"connecting\" | \"active\" | \"ending\";\n\n/**\n * Typed event map. Listen with `oi.on(\"transcript\", cb)` — the callback type\n * is inferred per event (Vapi/LiveKit style).\n */\nexport type OneInboxEvents = {\n /** Connected to the room; the agent is joining. */\n \"call-start\": () => void;\n /** Call ended (locally or remotely). `reason` is the LiveKit disconnect reason when known. */\n \"call-end\": (reason?: string) => void;\n /** Underlying LiveKit connection state changed. */\n \"connection-state\": (state: ConnectionState) => void;\n /** High-level status changed. */\n status: (status: CallStatus) => void;\n /** Incremental transcript chunk for the caller or the agent (`final` marks end-of-utterance). */\n transcript: (ev: TranscriptEvent) => void;\n /** Someone started speaking (best-effort, from active-speaker detection). */\n \"speech-start\": (who: TranscriptRole) => void;\n /** Someone stopped speaking. */\n \"speech-end\": (who: TranscriptRole) => void;\n /** Local microphone level, 0..1, sampled ~10x/sec while active (for mic UIs). */\n \"volume-level\": (level: number) => void;\n /** Any error (connection, token fetch, mic publish, server disconnect). */\n error: (err: OneInboxError) => void;\n};\n\nexport interface OneInboxConfig {\n /** OneInbox API base URL. Default `https://api.oneinbox.ai`. */\n baseUrl?: string;\n}\n\nexport interface StartOptions {\n /** Per-call template variables interpolated into the agent prompt. */\n variables?: Record<string, unknown>;\n /** Free-form metadata stored on the call record. */\n metadata?: Record<string, unknown>;\n /** Auto-publish the user's microphone on connect. Default `true`. */\n autoPublishMicrophone?: boolean;\n}\n\n/** Bring-your-own-token escape hatch: connect with a token your backend minted. */\nexport interface TokenStartOptions {\n /** From `POST /v1/web-calls/token` (or `/v1/calls/web`) → `participant_token`. */\n token: string;\n /** → `server_url`. */\n serverUrl: string;\n autoPublishMicrophone?: boolean;\n}\n\nconst VOLUME_SAMPLE_MS = 100;\n\n/**\n * Browser client for OneInbox voice agents over WebRTC.\n *\n * ```ts\n * const oi = new OneInbox(\"oi_pk_…\");\n * oi.on(\"transcript\", (t) => console.log(t.role, t.text));\n * await oi.start(agentId); // mic publishes, the agent answers\n * // …\n * await oi.stop();\n * ```\n */\nexport class OneInbox extends TypedEmitter<OneInboxEvents> {\n private room: Room | null = null;\n private _status: CallStatus = \"idle\";\n private _muted = false;\n private volumeTimer: ReturnType<typeof setInterval> | null = null;\n private speaking = new Set<TranscriptRole>();\n\n constructor(\n private readonly publishableKey: string,\n private readonly config: OneInboxConfig = {},\n ) {\n super();\n }\n\n /** Current high-level call status. */\n get status(): CallStatus {\n return this._status;\n }\n\n /** Whether the local microphone is currently muted. */\n isMuted(): boolean {\n return this._muted;\n }\n\n /** Underlying LiveKit room (escape hatch for advanced use). */\n get underlyingRoom(): Room | null {\n return this.room;\n }\n\n /**\n * Start a call.\n *\n * - `start(agentId, opts?)` — publishable-key path: fetches a short-lived\n * token from the OneInbox API, then connects.\n * - `start({ token, serverUrl })` — connect with a token your backend minted.\n */\n async start(agentId: string, opts?: StartOptions): Promise<void>;\n async start(opts: TokenStartOptions): Promise<void>;\n async start(arg: string | TokenStartOptions, opts?: StartOptions): Promise<void> {\n if (this._status !== \"idle\") {\n throw new Error(\"A call is already in progress — call stop() first.\");\n }\n this.setStatus(\"connecting\");\n try {\n let serverUrl: string;\n let token: string;\n let autoMic: boolean;\n if (typeof arg === \"string\") {\n const res = await this.fetchToken(arg, opts);\n serverUrl = res.server_url;\n token = res.participant_token;\n autoMic = opts?.autoPublishMicrophone !== false;\n } else {\n serverUrl = arg.serverUrl;\n token = arg.token;\n autoMic = arg.autoPublishMicrophone !== false;\n }\n await this.connectRoom(serverUrl, token, autoMic);\n } catch (err) {\n this.setStatus(\"idle\");\n throw err;\n }\n }\n\n /** End the call and release resources. The backend marks the call completed. */\n async stop(): Promise<void> {\n if (!this.room) {\n this.setStatus(\"idle\");\n return;\n }\n this.setStatus(\"ending\");\n await this.room.disconnect();\n // teardown is finalized in the Disconnected handler\n }\n\n /** Mute/unmute the local microphone (fire-and-forget, like Vapi's setMuted). */\n setMuted(muted: boolean): void {\n this._muted = muted;\n const lp = this.room?.localParticipant;\n if (!lp) return;\n void lp.setMicrophoneEnabled(!muted).catch((err: unknown) => {\n this.emit(\"error\", {\n code: \"MIC_TOGGLE_FAILED\",\n message: (err as Error)?.message ?? \"Failed to toggle microphone\",\n cause: err,\n });\n });\n }\n\n // ---- internals -------------------------------------------------------\n\n private async fetchToken(\n agentId: string,\n opts?: StartOptions,\n ): Promise<{ server_url: string; participant_token: string }> {\n const base = (this.config.baseUrl ?? \"https://api.oneinbox.ai\").replace(/\\/+$/, \"\");\n let res: Response;\n try {\n res = await fetch(`${base}/v1/web-calls/token`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.publishableKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n agent_id: agentId,\n variables: opts?.variables ?? {},\n metadata: opts?.metadata ?? {},\n }),\n });\n } catch (err) {\n throw this.fail(\"TOKEN_FETCH_FAILED\", (err as Error)?.message ?? \"Network error\", err);\n }\n if (!res.ok) {\n let code = `HTTP_${res.status}`;\n let detail = res.statusText;\n try {\n const body = (await res.json()) as { error_code?: string; detail?: string };\n code = body.error_code ?? code;\n detail = body.detail ?? detail;\n } catch {\n /* non-JSON error body */\n }\n throw this.fail(code, detail);\n }\n return res.json() as Promise<{ server_url: string; participant_token: string }>;\n }\n\n private async connectRoom(serverUrl: string, token: string, autoMic: boolean): Promise<void> {\n const room = new Room({ adaptiveStream: true, dynacast: true });\n this.room = room;\n this.bindRoomEvents(room);\n\n try {\n await room.connect(serverUrl, token);\n } catch (err) {\n this.room = null;\n throw this.fail(\"CONNECT_FAILED\", (err as Error)?.message ?? \"Connection failed\", err);\n }\n\n this.setStatus(\"active\");\n this.emit(\"call-start\");\n this.startVolumeSampling();\n\n if (autoMic) {\n try {\n await room.localParticipant.setMicrophoneEnabled(true);\n this._muted = false;\n } catch (err) {\n this.emit(\"error\", {\n code: \"MIC_PUBLISH_FAILED\",\n message: (err as Error)?.message ?? \"Failed to publish microphone\",\n cause: err,\n });\n }\n }\n }\n\n private bindRoomEvents(room: Room): void {\n room.on(RoomEvent.ConnectionStateChanged, (state) => {\n this.emit(\"connection-state\", state);\n });\n\n room.on(RoomEvent.TranscriptionReceived, (segments, participant) => {\n const role = roleFor(participant);\n for (const seg of segments) {\n this.emit(\"transcript\", {\n role,\n text: seg.text,\n final: seg.final,\n language: seg.language,\n });\n }\n });\n\n room.on(RoomEvent.ActiveSpeakersChanged, (speakers) => {\n const now = new Set<TranscriptRole>(speakers.map(roleFor));\n for (const role of now) {\n if (!this.speaking.has(role)) this.emit(\"speech-start\", role);\n }\n for (const role of this.speaking) {\n if (!now.has(role)) this.emit(\"speech-end\", role);\n }\n this.speaking = now;\n });\n\n room.on(RoomEvent.Disconnected, (reason?: DisconnectReason) => {\n const clientInitiated =\n reason === undefined || reason === DisconnectReason.CLIENT_INITIATED;\n if (!clientInitiated) {\n this.emit(\"error\", {\n code: \"DISCONNECTED\",\n message: `Room disconnected: ${DisconnectReason[reason] ?? reason}`,\n });\n }\n this.teardown(reason !== undefined ? DisconnectReason[reason] : undefined);\n });\n }\n\n private startVolumeSampling(): void {\n this.stopVolumeSampling();\n this.volumeTimer = setInterval(() => {\n const lp = this.room?.localParticipant;\n if (lp) this.emit(\"volume-level\", lp.audioLevel);\n }, VOLUME_SAMPLE_MS);\n }\n\n private stopVolumeSampling(): void {\n if (this.volumeTimer !== null) {\n clearInterval(this.volumeTimer);\n this.volumeTimer = null;\n }\n }\n\n private teardown(reason?: string): void {\n this.stopVolumeSampling();\n this.speaking.clear();\n this.room = null;\n this.setStatus(\"idle\");\n this.emit(\"call-end\", reason);\n }\n\n private setStatus(status: CallStatus): void {\n if (this._status === status) return;\n this._status = status;\n this.emit(\"status\", status);\n }\n\n private fail(code: string, message: string, cause?: unknown): OneInboxError {\n const err: OneInboxError = { code, message, cause };\n this.emit(\"error\", err);\n return err;\n }\n}\n\nfunction roleFor(participant: Participant | undefined): TranscriptRole {\n // The human is the local participant; the agent joins as a remote participant.\n if (!participant) return \"agent\";\n return participant.isLocal ? \"user\" : \"agent\";\n}\n\n// Re-export upstream types callers commonly need so they don't have to add a\n// direct livekit-client import just for types.\nexport { ConnectionState, DisconnectReason, RoomEvent, Track };\nexport type { LocalParticipant, Participant, RemoteParticipant, Room, TranscriptionSegment };\nexport { TypedEmitter } from \"./emitter\";\n"]}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@oneinbox/web-sdk",
3
+ "version": "0.1.0-beta.1",
4
+ "description": "OneInbox browser SDK — connect to a voice agent over WebRTC with a publishable key.",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "README.md"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "dev": "tsup --watch",
28
+ "typecheck": "tsc --noEmit",
29
+ "prepublishOnly": "tsup"
30
+ },
31
+ "peerDependencies": {
32
+ "livekit-client": "^2.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "livekit-client": "^2.5.0",
36
+ "tsup": "^8.0.0",
37
+ "typescript": "^5.4.0"
38
+ },
39
+ "keywords": ["voice", "ai", "agents", "webrtc", "livekit", "vapi"],
40
+ "author": "OneInbox",
41
+ "homepage": "https://www.npmjs.com/package/@oneinbox/web-sdk",
42
+ "license": "MIT",
43
+ "publishConfig": {
44
+ "access": "public"
45
+ }
46
+ }