@pyai/sdk 0.2.2 → 0.3.1

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 CHANGED
@@ -8,10 +8,10 @@ dependencies; runs in the browser and Node 18+.
8
8
  ## PyAI products
9
9
 
10
10
  - **[Hear](https://pyai.com/models/hear)**, Lightning-fast, telephony-native **speech-to-text**. Whisper-compatible transcription tuned for real phone-call audio, with live streaming partials so your app reacts mid-sentence, plus async batch transcription for big archives. `POST /v1/audio/transcriptions`
11
- - **[Speak](https://pyai.com/models/speak)**, Ultra-realistic **text-to-speech** that starts speaking in tens of milliseconds. Stream lifelike, expressive voices, choose from 36 studio-quality presets, or clone any voice instantly, for free. `POST /v1/audio/speech`
11
+ - **[Speak](https://pyai.com/models/speak)**, Ultra-realistic **text-to-speech** that starts speaking in tens of milliseconds. Stream lifelike, expressive voices, choose from 144 stock voices, or clone any voice instantly, for free. `POST /v1/audio/speech`
12
12
  - **[Omni](https://pyai.com/models/omni)** _(flagship)_, One **API for a complete, end-to-end voice AI agent**. A single WebSocket where your agent listens, thinks, and speaks, grounded in your knowledge bases and tools, with human-like turn-taking and instant barge-in, no STT, LLM, or TTS to stitch together yourself. `wss://api.pyai.com/v1/omni`
13
13
  - **[Trace](https://pyai.com/models/trace)** _(flagship)_, The **compliance API that keeps your AI agents safe**. Trace automatically checks every call for HIPAA, TCPA, and PII risks (plus your own brand-voice rules), flags the exact rule broken, redacts sensitive data, and seals each call with a tamper-evident audit trail, so a risky conversation never slips through. `GET /v1/trace/interactions`
14
- - **[Cue](https://pyai.com/models/cue)**, Realtime **turn detection + knowledge-grounded context** for your own stack. Bring your own LLM and voice; Cue nails the hard part, knowing the instant a speaker finishes and surfacing the right context. `wss://api.pyai.com/v1/audio/transcriptions/stream`
14
+ - **Cue**, reserved turn-detection and grounding fields on Hear streaming. Grounding is not active on the serving route yet.
15
15
  - **[AMD](https://pyai.com/models/amd)**, **Answering-machine detection** that tells your dialer who or what answered, human, voicemail, IVR, iPhone/Google screening, dead number, fax, in a fraction of Twilio's dead-air dwell, with the reason. A one-line-TwiML Twilio Media Streams drop-in; billed per answered call (first 5,000/month free). `wss://api.pyai.com/v1/amd/stream`
16
16
  - **[Telephony](https://pyai.com/models/telephony)**, Instant **managed phone numbers** for your voice agents. Provision a US number and route live calls straight into an Omni agent, no carrier contracts, no telephony glue. `POST /v1/telephony/numbers`
17
17
 
@@ -93,6 +93,16 @@ omni.sendDtmf("5");
93
93
  omni.close();
94
94
  ```
95
95
 
96
+ Live `0x02` transcript bodies are plain UTF-8 caller-text deltas, not JSON.
97
+ `onTranscript` receives the normalized
98
+ `{ event:"transcript", role:"user", text, final:false, mode:"delta" }` shape.
99
+ Coalesce successive deltas for the current caller turn. Bounded direct JSON
100
+ bodies remain accepted for older bridges.
101
+
102
+ `rate` configures caller input. A `rate: 16000` session still receives 24 kHz
103
+ agent audio; `rate: 8000` receives 8 kHz. Read `hello.audio_out` before playback.
104
+ Omni has no commit frame—keep streaming silence during caller pauses.
105
+
96
106
  From the browser, mint an ephemeral token server-side with
97
107
  `pyai.omni.createSession({ allowedOrigins })` and pass it as `token` so the page
98
108
  never holds a secret key:
@@ -101,31 +111,37 @@ never holds a secret key:
101
111
  const omni = pyai.omni.connect({ token: session.token, configure: { voice_id, persona } });
102
112
  ```
103
113
 
104
- > Omni uses the native `wss://api.pyai.com/v1/omni` surface and is **zero-state**
105
- >, no agent to create, `sessionLabel` is an optional opaque tag (never
106
- > required). Need the raw socket? `pyai.realtimeURL({ product: "omni" })` +
107
- > `pyai.realtimeSubprotocol()` (or `pyai.connectRealtime()`) still work;
108
- > `product: "flow"` uses `/v1/realtime`. The older `/v2/omni/chat` URL and the
109
- > `agentId` option are deprecated but still work.
114
+ > Omni connects only to `wss://api.pyai.com/v1/omni` and is **zero-state**, no
115
+ > agent to create. `sessionLabel` is an optional opaque tag (never required).
116
+ > Need the raw socket? Use `pyai.realtimeURL({ sessionLabel })` with
117
+ > `pyai.realtimeSubprotocol()` (or `pyai.connectRealtime()`).
118
+ > The raw URL helper accepts canonical `format`, `rate`, and `api_key` query
119
+ > parameters; retired connect aliases, model selectors, and token query names
120
+ > throw instead of being translated.
110
121
 
111
- ## Streaming speech-to-text (Hear / Cue)
122
+ ## Streaming speech-to-text (Hear)
112
123
 
113
124
  `transcriptions.stream()` hides the WebSocket frame protocol behind callbacks.
114
- It opens `wss://api.pyai.com/v1/audio/transcriptions/stream` (key carried as the
125
+ It opens `wss://api.pyai.com/v1/audio/transcriptions/stream?protocol=pyai-hear-v1` (key carried as the
115
126
  WS subprotocol, so it works in the browser), routes the wire frames to
116
- `onPartial`/`onFinal`/`onError`, and gives you `sendAudio`, `commit()`, and
117
- `close()`:
127
+ `onConfigAck`/`onPartial`/`onFinal`/`onError`, and gives you `sendAudio`,
128
+ `configureEndpointing()`, `commit()`, and `close()`:
118
129
 
119
130
  ```ts
120
131
  const hear = pyai.audio.transcriptions.stream({
121
132
  sampleRate: 16000,
133
+ endpointingMs: 800, // minimum trailing pause; may wait up to max(800, 1500) ms
134
+ onConfigAck: (ack) => {
135
+ if (ack.warnings.length) throw new Error(JSON.stringify(ack.warnings));
136
+ },
122
137
  onPartial: (f) => console.log("…", f.text),
123
- onFinal: (f) => console.log("✓", f.text, `(${f.audio_ms}ms)`),
138
+ onFinal: (f) => console.log("✓", f.text, f.endpoint_reason),
124
139
  onError: (e) => console.error(e),
125
140
  });
126
141
 
127
- micChunks.on("data", (pcm16) => hear.sendAudio(pcm16)); // binary frames
128
- vad.on("end", () => hear.commit()); // force-finalize an utterance
142
+ micChunks.on("data", (pcm16) => hear.sendAudio(pcm16)); // keep sending silence through pauses
143
+ hear.configureEndpointing(950); // update without reconnecting
144
+ vad.on("end", () => hear.commit()); // optional forced final
129
145
  // hear.close() also flushes a final for any buffered audio
130
146
  ```
131
147
 
@@ -163,8 +179,8 @@ const ulaw = await pyai.audio.speech({
163
179
 
164
180
  | `response_format` | sample rates (Hz) | Content-Type |
165
181
  |---|---|---|
166
- | `mp3` (default) | 8000 / 16000 / 24000 / 48000 | `audio/mpeg` |
167
- | `wav` | 8000 / 16000 / 24000 / 48000 | `audio/wav` |
182
+ | `wav` (default) | 8000 / 16000 / 24000 / 48000 | `audio/wav` |
183
+ | `mp3` | 8000 / 16000 / 24000 / 48000 | `audio/mpeg` |
168
184
  | `opus` | 8000 / 16000 / 24000 / 48000 | `audio/ogg` |
169
185
  | `aac` | 8000 / 16000 / 24000 / 48000 | `audio/aac` |
170
186
  | `flac` | 8000 / 16000 / 24000 / 48000 | `audio/flac` |
@@ -175,7 +191,7 @@ const ulaw = await pyai.audio.speech({
175
191
  `sample_rate` is optional, omit it for the engine's native 24 kHz (`g711_*` is
176
192
  always 8 kHz). The set is typed (`SpeechFormat`) and exported as `SPEECH_FORMATS`
177
193
  / `SPEECH_SAMPLE_RATES` for dropdowns and validation. Any other value is a
178
- `400 unsupported_format`; omit `response_format` for the default `mp3`.
194
+ `400 unsupported_format`; omit `response_format` for the default `wav`.
179
195
 
180
196
  > See [`examples/speak-telephony-formats`](../../examples/speak-telephony-formats)
181
197
  > for the full before/after: ~120 lines of resampler + μ-law replaced by one
@@ -292,7 +308,7 @@ Speak→Hear round-trip, and prints remediation hints for any failure:
292
308
  ```bash
293
309
  export PYAI_API_KEY=pyai_test_...
294
310
  npx pyai doctor
295
- # PASS key (/v1/me), env=test; 3 scope(s): hear:transcribe, voice:synthesize, hear:stream
311
+ # PASS key (/v1/me), env=test; 3 scope(s): hear:transcribe, speak:synthesize, hear:stream
296
312
  # PASS models.list, 12 models
297
313
  # PASS voices.list, 38 voices
298
314
  # PASS speak→hear round-trip, synth 45210 bytes → "the quick brown fox…"
package/dist/index.d.ts CHANGED
@@ -24,6 +24,9 @@ export declare class PyAIError extends Error {
24
24
  constructor(status: number, message: string, code?: string, type?: string, requestId?: string);
25
25
  }
26
26
  export interface Voice {
27
+ /** Canonical catalog identifier returned by GET /v1/voices. */
28
+ voice_id: string;
29
+ /** SDK compatibility alias, always equal to voice_id. */
27
30
  id: string;
28
31
  name?: string;
29
32
  gender?: string;
@@ -49,13 +52,13 @@ export interface TranscriptionJob {
49
52
  * Output container/codec for `audio.speech`. This is the **exact** set the
50
53
  * server accepts on `POST /v1/audio/speech`, any other value is rejected with
51
54
  * `400 unsupported_format`. The default (when `response_format` is omitted) is
52
- * `mp3`. Omit `sample_rate` for the engine's native 24 kHz (`g711_*` is always
55
+ * `wav`. Omit `sample_rate` for the engine's native 24 kHz (`g711_*` is always
53
56
  * 8 kHz).
54
57
  *
55
58
  * | format | rates (Hz) | Content-Type |
56
59
  * |---|---|---|
57
- * | `mp3` (default) | 8000/16000/24000/48000 | `audio/mpeg` |
58
- * | `wav` | 8000/16000/24000/48000 | `audio/wav` |
60
+ * | `wav` (default) | 8000/16000/24000/48000 | `audio/wav` |
61
+ * | `mp3` | 8000/16000/24000/48000 | `audio/mpeg` |
59
62
  * | `opus` | 8000/16000/24000/48000 | `audio/ogg` |
60
63
  * | `aac` | 8000/16000/24000/48000 | `audio/aac` |
61
64
  * | `flac` | 8000/16000/24000/48000 | `audio/flac` |
@@ -73,14 +76,16 @@ export declare const SPEECH_FORMATS: readonly ["wav", "mp3", "opus", "aac", "fla
73
76
  /** Sample rates (Hz) the server accepts for `audio.speech` (`g711_*` is always 8 kHz). */
74
77
  export declare const SPEECH_SAMPLE_RATES: readonly [8000, 16000, 24000, 48000];
75
78
  export type SpeechSampleRate = (typeof SPEECH_SAMPLE_RATES)[number];
79
+ /** Canonical Speak model plus the intentional OpenAI drop-in aliases. */
80
+ export type SpeakModel = "pyai-speak" | "tts-1" | "tts-1-hd";
76
81
  export interface SpeechParams {
77
82
  input: string;
78
83
  voice?: string;
79
- model?: string;
84
+ model?: SpeakModel;
80
85
  /**
81
86
  * Output container/codec, resampled+encoded server-side. One of
82
87
  * {@link SpeechFormat}, anything else is a `400 unsupported_format`. Omit for
83
- * the default of `mp3`.
88
+ * the default of `wav`.
84
89
  *
85
90
  * `g711_ulaw`/`g711_alaw` return raw 8 kHz mono G.711, the bytes Twilio/SIP
86
91
  * media streams expect, so you can hand the response straight to a telephony
@@ -97,15 +102,14 @@ export interface SpeechParams {
97
102
  * telephony pipelines, most often with `response_format: "pcm"`.
98
103
  */
99
104
  sample_rate?: SpeechSampleRate;
105
+ /** Reserved; currently returns `400 unsupported_parameter` when provided. */
100
106
  speed?: number;
101
107
  /**
102
- * Deterministic sampling seed for reproducible eval runs. Forward-compatible:
103
- * honored once the engine supports it (otherwise ignored server-side), so it's
104
- * always safe to send.
108
+ * Reserved; currently returns `400 unsupported_parameter` when provided.
105
109
  */
106
110
  seed?: number;
107
111
  /**
108
- * Sampling temperature (lower = more deterministic). Forward-compatible, * honored once the engine supports it, otherwise ignored.
112
+ * Reserved; currently returns `400 unsupported_parameter` when provided.
109
113
  */
110
114
  temperature?: number;
111
115
  }
@@ -119,8 +123,6 @@ export interface CreateJobParams {
119
123
  webhook_url?: string;
120
124
  }
121
125
  export interface RealtimeOptions {
122
- /** "omni" (agentic voice) or "flow" (legacy voice duplex). Default "omni". */
123
- product?: "omni" | "flow";
124
126
  /**
125
127
  * Optional opaque tag echoed to your `kb_endpoint` and recorded on the call.
126
128
  * Omni is zero-state: the session is authorized by the key's org, so there is
@@ -128,12 +130,10 @@ export interface RealtimeOptions {
128
130
  */
129
131
  sessionLabel?: string;
130
132
  /**
131
- * @deprecated Use {@link sessionLabel}. Kept for back-compat, emitted as the
132
- * `agent_id` query alias, which the gateway still accepts. Ignored if
133
- * `sessionLabel` is set.
133
+ * Extra canonical query params (`format`, `rate`, or the intentional
134
+ * server-side `api_key` auth option). Retired connect aliases and model/token
135
+ * selectors are rejected.
134
136
  */
135
- agentId?: string;
136
- /** Extra query params (e.g. format, rate). */
137
137
  query?: Record<string, string>;
138
138
  }
139
139
  /** Parameters for minting an ephemeral browser Omni session token (server-side). */
@@ -167,6 +167,8 @@ export interface OmniSession {
167
167
  }
168
168
  /** Frame `type`s emitted by the Hear streaming-STT WebSocket. */
169
169
  export declare const HearFrameType: {
170
+ /** Applied endpointing configuration and validation warnings. */
171
+ readonly ConfigAck: "config_ack";
170
172
  /** Eager live hypothesis for the current utterance. */
171
173
  readonly Partial: "partial";
172
174
  /** Partial whose prefix has stabilized (won't be revised). */
@@ -202,7 +204,7 @@ export declare const ErrorCode: {
202
204
  readonly Unauthorized: "unauthorized";
203
205
  readonly Forbidden: "forbidden";
204
206
  readonly OriginNotAllowed: "origin_not_allowed";
205
- readonly InvalidAgentId: "invalid_agent_id";
207
+ readonly InvalidSessionLabel: "invalid_session_label";
206
208
  readonly CreditExhausted: "credit_exhausted";
207
209
  readonly KeyBudgetExceeded: "key_budget_exceeded";
208
210
  readonly InsufficientQuota: "insufficient_quota";
@@ -219,6 +221,22 @@ export interface HearGroundingPassage {
219
221
  content: string;
220
222
  score: number;
221
223
  }
224
+ /** One endpointing validation result echoed in a `config_ack` frame. */
225
+ export interface HearConfigWarning {
226
+ field: string;
227
+ value: unknown;
228
+ effective?: number;
229
+ reason: "clamped_to_range" | "not_a_number" | "unknown_config_field" | (string & {});
230
+ }
231
+ /** Applied endpointing settings after connect-time or mid-session config. */
232
+ export interface HearConfigAckFrame {
233
+ type: "config_ack";
234
+ endpointing_ms: number;
235
+ effective_floor_ms: number;
236
+ effective_ceiling_ms: number;
237
+ score_interval_ms: number;
238
+ warnings: HearConfigWarning[];
239
+ }
222
240
  /** Live hypothesis frame (`partial` / `partial_stable`). */
223
241
  export interface HearPartialFrame {
224
242
  type: "partial" | "partial_stable";
@@ -232,6 +250,7 @@ export interface HearPartialFrame {
232
250
  t_ms: number;
233
251
  }
234
252
  /** Finalized-utterance frame (`speech_final` / `final`). */
253
+ export type HearEndpointReason = "peak_te_early" | "silence_backstop" | "commit" | (string & {});
235
254
  export interface HearFinalFrame {
236
255
  type: "speech_final" | "final";
237
256
  text: string;
@@ -239,6 +258,8 @@ export interface HearFinalFrame {
239
258
  t_ms: number;
240
259
  /** Active-speech length of the utterance (the billed signal), ms. */
241
260
  audio_ms: number;
261
+ /** Why the utterance ended. Log this when tuning automatic endpointing. */
262
+ endpoint_reason: HearEndpointReason;
242
263
  /** Present only with Cue grounding enabled (top KB passages). */
243
264
  grounding?: HearGroundingPassage[];
244
265
  }
@@ -250,7 +271,7 @@ export interface HearUsageFrame {
250
271
  type: "usage";
251
272
  /** `hear` for plain streaming, `cue` when grounding was enabled. */
252
273
  product: "hear" | "cue";
253
- /** The billed meter (`hear.requests` or `cue.minutes`). */
274
+ /** The billed meter (`hear.minutes` or `cue.minutes`). */
254
275
  meter: string;
255
276
  /** Summed active-speech audio billed for the session, in seconds. */
256
277
  audio_seconds: number;
@@ -263,7 +284,7 @@ export interface HearErrorFrame {
263
284
  code?: string;
264
285
  message: string;
265
286
  }
266
- export type HearFrame = HearPartialFrame | HearFinalFrame | HearUsageFrame | HearErrorFrame;
287
+ export type HearFrame = HearConfigAckFrame | HearPartialFrame | HearFinalFrame | HearUsageFrame | HearErrorFrame;
267
288
  /**
268
289
  * Minimal structural WebSocket, matches both the browser/Node global
269
290
  * `WebSocket` and the `ws` package, and lets tests inject a mock.
@@ -286,8 +307,12 @@ export type WebSocketCtor = new (url: string, protocols?: string | string[]) =>
286
307
  export interface HearStreamOptions {
287
308
  /** Streaming STT model. Server default `pyai-hear`. */
288
309
  model?: string;
289
- /** ISO-639-1 hint, e.g. "en". */
290
- language?: string;
310
+ /**
311
+ * Hear is English-only. Set `"en"` explicitly; omission also means English
312
+ * and does not enable language detection. Other values receive
313
+ * `400 unsupported_language`.
314
+ */
315
+ language?: "en";
291
316
  /** Input PCM sample rate in Hz. Default 16000 server-side. */
292
317
  sampleRate?: number;
293
318
  /** Audio frame encoding. Default "pcm16". */
@@ -301,16 +326,14 @@ export interface HearStreamOptions {
301
326
  */
302
327
  numerals?: boolean;
303
328
  /**
304
- * Turn-segmentation tuning: trailing-pause (ms, 50-2000) that ends an
305
- * utterance. Forwards `?endpointing_ms=`, clamped + honored once the engine
306
- * supports it; a no-op when omitted. Drive end-of-turn yourself with
307
- * {@link HearStream.commit} for full control today.
329
+ * Minimum trailing-pause length before an utterance may end (50-5000 ms).
330
+ * Turn detection may wait longer, bounded at `max(endpointingMs, 1500)`.
331
+ * The server confirms the applied value through `onConfigAck`.
308
332
  */
309
333
  endpointingMs?: number;
310
334
  /**
311
- * Enable Cue knowledge-base grounding: sends `{type:"config",grounding:true}`
312
- * on open, after which `speech_final`/`final` frames carry a `grounding`
313
- * array. Bills a single `cue.minutes` line instead of the Hear rate.
335
+ * Reserved Cue grounding configuration. Grounding is not active on the
336
+ * serving Hear stream; do not rely on grounding frames or Cue metering yet.
314
337
  */
315
338
  grounding?: boolean;
316
339
  /** Cue: number of KB passages to retrieve per turn (1-20, default 3). */
@@ -322,8 +345,11 @@ export interface HearStreamOptions {
322
345
  groundingTimeoutMs?: number;
323
346
  /** Extra query params merged onto the connect URL. */
324
347
  query?: Record<string, string>;
325
- /** Fired once the socket opens (after the optional grounding config). */
348
+ /** Fired once the socket opens. */
326
349
  onOpen?: () => void;
350
+ /** Fired after connect-time or mid-session endpointing config. Assert that
351
+ * `warnings` is empty before relying on the requested floor. */
352
+ onConfigAck?: (frame: HearConfigAckFrame) => void;
327
353
  /** Fired on `partial` / `partial_stable`. */
328
354
  onPartial?: (frame: HearPartialFrame) => void;
329
355
  /** Fired on `speech_final` / `final`. */
@@ -340,8 +366,10 @@ export interface HearStreamOptions {
340
366
  /**
341
367
  * A live Hear streaming-STT session. Hides the frame protocol: stream audio
342
368
  * with {@link HearStream.sendAudio}, get `onPartial`/`onFinal`/`onError`
343
- * callbacks, force-finalize with {@link HearStream.commit}, and flush+close
344
- * with {@link HearStream.close}. Construct via `pyai.audio.transcriptions.stream()`.
369
+ * callbacks, update the silence floor with
370
+ * {@link HearStream.configureEndpointing}, force-finalize with
371
+ * {@link HearStream.commit}, and flush+close with {@link HearStream.close}.
372
+ * Construct via `pyai.audio.transcriptions.stream()`.
345
373
  */
346
374
  export declare class HearStream {
347
375
  private readonly ws;
@@ -351,6 +379,8 @@ export declare class HearStream {
351
379
  private handleMessage;
352
380
  /** Send a chunk of audio (PCM16 or opus per `encoding`). */
353
381
  sendAudio(chunk: ArrayBufferLike | ArrayBufferView | Blob): void;
382
+ /** Change the minimum trailing-pause floor without reconnecting. */
383
+ configureEndpointing(endpointingMs: number): void;
354
384
  /** Force-finalize the current utterance (e.g. on VAD end-of-turn). */
355
385
  commit(): void;
356
386
  /** Close the socket; the server flushes a final for any buffered audio. */
@@ -420,6 +450,21 @@ export interface OmniServerFrame {
420
450
  event: string;
421
451
  [k: string]: unknown;
422
452
  }
453
+ /** Canonical, sanitized transcript delivered by the native Omni demux.
454
+ * The live wire sends caller text deltas; the JSON fields are SDK-owned
455
+ * normalization so applications don't have to special-case the byte payload. */
456
+ export interface OmniTranscriptFrame extends OmniServerFrame {
457
+ event: "transcript";
458
+ role: "user" | "assistant";
459
+ text: string;
460
+ final: boolean;
461
+ /** Live text frames append; legacy JSON frames replace unless they use `delta`. */
462
+ mode: "delta" | "replace";
463
+ /** Optional ordering hint on legacy JSON frames. */
464
+ sequence?: number;
465
+ }
466
+ /** Normalize the live UTF-8 `0x02` text body and bounded legacy JSON bodies. */
467
+ export declare function normalizeOmniTranscriptBody(bytes: Uint8Array): OmniTranscriptFrame | null;
423
468
  /** A binary agent-audio chunk delivered to {@link OmniConnectOptions.onAudio}. */
424
469
  export type OmniAudioChunk = ArrayBuffer | ArrayBufferView | Blob;
425
470
  export interface OmniToolDef {
@@ -430,7 +475,7 @@ export interface OmniToolDef {
430
475
  endpoint?: string;
431
476
  }
432
477
  export interface OmniToolCallFrame {
433
- type: "tool_call";
478
+ event: "tool_call";
434
479
  call_id: string;
435
480
  name: string;
436
481
  arguments?: Record<string, unknown>;
@@ -455,7 +500,7 @@ export interface OmniConfigure {
455
500
  /**
456
501
  * Session language, end to end (recognition, reasoning, voice). Also
457
502
  * settable on the agent profile (`language` on `POST /v1/agents`), which
458
- * applies automatically when connecting with `session_label={agent_id}`;
503
+ * applies automatically when `session_label` is the saved profile id;
459
504
  * an inline value here wins for the session. Default `en`. Fail-safe: an
460
505
  * unknown/not-yet-enabled language falls back to `en` (the `configured`
461
506
  * ack carries `language_active` + `language_fallback: true`), the call
@@ -477,7 +522,11 @@ export interface OmniConnectOptions {
477
522
  * so a page never holds a secret key.
478
523
  */
479
524
  token?: string;
480
- /** Connect-URL sample rate: 24000 browser, 16000 wideband telephony, 8000 G.711/Twilio. */
525
+ /**
526
+ * Caller-input sample rate. `24000` and `16000` sessions receive agent audio
527
+ * at 24 kHz; `8000` sessions receive 8 kHz. Read `hello.audio_out` rather than
528
+ * assuming output matches this value.
529
+ */
481
530
  rate?: 24000 | 16000 | 8000;
482
531
  /** Connect-URL audio format. Default `pcm16`. */
483
532
  format?: "pcm16";
@@ -500,7 +549,7 @@ export interface OmniConnectOptions {
500
549
  onSessionStarted?: (frame: OmniServerFrame) => void;
501
550
  /** Fired on `turn` boundaries. */
502
551
  onTurn?: (frame: OmniServerFrame) => void;
503
- /** Fired on `transcript` text frames. */
552
+ /** Fired for each normalized caller-transcript delta (`0x02` plain UTF-8 live). */
504
553
  onTranscript?: (frame: OmniServerFrame) => void;
505
554
  /** Fired on `barge_in` / `flush` (user interrupted). */
506
555
  onBargeIn?: (frame: OmniServerFrame) => void;
@@ -539,6 +588,8 @@ export declare class OmniConnection {
539
588
  private closed;
540
589
  /** Serializes async Blob reads in {@link sendAudio} so frames stay ordered. */
541
590
  private blobTail;
591
+ /** Serializes inbound Blob decoding so browser frames stay ordered. */
592
+ private inboundTail;
542
593
  constructor(url: string, subprotocol: string, opts: OmniConnectOptions);
543
594
  private handleMessage;
544
595
  private dispatchFrame;
@@ -841,8 +892,13 @@ export interface RecapCallTriggerInput {
841
892
  customer_name?: string;
842
893
  crm_fields?: Record<string, unknown>;
843
894
  }
844
- /** PyAI's richer answered-by vocabulary (superset of Twilio's enum). */
845
- export type AmdAnsweredBy = "human" | "voicemail" | "live_voicemail" | "screening" | "ivr" | "human_gatekeeper" | "sit_invalid" | "fax" | "silence" | "unknown";
895
+ /**
896
+ * The answered-by vocabulary on stored call records and the
897
+ * `amd.call.completed` webhook: the routing classes plus the machine subtypes.
898
+ */
899
+ export type AmdAnsweredBy = "human" | "machine" | "voicemail" | "screening" | "ivr" | "music" | "sit_invalid" | "unknown";
900
+ /** The routing classes pushed on the mid-call wire event (`event: "amd"`). */
901
+ export type AmdWireAnsweredBy = "human" | "machine" | "sit_invalid" | "unknown";
846
902
  /** Twilio's `AnsweredBy` enum, echoed for drop-in migration parity. */
847
903
  export type AmdTwilioAnsweredBy = "human" | "machine_start" | "machine_end_beep" | "machine_end_silence" | "machine_end_other" | "fax" | "unknown";
848
904
  export interface AmdConfigInput {
@@ -880,11 +936,16 @@ export interface AmdCall extends AmdCallSummary {
880
936
  meta?: Record<string, unknown> | null;
881
937
  error?: string | null;
882
938
  }
883
- /** A mid-call AMD decision event pushed on the stream (and to the webhook). */
939
+ /**
940
+ * A mid-call AMD decision event pushed on the stream (and to the per-call
941
+ * TwiML `webhook`). Carries the coarse routing class; the machine subtype
942
+ * (`voicemail`/`ivr`/`screening`/`music`) is on the stored call record
943
+ * (`AmdCall`) and the `amd.call.completed` webhook instead.
944
+ */
884
945
  export interface AmdDecisionEvent {
885
946
  event?: "amd";
886
947
  call_id?: string;
887
- answered_by?: AmdAnsweredBy;
948
+ answered_by?: AmdWireAnsweredBy;
888
949
  answered_by_twilio?: string | null;
889
950
  confidence?: number | null;
890
951
  decision_ms?: number | null;
@@ -954,7 +1015,11 @@ export declare class PyAI {
954
1015
  file: Blob;
955
1016
  filename?: string;
956
1017
  model?: string;
957
- language?: string;
1018
+ /**
1019
+ * Hear is English-only. Omission means English, not auto-detect; other
1020
+ * values receive `400 unsupported_language`.
1021
+ */
1022
+ language?: "en";
958
1023
  response_format?: "json" | "text" | "verbose_json";
959
1024
  /**
960
1025
  * Deterministic seed for reproducible eval runs. Forward-compatible:
@@ -994,7 +1059,7 @@ export declare class PyAI {
994
1059
  clones: {
995
1060
  /** List the org's cloned voices. */
996
1061
  list: () => Promise<ListResponse<Voice>>;
997
- /** Enroll a custom voice from reference audio (>= ~10s). Scope `voice:clone`. */
1062
+ /** Enroll a custom voice from reference audio (>= ~10s). Scope `speak:clone`. */
998
1063
  create: (params: {
999
1064
  name: string;
1000
1065
  file: Blob;
@@ -1006,7 +1071,7 @@ export declare class PyAI {
1006
1071
  * when the id isn't found.
1007
1072
  */
1008
1073
  get: (id: string) => Promise<Voice>;
1009
- /** Delete a cloned voice (tenant-isolated). Scope `voice:clone`. */
1074
+ /** Delete a cloned voice (tenant-isolated). Scope `speak:clone`. */
1010
1075
  delete: (id: string) => Promise<void>;
1011
1076
  };
1012
1077
  telephony: {
@@ -1151,7 +1216,7 @@ export declare class PyAI {
1151
1216
  */
1152
1217
  connect: (opts?: OmniConnectOptions) => OmniConnection;
1153
1218
  };
1154
- /** Build the realtime WebSocket URL for the chosen product. */
1219
+ /** Build the canonical Omni WebSocket URL. */
1155
1220
  realtimeURL(opts?: RealtimeOptions): string;
1156
1221
  /** The subprotocol that carries the key on a WS upgrade (browser-safe auth). */
1157
1222
  realtimeSubprotocol(): string;