@oshara/voice-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +198 -0
  2. package/dist/appearance-CNWT8x1G.cjs +2 -0
  3. package/dist/appearance-CNWT8x1G.cjs.map +1 -0
  4. package/dist/appearance-i6QBkpCk.js +650 -0
  5. package/dist/appearance-i6QBkpCk.js.map +1 -0
  6. package/dist/consent-CK9VXNPa.js +54 -0
  7. package/dist/consent-CK9VXNPa.js.map +1 -0
  8. package/dist/consent-D7QNSkQD.cjs +2 -0
  9. package/dist/consent-D7QNSkQD.cjs.map +1 -0
  10. package/dist/core/analytics.d.ts +30 -0
  11. package/dist/core/appearance.d.ts +113 -0
  12. package/dist/core/audioSettings.d.ts +69 -0
  13. package/dist/core/consent.d.ts +17 -0
  14. package/dist/core/createVoiceAgent.d.ts +79 -0
  15. package/dist/core/events.d.ts +103 -0
  16. package/dist/core/formController.d.ts +28 -0
  17. package/dist/core/forms.d.ts +235 -0
  18. package/dist/core/index.d.ts +29 -0
  19. package/dist/core/prevContext.d.ts +26 -0
  20. package/dist/core/transport.d.ts +30 -0
  21. package/dist/core/types.d.ts +49 -0
  22. package/dist/core/voice.d.ts +79 -0
  23. package/dist/createVoiceAgent-BM3HODS6.js +1058 -0
  24. package/dist/createVoiceAgent-BM3HODS6.js.map +1 -0
  25. package/dist/createVoiceAgent-CJWxWzz6.cjs +4 -0
  26. package/dist/createVoiceAgent-CJWxWzz6.cjs.map +1 -0
  27. package/dist/index.cjs +2 -0
  28. package/dist/index.cjs.map +1 -0
  29. package/dist/index.js +44 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/react/index.d.ts +60 -0
  32. package/dist/react.cjs +2 -0
  33. package/dist/react.cjs.map +1 -0
  34. package/dist/react.js +115 -0
  35. package/dist/react.js.map +1 -0
  36. package/dist/styles.css +1838 -0
  37. package/dist/ui/index.d.ts +21 -0
  38. package/dist/ui/ui.d.ts +165 -0
  39. package/dist/ui.cjs +284 -0
  40. package/dist/ui.cjs.map +1 -0
  41. package/dist/ui.js +1153 -0
  42. package/dist/ui.js.map +1 -0
  43. package/package.json +67 -0
  44. package/src/core/analytics.ts +111 -0
  45. package/src/core/appearance.ts +464 -0
  46. package/src/core/audioSettings.ts +180 -0
  47. package/src/core/consent.ts +78 -0
  48. package/src/core/createVoiceAgent.ts +280 -0
  49. package/src/core/events.ts +120 -0
  50. package/src/core/formController.ts +317 -0
  51. package/src/core/forms.ts +861 -0
  52. package/src/core/index.ts +121 -0
  53. package/src/core/prevContext.ts +153 -0
  54. package/src/core/transport.ts +118 -0
  55. package/src/core/types.ts +66 -0
  56. package/src/core/voice.ts +1179 -0
  57. package/src/react/index.ts +238 -0
  58. package/src/ui/index.ts +507 -0
  59. package/src/ui/styles.css +1838 -0
  60. package/src/ui/ui.ts +1672 -0
  61. package/src/vite-env.d.ts +10 -0
