@origonai/web-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.
@@ -0,0 +1,472 @@
1
+ export declare interface Attachment {
2
+ id: string;
3
+ name: string;
4
+ contentType: string;
5
+ url: string;
6
+ /**
7
+ * Client-only preview source (`blob:`, `file://`) the UI can render
8
+ * immediately. Stripped from outgoing wire bodies, preserved across
9
+ * event boundaries.
10
+ */
11
+ localUrl?: string;
12
+ }
13
+
14
+ export declare type AttachmentErrorCode = 'EMPTY_FILE' | 'POLICY_UNSUPPORTED_TYPE' | 'POLICY_TYPE_DISABLED' | 'POLICY_TOO_LARGE';
15
+
16
+ export declare interface AttachmentPolicy {
17
+ images: AttachmentRule;
18
+ documents: AttachmentRule;
19
+ videos: AttachmentRule;
20
+ audio: AttachmentRule;
21
+ }
22
+
23
+ export declare interface AttachmentRule {
24
+ enabled: boolean;
25
+ /** Maximum allowed size in megabytes. */
26
+ maxSize: number;
27
+ }
28
+
29
+ /** Per-frame stats emitted by the worklet (~5 Hz default). */
30
+ export declare interface AudioWorkletStats {
31
+ pipeline: unknown;
32
+ captureAlias: number | null;
33
+ outboundLevel: number;
34
+ inboundLevel: number;
35
+ /** Inbound frames dropped by the worklet's bounded queue (cumulative). */
36
+ droppedInbound: number;
37
+ }
38
+
39
+ export declare type Channel = 'chat' | 'voice';
40
+
41
+ export declare class ClientError extends Error {
42
+ readonly kind: ClientErrorKind;
43
+ readonly status?: number;
44
+ readonly code?: string;
45
+ constructor(json: ClientErrorJSON);
46
+ toJSON(): ClientErrorJSON;
47
+ static notInitialized(): ClientError;
48
+ static noSession(sessionId: string): ClientError;
49
+ static missingField(field: string): ClientError;
50
+ static serverUnavailable(status: number): ClientError;
51
+ static http(status: number, code: string, message: string): ClientError;
52
+ static attachment(code: AttachmentErrorCode, message: string): ClientError;
53
+ static cancelled(): ClientError;
54
+ static other(message: string): ClientError;
55
+ static session(message: string): ClientError;
56
+ }
57
+
58
+ export declare interface ClientErrorJSON {
59
+ kind: ClientErrorKind;
60
+ status?: number;
61
+ code?: string;
62
+ message: string;
63
+ }
64
+
65
+ export declare type ClientErrorKind = 'notInitialized' | 'noSession' | 'session' | 'missingField' | 'serverUnavailable' | 'http' | 'attachment' | 'cancelled' | 'other';
66
+
67
+ /**
68
+ * Cached response of `POST /config`. Shape matches the backend wire
69
+ * envelope — top-level booleans/objects, no nesting under `chat` /
70
+ * `call`. Open-ended for forward compat.
71
+ */
72
+ export declare interface ConfigData {
73
+ startMessage?: string;
74
+ theme?: 'light' | 'dark' | 'system';
75
+ accent?: string;
76
+ allowAttachments?: boolean;
77
+ attachmentPolicy?: AttachmentPolicy;
78
+ multiChannel?: boolean;
79
+ [key: string]: unknown;
80
+ }
81
+
82
+ export declare interface Contact {
83
+ id: string;
84
+ name: string;
85
+ }
86
+
87
+ /** Credentials cached by `initialize()` and consumed by `authenticate()`. */
88
+ export declare interface Credentials {
89
+ endpoint: string;
90
+ token?: string;
91
+ userId?: string;
92
+ attributes?: Record<string, unknown>;
93
+ /**
94
+ * Optional absolute base URL the voice assets are fetched from
95
+ * (`<base>/audio/audio-processor.js` and
96
+ * `<base>/audio/wasm-gen/origon_web_audio_bg.wasm`). By default the
97
+ * assets are emitted by the app's bundler next to the SDK's chunk and
98
+ * need no configuration; set this only when that relocation is
99
+ * unavailable (e.g. an esbuild-only pipeline) and the two files are
100
+ * copied to a known location instead. See docs/voice.md.
101
+ */
102
+ assetBaseUrl?: string;
103
+ }
104
+
105
+ /** Mirrors Rust's `DisconnectReason`. */
106
+ export declare type DisconnectReason = {
107
+ kind: 'localClose';
108
+ } | {
109
+ kind: 'networkLoss';
110
+ } | {
111
+ kind: 'remoteHangup';
112
+ } | {
113
+ kind: 'endpointNotProvisioned';
114
+ } | {
115
+ kind: 'endpointAlreadyConnected';
116
+ } | {
117
+ kind: 'tokenInvalid';
118
+ } | {
119
+ kind: 'tokenExpired';
120
+ } | {
121
+ kind: 'tokenReplayed';
122
+ } | {
123
+ kind: 'protocolViolation';
124
+ } | {
125
+ kind: 'capabilityMissing';
126
+ } | {
127
+ kind: 'illegalState';
128
+ } | {
129
+ kind: 'resourceExhausted';
130
+ } | {
131
+ kind: 'replayLost';
132
+ } | {
133
+ kind: 'sessionEnded';
134
+ } | {
135
+ kind: 'serverClosed';
136
+ code: number;
137
+ reason: string;
138
+ } | {
139
+ kind: 'transportClosed';
140
+ detail: string;
141
+ };
142
+
143
+ export declare function getSessionManager(): SessionManager;
144
+
145
+ /**
146
+ * Attach to a session whose `{sessionId, url, token}` was provisioned out of
147
+ * band — e.g. a Connect agent answering a pushed call offer, or a supervisor
148
+ * joining a live call — instead of the SDK originating it via `POST
149
+ * /session/start`. The triple mirrors `StartSessionResponse`; `url` is the
150
+ * media/chat base and `token` the per-session credential. See
151
+ * `SessionManager.joinSession` + CONTRACT.md "Origination modes".
152
+ */
153
+ export declare interface JoinSessionOptions {
154
+ channel: Channel;
155
+ sessionId: string;
156
+ url: string;
157
+ token: string;
158
+ /** Voice-only join behavior. Omit to retain the normal microphone join. */
159
+ voice?: VoiceJoinOptions;
160
+ }
161
+
162
+ export declare interface Message {
163
+ role: MessageRole;
164
+ id: string;
165
+ /**
166
+ * SDK-issued temporary id for outbound messages awaiting server
167
+ * confirmation. Set on the provisional `onMessageAdded` payload so the
168
+ * consumer can locate the row when `onMessageUpdated` arrives.
169
+ */
170
+ localId?: string;
171
+ text?: string;
172
+ html?: string;
173
+ timestamp?: string;
174
+ userId?: string;
175
+ userName?: string;
176
+ attachments: Attachment[];
177
+ errorText?: string;
178
+ status: MessageStatus;
179
+ state: MessageState;
180
+ /**
181
+ * Lifecycle action for a `role:'system'` message — `'queued' | 'joined' |
182
+ * 'ended'` (kept an open `string`: the server may add more; a distinct
183
+ * `'left'` was considered and dropped by the producer). Present
184
+ * on cx's lifecycle system rows; ABSENT on flow-bot system messages,
185
+ * which stay ordinary bubbles. The consumer's discriminator is
186
+ * action-**presence** (non-empty), not `role`. Producer: cx's
187
+ * `chat.v1.Message.action` (`src/orpc-gen/chat_pb.ts`; canonical
188
+ * side platform/cx CONTRACT §3/§6) — see "Inbound couplings".
189
+ */
190
+ action?: string;
191
+ /** Interactive prompt buttons; absent when the message carries none. */
192
+ buttons?: MessageButton[];
193
+ /** Gallery cards; absent when the message carries none. */
194
+ gallery?: MessageCard[];
195
+ }
196
+
197
+ /**
198
+ * Role enum re-exported so consumers can keep an idiom like
199
+ * `MESSAGE_ROLES.AI` instead of typing the string literal. Values are
200
+ * the same lowercase strings used on the wire.
201
+ */
202
+ export declare const MESSAGE_ROLES: {
203
+ readonly AI: "ai";
204
+ readonly EXTERNAL: "external";
205
+ readonly USER: "user";
206
+ readonly SYSTEM: "system";
207
+ };
208
+
209
+ /**
210
+ * One interactive prompt button — `{label, value, type}`. Wire shape:
211
+ * `chat.v1.Button` (`src/orpc-gen/chat_pb.ts`; the proto field is
212
+ * `button_type` but the wire key stays `type`, pinned by `json_name`).
213
+ * Clicking one sends `SendMessagePayload.buttonReply`.
214
+ */
215
+ export declare interface MessageButton {
216
+ label: string;
217
+ value: string;
218
+ type: string;
219
+ }
220
+
221
+ /**
222
+ * One gallery card — `{title, description, image?, buttons}`. Wire shape:
223
+ * `chat.v1.Card` (`src/orpc-gen/chat_pb.ts`); `image` is absent on a
224
+ * card authored without one (the wire renders it as JSON `null`).
225
+ */
226
+ export declare interface MessageCard {
227
+ title: string;
228
+ description: string;
229
+ image?: Attachment;
230
+ buttons: MessageButton[];
231
+ }
232
+
233
+ export declare type MessageRole = 'ai' | 'external' | 'user' | 'system';
234
+
235
+ export declare type MessageState = 'streaming' | 'completed';
236
+
237
+ export declare type MessageStatus = 'sending' | 'delivered' | 'failed';
238
+
239
+ /** Mirrors `media-server::MuteScope`. */
240
+ export declare type MuteScope = 'none' | 'uplink' | 'downlink' | 'both';
241
+
242
+ /**
243
+ * A non-OK orpc reply (an ERROR frame). `code` is the wire status — a base code
244
+ * (§5) or a `0x1000+` auth code; `reason` is the server's UTF-8 message.
245
+ */
246
+ export declare class OrpcError extends Error {
247
+ readonly code: number;
248
+ readonly reason: string;
249
+ constructor(code: number, reason: string);
250
+ }
251
+
252
+ /**
253
+ * Optional callbacks the SDK fires through `SessionManager.setCallbacks`.
254
+ * All callbacks receive `sessionId` and `channel` as their first two args
255
+ * so a single consumer can host multiple sessions simultaneously.
256
+ */
257
+ export declare interface SdkCallbacks {
258
+ /** New message added — fired both for inbound peer messages and the provisional row for outbound sends. */
259
+ onMessageAdded?: (sessionId: string, channel: Channel, message: Message) => void;
260
+ /** Existing message updated — server ack of outbound send, or streaming chunk for AI replies. */
261
+ onMessageUpdated?: (sessionId: string, channel: Channel, update: {
262
+ id: string;
263
+ message: Message;
264
+ }) => void;
265
+ /** Inbound typing indicator from the peer (debounced by SDK watchdog). */
266
+ onTyping?: (sessionId: string, channel: Channel, isTyping: boolean) => void;
267
+ /** Session id changed (e.g. server promoted a temp id, or reattach assigned a new one). */
268
+ onSessionUpdated?: (sessionId: string, channel: Channel) => void;
269
+ onConnected?: (sessionId: string, channel: Channel) => void;
270
+ /**
271
+ * The terminal signal for BOTH channels — chat ends land here too
272
+ * (`sessionEnded` / `serverClosed` / `replayLost` / `networkLoss` /
273
+ * `localClose`).
274
+ */
275
+ onDisconnected?: (sessionId: string, channel: Channel, info: {
276
+ reason: DisconnectReason;
277
+ }) => void;
278
+ onReconnecting?: (sessionId: string, channel: Channel, info: {
279
+ attempt: number;
280
+ reason: DisconnectReason;
281
+ }) => void;
282
+ onReconnected?: (sessionId: string, channel: Channel) => void;
283
+ onPeerAttached?: (sessionId: string, channel: Channel, peer: {
284
+ peerEndpointId: string;
285
+ alias: number;
286
+ }) => void;
287
+ onPeerDetached?: (sessionId: string, channel: Channel, peer: {
288
+ peerEndpointId: string;
289
+ alias: number;
290
+ }) => void;
291
+ onCallError?: (sessionId: string, channel: Channel, error: string | null) => void;
292
+ }
293
+
294
+ export declare interface SendMessagePayload {
295
+ text?: string;
296
+ html?: string;
297
+ attachments?: Attachment[];
298
+ /**
299
+ * Local-only hint for the provisional `onMessageAdded` role. Defaults to
300
+ * `'external'` when absent; staff dashboards set `'user'`. Stripped before
301
+ * POST.
302
+ */
303
+ role?: MessageRole;
304
+ /**
305
+ * Button/gallery reply — sent when the user clicks a quick-reply or
306
+ * gallery button. Maps onto the wire's top-level `value` /
307
+ * `gallery_label` (`MessageRequest`).
308
+ */
309
+ buttonReply?: {
310
+ value: string;
311
+ label?: string;
312
+ };
313
+ }
314
+
315
+ export declare type SessionControl = 'ai' | 'user';
316
+
317
+ export declare interface SessionHistory {
318
+ history: Message[];
319
+ control: SessionControl;
320
+ }
321
+
322
+ /**
323
+ * Top-level entry point. One instance per app — see `getSessionManager()`
324
+ * for the singleton accessor.
325
+ *
326
+ * Lifecycle: `initialize()` → `authenticate()` → `startSession({channel})`
327
+ * → chat / voice APIs → `endSession()`.
328
+ */
329
+ export declare class SessionManager {
330
+ private config;
331
+ private userId;
332
+ private http;
333
+ private sessionHttp;
334
+ private attributes;
335
+ private assetBaseUrl;
336
+ private readonly dispatcher;
337
+ private readonly sessions;
338
+ private readonly pendingUploads;
339
+ initialize(credentials: Credentials): void;
340
+ authenticate(): Promise<ConfigData>;
341
+ setCallbacks(callbacks: SdkCallbacks): void;
342
+ setAttributes(attributes: Record<string, unknown> | undefined): void;
343
+ getConfig(): ConfigData | null;
344
+ getUserId(): string | undefined;
345
+ activeSessionIds(): Array<{
346
+ sessionId: string;
347
+ channel: Channel;
348
+ }>;
349
+ getSessions(): Promise<SessionSummary[]>;
350
+ getSession(sessionId: string): Promise<SessionHistory>;
351
+ startSession(opts: StartSessionOptions): Promise<{
352
+ sessionId: string;
353
+ }>;
354
+ /**
355
+ * Attach to a session whose `{sessionId, url, token}` was provisioned out of
356
+ * band, skipping `POST /session/start`. The counterpart to `startSession`
357
+ * for consumers that are *handed* a session — a Connect agent answering a
358
+ * pushed call offer, or a supervisor joining a live call — rather than
359
+ * originating one. Requires `initialize()` (endpoint stays mandatory, as with
360
+ * `startSession`); `authenticate()`/`POST /config` is not needed on this path.
361
+ */
362
+ joinSession(opts: JoinSessionOptions): Promise<{
363
+ sessionId: string;
364
+ }>;
365
+ /**
366
+ * Shared core: construct the per-channel session from a
367
+ * `StartSessionResponse`, register it, fire `onSessionUpdated`, and bring
368
+ * its transport up. Driven by both `startSession` (after `POST
369
+ * /session/start`) and `joinSession` (from a caller-supplied response).
370
+ *
371
+ * Voice and chat differ in *when* `onConnected` fires: chat fires it here
372
+ * after `start()`; voice fires it from inside `VoiceSession.dial()` once
373
+ * the MoQ `ConnectOk` lands — so this method must NOT fire it for voice.
374
+ */
375
+ private attachSession;
376
+ endSession(sessionId: string): Promise<void>;
377
+ endAllSessions(): Promise<void>;
378
+ /**
379
+ * Re-key an active session from `oldId` to `newId` — for a control plane that
380
+ * reassigns a session's id mid-call (e.g. a Connect warm-transfer /
381
+ * conference completion) while the media endpoint persists. No transport is
382
+ * re-dialed: the live session is relabelled (its `rename()`) and its map
383
+ * entry moved. Fires `onSessionUpdated(newId)` so the consumer can rebind.
384
+ *
385
+ * Tolerant, mirroring the native SDK: unknown `oldId` → no-op (warn); `newId`
386
+ * already active → no-op (warn), so a live session is never clobbered.
387
+ */
388
+ migrateSessionId(oldId: string, newId: string): void;
389
+ sendMessage(sessionId: string, payload: SendMessagePayload): Promise<Message>;
390
+ notifyTyping(sessionId: string): void;
391
+ stopTyping(sessionId: string): void;
392
+ setMute(sessionId: string, scope: MuteScope): Promise<void>;
393
+ /** Enable microphone coaching on a receive-only voice join. */
394
+ enableCapture(sessionId: string): Promise<void>;
395
+ /** Return a coaching voice join to Listen and release its microphone. */
396
+ releaseCapture(sessionId: string): Promise<void>;
397
+ setMuteAll(scope: MuteScope): Promise<void>;
398
+ /**
399
+ * Subscribe to periodic audio level + pipeline stats for a voice session.
400
+ * Returns an unsubscribe function. Stats are computed in the worklet
401
+ * only while at least one subscriber is registered.
402
+ */
403
+ subscribeAudioStats(sessionId: string, cb: (stats: AudioWorkletStats) => void): () => void;
404
+ /**
405
+ * Upload an attachment. WIDGET-scoped and session-less: the URL is built
406
+ * from the `endpoint` given to `initialize()`, so an attachment can be the
407
+ * first thing a visitor sends — no session is opened, and none is required.
408
+ * Needs `authenticate()` only for the local policy precheck (absent config
409
+ * defers the whole decision to the server).
410
+ */
411
+ uploadAttachment(file: File, options: UploadOptions): Promise<Attachment>;
412
+ /**
413
+ * Dual purpose: cancel an in-flight upload (when `idOrUploadId` matches a
414
+ * pending `uploadId`) OR DELETE a stored attachment by id. Widget-scoped and
415
+ * session-less on both arms, so a visitor can remove a file they attached
416
+ * before sending anything.
417
+ */
418
+ deleteAttachment(idOrUploadId: string): Promise<void>;
419
+ private requireChatSession;
420
+ private requireVoiceSession;
421
+ private requireSessionHttp;
422
+ private requireHttp;
423
+ }
424
+
425
+ export declare interface SessionSummary {
426
+ sessionId: string;
427
+ subject: string;
428
+ channel: Channel;
429
+ /** True only while the session is live on the cx owner that served this directory. */
430
+ active: boolean;
431
+ createdAt: string;
432
+ updatedAt: string;
433
+ lastMessage?: Message;
434
+ contact?: Contact;
435
+ }
436
+
437
+ export declare interface StartSessionOptions {
438
+ channel: Channel;
439
+ sessionId?: string;
440
+ data?: Record<string, unknown>;
441
+ }
442
+
443
+ export declare interface StartSessionResponse {
444
+ sessionId: string;
445
+ url: string;
446
+ token: string;
447
+ }
448
+
449
+ export declare interface UploadOptions {
450
+ /** Caller-issued id used to cancel the upload via `delete_attachment`. */
451
+ uploadId: string;
452
+ onProgress?: (p: UploadProgress) => void;
453
+ signal?: AbortSignal;
454
+ }
455
+
456
+ export declare interface UploadProgress {
457
+ bytesUploaded: number;
458
+ totalBytes?: number;
459
+ percent?: number;
460
+ }
461
+
462
+ /** Additive options for an out-of-band provisioned voice join. */
463
+ export declare interface VoiceJoinOptions {
464
+ /**
465
+ * Start playout and transport without acquiring a microphone. After
466
+ * `ConnectOk`, the SDK sends `Mute(uplink)` and awaits `MuteOk` before
467
+ * reporting connected.
468
+ */
469
+ receiveOnly?: boolean;
470
+ }
471
+
472
+ export { }