@pyai/sdk 0.2.3 → 0.4.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/dist/index.d.ts CHANGED
@@ -24,10 +24,27 @@ 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;
30
33
  region?: string;
34
+ language?: string;
35
+ /** Customer-facing quality tier. */
36
+ tier?: "standard" | "natural";
37
+ /** Permanent convenience inputs accepted on the advertised surfaces. */
38
+ aliases?: string[];
39
+ /** Product surfaces on which this stock voice can be selected. */
40
+ available_on?: Array<"speak" | "omni">;
41
+ /** Accepted Speak delivery modes. Empty means the voice has no Speak surface. */
42
+ synthesis_modes?: Array<"streaming" | "async">;
43
+ /** Voice-specific amount on top of the selected product's base rate. */
44
+ pricing?: {
45
+ included_in_base_price: boolean;
46
+ additional_price_usd_per_minute: number;
47
+ };
31
48
  [k: string]: unknown;
32
49
  }
33
50
  export interface ListResponse<T> {
@@ -49,13 +66,13 @@ export interface TranscriptionJob {
49
66
  * Output container/codec for `audio.speech`. This is the **exact** set the
50
67
  * server accepts on `POST /v1/audio/speech`, any other value is rejected with
51
68
  * `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
69
+ * `wav`. Omit `sample_rate` for the engine's native 24 kHz (`g711_*` is always
53
70
  * 8 kHz).
54
71
  *
55
72
  * | format | rates (Hz) | Content-Type |
56
73
  * |---|---|---|
57
- * | `mp3` (default) | 8000/16000/24000/48000 | `audio/mpeg` |
58
- * | `wav` | 8000/16000/24000/48000 | `audio/wav` |
74
+ * | `wav` (default) | 8000/16000/24000/48000 | `audio/wav` |
75
+ * | `mp3` | 8000/16000/24000/48000 | `audio/mpeg` |
59
76
  * | `opus` | 8000/16000/24000/48000 | `audio/ogg` |
60
77
  * | `aac` | 8000/16000/24000/48000 | `audio/aac` |
61
78
  * | `flac` | 8000/16000/24000/48000 | `audio/flac` |
@@ -73,14 +90,23 @@ export declare const SPEECH_FORMATS: readonly ["wav", "mp3", "opus", "aac", "fla
73
90
  /** Sample rates (Hz) the server accepts for `audio.speech` (`g711_*` is always 8 kHz). */
74
91
  export declare const SPEECH_SAMPLE_RATES: readonly [8000, 16000, 24000, 48000];
75
92
  export type SpeechSampleRate = (typeof SPEECH_SAMPLE_RATES)[number];
93
+ /** Canonical Speak model plus the intentional OpenAI drop-in aliases. */
94
+ export type SpeakModel = "pyai-speak" | "tts-1" | "tts-1-hd";
76
95
  export interface SpeechParams {
77
96
  input: string;
78
97
  voice?: string;
79
- model?: string;
98
+ model?: SpeakModel;
99
+ /**
100
+ * Delivery mode. The API defaults to true and every catalog voice accepts
101
+ * both values. Set false when you want one complete buffered body with a
102
+ * `Content-Length`. A voice whose serving fleet has no streaming lane is
103
+ * rendered buffered either way and says so with `x-pyai-stream: buffered`.
104
+ */
105
+ stream?: boolean;
80
106
  /**
81
107
  * Output container/codec, resampled+encoded server-side. One of
82
108
  * {@link SpeechFormat}, anything else is a `400 unsupported_format`. Omit for
83
- * the default of `mp3`.
109
+ * the default of `wav`.
84
110
  *
85
111
  * `g711_ulaw`/`g711_alaw` return raw 8 kHz mono G.711, the bytes Twilio/SIP
86
112
  * media streams expect, so you can hand the response straight to a telephony
@@ -97,15 +123,14 @@ export interface SpeechParams {
97
123
  * telephony pipelines, most often with `response_format: "pcm"`.
98
124
  */
99
125
  sample_rate?: SpeechSampleRate;
126
+ /** Reserved; currently returns `400 unsupported_parameter` when provided. */
100
127
  speed?: number;
101
128
  /**
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.
129
+ * Reserved; currently returns `400 unsupported_parameter` when provided.
105
130
  */
106
131
  seed?: number;
107
132
  /**
108
- * Sampling temperature (lower = more deterministic). Forward-compatible, * honored once the engine supports it, otherwise ignored.
133
+ * Reserved; currently returns `400 unsupported_parameter` when provided.
109
134
  */
110
135
  temperature?: number;
111
136
  }
@@ -115,12 +140,59 @@ export interface CreateJobParams {
115
140
  diarize?: boolean;
116
141
  channel?: boolean;
117
142
  numerals?: boolean;
143
+ smart_format?: boolean;
144
+ dictation?: boolean;
145
+ drop_fillers?: boolean;
146
+ /**
147
+ * Per-job names, brands, products, or distinctive terms. PyAI keeps up to
148
+ * five valid entries after trimming, deduplication, and common-word
149
+ * filtering.
150
+ */
151
+ vocabulary?: string[];
118
152
  output_formats?: Array<"json" | "srt" | "vtt">;
119
153
  webhook_url?: string;
154
+ call_id?: string;
155
+ pack_id?: string;
156
+ call_direction?: "inbound" | "outbound";
157
+ customer_name?: string;
158
+ }
159
+ export type HearVocabularyProfile = "batch" | "hear_stream";
160
+ export interface HearVocabularyInput {
161
+ /** Organization-owned names, brands, products, or distinctive terms. */
162
+ terms: string[];
163
+ /** Stored terms remain inert unless a use case is selected here. */
164
+ enabledFor: HearVocabularyProfile[];
165
+ }
166
+ export interface HearVocabulary {
167
+ object: "hear.vocabulary";
168
+ /** Sanitized stored list, at most five terms. */
169
+ terms: string[];
170
+ /** Use cases that may add stored suggestions. */
171
+ enabled_for: HearVocabularyProfile[];
172
+ /** Unix milliseconds, or null before the first save. */
173
+ updated_at: number | null;
174
+ }
175
+ export interface AgentConfig {
176
+ name?: string;
177
+ persona_system_prompt?: string | null;
178
+ greeting?: string | null;
179
+ voice_id?: string | null;
180
+ language?: "en" | "fr" | "es" | "de" | "hi" | null;
181
+ /**
182
+ * Opt-in speech-recognition vocabulary. PyAI sanitizes and keeps at most
183
+ * five terms. An empty list or null turns it off.
184
+ */
185
+ vocabulary?: string[] | null;
186
+ [key: string]: unknown;
187
+ }
188
+ export interface Agent {
189
+ object: "agent";
190
+ agent_id: string;
191
+ name: string;
192
+ vocabulary: string[];
193
+ [key: string]: unknown;
120
194
  }
121
195
  export interface RealtimeOptions {
122
- /** "omni" (agentic voice) or "flow" (legacy voice duplex). Default "omni". */
123
- product?: "omni" | "flow";
124
196
  /**
125
197
  * Optional opaque tag echoed to your `kb_endpoint` and recorded on the call.
126
198
  * Omni is zero-state: the session is authorized by the key's org, so there is
@@ -128,12 +200,10 @@ export interface RealtimeOptions {
128
200
  */
129
201
  sessionLabel?: string;
130
202
  /**
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.
203
+ * Extra canonical query params (`format`, `rate`, or the intentional
204
+ * server-side `api_key` auth option). Retired connect aliases and model/token
205
+ * selectors are rejected.
134
206
  */
135
- agentId?: string;
136
- /** Extra query params (e.g. format, rate). */
137
207
  query?: Record<string, string>;
138
208
  }
139
209
  /** Parameters for minting an ephemeral browser Omni session token (server-side). */
@@ -167,6 +237,8 @@ export interface OmniSession {
167
237
  }
168
238
  /** Frame `type`s emitted by the Hear streaming-STT WebSocket. */
169
239
  export declare const HearFrameType: {
240
+ /** Applied endpointing configuration and validation warnings. */
241
+ readonly ConfigAck: "config_ack";
170
242
  /** Eager live hypothesis for the current utterance. */
171
243
  readonly Partial: "partial";
172
244
  /** Partial whose prefix has stabilized (won't be revised). */
@@ -181,10 +253,16 @@ export declare const HearFrameType: {
181
253
  readonly Error: "error";
182
254
  };
183
255
  export type HearFrameType = (typeof HearFrameType)[keyof typeof HearFrameType];
184
- /** WebSocket close codes used across the PyAI realtime/streaming surfaces. */
256
+ /**
257
+ * WebSocket close codes used across the PyAI realtime/streaming surfaces.
258
+ * Browser callers may initiate only Normal or values in 3000-4999; the
259
+ * standards-reserved values below are server close observations.
260
+ */
185
261
  export declare const WSCloseCode: {
186
262
  /** Normal closure. */
187
263
  readonly Normal: 1000;
264
+ /** Browser-safe private application code for a malformed peer protocol frame. */
265
+ readonly ProtocolViolation: 4002;
188
266
  /** Auth/policy: bad key, missing scope, or revoked token. */
189
267
  readonly PolicyViolation: 1008;
190
268
  /** Engine/internal error. */
@@ -202,7 +280,7 @@ export declare const ErrorCode: {
202
280
  readonly Unauthorized: "unauthorized";
203
281
  readonly Forbidden: "forbidden";
204
282
  readonly OriginNotAllowed: "origin_not_allowed";
205
- readonly InvalidAgentId: "invalid_agent_id";
283
+ readonly InvalidSessionLabel: "invalid_session_label";
206
284
  readonly CreditExhausted: "credit_exhausted";
207
285
  readonly KeyBudgetExceeded: "key_budget_exceeded";
208
286
  readonly InsufficientQuota: "insufficient_quota";
@@ -212,6 +290,7 @@ export declare const ErrorCode: {
212
290
  readonly IdempotencyConflict: "idempotency_conflict";
213
291
  readonly NotFound: "not_found";
214
292
  readonly NumberInUse: "number_in_use";
293
+ readonly UnsupportedToolTransport: "unsupported_tool_transport";
215
294
  };
216
295
  export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
217
296
  /** A top-3 knowledge-base passage attached to Cue (grounded) finals. */
@@ -219,6 +298,22 @@ export interface HearGroundingPassage {
219
298
  content: string;
220
299
  score: number;
221
300
  }
301
+ /** One endpointing validation result echoed in a `config_ack` frame. */
302
+ export interface HearConfigWarning {
303
+ field: string;
304
+ value: unknown;
305
+ effective?: number;
306
+ reason: "clamped_to_range" | "not_a_number" | "unknown_config_field" | (string & {});
307
+ }
308
+ /** Applied endpointing settings after connect-time or mid-session config. */
309
+ export interface HearConfigAckFrame {
310
+ type: "config_ack";
311
+ endpointing_ms: number;
312
+ effective_floor_ms: number;
313
+ effective_ceiling_ms: number;
314
+ score_interval_ms: number;
315
+ warnings: HearConfigWarning[];
316
+ }
222
317
  /** Live hypothesis frame (`partial` / `partial_stable`). */
223
318
  export interface HearPartialFrame {
224
319
  type: "partial" | "partial_stable";
@@ -232,6 +327,7 @@ export interface HearPartialFrame {
232
327
  t_ms: number;
233
328
  }
234
329
  /** Finalized-utterance frame (`speech_final` / `final`). */
330
+ export type HearEndpointReason = "peak_te_early" | "silence_backstop" | "commit" | (string & {});
235
331
  export interface HearFinalFrame {
236
332
  type: "speech_final" | "final";
237
333
  text: string;
@@ -239,6 +335,8 @@ export interface HearFinalFrame {
239
335
  t_ms: number;
240
336
  /** Active-speech length of the utterance (the billed signal), ms. */
241
337
  audio_ms: number;
338
+ /** Why the utterance ended. Log this when tuning automatic endpointing. */
339
+ endpoint_reason: HearEndpointReason;
242
340
  /** Present only with Cue grounding enabled (top KB passages). */
243
341
  grounding?: HearGroundingPassage[];
244
342
  }
@@ -250,7 +348,7 @@ export interface HearUsageFrame {
250
348
  type: "usage";
251
349
  /** `hear` for plain streaming, `cue` when grounding was enabled. */
252
350
  product: "hear" | "cue";
253
- /** The billed meter (`hear.requests` or `cue.minutes`). */
351
+ /** The billed meter (`hear.minutes` or `cue.minutes`). */
254
352
  meter: string;
255
353
  /** Summed active-speech audio billed for the session, in seconds. */
256
354
  audio_seconds: number;
@@ -263,7 +361,7 @@ export interface HearErrorFrame {
263
361
  code?: string;
264
362
  message: string;
265
363
  }
266
- export type HearFrame = HearPartialFrame | HearFinalFrame | HearUsageFrame | HearErrorFrame;
364
+ export type HearFrame = HearConfigAckFrame | HearPartialFrame | HearFinalFrame | HearUsageFrame | HearErrorFrame;
267
365
  /**
268
366
  * Minimal structural WebSocket, matches both the browser/Node global
269
367
  * `WebSocket` and the `ws` package, and lets tests inject a mock.
@@ -282,35 +380,68 @@ export interface WebSocketLike {
282
380
  reason: string;
283
381
  }) => void) | null;
284
382
  }
383
+ /**
384
+ * The non-secret marker every PyAI realtime client offers ALONGSIDE its
385
+ * `pyai-key.<KEY>` credential token.
386
+ *
387
+ * A browser cannot set `Authorization` on a WebSocket, so the key rides in
388
+ * `Sec-WebSocket-Protocol`. RFC 6455 makes the server echo the subprotocol it
389
+ * SELECTS, so a server that selects the credential publishes the credential —
390
+ * which api.pyai.com did until 2026-09-07. The edge now echoes only this
391
+ * marker. Offering it is not optional for Node clients: `ws` throws
392
+ * "Server sent no subprotocol" when it offered subprotocols and the 101
393
+ * selected none, and RFC 6455 lets the server select only a value the client
394
+ * actually offered.
395
+ */
396
+ export declare const REALTIME_SUBPROTOCOL_MARKER = "pyai.v1";
285
397
  export type WebSocketCtor = new (url: string, protocols?: string | string[]) => WebSocketLike;
286
398
  export interface HearStreamOptions {
287
399
  /** Streaming STT model. Server default `pyai-hear`. */
288
400
  model?: string;
289
- /** ISO-639-1 hint, e.g. "en". */
290
- language?: string;
401
+ /** Omit or use auto for automatic detection; an explicit code pins recognition. */
402
+ language?: "auto" | "en" | "es" | "fr" | "de" | "hi" | "it" | "pt" | "nl";
291
403
  /** Input PCM sample rate in Hz. Default 16000 server-side. */
292
404
  sampleRate?: number;
293
405
  /** Audio frame encoding. Default "pcm16". */
294
- encoding?: "pcm16" | "opus";
406
+ encoding?: "pcm16";
295
407
  /** Emit eager partial hypotheses. Default true server-side. */
296
408
  interimResults?: boolean;
297
409
  /**
298
- * Format spoken numbers as digits in the transcript (e.g. "one two three" →
299
- * "123"). Useful for voice agents that read back phone numbers, codes, and
300
- * amounts. Default false (spoken form). Forwards `?numerals=true` on the URL.
410
+ * Tri-state number formatting on **final** transcripts. `true` forces digits,
411
+ * `false` keeps spoken form, omitted keeps the live engine default (ITN on).
412
+ * Never applied to interim partials. Independent of `smartFormat`.
301
413
  */
302
414
  numerals?: boolean;
303
415
  /**
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.
416
+ * Opt-in English punctuation and sentence capitalization on **final**
417
+ * transcripts only. Interim partials are never formatted. Default false.
418
+ */
419
+ smartFormat?: boolean;
420
+ /**
421
+ * Spoken punctuation commands (`period`, `comma`, `new paragraph`,
422
+ * `question mark`) on finals only. Separate from `smartFormat`. Off by default.
423
+ */
424
+ dictation?: boolean;
425
+ /**
426
+ * Strip `um` / `uh` / `umm` / `uhh` / `er` on finals. Off by default.
427
+ * Do not enable on legal or compliance audio by default.
428
+ */
429
+ dropFillers?: boolean;
430
+ /**
431
+ * Per-session names, brands, products, or other distinctive terms. PyAI
432
+ * sanitizes up to five effective terms. Request terms come first, then stored
433
+ * `hear_stream` suggestions fill remaining slots. The list is fixed at open.
434
+ */
435
+ vocabulary?: string[];
436
+ /**
437
+ * Minimum trailing-pause length before an utterance may end (50-5000 ms).
438
+ * Turn detection may wait longer, bounded at `max(endpointingMs, 1500)`.
439
+ * The server confirms the applied value through `onConfigAck`.
308
440
  */
309
441
  endpointingMs?: number;
310
442
  /**
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.
443
+ * Reserved Cue grounding configuration. Grounding is not active on the
444
+ * serving Hear stream; do not rely on grounding frames or Cue metering yet.
314
445
  */
315
446
  grounding?: boolean;
316
447
  /** Cue: number of KB passages to retrieve per turn (1-20, default 3). */
@@ -322,8 +453,11 @@ export interface HearStreamOptions {
322
453
  groundingTimeoutMs?: number;
323
454
  /** Extra query params merged onto the connect URL. */
324
455
  query?: Record<string, string>;
325
- /** Fired once the socket opens (after the optional grounding config). */
456
+ /** Fired once the socket opens. */
326
457
  onOpen?: () => void;
458
+ /** Fired after connect-time or mid-session endpointing config. Assert that
459
+ * `warnings` is empty before relying on the requested floor. */
460
+ onConfigAck?: (frame: HearConfigAckFrame) => void;
327
461
  /** Fired on `partial` / `partial_stable`. */
328
462
  onPartial?: (frame: HearPartialFrame) => void;
329
463
  /** Fired on `speech_final` / `final`. */
@@ -340,8 +474,10 @@ export interface HearStreamOptions {
340
474
  /**
341
475
  * A live Hear streaming-STT session. Hides the frame protocol: stream audio
342
476
  * 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()`.
477
+ * callbacks, update the silence floor with
478
+ * {@link HearStream.configureEndpointing}, force-finalize with
479
+ * {@link HearStream.commit}, and flush+close with {@link HearStream.close}.
480
+ * Construct via `pyai.audio.transcriptions.stream()`.
345
481
  */
346
482
  export declare class HearStream {
347
483
  private readonly ws;
@@ -351,6 +487,8 @@ export declare class HearStream {
351
487
  private handleMessage;
352
488
  /** Send a chunk of audio (PCM16 or opus per `encoding`). */
353
489
  sendAudio(chunk: ArrayBufferLike | ArrayBufferView | Blob): void;
490
+ /** Change the minimum trailing-pause floor without reconnecting. */
491
+ configureEndpointing(endpointingMs: number): void;
354
492
  /** Force-finalize the current utterance (e.g. on VAD end-of-turn). */
355
493
  commit(): void;
356
494
  /** Close the socket; the server flushes a final for any buffered audio. */
@@ -409,6 +547,8 @@ export declare const OmniEvent: {
409
547
  readonly Flush: "flush";
410
548
  /** Engine requests a client-loop tool invocation. */
411
549
  readonly ToolCall: "tool_call";
550
+ /** A write tool is waiting for the caller to confirm; it has not run. */
551
+ readonly ToolConfirmationRequired: "tool_confirmation_required";
412
552
  /** Session is closing; see close code. */
413
553
  readonly SessionEnd: "session_end";
414
554
  /** Server fault frame. */
@@ -420,16 +560,20 @@ export interface OmniServerFrame {
420
560
  event: string;
421
561
  [k: string]: unknown;
422
562
  }
423
- /** Canonical, sanitized transcript delivered by the native Omni demux. */
563
+ /** Canonical, sanitized transcript delivered by the native Omni demux.
564
+ * The live wire sends caller text deltas; the JSON fields are SDK-owned
565
+ * normalization so applications don't have to special-case the byte payload. */
424
566
  export interface OmniTranscriptFrame extends OmniServerFrame {
425
567
  event: "transcript";
426
568
  role: "user" | "assistant";
427
569
  text: string;
428
570
  final: boolean;
571
+ /** Live text frames append; legacy JSON frames replace unless they use `delta`. */
429
572
  mode: "delta" | "replace";
573
+ /** Optional ordering hint on legacy JSON frames. */
430
574
  sequence?: number;
431
575
  }
432
- /** Normalize the live UTF-8 0x02 body and the documented direct-object legacy shape. */
576
+ /** Normalize the live UTF-8 `0x02` text body and bounded legacy JSON bodies. */
433
577
  export declare function normalizeOmniTranscriptBody(bytes: Uint8Array): OmniTranscriptFrame | null;
434
578
  /** A binary agent-audio chunk delivered to {@link OmniConnectOptions.onAudio}. */
435
579
  export type OmniAudioChunk = ArrayBuffer | ArrayBufferView | Blob;
@@ -437,11 +581,9 @@ export interface OmniToolDef {
437
581
  name: string;
438
582
  description?: string;
439
583
  parameters?: Record<string, unknown>;
440
- /** When set, engine-POST mode. Omit for client-loop (default). */
441
- endpoint?: string;
442
584
  }
443
585
  export interface OmniToolCallFrame {
444
- type: "tool_call";
586
+ event: "tool_call";
445
587
  call_id: string;
446
588
  name: string;
447
589
  arguments?: Record<string, unknown>;
@@ -466,7 +608,7 @@ export interface OmniConfigure {
466
608
  /**
467
609
  * Session language, end to end (recognition, reasoning, voice). Also
468
610
  * settable on the agent profile (`language` on `POST /v1/agents`), which
469
- * applies automatically when connecting with `session_label={agent_id}`;
611
+ * applies automatically when `session_label` is the saved profile id;
470
612
  * an inline value here wins for the session. Default `en`. Fail-safe: an
471
613
  * unknown/not-yet-enabled language falls back to `en` (the `configured`
472
614
  * ack carries `language_active` + `language_fallback: true`), the call
@@ -474,7 +616,9 @@ export interface OmniConfigure {
474
616
  * staged, see the Language support reference.
475
617
  */
476
618
  language?: "en" | "fr" | "es" | "de" | "hi";
477
- /** Function calling definitions (client-loop when `endpoint` is omitted). */
619
+ /** Function calling: hosted catalog names, client-loop schemas, or names of
620
+ * server tools already registered with POST /v1/tools. An inline `endpoint`
621
+ * is rejected with `unsupported_tool_transport`. */
478
622
  tools?: OmniToolDef[];
479
623
  /** Forward-compatible: any other key the engine honors. */
480
624
  [k: string]: unknown;
@@ -488,7 +632,11 @@ export interface OmniConnectOptions {
488
632
  * so a page never holds a secret key.
489
633
  */
490
634
  token?: string;
491
- /** Connect-URL sample rate: 24000 browser, 16000 wideband telephony, 8000 G.711/Twilio. */
635
+ /**
636
+ * Caller-input sample rate. `24000` and `16000` sessions receive agent audio
637
+ * at 24 kHz; `8000` sessions receive 8 kHz. Read `hello.audio_out` rather than
638
+ * assuming output matches this value.
639
+ */
492
640
  rate?: 24000 | 16000 | 8000;
493
641
  /** Connect-URL audio format. Default `pcm16`. */
494
642
  format?: "pcm16";
@@ -511,7 +659,7 @@ export interface OmniConnectOptions {
511
659
  onSessionStarted?: (frame: OmniServerFrame) => void;
512
660
  /** Fired on `turn` boundaries. */
513
661
  onTurn?: (frame: OmniServerFrame) => void;
514
- /** Fired on `transcript` text frames. */
662
+ /** Fired for each normalized caller-transcript delta (`0x02` plain UTF-8 live). */
515
663
  onTranscript?: (frame: OmniServerFrame) => void;
516
664
  /** Fired on `barge_in` / `flush` (user interrupted). */
517
665
  onBargeIn?: (frame: OmniServerFrame) => void;
@@ -836,8 +984,45 @@ export interface RecapCallSummary {
836
984
  created_at?: number;
837
985
  completed_at?: number | null;
838
986
  }
987
+ export interface RecapActionItem {
988
+ owner?: string | null;
989
+ task: string;
990
+ due?: string | null;
991
+ }
992
+ export interface RecapTalkRatio {
993
+ agent: number;
994
+ customer: number;
995
+ }
996
+ export interface RecapSignal {
997
+ kind: string;
998
+ text: string;
999
+ at_s?: number | null;
1000
+ }
1001
+ export interface RecapRecord {
1002
+ format: "recap.record.v1";
1003
+ tldr: string | null;
1004
+ summary: string | null;
1005
+ action_items: RecapActionItem[];
1006
+ disposition: string | null;
1007
+ next_steps: string | null;
1008
+ talk_ratio: RecapTalkRatio | null;
1009
+ signals: RecapSignal[];
1010
+ fields: Record<string, unknown>;
1011
+ rep?: Record<string, unknown>;
1012
+ manager?: Record<string, unknown>;
1013
+ ops?: Record<string, unknown>;
1014
+ }
839
1015
  export interface RecapCall extends RecapCallSummary {
840
- record?: unknown;
1016
+ record?: RecapRecord;
1017
+ transcript?: {
1018
+ format: "utterances.v1";
1019
+ utterances: Array<{
1020
+ speaker_role: "agent" | "customer";
1021
+ text: string;
1022
+ offset_s: number;
1023
+ duration_s: number;
1024
+ }>;
1025
+ };
841
1026
  error?: string | null;
842
1027
  crm_write_status?: string | null;
843
1028
  }
@@ -854,8 +1039,13 @@ export interface RecapCallTriggerInput {
854
1039
  customer_name?: string;
855
1040
  crm_fields?: Record<string, unknown>;
856
1041
  }
857
- /** PyAI's richer answered-by vocabulary (superset of Twilio's enum). */
858
- export type AmdAnsweredBy = "human" | "voicemail" | "live_voicemail" | "screening" | "ivr" | "human_gatekeeper" | "sit_invalid" | "fax" | "silence" | "unknown";
1042
+ /**
1043
+ * The answered-by vocabulary on stored call records and the
1044
+ * `amd.call.completed` webhook: the routing classes plus the machine subtypes.
1045
+ */
1046
+ export type AmdAnsweredBy = "human" | "machine" | "voicemail" | "screening" | "ivr" | "music" | "sit_invalid" | "unknown";
1047
+ /** The routing classes pushed on the mid-call wire event (`event: "amd"`). */
1048
+ export type AmdWireAnsweredBy = "human" | "machine" | "sit_invalid" | "unknown";
859
1049
  /** Twilio's `AnsweredBy` enum, echoed for drop-in migration parity. */
860
1050
  export type AmdTwilioAnsweredBy = "human" | "machine_start" | "machine_end_beep" | "machine_end_silence" | "machine_end_other" | "fax" | "unknown";
861
1051
  export interface AmdConfigInput {
@@ -893,11 +1083,16 @@ export interface AmdCall extends AmdCallSummary {
893
1083
  meta?: Record<string, unknown> | null;
894
1084
  error?: string | null;
895
1085
  }
896
- /** A mid-call AMD decision event pushed on the stream (and to the webhook). */
1086
+ /**
1087
+ * A mid-call AMD decision event pushed on the stream (and to the per-call
1088
+ * TwiML `webhook`). Carries the coarse routing class; the machine subtype
1089
+ * (`voicemail`/`ivr`/`screening`/`music`) is on the stored call record
1090
+ * (`AmdCall`) and the `amd.call.completed` webhook instead.
1091
+ */
897
1092
  export interface AmdDecisionEvent {
898
1093
  event?: "amd";
899
1094
  call_id?: string;
900
- answered_by?: AmdAnsweredBy;
1095
+ answered_by?: AmdWireAnsweredBy;
901
1096
  answered_by_twilio?: string | null;
902
1097
  confidence?: number | null;
903
1098
  decision_ms?: number | null;
@@ -946,19 +1141,43 @@ export declare class PyAI {
946
1141
  list: (params?: {
947
1142
  gender?: string;
948
1143
  region?: string;
1144
+ language?: string;
1145
+ tier?: "standard" | "natural";
1146
+ q?: string;
949
1147
  }) => Promise<ListResponse<Voice>>;
950
1148
  get: (id: string) => Promise<Voice>;
951
1149
  };
1150
+ hear: {
1151
+ vocabulary: {
1152
+ /** Read organization-owned vocabulary. Scope `hear:configure`. */
1153
+ get: () => Promise<HearVocabulary>;
1154
+ /** Replace vocabulary and activation profiles. Scope `hear:configure`. */
1155
+ set: (input: HearVocabularyInput) => Promise<HearVocabulary>;
1156
+ };
1157
+ };
1158
+ agents: {
1159
+ list: () => Promise<ListResponse<Agent>>;
1160
+ get: (agentId: string) => Promise<Agent>;
1161
+ create: (input: AgentConfig & {
1162
+ name: string;
1163
+ }) => Promise<Agent>;
1164
+ update: (agentId: string, patch: AgentConfig) => Promise<Agent>;
1165
+ delete: (agentId: string) => Promise<{
1166
+ object: "agent.deleted";
1167
+ agent_id: string;
1168
+ deleted: boolean;
1169
+ }>;
1170
+ };
952
1171
  audio: {
953
1172
  /** Text-to-speech. Returns the raw audio bytes (default WAV). */
954
1173
  speech: (params: SpeechParams) => Promise<ArrayBuffer>;
955
1174
  /**
956
1175
  * Text-to-speech, streamed. Resolves as soon as the response headers arrive
957
1176
  * with the body as a `ReadableStream` of audio bytes, so you can start
958
- * playback or forward the audio at the first chunk (the engine's
959
- * time-to-first-byte is tens of ms) instead of buffering the whole clip.
960
- * Use `mp3` for the smoothest progressive playback. Returns an async
961
- * iterable of Uint8Array chunks.
1177
+ * playback or forward the audio at the first chunk. Use `pcm`, `wav`,
1178
+ * or G.711 for streaming; `mp3` and `opus` are buffered server-side.
1179
+ * Consume the stream immediately and cancel its reader when stopping early.
1180
+ * HTTP connection reuse and protocol negotiation are controlled by fetch.
962
1181
  */
963
1182
  speechStream: (params: SpeechParams) => Promise<ReadableStream<Uint8Array>>;
964
1183
  /** Synchronous speech-to-text (multipart upload). */
@@ -967,7 +1186,13 @@ export declare class PyAI {
967
1186
  file: Blob;
968
1187
  filename?: string;
969
1188
  model?: string;
970
- language?: string;
1189
+ /** Optional language hint. Omission enables automatic detection. */
1190
+ language?: "en" | "es" | "fr" | "de" | "hi" | "it" | "pt" | "nl";
1191
+ numerals?: boolean;
1192
+ smart_format?: boolean;
1193
+ dictation?: boolean;
1194
+ drop_fillers?: boolean;
1195
+ vocabulary?: string[];
971
1196
  response_format?: "json" | "text" | "verbose_json";
972
1197
  /**
973
1198
  * Deterministic seed for reproducible eval runs. Forward-compatible:
@@ -1007,7 +1232,7 @@ export declare class PyAI {
1007
1232
  clones: {
1008
1233
  /** List the org's cloned voices. */
1009
1234
  list: () => Promise<ListResponse<Voice>>;
1010
- /** Enroll a custom voice from reference audio (>= ~10s). Scope `voice:clone`. */
1235
+ /** Enroll a custom voice from reference audio (>= ~10s). Scope `speak:clone`. */
1011
1236
  create: (params: {
1012
1237
  name: string;
1013
1238
  file: Blob;
@@ -1019,7 +1244,7 @@ export declare class PyAI {
1019
1244
  * when the id isn't found.
1020
1245
  */
1021
1246
  get: (id: string) => Promise<Voice>;
1022
- /** Delete a cloned voice (tenant-isolated). Scope `voice:clone`. */
1247
+ /** Delete a cloned voice (tenant-isolated). Scope `speak:clone`. */
1023
1248
  delete: (id: string) => Promise<void>;
1024
1249
  };
1025
1250
  telephony: {
@@ -1148,7 +1373,7 @@ export declare class PyAI {
1148
1373
  * realtime. **Call this from your server** with a secret key holding
1149
1374
  * `omni:session`; never ship the secret key to a page. Hand the returned
1150
1375
  * `token` to the browser, which connects with
1151
- * `new WebSocket(session.url, ["pyai-key." + session.token])`. The token
1376
+ * `new WebSocket(session.url, ["pyai.v1", "pyai-key." + session.token])`. The token
1152
1377
  * expires after `ttlSeconds` (default 60s) and only works from
1153
1378
  * `allowedOrigins`. Scope `omni:session`.
1154
1379
  */
@@ -1164,10 +1389,17 @@ export declare class PyAI {
1164
1389
  */
1165
1390
  connect: (opts?: OmniConnectOptions) => OmniConnection;
1166
1391
  };
1167
- /** Build the realtime WebSocket URL for the chosen product. */
1392
+ /** Build the canonical Omni WebSocket URL. */
1168
1393
  realtimeURL(opts?: RealtimeOptions): string;
1169
- /** The subprotocol that carries the key on a WS upgrade (browser-safe auth). */
1394
+ /** The subprotocol token that carries the key on a WS upgrade. */
1170
1395
  realtimeSubprotocol(): string;
1396
+ /**
1397
+ * The full subprotocol list to open a PyAI WebSocket with:
1398
+ * `[marker, credential]`. Always pass BOTH — see
1399
+ * {@link REALTIME_SUBPROTOCOL_MARKER}. Pass `token` to use an ephemeral
1400
+ * session token (from `omni.createSession`) instead of the secret key.
1401
+ */
1402
+ realtimeSubprotocols(token?: string): string[];
1171
1403
  /** Build the Hear streaming-STT WebSocket URL (`/v1/audio/transcriptions/stream`). */
1172
1404
  hearStreamURL(opts?: HearStreamOptions): string;
1173
1405
  /** Build the AMD detection WebSocket URL (`/v1/amd/stream`). */