@@ -0,0 +1,103 @@
1
+ import { AppearanceConfig } from './appearance';
2
+ import { FormDefinition, FieldValidationError } from './forms';
3
+ import { AudioStateSnapshot } from './voice';
4
+ import { OrbState, ConnectionPhase, ControlsState } from './types';
5
+ export interface VoiceAgentEvents {
6
+ /** Orb state machine + contextual status line (e.g. "Searching…"). */
7
+ state: {
8
+ orb: OrbState;
9
+ statusLabel: string | null;
10
+ };
11
+ /** Free-form call status text (Connecting…, Connected, Muted, errors). */
12
+ "call:status": {
13
+ status: string;
14
+ };
15
+ /** Remaining call time in ms, or null to hide the timer. */
16
+ "call:timer": {
17
+ remainingMs: number | null;
18
+ };
19
+ /** Connection lifecycle. UI maps this to which screen to show. */
20
+ connection: {
21
+ phase: ConnectionPhase;
22
+ error?: string;
23
+ };
24
+ /** Which call controls should be enabled. */
25
+ controls: ControlsState;
26
+ /** A transcript segment (interim or final) for either party. */
27
+ transcript: {
28
+ role: "user" | "agent";
29
+ segmentId: string;
30
+ text: string;
31
+ isFinal: boolean;
32
+ };
33
+ /** Clear all transcript content (new call). */
34
+ "transcript:clear": Record<string, never>;
35
+ /** A system transcript line (e.g. agent handoff transition message). */
36
+ "transcript:system": {
37
+ text: string;
38
+ };
39
+ /** Mute state changed. */
40
+ mute: {
41
+ muted: boolean;
42
+ };
43
+ /** Audio preferences / applied settings snapshot changed. */
44
+ audio: AudioStateSnapshot;
45
+ /** Raw JSON data message received from the agent over LiveKit. */
46
+ data: {
47
+ data: unknown;
48
+ topic: string | undefined;
49
+ };
50
+ /** A form should be shown (agent-triggered or programmatic). */
51
+ "form:show": {
52
+ definition: FormDefinition;
53
+ draft: Record<string, string>;
54
+ stepIndex: number;
55
+ inCall: boolean;
56
+ transcriptionEnabled: boolean;
57
+ };
58
+ /** The form values / current step changed (agent merge or step nav). */
59
+ "form:update": {
60
+ values: Record<string, string>;
61
+ stepIndex: number;
62
+ };
63
+ /** Client-side validation failed; render these inline errors. */
64
+ "form:validation": {
65
+ errors: FieldValidationError[];
66
+ };
67
+ /** A submit POST is in flight. */
68
+ "form:submitting": Record<string, never>;
69
+ /** A form was submitted successfully. */
70
+ "form:submitted": {
71
+ formId: string;
72
+ values: Record<string, string>;
73
+ successMessage: string;
74
+ };
75
+ /** A submit POST failed. */
76
+ "form:error": {
77
+ message: string;
78
+ };
79
+ /** The active form was closed. */
80
+ "form:close": Record<string, never>;
81
+ /** Mid-call handoff to another agent. */
82
+ "agent:handoff": {
83
+ agentName: string;
84
+ };
85
+ /** Appearance config resolved (after init fetch). */
86
+ appearance: AppearanceConfig;
87
+ /** A non-fatal error occurred in some scope. */
88
+ error: {
89
+ scope: string;
90
+ error: Error;
91
+ };
92
+ }
93
+ export type EventName = keyof VoiceAgentEvents;
94
+ export type EventHandler<K extends EventName> = (payload: VoiceAgentEvents[K]) => void;
95
+ export type Emit = <K extends EventName>(event: K, payload: VoiceAgentEvents[K]) => void;
96
+ /** Minimal typed event emitter. No deps, works in browser and Node. */
97
+ export declare class Emitter {
98
+ private listeners;
99
+ on<K extends EventName>(event: K, handler: EventHandler<K>): () => void;
100
+ off<K extends EventName>(event: K, handler: EventHandler<K>): void;
101
+ emit: Emit;
102
+ clear(): void;
103
+ }
@@ -0,0 +1,28 @@
1
+ import { Emit } from './events';
2
+ import { FormDefinition } from './forms';
3
+ import { VoiceController } from './voice';
4
+ export interface FormControllerOptions {
5
+ emit: Emit;
6
+ voice: VoiceController;
7
+ apiUrl: string;
8
+ agentSlug: string;
9
+ apiKey?: string;
10
+ fetch?: typeof fetch;
11
+ }
12
+ export interface FormController {
13
+ open: (definition: FormDefinition, draft?: Record<string, string>) => void;
14
+ merge: (draft: Record<string, string>) => void;
15
+ close: () => void;
16
+ current: () => string | null;
17
+ step: (direction: "next" | "back" | number) => void;
18
+ submit: () => void;
19
+ /** Push on-screen edits into the values model (replaces readFormValues). */
20
+ updateValues: (values: Record<string, string>) => void;
21
+ /** Snapshot of the active form, or null. */
22
+ getActive: () => {
23
+ definition: FormDefinition;
24
+ values: Record<string, string>;
25
+ stepIndex: number;
26
+ } | null;
27
+ }
28
+ export declare function createFormController(opts: FormControllerOptions): FormController;
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Agent-driven form support for the chat widget.
3
+ *
4
+ * The voice agent can prompt the visitor to fill a structured form (book a
5
+ * demo, table reservation, lead capture, etc.) by publishing a LiveKit data
6
+ * message. The widget listens on `RoomEvent.DataReceived`, matches the message
7
+ * against a registered form definition, prefills any draft fields, and opens
8
+ * a review screen so the user can confirm and submit.
9
+ *
10
+ * Form definitions are generic — `book-demo` ships as the default, but
11
+ * additional forms (reservation, support ticket, lead capture, etc.) can be
12
+ * added either by extending DEFAULT_FORM_DEFINITIONS below or by sending them
13
+ * from the backend through the appearance config (`appearance.forms`).
14
+ *
15
+ * ---------------------------------------------------------------------------
16
+ * Agent → widget protocol (LiveKit data message payload, UTF-8 JSON):
17
+ *
18
+ * 1. Topic-based match (preferred):
19
+ * topic: "form.book-demo" | "book-demo.form" | "book-demo.review"
20
+ * payload: { ...draft fields }
21
+ *
22
+ * 2. Form-id field:
23
+ * payload: { form_id: "reservation", ...draft fields }
24
+ *
25
+ * 3. Event-type field (back-compat with the help-desk-call payload shape):
26
+ * payload: { type: "book_demo_form", form: { ...draft fields } }
27
+ *
28
+ * Draft fields can live at the payload root or nested under `form`/`payload`/
29
+ * `data`/`fields`/`values` — extractFormDraft() looks in all of them.
30
+ */
31
+ export type FormFieldType = "text" | "email" | "tel" | "textarea" | "select" | "number" | "date" | "time" | "checkbox" | "radio" | "display";
32
+ /** Option for select/radio/checkbox-group. A plain string is shorthand for { value, label }. */
33
+ export type FormFieldOption = string | {
34
+ value: string;
35
+ label?: string;
36
+ };
37
+ export interface FormFieldDef {
38
+ /** Field key — sent in the POST body and matched against incoming drafts.
39
+ * Not required for `display` blocks (which render no input). */
40
+ name?: string;
41
+ label: string;
42
+ type: FormFieldType;
43
+ placeholder?: string;
44
+ required?: boolean;
45
+ /** Options for select/radio/checkbox-group. */
46
+ options?: FormFieldOption[];
47
+ /** Row count for `type: "textarea"`. */
48
+ rows?: number;
49
+ /** Optional default applied when the form is opened with no draft value. */
50
+ default_value?: string;
51
+ /** Helper text rendered beneath the input (or as the body of a `display` block). */
52
+ help_text?: string;
53
+ /** Optional regex (string form) to validate text-like fields on submit. */
54
+ pattern?: string;
55
+ /** Min/max for `type: "number"` (or min/max length for text — not enforced in v1). */
56
+ min?: number;
57
+ max?: number;
58
+ /** Layout width when the form's `field_layout: "grid"`. "full" (default) or "half". */
59
+ width?: "full" | "half";
60
+ }
61
+ export interface FormStep {
62
+ id: string;
63
+ title?: string;
64
+ subtitle?: string;
65
+ fields: FormFieldDef[];
66
+ next_label?: string;
67
+ back_label?: string;
68
+ }
69
+ export interface FormLayout {
70
+ field_layout?: "stack" | "grid";
71
+ density?: "comfortable" | "compact";
72
+ label_position?: "top" | "inline";
73
+ }
74
+ export interface FormDefinition {
75
+ /** Stable identifier — used to match topics, event types, and form_id. */
76
+ id: string;
77
+ title: string;
78
+ subtitle?: string;
79
+ /** Single-page fields. Ignored when `steps` is set. */
80
+ fields: FormFieldDef[];
81
+ /** When set, the form renders as a stepper wizard. */
82
+ steps?: FormStep[];
83
+ /** Form-level layout knobs. */
84
+ layout?: FormLayout;
85
+ /** When true, the auto-derived agent tool is skipped (form is hidden from the LLM). */
86
+ disabled?: boolean;
87
+ /**
88
+ * Where to POST the form on submit. Absolute URL (starts with http) is
89
+ * used as-is; otherwise treated as a path joined onto BootConfig.apiUrl.
90
+ * When null, the platform stores the response in managed storage.
91
+ */
92
+ submit_url: string | null;
93
+ submit_method?: "POST" | "PUT" | "PATCH";
94
+ submit_label?: string;
95
+ success_message?: string;
96
+ /** Extra LiveKit topics to match against (in addition to the id-based ones). */
97
+ topics?: string[];
98
+ /** Extra event-type aliases to match (e.g. legacy "book_demo_form"). */
99
+ event_types?: string[];
100
+ /**
101
+ * LiveKit topic used when echoing the confirmation back to the agent.
102
+ * Defaults to "voice.user_text".
103
+ */
104
+ confirmation_topic?: string;
105
+ /**
106
+ * `type` field on the confirmation payload sent back to the agent.
107
+ * Defaults to `<id>_submitted`.
108
+ */
109
+ confirmation_type?: string;
110
+ }
111
+ export interface FormSession {
112
+ definition: FormDefinition;
113
+ values: Record<string, string>;
114
+ }
115
+ /**
116
+ * Minimal surface a form controller must expose so the agent can drive it
117
+ * via data-channel messages (step / submit / close). The widget's
118
+ * `createFormController` returns an object that satisfies this shape; we
119
+ * keep the type narrow so the dispatcher below can't accidentally reach
120
+ * into internals.
121
+ */
122
+ export interface FormActionTarget {
123
+ current: () => string | null;
124
+ step: (direction: "next" | "back" | number) => void;
125
+ submit: () => void;
126
+ close: () => void;
127
+ }
128
+ /**
129
+ * Snapshot of the open form that the widget publishes back to the agent on
130
+ * every meaningful change (field edit, step move, open, close). Lives on
131
+ * the `form.state` LiveKit topic.
132
+ */
133
+ /** Compact field descriptor sent to the agent so it can build accurate,
134
+ * enum-constrained voice-fill tools for forms that aren't defined in the
135
+ * session token (appearance.forms). Mirrors the subset of FormFieldDef the
136
+ * agent's _field_to_json_schema needs. */
137
+ export interface FormFieldSchema {
138
+ name: string;
139
+ label: string;
140
+ type: FormFieldType;
141
+ required: boolean;
142
+ /** Zero-based step this field lives on (0 for single-page forms). Lets the
143
+ * agent guide the visitor through a stepper one step at a time. */
144
+ step: number;
145
+ options?: {
146
+ value: string;
147
+ label: string;
148
+ }[];
149
+ /** Validation rules mirrored so the agent can reject malformed input in its
150
+ * submit guard — before claiming success — exactly like the widget does. */
151
+ pattern?: string;
152
+ min?: number;
153
+ max?: number;
154
+ }
155
+ export interface FormStateSnapshot {
156
+ type: "form_state";
157
+ form_id: string;
158
+ is_open: boolean;
159
+ step_index: number;
160
+ total_steps: number;
161
+ values: Record<string, string>;
162
+ /** Schema of the form's input fields, so the agent can register
163
+ * enum-aware render tools even without appearance.forms in the token. */
164
+ fields: FormFieldSchema[];
165
+ }
166
+ /** Build the compact field schema the agent needs to construct voice-fill
167
+ * tools (enum values for select/radio/checkbox, types for the rest) and to
168
+ * guide the visitor through a multi-step form one step at a time. */
169
+ export declare function buildFieldSchema(definition: FormDefinition): FormFieldSchema[];
170
+ /**
171
+ * Topic pattern the agent uses to drive form actions: `form.{id}.action`.
172
+ * Payload carries `{type: "form_step"|"form_submit"|"form_close", form_id,
173
+ * direction?, step_index?}`.
174
+ *
175
+ * Returns true if the message was a form-action and was dispatched (so the
176
+ * caller can short-circuit further matching), false otherwise.
177
+ */
178
+ export declare function handleFormAction(topic: string | undefined, value: unknown, target: FormActionTarget): boolean;
179
+ /** Flatten a form to its full field list — single-page or stepper. Excludes display blocks. */
180
+ export declare function collectInputFields(definition: FormDefinition): FormFieldDef[];
181
+ /** Fields for a specific step (or the whole form when no stepper). */
182
+ export declare function fieldsForStep(definition: FormDefinition, stepIndex: number): FormFieldDef[];
183
+ export declare function totalSteps(definition: FormDefinition): number;
184
+ /** A single field that failed local validation. */
185
+ export interface FieldValidationError {
186
+ name: string;
187
+ label: string;
188
+ message: string;
189
+ }
190
+ /**
191
+ * Validate the given values against their field definitions, returning one
192
+ * error per offending field. Runs entirely in the browser so obvious mistakes
193
+ * (missing required fields, malformed email/phone, out-of-range numbers, custom
194
+ * pattern mismatches) are caught before anything is sent to the server/agent.
195
+ *
196
+ * Format checks are skipped for empty optional fields — only `required` flags
197
+ * empties. `display` blocks and unnamed fields are ignored.
198
+ */
199
+ export declare function validateFields(fields: FormFieldDef[], values: Record<string, string>): FieldValidationError[];
200
+ export declare const DEFAULT_FORM_DEFINITIONS: FormDefinition[];
201
+ /**
202
+ * Try to identify which form definition an incoming LiveKit data message is
203
+ * asking the widget to open. Returns null if no definition matches.
204
+ */
205
+ export declare function matchForm(topic: string | undefined, value: unknown, forms: FormDefinition[]): FormDefinition | null;
206
+ /**
207
+ * Pull a partial field/value draft out of an incoming data message. Looks at
208
+ * the payload root and common nested keys (`form`, `payload`, `data`,
209
+ * `fields`, `values`). Returns null when nothing matches the form's fields.
210
+ */
211
+ export declare function extractFormDraft(value: unknown, definition: FormDefinition): Record<string, string> | null;
212
+ export declare function initialFormValues(definition: FormDefinition): Record<string, string>;
213
+ export declare function mergeFormDraft(current: Record<string, string>, draft: Record<string, string> | null | undefined): Record<string, string>;
214
+ /** Plain-text summary of a submission — echoed back to the agent on confirm. */
215
+ export declare function buildSubmissionText(definition: FormDefinition, values: Record<string, string>): string;
216
+ export interface SubmitFormArgs {
217
+ definition: FormDefinition;
218
+ values: Record<string, string>;
219
+ apiUrl: string;
220
+ /** Agent slug — required when submit_url is null (managed storage path). */
221
+ slug: string;
222
+ /** Active session ID to link the form response to its billing session. */
223
+ sessionId?: string | null;
224
+ /** Secret key sent as `x-api-key` on the managed-storage POST. */
225
+ apiKey?: string;
226
+ /** Custom fetch (Node <18 / testing). Defaults to globalThis.fetch. */
227
+ fetch?: typeof fetch;
228
+ }
229
+ export declare function submitForm({ definition, values, apiUrl, slug, sessionId, apiKey, fetch: fetchImpl, }: SubmitFormArgs): Promise<unknown>;
230
+ /**
231
+ * Accept a partial `forms` array from the appearance config (or anywhere
232
+ * else) and turn it into a clean array of FormDefinitions, dropping entries
233
+ * that don't have an id + at least one field.
234
+ */
235
+ export declare function normalizeFormDefinitions(value: unknown): FormDefinition[];
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @oshara/voice-sdk — headless core.
3
+ *
4
+ * `import { createVoiceAgent } from "@oshara/voice-sdk"` pulls in zero DOM/UI
5
+ * code. Build a custom UI on the events, or mount the prebuilt UI from
6
+ * `@oshara/voice-sdk/ui`.
7
+ */
8
+ export { createVoiceAgent } from './createVoiceAgent';
9
+ export type { VoiceAgentConfig, VoiceAgentClient } from './createVoiceAgent';
10
+ export { Emitter } from './events';
11
+ export type { VoiceAgentEvents, EventName, EventHandler, Emit, } from './events';
12
+ export type { OrbState, SessionInit, ConnectionPhase, ControlsState, DeepFilterUrls, } from './types';
13
+ export { DEFAULT_DEEPFILTER_MODULE_URL } from './types';
14
+ export { createVoiceController, } from './voice';
15
+ export type { VoiceController, VoiceControllerOptions, AudioStateSnapshot, AudioStats, } from './voice';
16
+ export { createFormController, } from './formController';
17
+ export type { FormController } from './formController';
18
+ export { buildFieldSchema, collectInputFields, extractFormDraft, fieldsForStep, handleFormAction, initialFormValues, matchForm, mergeFormDraft, normalizeFormDefinitions, submitForm, totalSteps, validateFields, DEFAULT_FORM_DEFINITIONS, } from './forms';
19
+ export type { FormDefinition, FormFieldDef, FormFieldType, FormFieldOption, FormStep, FormLayout, FormStateSnapshot, FieldValidationError, } from './forms';
20
+ export { fetchAppearance, mergeAppearance, DEFAULT_APPEARANCE, } from './appearance';
21
+ export type { AppearanceConfig, AppearanceTheme, AppearanceDimensions, AppearanceLayout, AppearanceLabels, AppearanceLanguage, WidgetPosition, } from './appearance';
22
+ export { DEFAULT_AUDIO_PREFS, loadAudioPrefs, saveAudioPrefs, enumerateAudioDevices, probeAudioCapabilities, } from './audioSettings';
23
+ export type { AudioPrefs, NoiseFilterEngine, AudioDevices, AudioCapabilities, } from './audioSettings';
24
+ export { loadPrevContext, savePrevContext, clearPrevContext, formatPrevContextForAgent, } from './prevContext';
25
+ export type { PrevTurn } from './prevContext';
26
+ export { loadConsent, saveConsent, hasAcceptedTerms } from './consent';
27
+ export { trackWidgetEvent, getVisitorId } from './analytics';
28
+ export { buildHeaders, fetchSession, warnIfBrowserSecret, } from './transport';
29
+ export type { TransportConfig, FetchSessionArgs } from './transport';
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Per-agent storage for the most-recent conversation transcript, so the
3
+ * agent can be primed with prior context when the user returns.
4
+ *
5
+ * The widget stores the *raw last turns* (not an LLM-generated summary) —
6
+ * the agent-side LLM is perfectly capable of using the transcript as
7
+ * context, and skipping a summarization step keeps the widget standalone.
8
+ */
9
+ export interface PrevTurn {
10
+ role: "user" | "agent";
11
+ text: string;
12
+ }
13
+ export interface PrevContextRecord {
14
+ savedAt: number;
15
+ turns: PrevTurn[];
16
+ /** Agent-generated short user-profile summary (preferred over raw turns
17
+ * when priming the next session). May be empty. */
18
+ summary?: string;
19
+ }
20
+ export declare function loadPrevContext(agentSlug: string): PrevContextRecord | null;
21
+ export declare function savePrevContext(agentSlug: string, turns: PrevTurn[], summary?: string): void;
22
+ export declare function clearPrevContext(agentSlug: string): void;
23
+ /** Format stored context for the agent's system prompt.
24
+ * Prefers the agent-generated user-profile summary when available; falls
25
+ * back to a labelled raw-turn transcript otherwise. */
26
+ export declare function formatPrevContextForAgent(record: PrevContextRecord): string;
@@ -0,0 +1,30 @@
1
+ import { SessionInit } from './types';
2
+ export interface TransportConfig {
3
+ apiUrl: string;
4
+ /** Secret API key (sk_…). Sent as `x-api-key`. Omit for public embeds. */
5
+ apiKey?: string;
6
+ /** Custom fetch (Node <18 / testing). Defaults to globalThis.fetch. */
7
+ fetch?: typeof fetch;
8
+ }
9
+ /** Merge auth headers onto a base header set. */
10
+ export declare function buildHeaders(config: Pick<TransportConfig, "apiKey">, base?: Record<string, string>): Record<string, string>;
11
+ /** Resolve a usable fetch implementation or throw a helpful error. */
12
+ export declare function resolveFetch(fetchImpl?: typeof fetch): typeof fetch;
13
+ /**
14
+ * Warn when a secret key is embedded in browser JS, where it is exposed to
15
+ * anyone who views source. Call once at init.
16
+ */
17
+ export declare function warnIfBrowserSecret(apiKey?: string): void;
18
+ export interface FetchSessionArgs {
19
+ apiUrl: string;
20
+ agentSlug: string;
21
+ language: string;
22
+ apiKey?: string;
23
+ fetch?: typeof fetch;
24
+ /** Page URL recorded server-side; defaults to window.location.href. */
25
+ originUrl?: string;
26
+ /** Optional extra fields honored only with a valid x-api-key (system_prompt, etc.). */
27
+ extra?: Record<string, unknown>;
28
+ }
29
+ /** Mint a LiveKit session token. Moved verbatim from the widget's fetchSession. */
30
+ export declare function fetchSession(args: FetchSessionArgs): Promise<SessionInit>;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Cross-cutting types shared between the headless core, the prebuilt UI, and
3
+ * external SDK consumers. Kept free of any DOM or LiveKit imports so this
4
+ * module is safe to import from anywhere (including Node).
5
+ */
6
+ /** Visual state of the agent orb / call indicator. */
7
+ export type OrbState = "idle" | "listening" | "speaking" | "connecting" | "thinking";
8
+ /** Shape returned by Django's `POST /api/agents/agent-session/`. */
9
+ export interface SessionInit {
10
+ token: string;
11
+ livekit_url: string;
12
+ room_name: string;
13
+ participant_identity: string;
14
+ expires_in_seconds: number;
15
+ session_id: string;
16
+ character_slug: string;
17
+ system_prompt: string;
18
+ greeting: string;
19
+ voice_inference_url: string;
20
+ }
21
+ /** High-level connection lifecycle phase, surfaced via the `connection` event. */
22
+ export type ConnectionPhase = "connecting" | "connected" | "reconnecting" | "disconnected" | "failed";
23
+ /**
24
+ * Which call controls the UI should enable. Replaces the direct
25
+ * `refs.startBtn.disabled = …` side effects the controller used to perform.
26
+ */
27
+ export interface ControlsState {
28
+ canStart: boolean;
29
+ canMute: boolean;
30
+ canEnd: boolean;
31
+ }
32
+ /**
33
+ * DeepFilterNet3 asset overrides. Mirrors the widget's boot config exactly,
34
+ * including precedence: `wasmUrl`/`onnxUrl` take priority over `cdnUrl`.
35
+ * Passed to the voice controller per-instance (no module-level globals) so
36
+ * multiple agents can coexist on one page.
37
+ */
38
+ export interface DeepFilterUrls {
39
+ /** Base CDN URL; the package appends `/v2/pkg/df_bg.wasm` etc. Empty = default. */
40
+ cdnUrl?: string;
41
+ /** Direct URL to `df_bg.wasm`. Takes precedence over `cdnUrl`. */
42
+ wasmUrl?: string;
43
+ /** Direct URL to `DeepFilterNet3_onnx.tar.gz`. Takes precedence over `cdnUrl`. */
44
+ onnxUrl?: string;
45
+ /** Where the DeepFilterNet3 ESM module is loaded from. Empty = esm.sh default. */
46
+ moduleUrl?: string;
47
+ }
48
+ /** Default DeepFilterNet3 ESM module mirror (esm.sh) when none is configured. */
49
+ export declare const DEFAULT_DEEPFILTER_MODULE_URL = "https://esm.sh/deepfilternet3-noise-filter@1.2.1";
@@ -0,0 +1,79 @@
1
+ import { AudioPrefs, NoiseFilterEngine } from './audioSettings';
2
+ import { AppearanceConfig } from './appearance';
3
+ import { Emit } from './events';
4
+ import { DeepFilterUrls, SessionInit } from './types';
5
+ export type { SessionInit } from './types';
6
+ /**
7
+ * Snapshot reported back to the UI after the mic publishes or when any
8
+ * audio setting changes. The `applied*` flags are read from the live
9
+ * MediaStreamTrack via getSettings(), so they reflect what the browser
10
+ * actually honored — which may differ from what we requested.
11
+ */
12
+ export interface AudioStateSnapshot {
13
+ prefs: AudioPrefs;
14
+ applied: {
15
+ echoCancellation: boolean | undefined;
16
+ noiseSuppression: boolean | undefined;
17
+ autoGainControl: boolean | undefined;
18
+ voiceIsolation: boolean | undefined;
19
+ sampleRate: number | undefined;
20
+ channelCount: number | undefined;
21
+ deviceId: string | undefined;
22
+ };
23
+ /**
24
+ * Effective state of the deep-learning NS engine:
25
+ * - `engine` — what the user picked (off / krisp / deepfilter).
26
+ * - `status` — what actually happened: "active" if the processor is
27
+ * attached, "unsupported" if the chosen engine isn't available in
28
+ * this browser, "failed" if attach errored, "off" if engine === "off".
29
+ */
30
+ noiseFilter: {
31
+ engine: NoiseFilterEngine;
32
+ status: "active" | "off" | "unsupported" | "failed";
33
+ };
34
+ }
35
+ export interface AudioStats {
36
+ /** Local outbound audio level (0-1) from RTCStats. */
37
+ outboundAudioLevel: number;
38
+ /** Remote inbound audio level (0-1) from RTCStats. */
39
+ inboundAudioLevel: number;
40
+ /** Packets lost on inbound (agent → user) stream. */
41
+ packetsLost: number;
42
+ /** Inbound jitter in ms. */
43
+ jitter: number;
44
+ /** Round-trip time in ms (peer connection). */
45
+ roundTripTime: number;
46
+ }
47
+ export interface VoiceController {
48
+ start: () => Promise<void>;
49
+ end: () => Promise<void>;
50
+ toggleMute: () => Promise<boolean>;
51
+ isActive: () => boolean;
52
+ /** Returns the session_id for the active session, or null if no session is running. */
53
+ sessionId: () => string | null;
54
+ /** Publish a JSON data message back to the agent (used after form submit). */
55
+ publishData: (payload: unknown, topic: string) => Promise<void>;
56
+ /** Apply a partial update to the audio preferences (live, no reconnect). */
57
+ updateAudioSettings: (delta: Partial<AudioPrefs>) => Promise<AudioStateSnapshot>;
58
+ /** Read the current audio state (preferences + actually-applied values). */
59
+ getAudioState: () => AudioStateSnapshot;
60
+ /** Poll a snapshot of audio RTC stats (returns null if no call). */
61
+ getAudioStats: () => Promise<AudioStats | null>;
62
+ }
63
+ export interface VoiceControllerOptions {
64
+ /** Mint a LiveKit session (POST /api/agents/agent-session/). */
65
+ fetchSession: () => Promise<SessionInit>;
66
+ /** AICharacter slug — namespaces persisted prefs / context. */
67
+ agentSlug: string;
68
+ /** Read the live appearance config (labels, max_call_seconds). */
69
+ getAppearance: () => AppearanceConfig;
70
+ /** Typed event emitter — replaces all the old direct UI calls. */
71
+ emit: Emit;
72
+ /** Per-instance DeepFilterNet3 asset overrides. */
73
+ deepFilter?: DeepFilterUrls;
74
+ /** Pre-resolved audio prefs to start from. Defaults to loadAudioPrefs(slug). */
75
+ initialPrefs?: AudioPrefs;
76
+ /** Persist pref changes to localStorage (default true). */
77
+ persistPrefs?: boolean;
78
+ }
79
+ export declare function createVoiceController(opts: VoiceControllerOptions): VoiceController;