@oneinbox/web-sdk 0.1.0-beta.1 → 0.1.0-beta.2
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/dist/index.cjs +40 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +41 -1
- package/dist/index.js.map +1 -1
- package/package.json +10 -3
package/dist/index.cjs
CHANGED
|
@@ -51,6 +51,9 @@ var OneInbox = class extends TypedEmitter {
|
|
|
51
51
|
_muted = false;
|
|
52
52
|
volumeTimer = null;
|
|
53
53
|
speaking = /* @__PURE__ */ new Set();
|
|
54
|
+
// Audio <audio> elements created for the agent's remote tracks, so we can
|
|
55
|
+
// play them and tear them down on call end.
|
|
56
|
+
audioElements = /* @__PURE__ */ new Set();
|
|
54
57
|
/** Current high-level call status. */
|
|
55
58
|
get status() {
|
|
56
59
|
return this._status;
|
|
@@ -97,6 +100,15 @@ var OneInbox = class extends TypedEmitter {
|
|
|
97
100
|
this.setStatus("ending");
|
|
98
101
|
await this.room.disconnect();
|
|
99
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Resume audio playback if the browser blocked autoplay (call from a click /
|
|
105
|
+
* tap handler). No-op when audio is already playing.
|
|
106
|
+
*/
|
|
107
|
+
async resumeAudio() {
|
|
108
|
+
if (this.room && !this.room.canPlaybackAudio) {
|
|
109
|
+
await this.room.startAudio();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
100
112
|
/** Mute/unmute the local microphone (fire-and-forget, like Vapi's setMuted). */
|
|
101
113
|
setMuted(muted) {
|
|
102
114
|
this._muted = muted;
|
|
@@ -173,6 +185,28 @@ var OneInbox = class extends TypedEmitter {
|
|
|
173
185
|
room.on(livekitClient.RoomEvent.ConnectionStateChanged, (state) => {
|
|
174
186
|
this.emit("connection-state", state);
|
|
175
187
|
});
|
|
188
|
+
room.on(livekitClient.RoomEvent.TrackSubscribed, (track) => {
|
|
189
|
+
if (track.kind !== livekitClient.Track.Kind.Audio) return;
|
|
190
|
+
const el = track.attach();
|
|
191
|
+
el.setAttribute("data-oneinbox", "agent-audio");
|
|
192
|
+
if (typeof document !== "undefined") document.body.appendChild(el);
|
|
193
|
+
this.audioElements.add(el);
|
|
194
|
+
});
|
|
195
|
+
room.on(livekitClient.RoomEvent.TrackUnsubscribed, (track) => {
|
|
196
|
+
if (track.kind !== livekitClient.Track.Kind.Audio) return;
|
|
197
|
+
for (const el of track.detach()) {
|
|
198
|
+
el.remove();
|
|
199
|
+
this.audioElements.delete(el);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
room.on(livekitClient.RoomEvent.AudioPlaybackStatusChanged, () => {
|
|
203
|
+
if (!room.canPlaybackAudio) {
|
|
204
|
+
this.emit("error", {
|
|
205
|
+
code: "AUDIO_PLAYBACK_BLOCKED",
|
|
206
|
+
message: "Browser blocked audio autoplay \u2014 call resumeAudio() after a user gesture."
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
});
|
|
176
210
|
room.on(livekitClient.RoomEvent.TranscriptionReceived, (segments, participant) => {
|
|
177
211
|
const role = roleFor(participant);
|
|
178
212
|
for (const seg of segments) {
|
|
@@ -221,6 +255,12 @@ var OneInbox = class extends TypedEmitter {
|
|
|
221
255
|
teardown(reason) {
|
|
222
256
|
this.stopVolumeSampling();
|
|
223
257
|
this.speaking.clear();
|
|
258
|
+
for (const el of this.audioElements) {
|
|
259
|
+
el.pause();
|
|
260
|
+
el.srcObject = null;
|
|
261
|
+
el.remove();
|
|
262
|
+
}
|
|
263
|
+
this.audioElements.clear();
|
|
224
264
|
this.room = null;
|
|
225
265
|
this.setStatus("idle");
|
|
226
266
|
this.emit("call-end", reason);
|
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/emitter.ts","../src/index.ts"],"names":["Room","RoomEvent","Track","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 // 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 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 /**\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 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 // 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 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/dist/index.d.cts
CHANGED
|
@@ -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;
|
|
@@ -118,6 +119,11 @@ declare class OneInbox extends TypedEmitter<OneInboxEvents> {
|
|
|
118
119
|
start(opts: TokenStartOptions): Promise<void>;
|
|
119
120
|
/** End the call and release resources. The backend marks the call completed. */
|
|
120
121
|
stop(): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* Resume audio playback if the browser blocked autoplay (call from a click /
|
|
124
|
+
* tap handler). No-op when audio is already playing.
|
|
125
|
+
*/
|
|
126
|
+
resumeAudio(): Promise<void>;
|
|
121
127
|
/** Mute/unmute the local microphone (fire-and-forget, like Vapi's setMuted). */
|
|
122
128
|
setMuted(muted: boolean): void;
|
|
123
129
|
private fetchToken;
|
package/dist/index.d.ts
CHANGED
|
@@ -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;
|
|
@@ -118,6 +119,11 @@ declare class OneInbox extends TypedEmitter<OneInboxEvents> {
|
|
|
118
119
|
start(opts: TokenStartOptions): Promise<void>;
|
|
119
120
|
/** End the call and release resources. The backend marks the call completed. */
|
|
120
121
|
stop(): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* Resume audio playback if the browser blocked autoplay (call from a click /
|
|
124
|
+
* tap handler). No-op when audio is already playing.
|
|
125
|
+
*/
|
|
126
|
+
resumeAudio(): Promise<void>;
|
|
121
127
|
/** Mute/unmute the local microphone (fire-and-forget, like Vapi's setMuted). */
|
|
122
128
|
setMuted(muted: boolean): void;
|
|
123
129
|
private fetchToken;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Room, RoomEvent, DisconnectReason } from 'livekit-client';
|
|
1
|
+
import { Room, RoomEvent, Track, DisconnectReason } from 'livekit-client';
|
|
2
2
|
export { ConnectionState, DisconnectReason, RoomEvent, Track } from 'livekit-client';
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
@@ -50,6 +50,9 @@ var OneInbox = class extends TypedEmitter {
|
|
|
50
50
|
_muted = false;
|
|
51
51
|
volumeTimer = null;
|
|
52
52
|
speaking = /* @__PURE__ */ new Set();
|
|
53
|
+
// Audio <audio> elements created for the agent's remote tracks, so we can
|
|
54
|
+
// play them and tear them down on call end.
|
|
55
|
+
audioElements = /* @__PURE__ */ new Set();
|
|
53
56
|
/** Current high-level call status. */
|
|
54
57
|
get status() {
|
|
55
58
|
return this._status;
|
|
@@ -96,6 +99,15 @@ var OneInbox = class extends TypedEmitter {
|
|
|
96
99
|
this.setStatus("ending");
|
|
97
100
|
await this.room.disconnect();
|
|
98
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Resume audio playback if the browser blocked autoplay (call from a click /
|
|
104
|
+
* tap handler). No-op when audio is already playing.
|
|
105
|
+
*/
|
|
106
|
+
async resumeAudio() {
|
|
107
|
+
if (this.room && !this.room.canPlaybackAudio) {
|
|
108
|
+
await this.room.startAudio();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
99
111
|
/** Mute/unmute the local microphone (fire-and-forget, like Vapi's setMuted). */
|
|
100
112
|
setMuted(muted) {
|
|
101
113
|
this._muted = muted;
|
|
@@ -172,6 +184,28 @@ var OneInbox = class extends TypedEmitter {
|
|
|
172
184
|
room.on(RoomEvent.ConnectionStateChanged, (state) => {
|
|
173
185
|
this.emit("connection-state", state);
|
|
174
186
|
});
|
|
187
|
+
room.on(RoomEvent.TrackSubscribed, (track) => {
|
|
188
|
+
if (track.kind !== Track.Kind.Audio) return;
|
|
189
|
+
const el = track.attach();
|
|
190
|
+
el.setAttribute("data-oneinbox", "agent-audio");
|
|
191
|
+
if (typeof document !== "undefined") document.body.appendChild(el);
|
|
192
|
+
this.audioElements.add(el);
|
|
193
|
+
});
|
|
194
|
+
room.on(RoomEvent.TrackUnsubscribed, (track) => {
|
|
195
|
+
if (track.kind !== Track.Kind.Audio) return;
|
|
196
|
+
for (const el of track.detach()) {
|
|
197
|
+
el.remove();
|
|
198
|
+
this.audioElements.delete(el);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
room.on(RoomEvent.AudioPlaybackStatusChanged, () => {
|
|
202
|
+
if (!room.canPlaybackAudio) {
|
|
203
|
+
this.emit("error", {
|
|
204
|
+
code: "AUDIO_PLAYBACK_BLOCKED",
|
|
205
|
+
message: "Browser blocked audio autoplay \u2014 call resumeAudio() after a user gesture."
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
});
|
|
175
209
|
room.on(RoomEvent.TranscriptionReceived, (segments, participant) => {
|
|
176
210
|
const role = roleFor(participant);
|
|
177
211
|
for (const seg of segments) {
|
|
@@ -220,6 +254,12 @@ var OneInbox = class extends TypedEmitter {
|
|
|
220
254
|
teardown(reason) {
|
|
221
255
|
this.stopVolumeSampling();
|
|
222
256
|
this.speaking.clear();
|
|
257
|
+
for (const el of this.audioElements) {
|
|
258
|
+
el.pause();
|
|
259
|
+
el.srcObject = null;
|
|
260
|
+
el.remove();
|
|
261
|
+
}
|
|
262
|
+
this.audioElements.clear();
|
|
223
263
|
this.room = null;
|
|
224
264
|
this.setStatus("idle");
|
|
225
265
|
this.emit("call-end", reason);
|
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/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 // 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 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 /**\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 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 // 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 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
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.2",
|
|
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",
|