@oneinbox/web-sdk 0.1.0-beta.1 → 0.1.0-beta.3
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 +5 -2
- package/dist/index.cjs +92 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +62 -11
- package/dist/index.js.map +1 -1
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -52,15 +52,18 @@ await oi.start({ token, serverUrl }); // from your backend
|
|
|
52
52
|
| `start({ token, serverUrl })` | Connect with a token you minted server-side. |
|
|
53
53
|
| `stop()` | End the call. |
|
|
54
54
|
| `setMuted(muted)` / `isMuted()` | Toggle / read mic state. |
|
|
55
|
+
| `resumeAudio()` | Resume playback if the browser blocked autoplay (call from a click handler). |
|
|
55
56
|
| `status` | `"idle" \| "connecting" \| "active" \| "ending"`. |
|
|
56
57
|
| `underlyingRoom` | Escape hatch to the raw `livekit-client` `Room`. |
|
|
57
58
|
| `on(event, cb)` / `once` / `off` | Typed event subscription; `on` returns an unsubscribe fn. |
|
|
58
59
|
|
|
60
|
+
> **Errors come through the `error` event, not exceptions.** `start()` never throws/rejects — it resolves once the attempt settles, and every failure (including "a call is already in progress") is delivered once via `oi.on("error", …)`. Check `status` or the `call-start` event for success.
|
|
61
|
+
|
|
59
62
|
### Events
|
|
60
63
|
|
|
61
|
-
`call-start`, `call-end` (`reason?`), `status` (`CallStatus`), `connection-state` (LiveKit `ConnectionState`), `transcript` (`{ role, text, final, language? }`), `speech-start` / `speech-end` (`
|
|
64
|
+
`call-start`, `call-end` (`reason?`), `status` (`CallStatus`), `connection-state` (LiveKit `ConnectionState`), `transcript` (`{ role, text, final, language? }` where `role` is `"user" | "agent" | "unknown"`), `speech-start` / `speech-end` (`TranscriptRole`), `volume-level` (`number`, 0..1), `error` (`{ code, message, cause? }`).
|
|
62
65
|
|
|
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).
|
|
66
|
+
Error codes: `TOKEN_FETCH_FAILED`, `ORIGIN_NOT_ALLOWED`, `INVALID_PUBLISHABLE_KEY`, `CONNECT_FAILED`, `MIC_PUBLISH_FAILED`, `MIC_TOGGLE_FAILED`, `CALL_IN_PROGRESS`, `AUDIO_PLAYBACK_BLOCKED`, `DISCONNECTED` (plus server `error_code`s passed through from the token endpoint).
|
|
64
67
|
|
|
65
68
|
## License
|
|
66
69
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
var
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
4
19
|
|
|
5
20
|
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ConnectionState: () => import_livekit_client.ConnectionState,
|
|
24
|
+
DisconnectReason: () => import_livekit_client.DisconnectReason,
|
|
25
|
+
OneInbox: () => OneInbox,
|
|
26
|
+
RoomEvent: () => import_livekit_client.RoomEvent,
|
|
27
|
+
Track: () => import_livekit_client.Track,
|
|
28
|
+
TypedEmitter: () => TypedEmitter
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
var import_livekit_client = require("livekit-client");
|
|
6
32
|
|
|
7
33
|
// src/emitter.ts
|
|
8
34
|
var TypedEmitter = class {
|
|
@@ -51,6 +77,9 @@ var OneInbox = class extends TypedEmitter {
|
|
|
51
77
|
_muted = false;
|
|
52
78
|
volumeTimer = null;
|
|
53
79
|
speaking = /* @__PURE__ */ new Set();
|
|
80
|
+
// Audio <audio> elements created for the agent's remote tracks, so we can
|
|
81
|
+
// play them and tear them down on call end.
|
|
82
|
+
audioElements = /* @__PURE__ */ new Set();
|
|
54
83
|
/** Current high-level call status. */
|
|
55
84
|
get status() {
|
|
56
85
|
return this._status;
|
|
@@ -65,7 +94,8 @@ var OneInbox = class extends TypedEmitter {
|
|
|
65
94
|
}
|
|
66
95
|
async start(arg, opts) {
|
|
67
96
|
if (this._status !== "idle") {
|
|
68
|
-
|
|
97
|
+
this.fail("CALL_IN_PROGRESS", "A call is already in progress \u2014 call stop() first.");
|
|
98
|
+
return;
|
|
69
99
|
}
|
|
70
100
|
this.setStatus("connecting");
|
|
71
101
|
try {
|
|
@@ -83,9 +113,8 @@ var OneInbox = class extends TypedEmitter {
|
|
|
83
113
|
autoMic = arg.autoPublishMicrophone !== false;
|
|
84
114
|
}
|
|
85
115
|
await this.connectRoom(serverUrl, token, autoMic);
|
|
86
|
-
} catch
|
|
116
|
+
} catch {
|
|
87
117
|
this.setStatus("idle");
|
|
88
|
-
throw err;
|
|
89
118
|
}
|
|
90
119
|
}
|
|
91
120
|
/** End the call and release resources. The backend marks the call completed. */
|
|
@@ -97,12 +126,23 @@ var OneInbox = class extends TypedEmitter {
|
|
|
97
126
|
this.setStatus("ending");
|
|
98
127
|
await this.room.disconnect();
|
|
99
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Resume audio playback if the browser blocked autoplay (call from a click /
|
|
131
|
+
* tap handler). No-op when audio is already playing.
|
|
132
|
+
*/
|
|
133
|
+
async resumeAudio() {
|
|
134
|
+
if (this.room && !this.room.canPlaybackAudio) {
|
|
135
|
+
await this.room.startAudio();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
100
138
|
/** Mute/unmute the local microphone (fire-and-forget, like Vapi's setMuted). */
|
|
101
139
|
setMuted(muted) {
|
|
102
|
-
this._muted = muted;
|
|
103
140
|
const lp = this.room?.localParticipant;
|
|
141
|
+
const previous = this._muted;
|
|
142
|
+
this._muted = muted;
|
|
104
143
|
if (!lp) return;
|
|
105
144
|
void lp.setMicrophoneEnabled(!muted).catch((err) => {
|
|
145
|
+
this._muted = previous;
|
|
106
146
|
this.emit("error", {
|
|
107
147
|
code: "MIC_TOGGLE_FAILED",
|
|
108
148
|
message: err?.message ?? "Failed to toggle microphone",
|
|
@@ -144,7 +184,7 @@ var OneInbox = class extends TypedEmitter {
|
|
|
144
184
|
return res.json();
|
|
145
185
|
}
|
|
146
186
|
async connectRoom(serverUrl, token, autoMic) {
|
|
147
|
-
const room = new
|
|
187
|
+
const room = new import_livekit_client.Room({ adaptiveStream: true, dynacast: true });
|
|
148
188
|
this.room = room;
|
|
149
189
|
this.bindRoomEvents(room);
|
|
150
190
|
try {
|
|
@@ -170,10 +210,32 @@ var OneInbox = class extends TypedEmitter {
|
|
|
170
210
|
}
|
|
171
211
|
}
|
|
172
212
|
bindRoomEvents(room) {
|
|
173
|
-
room.on(
|
|
213
|
+
room.on(import_livekit_client.RoomEvent.ConnectionStateChanged, (state) => {
|
|
174
214
|
this.emit("connection-state", state);
|
|
175
215
|
});
|
|
176
|
-
room.on(
|
|
216
|
+
room.on(import_livekit_client.RoomEvent.TrackSubscribed, (track) => {
|
|
217
|
+
if (track.kind !== import_livekit_client.Track.Kind.Audio) return;
|
|
218
|
+
const el = track.attach();
|
|
219
|
+
el.setAttribute("data-oneinbox", "agent-audio");
|
|
220
|
+
if (typeof document !== "undefined") document.body.appendChild(el);
|
|
221
|
+
this.audioElements.add(el);
|
|
222
|
+
});
|
|
223
|
+
room.on(import_livekit_client.RoomEvent.TrackUnsubscribed, (track) => {
|
|
224
|
+
if (track.kind !== import_livekit_client.Track.Kind.Audio) return;
|
|
225
|
+
for (const el of track.detach()) {
|
|
226
|
+
el.remove();
|
|
227
|
+
this.audioElements.delete(el);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
room.on(import_livekit_client.RoomEvent.AudioPlaybackStatusChanged, () => {
|
|
231
|
+
if (!room.canPlaybackAudio) {
|
|
232
|
+
this.emit("error", {
|
|
233
|
+
code: "AUDIO_PLAYBACK_BLOCKED",
|
|
234
|
+
message: "Browser blocked audio autoplay \u2014 call resumeAudio() after a user gesture."
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
room.on(import_livekit_client.RoomEvent.TranscriptionReceived, (segments, participant) => {
|
|
177
239
|
const role = roleFor(participant);
|
|
178
240
|
for (const seg of segments) {
|
|
179
241
|
this.emit("transcript", {
|
|
@@ -184,7 +246,7 @@ var OneInbox = class extends TypedEmitter {
|
|
|
184
246
|
});
|
|
185
247
|
}
|
|
186
248
|
});
|
|
187
|
-
room.on(
|
|
249
|
+
room.on(import_livekit_client.RoomEvent.ActiveSpeakersChanged, (speakers) => {
|
|
188
250
|
const now = new Set(speakers.map(roleFor));
|
|
189
251
|
for (const role of now) {
|
|
190
252
|
if (!this.speaking.has(role)) this.emit("speech-start", role);
|
|
@@ -194,15 +256,15 @@ var OneInbox = class extends TypedEmitter {
|
|
|
194
256
|
}
|
|
195
257
|
this.speaking = now;
|
|
196
258
|
});
|
|
197
|
-
room.on(
|
|
198
|
-
const clientInitiated = reason === void 0 || reason ===
|
|
259
|
+
room.on(import_livekit_client.RoomEvent.Disconnected, (reason) => {
|
|
260
|
+
const clientInitiated = reason === void 0 || reason === import_livekit_client.DisconnectReason.CLIENT_INITIATED;
|
|
199
261
|
if (!clientInitiated) {
|
|
200
262
|
this.emit("error", {
|
|
201
263
|
code: "DISCONNECTED",
|
|
202
|
-
message: `Room disconnected: ${
|
|
264
|
+
message: `Room disconnected: ${import_livekit_client.DisconnectReason[reason] ?? reason}`
|
|
203
265
|
});
|
|
204
266
|
}
|
|
205
|
-
this.teardown(reason !== void 0 ?
|
|
267
|
+
this.teardown(reason !== void 0 ? import_livekit_client.DisconnectReason[reason] : void 0);
|
|
206
268
|
});
|
|
207
269
|
}
|
|
208
270
|
startVolumeSampling() {
|
|
@@ -221,6 +283,12 @@ var OneInbox = class extends TypedEmitter {
|
|
|
221
283
|
teardown(reason) {
|
|
222
284
|
this.stopVolumeSampling();
|
|
223
285
|
this.speaking.clear();
|
|
286
|
+
for (const el of this.audioElements) {
|
|
287
|
+
el.pause();
|
|
288
|
+
el.srcObject = null;
|
|
289
|
+
el.remove();
|
|
290
|
+
}
|
|
291
|
+
this.audioElements.clear();
|
|
224
292
|
this.room = null;
|
|
225
293
|
this.setStatus("idle");
|
|
226
294
|
this.emit("call-end", reason);
|
|
@@ -237,27 +305,16 @@ var OneInbox = class extends TypedEmitter {
|
|
|
237
305
|
}
|
|
238
306
|
};
|
|
239
307
|
function roleFor(participant) {
|
|
240
|
-
if (!participant) return "
|
|
308
|
+
if (!participant) return "unknown";
|
|
241
309
|
return participant.isLocal ? "user" : "agent";
|
|
242
310
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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; }
|
|
311
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
312
|
+
0 && (module.exports = {
|
|
313
|
+
ConnectionState,
|
|
314
|
+
DisconnectReason,
|
|
315
|
+
OneInbox,
|
|
316
|
+
RoomEvent,
|
|
317
|
+
Track,
|
|
318
|
+
TypedEmitter
|
|
259
319
|
});
|
|
260
|
-
exports.OneInbox = OneInbox;
|
|
261
|
-
exports.TypedEmitter = TypedEmitter;
|
|
262
|
-
//# sourceMappingURL=index.cjs.map
|
|
263
320
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +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"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/emitter.ts"],"sourcesContent":["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\" | \"unknown\";\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 // Audio <audio> elements created for the agent's remote tracks, so we can\n // play them and tear them down on call end.\n private audioElements = new Set<HTMLMediaElement>();\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 * Errors are delivered through the `error` event (never thrown), so a single\n * `oi.on(\"error\", …)` handler catches every failure path consistently. The\n * returned promise resolves once the attempt settles (check `status` or the\n * `call-start` event for success).\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 // Bug-fix: emit \"error\" like every other failure path instead of throwing\n // (a throw here was an unhandled rejection for `oi.on(\"error\")` users).\n this.fail(\"CALL_IN_PROGRESS\", \"A call is already in progress — call stop() first.\");\n return;\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 {\n // fetchToken / connectRoom already emitted \"error\" via fail(); just reset\n // status. We deliberately do NOT re-throw — that double-delivered the\n // same failure (once via the event, once via the rejection).\n this.setStatus(\"idle\");\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 /**\n * Resume audio playback if the browser blocked autoplay (call from a click /\n * tap handler). No-op when audio is already playing.\n */\n async resumeAudio(): Promise<void> {\n if (this.room && !this.room.canPlaybackAudio) {\n await this.room.startAudio();\n }\n }\n\n /** Mute/unmute the local microphone (fire-and-forget, like Vapi's setMuted). */\n setMuted(muted: boolean): void {\n const lp = this.room?.localParticipant;\n const previous = this._muted;\n this._muted = muted;\n if (!lp) return;\n void lp.setMicrophoneEnabled(!muted).catch((err: unknown) => {\n // Bug-fix: the track toggle failed, so isMuted() must not keep reporting\n // the requested-but-never-applied state — revert to the previous value.\n this._muted = previous;\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 // Play the agent's audio. livekit-client does NOT auto-play remote tracks —\n // we must attach each subscribed audio track to an <audio> element. Without\n // this the agent is heard by no one even though transcripts arrive.\n room.on(RoomEvent.TrackSubscribed, (track) => {\n if (track.kind !== Track.Kind.Audio) return;\n const el = track.attach(); // creates an autoplaying <audio> element\n el.setAttribute(\"data-oneinbox\", \"agent-audio\");\n // Some browsers need the element in the DOM to actually play.\n if (typeof document !== \"undefined\") document.body.appendChild(el);\n this.audioElements.add(el);\n });\n\n room.on(RoomEvent.TrackUnsubscribed, (track) => {\n if (track.kind !== Track.Kind.Audio) return;\n for (const el of track.detach()) {\n el.remove();\n this.audioElements.delete(el);\n }\n });\n\n // If the browser blocks autoplay (no prior gesture), surface it so the app\n // can prompt a tap. Calling startAudio() resumes playback.\n room.on(RoomEvent.AudioPlaybackStatusChanged, () => {\n if (!room.canPlaybackAudio) {\n this.emit(\"error\", {\n code: \"AUDIO_PLAYBACK_BLOCKED\",\n message: \"Browser blocked audio autoplay — call resumeAudio() after a user gesture.\",\n });\n }\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 for (const el of this.audioElements) {\n el.pause();\n el.srcObject = null;\n el.remove();\n }\n this.audioElements.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 // Bug-fix: when LiveKit fires without a participant we genuinely don't know\n // who spoke — return \"unknown\" rather than mislabelling it as the agent.\n if (!participant) return \"unknown\";\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","/**\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"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -22,7 +22,7 @@ declare class TypedEmitter<E extends EventMap> {
|
|
|
22
22
|
protected emit<K extends keyof E>(event: K, ...args: Parameters<E[K]>): void;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
type TranscriptRole = "user" | "agent";
|
|
25
|
+
type TranscriptRole = "user" | "agent" | "unknown";
|
|
26
26
|
interface TranscriptEvent {
|
|
27
27
|
role: TranscriptRole;
|
|
28
28
|
text: string;
|
|
@@ -100,6 +100,7 @@ declare class OneInbox extends TypedEmitter<OneInboxEvents> {
|
|
|
100
100
|
private _muted;
|
|
101
101
|
private volumeTimer;
|
|
102
102
|
private speaking;
|
|
103
|
+
private audioElements;
|
|
103
104
|
constructor(publishableKey: string, config?: OneInboxConfig);
|
|
104
105
|
/** Current high-level call status. */
|
|
105
106
|
get status(): CallStatus;
|
|
@@ -113,11 +114,21 @@ declare class OneInbox extends TypedEmitter<OneInboxEvents> {
|
|
|
113
114
|
* - `start(agentId, opts?)` — publishable-key path: fetches a short-lived
|
|
114
115
|
* token from the OneInbox API, then connects.
|
|
115
116
|
* - `start({ token, serverUrl })` — connect with a token your backend minted.
|
|
117
|
+
*
|
|
118
|
+
* Errors are delivered through the `error` event (never thrown), so a single
|
|
119
|
+
* `oi.on("error", …)` handler catches every failure path consistently. The
|
|
120
|
+
* returned promise resolves once the attempt settles (check `status` or the
|
|
121
|
+
* `call-start` event for success).
|
|
116
122
|
*/
|
|
117
123
|
start(agentId: string, opts?: StartOptions): Promise<void>;
|
|
118
124
|
start(opts: TokenStartOptions): Promise<void>;
|
|
119
125
|
/** End the call and release resources. The backend marks the call completed. */
|
|
120
126
|
stop(): Promise<void>;
|
|
127
|
+
/**
|
|
128
|
+
* Resume audio playback if the browser blocked autoplay (call from a click /
|
|
129
|
+
* tap handler). No-op when audio is already playing.
|
|
130
|
+
*/
|
|
131
|
+
resumeAudio(): Promise<void>;
|
|
121
132
|
/** Mute/unmute the local microphone (fire-and-forget, like Vapi's setMuted). */
|
|
122
133
|
setMuted(muted: boolean): void;
|
|
123
134
|
private fetchToken;
|
package/dist/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ declare class TypedEmitter<E extends EventMap> {
|
|
|
22
22
|
protected emit<K extends keyof E>(event: K, ...args: Parameters<E[K]>): void;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
type TranscriptRole = "user" | "agent";
|
|
25
|
+
type TranscriptRole = "user" | "agent" | "unknown";
|
|
26
26
|
interface TranscriptEvent {
|
|
27
27
|
role: TranscriptRole;
|
|
28
28
|
text: string;
|
|
@@ -100,6 +100,7 @@ declare class OneInbox extends TypedEmitter<OneInboxEvents> {
|
|
|
100
100
|
private _muted;
|
|
101
101
|
private volumeTimer;
|
|
102
102
|
private speaking;
|
|
103
|
+
private audioElements;
|
|
103
104
|
constructor(publishableKey: string, config?: OneInboxConfig);
|
|
104
105
|
/** Current high-level call status. */
|
|
105
106
|
get status(): CallStatus;
|
|
@@ -113,11 +114,21 @@ declare class OneInbox extends TypedEmitter<OneInboxEvents> {
|
|
|
113
114
|
* - `start(agentId, opts?)` — publishable-key path: fetches a short-lived
|
|
114
115
|
* token from the OneInbox API, then connects.
|
|
115
116
|
* - `start({ token, serverUrl })` — connect with a token your backend minted.
|
|
117
|
+
*
|
|
118
|
+
* Errors are delivered through the `error` event (never thrown), so a single
|
|
119
|
+
* `oi.on("error", …)` handler catches every failure path consistently. The
|
|
120
|
+
* returned promise resolves once the attempt settles (check `status` or the
|
|
121
|
+
* `call-start` event for success).
|
|
116
122
|
*/
|
|
117
123
|
start(agentId: string, opts?: StartOptions): Promise<void>;
|
|
118
124
|
start(opts: TokenStartOptions): Promise<void>;
|
|
119
125
|
/** End the call and release resources. The backend marks the call completed. */
|
|
120
126
|
stop(): Promise<void>;
|
|
127
|
+
/**
|
|
128
|
+
* Resume audio playback if the browser blocked autoplay (call from a click /
|
|
129
|
+
* tap handler). No-op when audio is already playing.
|
|
130
|
+
*/
|
|
131
|
+
resumeAudio(): Promise<void>;
|
|
121
132
|
/** Mute/unmute the local microphone (fire-and-forget, like Vapi's setMuted). */
|
|
122
133
|
setMuted(muted: boolean): void;
|
|
123
134
|
private fetchToken;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import { Room, RoomEvent, DisconnectReason } from 'livekit-client';
|
|
2
|
-
export { ConnectionState, DisconnectReason, RoomEvent, Track } from 'livekit-client';
|
|
3
|
-
|
|
4
1
|
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
ConnectionState,
|
|
4
|
+
DisconnectReason,
|
|
5
|
+
Room,
|
|
6
|
+
RoomEvent,
|
|
7
|
+
Track
|
|
8
|
+
} from "livekit-client";
|
|
5
9
|
|
|
6
10
|
// src/emitter.ts
|
|
7
11
|
var TypedEmitter = class {
|
|
@@ -50,6 +54,9 @@ var OneInbox = class extends TypedEmitter {
|
|
|
50
54
|
_muted = false;
|
|
51
55
|
volumeTimer = null;
|
|
52
56
|
speaking = /* @__PURE__ */ new Set();
|
|
57
|
+
// Audio <audio> elements created for the agent's remote tracks, so we can
|
|
58
|
+
// play them and tear them down on call end.
|
|
59
|
+
audioElements = /* @__PURE__ */ new Set();
|
|
53
60
|
/** Current high-level call status. */
|
|
54
61
|
get status() {
|
|
55
62
|
return this._status;
|
|
@@ -64,7 +71,8 @@ var OneInbox = class extends TypedEmitter {
|
|
|
64
71
|
}
|
|
65
72
|
async start(arg, opts) {
|
|
66
73
|
if (this._status !== "idle") {
|
|
67
|
-
|
|
74
|
+
this.fail("CALL_IN_PROGRESS", "A call is already in progress \u2014 call stop() first.");
|
|
75
|
+
return;
|
|
68
76
|
}
|
|
69
77
|
this.setStatus("connecting");
|
|
70
78
|
try {
|
|
@@ -82,9 +90,8 @@ var OneInbox = class extends TypedEmitter {
|
|
|
82
90
|
autoMic = arg.autoPublishMicrophone !== false;
|
|
83
91
|
}
|
|
84
92
|
await this.connectRoom(serverUrl, token, autoMic);
|
|
85
|
-
} catch
|
|
93
|
+
} catch {
|
|
86
94
|
this.setStatus("idle");
|
|
87
|
-
throw err;
|
|
88
95
|
}
|
|
89
96
|
}
|
|
90
97
|
/** End the call and release resources. The backend marks the call completed. */
|
|
@@ -96,12 +103,23 @@ var OneInbox = class extends TypedEmitter {
|
|
|
96
103
|
this.setStatus("ending");
|
|
97
104
|
await this.room.disconnect();
|
|
98
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Resume audio playback if the browser blocked autoplay (call from a click /
|
|
108
|
+
* tap handler). No-op when audio is already playing.
|
|
109
|
+
*/
|
|
110
|
+
async resumeAudio() {
|
|
111
|
+
if (this.room && !this.room.canPlaybackAudio) {
|
|
112
|
+
await this.room.startAudio();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
99
115
|
/** Mute/unmute the local microphone (fire-and-forget, like Vapi's setMuted). */
|
|
100
116
|
setMuted(muted) {
|
|
101
|
-
this._muted = muted;
|
|
102
117
|
const lp = this.room?.localParticipant;
|
|
118
|
+
const previous = this._muted;
|
|
119
|
+
this._muted = muted;
|
|
103
120
|
if (!lp) return;
|
|
104
121
|
void lp.setMicrophoneEnabled(!muted).catch((err) => {
|
|
122
|
+
this._muted = previous;
|
|
105
123
|
this.emit("error", {
|
|
106
124
|
code: "MIC_TOGGLE_FAILED",
|
|
107
125
|
message: err?.message ?? "Failed to toggle microphone",
|
|
@@ -172,6 +190,28 @@ var OneInbox = class extends TypedEmitter {
|
|
|
172
190
|
room.on(RoomEvent.ConnectionStateChanged, (state) => {
|
|
173
191
|
this.emit("connection-state", state);
|
|
174
192
|
});
|
|
193
|
+
room.on(RoomEvent.TrackSubscribed, (track) => {
|
|
194
|
+
if (track.kind !== Track.Kind.Audio) return;
|
|
195
|
+
const el = track.attach();
|
|
196
|
+
el.setAttribute("data-oneinbox", "agent-audio");
|
|
197
|
+
if (typeof document !== "undefined") document.body.appendChild(el);
|
|
198
|
+
this.audioElements.add(el);
|
|
199
|
+
});
|
|
200
|
+
room.on(RoomEvent.TrackUnsubscribed, (track) => {
|
|
201
|
+
if (track.kind !== Track.Kind.Audio) return;
|
|
202
|
+
for (const el of track.detach()) {
|
|
203
|
+
el.remove();
|
|
204
|
+
this.audioElements.delete(el);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
room.on(RoomEvent.AudioPlaybackStatusChanged, () => {
|
|
208
|
+
if (!room.canPlaybackAudio) {
|
|
209
|
+
this.emit("error", {
|
|
210
|
+
code: "AUDIO_PLAYBACK_BLOCKED",
|
|
211
|
+
message: "Browser blocked audio autoplay \u2014 call resumeAudio() after a user gesture."
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
});
|
|
175
215
|
room.on(RoomEvent.TranscriptionReceived, (segments, participant) => {
|
|
176
216
|
const role = roleFor(participant);
|
|
177
217
|
for (const seg of segments) {
|
|
@@ -220,6 +260,12 @@ var OneInbox = class extends TypedEmitter {
|
|
|
220
260
|
teardown(reason) {
|
|
221
261
|
this.stopVolumeSampling();
|
|
222
262
|
this.speaking.clear();
|
|
263
|
+
for (const el of this.audioElements) {
|
|
264
|
+
el.pause();
|
|
265
|
+
el.srcObject = null;
|
|
266
|
+
el.remove();
|
|
267
|
+
}
|
|
268
|
+
this.audioElements.clear();
|
|
223
269
|
this.room = null;
|
|
224
270
|
this.setStatus("idle");
|
|
225
271
|
this.emit("call-end", reason);
|
|
@@ -236,10 +282,15 @@ var OneInbox = class extends TypedEmitter {
|
|
|
236
282
|
}
|
|
237
283
|
};
|
|
238
284
|
function roleFor(participant) {
|
|
239
|
-
if (!participant) return "
|
|
285
|
+
if (!participant) return "unknown";
|
|
240
286
|
return participant.isLocal ? "user" : "agent";
|
|
241
287
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
288
|
+
export {
|
|
289
|
+
ConnectionState,
|
|
290
|
+
DisconnectReason,
|
|
291
|
+
OneInbox,
|
|
292
|
+
RoomEvent,
|
|
293
|
+
Track,
|
|
294
|
+
TypedEmitter
|
|
295
|
+
};
|
|
245
296
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/emitter.ts"],"sourcesContent":["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\" | \"unknown\";\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 // Audio <audio> elements created for the agent's remote tracks, so we can\n // play them and tear them down on call end.\n private audioElements = new Set<HTMLMediaElement>();\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 * Errors are delivered through the `error` event (never thrown), so a single\n * `oi.on(\"error\", …)` handler catches every failure path consistently. The\n * returned promise resolves once the attempt settles (check `status` or the\n * `call-start` event for success).\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 // Bug-fix: emit \"error\" like every other failure path instead of throwing\n // (a throw here was an unhandled rejection for `oi.on(\"error\")` users).\n this.fail(\"CALL_IN_PROGRESS\", \"A call is already in progress — call stop() first.\");\n return;\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 {\n // fetchToken / connectRoom already emitted \"error\" via fail(); just reset\n // status. We deliberately do NOT re-throw — that double-delivered the\n // same failure (once via the event, once via the rejection).\n this.setStatus(\"idle\");\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 /**\n * Resume audio playback if the browser blocked autoplay (call from a click /\n * tap handler). No-op when audio is already playing.\n */\n async resumeAudio(): Promise<void> {\n if (this.room && !this.room.canPlaybackAudio) {\n await this.room.startAudio();\n }\n }\n\n /** Mute/unmute the local microphone (fire-and-forget, like Vapi's setMuted). */\n setMuted(muted: boolean): void {\n const lp = this.room?.localParticipant;\n const previous = this._muted;\n this._muted = muted;\n if (!lp) return;\n void lp.setMicrophoneEnabled(!muted).catch((err: unknown) => {\n // Bug-fix: the track toggle failed, so isMuted() must not keep reporting\n // the requested-but-never-applied state — revert to the previous value.\n this._muted = previous;\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 // Play the agent's audio. livekit-client does NOT auto-play remote tracks —\n // we must attach each subscribed audio track to an <audio> element. Without\n // this the agent is heard by no one even though transcripts arrive.\n room.on(RoomEvent.TrackSubscribed, (track) => {\n if (track.kind !== Track.Kind.Audio) return;\n const el = track.attach(); // creates an autoplaying <audio> element\n el.setAttribute(\"data-oneinbox\", \"agent-audio\");\n // Some browsers need the element in the DOM to actually play.\n if (typeof document !== \"undefined\") document.body.appendChild(el);\n this.audioElements.add(el);\n });\n\n room.on(RoomEvent.TrackUnsubscribed, (track) => {\n if (track.kind !== Track.Kind.Audio) return;\n for (const el of track.detach()) {\n el.remove();\n this.audioElements.delete(el);\n }\n });\n\n // If the browser blocks autoplay (no prior gesture), surface it so the app\n // can prompt a tap. Calling startAudio() resumes playback.\n room.on(RoomEvent.AudioPlaybackStatusChanged, () => {\n if (!room.canPlaybackAudio) {\n this.emit(\"error\", {\n code: \"AUDIO_PLAYBACK_BLOCKED\",\n message: \"Browser blocked audio autoplay — call resumeAudio() after a user gesture.\",\n });\n }\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 for (const el of this.audioElements) {\n el.pause();\n el.srcObject = null;\n el.remove();\n }\n this.audioElements.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 // Bug-fix: when LiveKit fires without a participant we genuinely don't know\n // who spoke — return \"unknown\" rather than mislabelling it as the agent.\n if (!participant) return \"unknown\";\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","/**\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"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oneinbox/web-sdk",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
4
|
-
"description": "OneInbox browser SDK
|
|
3
|
+
"version": "0.1.0-beta.3",
|
|
4
|
+
"description": "OneInbox browser SDK \u2014 connect to a voice agent over WebRTC with a publishable key.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -36,7 +36,14 @@
|
|
|
36
36
|
"tsup": "^8.0.0",
|
|
37
37
|
"typescript": "^5.4.0"
|
|
38
38
|
},
|
|
39
|
-
"keywords": [
|
|
39
|
+
"keywords": [
|
|
40
|
+
"voice",
|
|
41
|
+
"ai",
|
|
42
|
+
"agents",
|
|
43
|
+
"webrtc",
|
|
44
|
+
"livekit",
|
|
45
|
+
"vapi"
|
|
46
|
+
],
|
|
40
47
|
"author": "OneInbox",
|
|
41
48
|
"homepage": "https://www.npmjs.com/package/@oneinbox/web-sdk",
|
|
42
49
|
"license": "MIT",
|