@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.
- package/README.md +198 -0
- package/dist/appearance-CNWT8x1G.cjs +2 -0
- package/dist/appearance-CNWT8x1G.cjs.map +1 -0
- package/dist/appearance-i6QBkpCk.js +650 -0
- package/dist/appearance-i6QBkpCk.js.map +1 -0
- package/dist/consent-CK9VXNPa.js +54 -0
- package/dist/consent-CK9VXNPa.js.map +1 -0
- package/dist/consent-D7QNSkQD.cjs +2 -0
- package/dist/consent-D7QNSkQD.cjs.map +1 -0
- package/dist/core/analytics.d.ts +30 -0
- package/dist/core/appearance.d.ts +113 -0
- package/dist/core/audioSettings.d.ts +69 -0
- package/dist/core/consent.d.ts +17 -0
- package/dist/core/createVoiceAgent.d.ts +79 -0
- package/dist/core/events.d.ts +103 -0
- package/dist/core/formController.d.ts +28 -0
- package/dist/core/forms.d.ts +235 -0
- package/dist/core/index.d.ts +29 -0
- package/dist/core/prevContext.d.ts +26 -0
- package/dist/core/transport.d.ts +30 -0
- package/dist/core/types.d.ts +49 -0
- package/dist/core/voice.d.ts +79 -0
- package/dist/createVoiceAgent-BM3HODS6.js +1058 -0
- package/dist/createVoiceAgent-BM3HODS6.js.map +1 -0
- package/dist/createVoiceAgent-CJWxWzz6.cjs +4 -0
- package/dist/createVoiceAgent-CJWxWzz6.cjs.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.d.ts +60 -0
- package/dist/react.cjs +2 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.js +115 -0
- package/dist/react.js.map +1 -0
- package/dist/styles.css +1838 -0
- package/dist/ui/index.d.ts +21 -0
- package/dist/ui/ui.d.ts +165 -0
- package/dist/ui.cjs +284 -0
- package/dist/ui.cjs.map +1 -0
- package/dist/ui.js +1153 -0
- package/dist/ui.js.map +1 -0
- package/package.json +67 -0
- package/src/core/analytics.ts +111 -0
- package/src/core/appearance.ts +464 -0
- package/src/core/audioSettings.ts +180 -0
- package/src/core/consent.ts +78 -0
- package/src/core/createVoiceAgent.ts +280 -0
- package/src/core/events.ts +120 -0
- package/src/core/formController.ts +317 -0
- package/src/core/forms.ts +861 -0
- package/src/core/index.ts +121 -0
- package/src/core/prevContext.ts +153 -0
- package/src/core/transport.ts +118 -0
- package/src/core/types.ts +66 -0
- package/src/core/voice.ts +1179 -0
- package/src/react/index.ts +238 -0
- package/src/ui/index.ts +507 -0
- package/src/ui/styles.css +1838 -0
- package/src/ui/ui.ts +1672 -0
- 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;
|