@pyai/sdk 0.1.2 → 0.2.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/dist/index.d.ts CHANGED
@@ -46,18 +46,70 @@ export interface TranscriptionJob {
46
46
  result_url?: string;
47
47
  error?: string;
48
48
  }
49
+ /**
50
+ * Output container/codec for `audio.speech`. This is the **exact** set the
51
+ * server accepts on `POST /v1/audio/speech` — any other value is rejected with
52
+ * `400 unsupported_format`. The default (when `response_format` is omitted) is
53
+ * `mp3`. Omit `sample_rate` for the engine's native 24 kHz (`g711_*` is always
54
+ * 8 kHz).
55
+ *
56
+ * | format | rates (Hz) | Content-Type |
57
+ * |---|---|---|
58
+ * | `mp3` (default) | 8000/16000/24000/48000 | `audio/mpeg` |
59
+ * | `wav` | 8000/16000/24000/48000 | `audio/wav` |
60
+ * | `opus` | 8000/16000/24000/48000 | `audio/ogg` |
61
+ * | `aac` | 8000/16000/24000/48000 | `audio/aac` |
62
+ * | `flac` | 8000/16000/24000/48000 | `audio/flac` |
63
+ * | `pcm` | 8000/16000/24000/48000 | `audio/pcm` (raw int16 LE mono, no header) |
64
+ * | `g711_ulaw` | 8000 (forced) | `audio/basic` |
65
+ * | `g711_alaw` | 8000 (forced) | `audio/basic` |
66
+ */
67
+ export type SpeechFormat = "wav" | "mp3" | "opus" | "aac" | "flac" | "pcm" | "g711_ulaw" | "g711_alaw";
68
+ /**
69
+ * The runtime list of accepted `audio.speech` formats (mirrors {@link SpeechFormat}),
70
+ * for building dropdowns / validating input before a request. Branch on the
71
+ * named values; the order matches the contract doc.
72
+ */
73
+ export declare const SPEECH_FORMATS: readonly ["wav", "mp3", "opus", "aac", "flac", "pcm", "g711_ulaw", "g711_alaw"];
74
+ /** Sample rates (Hz) the server accepts for `audio.speech` (`g711_*` is always 8 kHz). */
75
+ export declare const SPEECH_SAMPLE_RATES: readonly [8000, 16000, 24000, 48000];
76
+ export type SpeechSampleRate = (typeof SPEECH_SAMPLE_RATES)[number];
49
77
  export interface SpeechParams {
50
78
  input: string;
51
79
  voice?: string;
52
80
  model?: string;
53
- response_format?: "wav" | "mp3" | "opus" | "aac" | "flac" | "pcm";
54
81
  /**
55
- * Output sample rate in Hz (8000-48000). Omit for the native 24 kHz. Most
56
- * useful with `response_format: "pcm"` (raw 16-bit mono samples), e.g. set
57
- * `8000`/`16000` for telephony pipelines.
82
+ * Output container/codec, resampled+encoded server-side. One of
83
+ * {@link SpeechFormat} anything else is a `400 unsupported_format`. Omit for
84
+ * the default of `mp3`.
85
+ *
86
+ * `g711_ulaw`/`g711_alaw` return raw 8 kHz mono G.711 — the bytes Twilio/SIP
87
+ * media streams expect, so you can hand the response straight to a telephony
88
+ * frame without a client-side resampler or μ-law encoder. `sample_rate` is
89
+ * forced to 8000 for those (omit it, or set exactly 8000). `pcm` is raw,
90
+ * headerless int16 LE mono at `sample_rate`. `mp3`/`opus` are buffered (not
91
+ * chunk-streamed) — use them with `speech`, not `speechStream`.
58
92
  */
59
- sample_rate?: number;
93
+ response_format?: SpeechFormat;
94
+ /**
95
+ * Output sample rate in Hz. One of {@link SpeechSampleRate}
96
+ * (8000/16000/24000/48000) — anything else is a `400`. Omit for the engine's
97
+ * native 24 kHz; `g711_*` is always 8000 (forced). Set `8000`/`16000` for
98
+ * telephony pipelines, most often with `response_format: "pcm"`.
99
+ */
100
+ sample_rate?: SpeechSampleRate;
60
101
  speed?: number;
102
+ /**
103
+ * Deterministic sampling seed for reproducible eval runs. Forward-compatible:
104
+ * honored once the engine supports it (otherwise ignored server-side), so it's
105
+ * always safe to send.
106
+ */
107
+ seed?: number;
108
+ /**
109
+ * Sampling temperature (lower = more deterministic). Forward-compatible —
110
+ * honored once the engine supports it, otherwise ignored.
111
+ */
112
+ temperature?: number;
61
113
  }
62
114
  export interface CreateJobParams {
63
115
  audio_url: string;
@@ -69,13 +121,660 @@ export interface CreateJobParams {
69
121
  webhook_url?: string;
70
122
  }
71
123
  export interface RealtimeOptions {
72
- /** "omni" (agentic, needs agentId) or "flow" (voice duplex). Default "omni". */
124
+ /** "omni" (agentic voice) or "flow" (legacy voice duplex). Default "omni". */
73
125
  product?: "omni" | "flow";
74
- /** Required for omni: the agent to drive. */
126
+ /**
127
+ * Optional opaque tag echoed to your `kb_endpoint` and recorded on the call.
128
+ * Omni is zero-state: the session is authorized by the key's org, so there is
129
+ * nothing to create first and this tag is never required.
130
+ */
131
+ sessionLabel?: string;
132
+ /**
133
+ * @deprecated Use {@link sessionLabel}. Kept for back-compat — emitted as the
134
+ * `agent_id` query alias, which the gateway still accepts. Ignored if
135
+ * `sessionLabel` is set.
136
+ */
75
137
  agentId?: string;
76
138
  /** Extra query params (e.g. format, rate). */
77
139
  query?: Record<string, string>;
78
140
  }
141
+ /** Parameters for minting an ephemeral browser Omni session token (server-side). */
142
+ export interface OmniSessionParams {
143
+ /**
144
+ * Browser origins (scheme://host[:port]) the minted token may connect from.
145
+ * Required and non-empty — a browser token must be origin-locked. `*` is not
146
+ * allowed.
147
+ */
148
+ allowedOrigins: string[];
149
+ /** Token lifetime in seconds. Default 60 server-side; max 600. Keep it short. */
150
+ ttlSeconds?: number;
151
+ /** Optional opaque tag echoed back to your `kb_endpoint` and recorded on the call. */
152
+ sessionLabel?: string;
153
+ }
154
+ /**
155
+ * The result of {@link PyAI.omni}.createSession — a short-lived credential safe
156
+ * to hand to the browser. Use {@link OmniSession.token} as the WebSocket
157
+ * subprotocol `pyai-key.<token>` against {@link OmniSession.url}.
158
+ */
159
+ export interface OmniSession {
160
+ object?: "omni.session";
161
+ /** The ephemeral session token. Short-lived + origin-locked; browser-safe. */
162
+ token: string;
163
+ /** Token expiry, Unix epoch milliseconds. */
164
+ expires_at: number;
165
+ /** The Omni realtime WebSocket URL to connect to. */
166
+ url: string;
167
+ /** Echoed back when supplied on the request. */
168
+ session_label?: string;
169
+ }
170
+ /** Frame `type`s emitted by the Hear streaming-STT WebSocket. */
171
+ export declare const HearFrameType: {
172
+ /** Eager live hypothesis for the current utterance. */
173
+ readonly Partial: "partial";
174
+ /** Partial whose prefix has stabilized (won't be revised). */
175
+ readonly PartialStable: "partial_stable";
176
+ /** Stable transcript at end-of-utterance (endpoint or commit). */
177
+ readonly SpeechFinal: "speech_final";
178
+ /** Corrected, full-context transcript following `speech_final`. */
179
+ readonly Final: "final";
180
+ /** Final billed-usage summary, emitted just before a graceful close. */
181
+ readonly Usage: "usage";
182
+ /** Server-side fault frame. */
183
+ readonly Error: "error";
184
+ };
185
+ export type HearFrameType = (typeof HearFrameType)[keyof typeof HearFrameType];
186
+ /** WebSocket close codes used across the PyAI realtime/streaming surfaces. */
187
+ export declare const WSCloseCode: {
188
+ /** Normal closure. */
189
+ readonly Normal: 1000;
190
+ /** Auth/policy: bad key, missing scope, or revoked token. */
191
+ readonly PolicyViolation: 1008;
192
+ /** Engine/internal error. */
193
+ readonly InternalError: 1011;
194
+ /** Over the concurrency cap (PyAI-specific; mirrors HTTP 429). */
195
+ readonly OverCapacity: 4429;
196
+ };
197
+ export type WSCloseCode = (typeof WSCloseCode)[keyof typeof WSCloseCode];
198
+ /**
199
+ * Stable, machine-readable error `code`s (the documented contract). Branch on
200
+ * these. The set is treated as open — `PyAIError.code` stays `string` — so a
201
+ * new server code never breaks the build, but the known ones are named here.
202
+ */
203
+ export declare const ErrorCode: {
204
+ readonly Unauthorized: "unauthorized";
205
+ readonly Forbidden: "forbidden";
206
+ readonly OriginNotAllowed: "origin_not_allowed";
207
+ readonly InvalidAgentId: "invalid_agent_id";
208
+ readonly CreditExhausted: "credit_exhausted";
209
+ readonly KeyBudgetExceeded: "key_budget_exceeded";
210
+ readonly InsufficientQuota: "insufficient_quota";
211
+ readonly RateLimitExceeded: "rate_limit_exceeded";
212
+ readonly ConcurrencyLimitExceeded: "concurrency_limit_exceeded";
213
+ readonly DailyCapExceeded: "daily_cap_exceeded";
214
+ readonly IdempotencyConflict: "idempotency_conflict";
215
+ readonly NotFound: "not_found";
216
+ readonly NumberInUse: "number_in_use";
217
+ };
218
+ export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
219
+ /** A top-3 knowledge-base passage attached to Cue (grounded) finals. */
220
+ export interface HearGroundingPassage {
221
+ content: string;
222
+ score: number;
223
+ }
224
+ /** Live hypothesis frame (`partial` / `partial_stable`). */
225
+ export interface HearPartialFrame {
226
+ type: "partial" | "partial_stable";
227
+ text: string;
228
+ /** Prefix that has stabilized (won't change). */
229
+ stable_text?: string;
230
+ /** The still-changing tail. */
231
+ active_text?: string;
232
+ utterance_id: string;
233
+ /** Audio-timeline position of the hypothesis, ms. */
234
+ t_ms: number;
235
+ }
236
+ /** Finalized-utterance frame (`speech_final` / `final`). */
237
+ export interface HearFinalFrame {
238
+ type: "speech_final" | "final";
239
+ text: string;
240
+ utterance_id: string;
241
+ t_ms: number;
242
+ /** Active-speech length of the utterance (the billed signal), ms. */
243
+ audio_ms: number;
244
+ /** Present only with Cue grounding enabled (top KB passages). */
245
+ grounding?: HearGroundingPassage[];
246
+ }
247
+ /** Final billed-usage summary (`usage`), emitted just before a graceful close
248
+ * so realtime spend can be reconciled in-band (a realtime WS carries no
249
+ * `x-pyai-units` response header). Best-effort: absent if there was no billable
250
+ * audio or the close was abnormal. */
251
+ export interface HearUsageFrame {
252
+ type: "usage";
253
+ /** `hear` for plain streaming, `cue` when grounding was enabled. */
254
+ product: "hear" | "cue";
255
+ /** The billed meter (`hear.requests` or `cue.minutes`). */
256
+ meter: string;
257
+ /** Summed active-speech audio billed for the session, in seconds. */
258
+ audio_seconds: number;
259
+ /** The same quantity in minutes. */
260
+ minutes: number;
261
+ }
262
+ /** Server fault frame (`error`). */
263
+ export interface HearErrorFrame {
264
+ type: "error";
265
+ code?: string;
266
+ message: string;
267
+ }
268
+ export type HearFrame = HearPartialFrame | HearFinalFrame | HearUsageFrame | HearErrorFrame;
269
+ /**
270
+ * Minimal structural WebSocket — matches both the browser/Node global
271
+ * `WebSocket` and the `ws` package, and lets tests inject a mock.
272
+ */
273
+ export interface WebSocketLike {
274
+ send(data: string | ArrayBufferLike | ArrayBufferView | Blob): void;
275
+ close(code?: number, reason?: string): void;
276
+ readonly readyState: number;
277
+ onopen: ((ev: unknown) => void) | null;
278
+ onmessage: ((ev: {
279
+ data: unknown;
280
+ }) => void) | null;
281
+ onerror: ((ev: unknown) => void) | null;
282
+ onclose: ((ev: {
283
+ code: number;
284
+ reason: string;
285
+ }) => void) | null;
286
+ }
287
+ export type WebSocketCtor = new (url: string, protocols?: string | string[]) => WebSocketLike;
288
+ export interface HearStreamOptions {
289
+ /** Streaming STT model. Server default `pyai-hear`. */
290
+ model?: string;
291
+ /** ISO-639-1 hint, e.g. "en". */
292
+ language?: string;
293
+ /** Input PCM sample rate in Hz. Default 16000 server-side. */
294
+ sampleRate?: number;
295
+ /** Audio frame encoding. Default "pcm16". */
296
+ encoding?: "pcm16" | "opus";
297
+ /** Emit eager partial hypotheses. Default true server-side. */
298
+ interimResults?: boolean;
299
+ /**
300
+ * Format spoken numbers as digits in the transcript (e.g. "one two three" →
301
+ * "123"). Useful for voice agents that read back phone numbers, codes, and
302
+ * amounts. Default false (spoken form). Forwards `?numerals=true` on the URL.
303
+ */
304
+ numerals?: boolean;
305
+ /**
306
+ * Turn-segmentation tuning: trailing-pause (ms, 50–2000) that ends an
307
+ * utterance. Forwards `?endpointing_ms=` — clamped + honored once the engine
308
+ * supports it; a no-op when omitted. Drive end-of-turn yourself with
309
+ * {@link HearStream.commit} for full control today.
310
+ */
311
+ endpointingMs?: number;
312
+ /**
313
+ * Enable Cue knowledge-base grounding: sends `{type:"config",grounding:true}`
314
+ * on open, after which `speech_final`/`final` frames carry a `grounding`
315
+ * array. Bills a single `cue.minutes` line instead of the Hear rate.
316
+ */
317
+ grounding?: boolean;
318
+ /** Cue: number of KB passages to retrieve per turn (1–20, default 3). */
319
+ groundingK?: number;
320
+ /** Cue: drop passages scoring below this (0–1, default 0 = keep all). */
321
+ groundingMinScore?: number;
322
+ /** Cue: max ms to wait for retrieval at the final before failing open to
323
+ * `grounding: []` (50–2000, default 450). */
324
+ groundingTimeoutMs?: number;
325
+ /** Extra query params merged onto the connect URL. */
326
+ query?: Record<string, string>;
327
+ /** Fired once the socket opens (after the optional grounding config). */
328
+ onOpen?: () => void;
329
+ /** Fired on `partial` / `partial_stable`. */
330
+ onPartial?: (frame: HearPartialFrame) => void;
331
+ /** Fired on `speech_final` / `final`. */
332
+ onFinal?: (frame: HearFinalFrame) => void;
333
+ /** Fired on the final `usage` summary (in-band realtime reconciliation). */
334
+ onUsage?: (frame: HearUsageFrame) => void;
335
+ /** Fired on an `error` frame or a transport-level error. */
336
+ onError?: (err: HearErrorFrame | Error) => void;
337
+ /** Fired when the socket closes (code per `WSCloseCode`). */
338
+ onClose?: (code: number, reason: string) => void;
339
+ /** Injectable WebSocket constructor (defaults to the global). */
340
+ webSocket?: WebSocketCtor;
341
+ }
342
+ /**
343
+ * A live Hear streaming-STT session. Hides the frame protocol: stream audio
344
+ * with {@link HearStream.sendAudio}, get `onPartial`/`onFinal`/`onError`
345
+ * callbacks, force-finalize with {@link HearStream.commit}, and flush+close
346
+ * with {@link HearStream.close}. Construct via `pyai.audio.transcriptions.stream()`.
347
+ */
348
+ export declare class HearStream {
349
+ private readonly ws;
350
+ private readonly opts;
351
+ private closed;
352
+ constructor(url: string, subprotocol: string, opts: HearStreamOptions);
353
+ private handleMessage;
354
+ /** Send a chunk of audio (PCM16 or opus per `encoding`). */
355
+ sendAudio(chunk: ArrayBufferLike | ArrayBufferView | Blob): void;
356
+ /** Force-finalize the current utterance (e.g. on VAD end-of-turn). */
357
+ commit(): void;
358
+ /** Close the socket; the server flushes a final for any buffered audio. */
359
+ close(code?: number, reason?: string): void;
360
+ /** The underlying socket (escape hatch for advanced use). */
361
+ get socket(): WebSocketLike;
362
+ /** Current WebSocket readyState. */
363
+ get readyState(): number;
364
+ }
365
+ /**
366
+ * Event names on Omni **server → client** frames. ⚠️ Note the asymmetry:
367
+ * inbound frames are keyed on `event`, but your **outbound** control frames
368
+ * (`configure`, `dtmf`, …) are keyed on `type`. {@link OmniConnection} handles
369
+ * both sides for you; this map is for matching frames in `onEvent`.
370
+ */
371
+ export declare const OmniEvent: {
372
+ /** Handshake; advertises protocol version + audio formats. */
373
+ readonly Hello: "hello";
374
+ /** Ack for your `configure` frame (echoes the resolved `voice_id`). */
375
+ readonly Configured: "configured";
376
+ /** Session is live; includes the resolved agent + audio caps. */
377
+ readonly SessionStarted: "session_started";
378
+ /** Turn boundary (user/assistant speaking). */
379
+ readonly Turn: "turn";
380
+ /** Incremental/final transcript text. */
381
+ readonly Transcript: "transcript";
382
+ /** User interrupted; assistant audio is being cut. */
383
+ readonly BargeIn: "barge_in";
384
+ /** Alias for barge-in on some engine builds. */
385
+ readonly Flush: "flush";
386
+ /** Session is closing; see close code. */
387
+ readonly SessionEnd: "session_end";
388
+ /** Server fault frame. */
389
+ readonly Error: "error";
390
+ };
391
+ export type OmniEvent = (typeof OmniEvent)[keyof typeof OmniEvent];
392
+ /** A server → client Omni frame. Keyed on `event` (not `type`); open for forward-compat. */
393
+ export interface OmniServerFrame {
394
+ event: string;
395
+ [k: string]: unknown;
396
+ }
397
+ /** A binary agent-audio chunk delivered to {@link OmniConnectOptions.onAudio}. */
398
+ export type OmniAudioChunk = ArrayBuffer | ArrayBufferView | Blob;
399
+ /**
400
+ * The agent config sent as the `configure` control frame. These are the wire
401
+ * (snake_case) fields the engine reads; it's an **open bag**, so forward-compat
402
+ * fields (e.g. `greeting` / `language` once the engine acks them) pass straight
403
+ * through. The SDK supplies the `{"type":"configure"}` envelope for you — which
404
+ * is the whole point: a hand-rolled `{"event":"configure"}` is acked but
405
+ * silently dropped, leaving the agent with no brain and zero turns.
406
+ */
407
+ export interface OmniConfigure {
408
+ /** Voice to speak with (stock / clone / designed id). */
409
+ voice_id?: string;
410
+ /** System prompt / role + instructions for the agent. */
411
+ persona?: string;
412
+ /** Customer-hosted URL the engine calls per turn for grounding. */
413
+ kb_endpoint?: string;
414
+ /** Bearer the engine presents to `kb_endpoint`. */
415
+ kb_token?: string;
416
+ /** Forward-compatible: any other key the engine honors. */
417
+ [k: string]: unknown;
418
+ }
419
+ export interface OmniConnectOptions {
420
+ /** Optional opaque per-session tag echoed to your `kb_endpoint`. Omni is zero-state. */
421
+ sessionLabel?: string;
422
+ /**
423
+ * Ephemeral browser token from `omni.createSession`. When set it is used as
424
+ * the WS subprotocol (`pyai-key.<token>`) instead of the client's secret key,
425
+ * so a page never holds a secret key.
426
+ */
427
+ token?: string;
428
+ /** Connect-URL sample rate: 24000 browser, 16000 wideband telephony, 8000 G.711/Twilio. */
429
+ rate?: 24000 | 16000 | 8000;
430
+ /** Connect-URL audio format. Default `pcm16`. */
431
+ format?: "pcm16";
432
+ /**
433
+ * Agent config sent as `{"type":"configure",...}` the instant the socket
434
+ * opens. Omit to send it yourself later via {@link OmniConnection.configure}.
435
+ */
436
+ configure?: OmniConfigure;
437
+ /** Extra query params merged onto the connect URL. */
438
+ query?: Record<string, string>;
439
+ /** Fired once the socket opens (after the optional auto-configure). */
440
+ onOpen?: () => void;
441
+ /** Fired for each binary agent-audio chunk — play it out as it arrives. */
442
+ onAudio?: (chunk: OmniAudioChunk) => void;
443
+ /** Fired on the `hello` handshake frame. */
444
+ onHello?: (frame: OmniServerFrame) => void;
445
+ /** Fired on the `configured` ack. */
446
+ onConfigured?: (frame: OmniServerFrame) => void;
447
+ /** Fired on `session_started`. */
448
+ onSessionStarted?: (frame: OmniServerFrame) => void;
449
+ /** Fired on `turn` boundaries. */
450
+ onTurn?: (frame: OmniServerFrame) => void;
451
+ /** Fired on `transcript` text frames. */
452
+ onTranscript?: (frame: OmniServerFrame) => void;
453
+ /** Fired on `barge_in` / `flush` (user interrupted). */
454
+ onBargeIn?: (frame: OmniServerFrame) => void;
455
+ /** Fired on `session_end`. */
456
+ onSessionEnd?: (frame: OmniServerFrame) => void;
457
+ /** Fired on EVERY JSON frame (including unknown/forward-compat ones). */
458
+ onEvent?: (frame: OmniServerFrame) => void;
459
+ /** Fired on an `error` frame or a transport-level error. */
460
+ onError?: (err: OmniServerFrame | Error) => void;
461
+ /** Fired when the socket closes (code per {@link WSCloseCode}). */
462
+ onClose?: (code: number, reason: string) => void;
463
+ /** Injectable WebSocket constructor (defaults to the global). */
464
+ webSocket?: WebSocketCtor;
465
+ }
466
+ /**
467
+ * A live Omni agentic-voice session over `/v1/omni`. Hides the wire protocol's
468
+ * frame-key asymmetry: it sends control frames keyed on `type` (`configure`,
469
+ * `dtmf`) and parses server frames keyed on `event`, so you cannot trip the #1
470
+ * Omni integration bug (mirroring the server's `event` key on outbound, which
471
+ * is silently dropped). Construct via `pyai.omni.connect()`.
472
+ *
473
+ * @example
474
+ * const omni = pyai.omni.connect({
475
+ * rate: 16000,
476
+ * configure: { voice_id: "stock_emma_en_gb", persona: "You are a receptionist." },
477
+ * onAudio: (chunk) => speaker.write(chunk),
478
+ * onTranscript: (f) => console.log(f.text),
479
+ * });
480
+ * omni.sendAudio(pcm16Chunk); // stream caller audio continuously
481
+ */
482
+ export declare class OmniConnection {
483
+ private readonly ws;
484
+ private readonly opts;
485
+ private closed;
486
+ constructor(url: string, subprotocol: string, opts: OmniConnectOptions);
487
+ private handleMessage;
488
+ /**
489
+ * Send (or update) the agent config. Always emitted as
490
+ * `{"type":"configure", ...}` — the correct key. (A hand-rolled
491
+ * `{"event":"configure"}` is acked but silently dropped by the engine.)
492
+ */
493
+ configure(cfg: OmniConfigure): void;
494
+ /** Stream a chunk of caller audio (PCM16 LE mono at the negotiated rate). */
495
+ sendAudio(chunk: ArrayBufferLike | ArrayBufferView | Blob): void;
496
+ /** Send DTMF digits as a `{"type":"dtmf"}` control frame. */
497
+ sendDtmf(digits: string): void;
498
+ /**
499
+ * Send an arbitrary control frame for forward-compat control types the SDK
500
+ * does not model yet. Reminder: client → server frames are keyed on `type`,
501
+ * never `event`.
502
+ */
503
+ send(frame: Record<string, unknown>): void;
504
+ /** Close the session. */
505
+ close(code?: number, reason?: string): void;
506
+ /** The underlying socket (escape hatch for advanced use). */
507
+ get socket(): WebSocketLike;
508
+ /** Current WebSocket readyState. */
509
+ get readyState(): number;
510
+ }
511
+ /** Shape of `GET /v1/me`. Fields are best-effort / forward-compatible. */
512
+ export interface MeResponse {
513
+ object?: string;
514
+ key_id?: string;
515
+ org_id?: string;
516
+ /** "live" | "test" (a.k.a. environment). */
517
+ environment?: string;
518
+ env?: string;
519
+ scopes?: string[];
520
+ limits?: Record<string, unknown>;
521
+ [k: string]: unknown;
522
+ }
523
+ export interface TelephonyCapabilities {
524
+ voice?: boolean;
525
+ sms?: boolean;
526
+ }
527
+ export interface TelephonyAvailableNumber {
528
+ object?: "telephony.available_number";
529
+ /** E.164. */
530
+ phone_number: string;
531
+ country?: string;
532
+ area_code?: string | null;
533
+ locality?: string | null;
534
+ region?: string | null;
535
+ capabilities?: TelephonyCapabilities;
536
+ monthly_cost_cents?: number;
537
+ }
538
+ export interface TelephonyNumber {
539
+ object?: "telephony.number";
540
+ id: string;
541
+ /** E.164. */
542
+ phone_number: string;
543
+ country?: string;
544
+ area_code?: string | null;
545
+ capabilities?: TelephonyCapabilities;
546
+ /** Agent that answers inbound calls to this number. */
547
+ agent_id?: string | null;
548
+ recording?: boolean;
549
+ monthly_cost_cents?: number;
550
+ status?: "active" | "released";
551
+ created_at?: number;
552
+ released_at?: number | null;
553
+ }
554
+ export type TraceVerdict = "PASS" | "WARN" | "FAIL";
555
+ export type TraceSeverity = "low" | "medium" | "high" | "critical";
556
+ export interface TraceInteraction {
557
+ object?: "trace.interaction";
558
+ /** call_id */
559
+ id: string;
560
+ agent_id?: string | null;
561
+ product?: string | null;
562
+ verdict: TraceVerdict;
563
+ findings?: number;
564
+ blocked_turns?: number;
565
+ modified_turns?: number;
566
+ packs_enforced?: string;
567
+ scored_at?: number;
568
+ }
569
+ export interface TraceFinding {
570
+ object?: "trace.finding";
571
+ id: string;
572
+ /** call_id */
573
+ interaction_id: string;
574
+ agent_id?: string | null;
575
+ check_id: string;
576
+ severity: TraceSeverity;
577
+ verdict?: string;
578
+ confidence?: number;
579
+ action?: "flag" | "preempt_next" | "escalate";
580
+ speaker?: "agent" | "caller" | "any" | null;
581
+ reason?: string;
582
+ preempt_instruction?: string | null;
583
+ at_t?: number | null;
584
+ tier?: number;
585
+ }
586
+ /** A tool invocation recorded on a timeline turn. */
587
+ export interface TraceToolCall {
588
+ name: string;
589
+ /** Arguments the agent passed (engine-shaped JSON). */
590
+ args?: unknown;
591
+ /** Tool result returned to the agent (engine-shaped JSON). */
592
+ result?: unknown;
593
+ /** Audio-timeline position of the call, ms. */
594
+ t_ms?: number;
595
+ }
596
+ /** Barge-in timing for a timeline turn. */
597
+ export interface TraceBargeMetrics {
598
+ /** Time from caller speech onset to barge-in detection, ms. */
599
+ detect_ms?: number;
600
+ /** Whether the agent cleanly recovered after the barge-in. */
601
+ recovered?: boolean;
602
+ }
603
+ /**
604
+ * Known timeline-turn roles. Left open (see {@link TraceTimelineTurn.role}) so a
605
+ * new server-side role never breaks the build — branch defensively.
606
+ */
607
+ export type TraceTimelineRole = "agent" | "caller" | "system" | "tool";
608
+ /** One turn of a per-call operational timeline (eval scorecard-v1). */
609
+ export interface TraceTimelineTurn {
610
+ /** Monotonic turn index within the call. */
611
+ seq: number;
612
+ /** Audio-timeline position of the turn, ms. */
613
+ t_ms: number;
614
+ /** Who spoke/acted this turn. Open-ended for forward-compat. */
615
+ role: TraceTimelineRole | (string & {});
616
+ /** Transcript/text for the turn, when available. */
617
+ text?: string;
618
+ /** Time-to-first-audio for the turn (latency scoring), ms. */
619
+ ttfb_ms?: number;
620
+ /** Turn-detection (endpointing) latency, ms. */
621
+ endpointing_ms?: number;
622
+ /** Barge-in detect/recovery timing for the turn. */
623
+ barge?: TraceBargeMetrics;
624
+ /** Tool calls made during the turn (tool-use scoring). */
625
+ tool_calls?: TraceToolCall[];
626
+ }
627
+ /**
628
+ * Aggregate per-call quality metrics (eval scorecard-v1). All optional and
629
+ * forward-compatible — present once the engine emits them.
630
+ */
631
+ export interface QualityMetrics {
632
+ /** Word error rate vs. reference transcript, 0–1 (lower is better). */
633
+ wer?: number;
634
+ /** Representative time-to-first-audio across turns, ms. */
635
+ ttfb_ms?: number;
636
+ /** 95th-percentile end-to-end turn latency, ms. */
637
+ turn_p95_ms?: number;
638
+ /** Barge-in recovery rate, 0–1. */
639
+ barge_recovery?: number;
640
+ /** Task-success score, 0–1. */
641
+ task_success?: number;
642
+ /** Composite voice-agent quality index (engine-defined scale). */
643
+ vaqi?: number;
644
+ [k: string]: unknown;
645
+ }
646
+ export interface TraceInteractionDetail extends TraceInteraction {
647
+ /** Hash-chain link proving the record is unaltered. */
648
+ audit_hash?: string;
649
+ scorecard?: Record<string, unknown>;
650
+ tier2_findings?: TraceFinding[];
651
+ /**
652
+ * Scorecard schema version, e.g. `"trace-scorecard-v1"`. Present once the
653
+ * engine emits the eval block below.
654
+ */
655
+ scorecard_version?: string;
656
+ /**
657
+ * Per-call operational timeline. Forward-compatible: empty/undefined until
658
+ * the engine emits per-turn timing (Ask 1 of the evals engine plan).
659
+ */
660
+ timeline?: TraceTimelineTurn[];
661
+ /** Aggregate per-call quality metrics. Forward-compatible (see above). */
662
+ quality_metrics?: QualityMetrics;
663
+ }
664
+ export interface TraceViolation {
665
+ object?: "trace.violation";
666
+ id: string;
667
+ /** call_id */
668
+ interaction_id: string;
669
+ agent_id?: string | null;
670
+ rule_id: string;
671
+ pack_id?: string | null;
672
+ severity: TraceSeverity;
673
+ action_taken?: "pass" | "flag" | "modify" | "block";
674
+ citation?: string;
675
+ reason?: string;
676
+ at_t?: number | null;
677
+ }
678
+ export interface TraceConfigInput {
679
+ agent_id?: string;
680
+ enabled?: boolean;
681
+ channels?: Array<"voice" | "text">;
682
+ /** Map of pack_id → { enabled, version }. */
683
+ rule_packs?: Record<string, {
684
+ enabled?: boolean;
685
+ version?: string | null;
686
+ }>;
687
+ guardrails?: Record<string, unknown>;
688
+ [k: string]: unknown;
689
+ }
690
+ export interface TraceConfig {
691
+ object?: "trace.config";
692
+ agent_id?: string | null;
693
+ enabled?: boolean;
694
+ mode?: "warn" | "modify" | "block" | "human_handoff";
695
+ etag?: string;
696
+ updated_at?: number;
697
+ config?: TraceConfigInput;
698
+ }
699
+ export interface TraceRulePackSpec {
700
+ pack_id: string;
701
+ version: string;
702
+ jurisdiction?: string;
703
+ legal_status?: string;
704
+ rules: Array<Record<string, unknown>>;
705
+ }
706
+ export interface TraceRulePack {
707
+ object?: "trace.rule_pack";
708
+ id?: string;
709
+ pack_id: string;
710
+ version?: string;
711
+ builtin?: boolean;
712
+ jurisdiction?: string | null;
713
+ legal_status?: string | null;
714
+ etag?: string;
715
+ status?: "active" | "deprecated";
716
+ created_at?: number;
717
+ /** The authored DSL (only on the single-pack GET). */
718
+ spec?: Record<string, unknown>;
719
+ }
720
+ export interface TraceExposure {
721
+ object?: "trace.exposure";
722
+ window_days?: number;
723
+ interactions_scanned?: number;
724
+ with_a_gap?: number;
725
+ gap_rate?: number;
726
+ by_rule?: Array<{
727
+ rule_id: string;
728
+ pack_id?: string | null;
729
+ count: number;
730
+ rate: number;
731
+ }>;
732
+ by_verdict?: {
733
+ PASS?: number;
734
+ WARN?: number;
735
+ FAIL?: number;
736
+ };
737
+ top_exposure?: string | null;
738
+ }
739
+ export interface RecapConfigInput {
740
+ enabled?: boolean;
741
+ webhook_url?: string | null;
742
+ default_pack_id?: string;
743
+ }
744
+ export interface RecapConfig {
745
+ object?: "recap.config";
746
+ enabled?: boolean;
747
+ webhook_url?: string | null;
748
+ default_pack_id?: string;
749
+ updated_at?: number;
750
+ }
751
+ export interface RecapCallSummary {
752
+ object?: "recap.call";
753
+ call_id: string;
754
+ pack_id?: string;
755
+ status?: "pending" | "processing" | "complete" | "failed";
756
+ call_duration_s?: number | null;
757
+ created_at?: number;
758
+ completed_at?: number | null;
759
+ }
760
+ export interface RecapCall extends RecapCallSummary {
761
+ record?: unknown;
762
+ error?: string | null;
763
+ crm_write_status?: string | null;
764
+ }
765
+ export interface RecapCallTriggerInput {
766
+ utterances: Array<{
767
+ speaker_role?: "agent" | "customer";
768
+ text: string;
769
+ offset_s?: number;
770
+ duration_s?: number;
771
+ }>;
772
+ pack_id?: string;
773
+ call_duration_s?: number;
774
+ call_direction?: "inbound" | "outbound";
775
+ customer_name?: string;
776
+ crm_fields?: Record<string, unknown>;
777
+ }
79
778
  export declare class PyAI {
80
779
  private readonly apiKey;
81
780
  private readonly baseURL;
@@ -86,6 +785,9 @@ export declare class PyAI {
86
785
  private request;
87
786
  private toError;
88
787
  private getJson;
788
+ private postJson;
789
+ private putJson;
790
+ private deleteReq;
89
791
  models: {
90
792
  list: () => Promise<ListResponse<{
91
793
  id: string;
@@ -116,10 +818,26 @@ export declare class PyAI {
116
818
  file: Blob;
117
819
  filename?: string;
118
820
  model?: string;
821
+ language?: string;
822
+ response_format?: "json" | "text" | "verbose_json";
823
+ /**
824
+ * Deterministic seed for reproducible eval runs. Forward-compatible:
825
+ * honored once the engine supports it, otherwise ignored.
826
+ */
827
+ seed?: number;
828
+ /** Sampling temperature. Forward-compatible (honored when supported). */
829
+ temperature?: number;
119
830
  }) => Promise<{
120
831
  text: string;
121
832
  [k: string]: unknown;
122
833
  }>;
834
+ /**
835
+ * Open a live streaming-STT WebSocket (Hear). Hides the frame protocol
836
+ * behind `onPartial`/`onFinal`/`onError`; stream audio with `sendAudio`,
837
+ * force-finalize with `commit()`, flush+close with `close()`. Set
838
+ * `grounding: true` for Cue (turn detection + KB context).
839
+ */
840
+ stream: (opts?: HearStreamOptions) => HearStream;
123
841
  };
124
842
  };
125
843
  transcriptionJobs: {
@@ -132,10 +850,153 @@ export declare class PyAI {
132
850
  cursor?: string;
133
851
  }) => Promise<ListResponse<TranscriptionJob>>;
134
852
  };
853
+ /**
854
+ * Introspect the calling key: scopes, environment, and limits. Useful for a
855
+ * preflight/doctor check. (New route; older deployments may 404 — handle it.)
856
+ */
857
+ me: () => Promise<MeResponse>;
858
+ clones: {
859
+ /** List the org's cloned voices. */
860
+ list: () => Promise<ListResponse<Voice>>;
861
+ /** Enroll a custom voice from reference audio (>= ~10s). Scope `voice:clone`. */
862
+ create: (params: {
863
+ name: string;
864
+ file: Blob;
865
+ filename?: string;
866
+ }) => Promise<Voice>;
867
+ /**
868
+ * Fetch a single cloned voice by id. The API exposes no GET-by-id for
869
+ * clones, so this filters `list()` client-side and throws a 404 `PyAIError`
870
+ * when the id isn't found.
871
+ */
872
+ get: (id: string) => Promise<Voice>;
873
+ /** Delete a cloned voice (tenant-isolated). Scope `voice:clone`. */
874
+ delete: (id: string) => Promise<void>;
875
+ };
876
+ telephony: {
877
+ numbers: {
878
+ /** Search the carrier's available US local numbers. */
879
+ available: (params?: {
880
+ areaCode?: string;
881
+ contains?: string;
882
+ limit?: number;
883
+ }) => Promise<ListResponse<TelephonyAvailableNumber>>;
884
+ /** List the org's managed numbers (active only unless `includeReleased`). */
885
+ list: (params?: {
886
+ includeReleased?: boolean;
887
+ }) => Promise<ListResponse<TelephonyNumber>>;
888
+ /** Provision (buy) a specific available number, optionally bound to an agent. */
889
+ buy: (params: {
890
+ phone_number: string;
891
+ agent_id?: string | null;
892
+ }) => Promise<TelephonyNumber>;
893
+ /** Route a number to an agent (`agentId: null` to unassign). */
894
+ assign: (id: string, agentId: string | null) => Promise<TelephonyNumber>;
895
+ /** Release a number back to the carrier (idempotent). */
896
+ release: (id: string) => Promise<TelephonyNumber>;
897
+ };
898
+ };
899
+ trace: {
900
+ interactions: {
901
+ /** List scanned interactions (scorecards), newest first. Scope `trace:read`. */
902
+ list: (params?: {
903
+ verdict?: TraceVerdict;
904
+ agentId?: string;
905
+ limit?: number;
906
+ cursor?: string;
907
+ }) => Promise<ListResponse<TraceInteraction>>;
908
+ /** The full per-call evidence view (findings, redactions, audit hash). */
909
+ get: (id: string) => Promise<TraceInteractionDetail>;
910
+ };
911
+ violations: {
912
+ /** Drill-down of every fired Tier-0 rule across scorecards. Scope `trace:read`. */
913
+ list: (params?: {
914
+ ruleId?: string;
915
+ severity?: TraceSeverity;
916
+ interactionId?: string;
917
+ limit?: number;
918
+ cursor?: string;
919
+ }) => Promise<ListResponse<TraceViolation>>;
920
+ };
921
+ findings: {
922
+ /** List Tier-2 (async semantic) findings — advisory, non-blocking. Scope `trace:read`. */
923
+ list: (params?: {
924
+ checkId?: string;
925
+ action?: "flag" | "preempt_next" | "escalate";
926
+ severity?: TraceSeverity;
927
+ interactionId?: string;
928
+ limit?: number;
929
+ cursor?: string;
930
+ }) => Promise<ListResponse<TraceFinding>>;
931
+ };
932
+ config: {
933
+ /** Read per-agent Trace config (omit `agentId` for the org default). Scope `trace:configure`. */
934
+ get: (agentId?: string) => Promise<TraceConfig>;
935
+ /** Upsert per-agent Trace config. Scope `trace:configure`. */
936
+ set: (input: TraceConfigInput) => Promise<TraceConfig>;
937
+ };
938
+ rulePacks: {
939
+ /** List built-in + custom rule packs. Scope `trace:configure`. */
940
+ list: () => Promise<ListResponse<TraceRulePack>>;
941
+ /** Upload a custom rule pack (Trace DSL). Scope `trace:configure`. */
942
+ create: (spec: TraceRulePackSpec) => Promise<TraceRulePack>;
943
+ /** Resolve a rule pack by id (latest active, or pin `version`). */
944
+ get: (id: string, version?: string) => Promise<TraceRulePack>;
945
+ };
946
+ /** Compliance exposure summary over a trailing window. Scope `trace:read`. */
947
+ exposure: (windowDays?: number) => Promise<TraceExposure>;
948
+ /**
949
+ * Convenience: the per-call operational timeline (eval scorecard-v1). A thin
950
+ * wrapper over `interactions.get(id)` that returns the `timeline` array, or
951
+ * `[]` when the engine hasn't emitted one yet (forward-compatible). Scope
952
+ * `trace:read`.
953
+ */
954
+ callTimeline: (id: string) => Promise<TraceTimelineTurn[]>;
955
+ };
956
+ recap: {
957
+ config: {
958
+ get: () => Promise<RecapConfig>;
959
+ set: (input: RecapConfigInput) => Promise<RecapConfig>;
960
+ };
961
+ calls: {
962
+ list: (params?: {
963
+ limit?: number;
964
+ cursor?: string;
965
+ status?: string;
966
+ }) => Promise<ListResponse<RecapCallSummary>>;
967
+ get: (callId: string) => Promise<RecapCall>;
968
+ trigger: (callId: string, input: RecapCallTriggerInput) => Promise<RecapCallSummary>;
969
+ };
970
+ };
971
+ omni: {
972
+ /**
973
+ * Mint an ephemeral, origin-locked Omni session token a browser can use to
974
+ * open ONE realtime session **directly** — the public/private split for
975
+ * realtime. **Call this from your server** with a secret key holding
976
+ * `omni:session`; never ship the secret key to a page. Hand the returned
977
+ * `token` to the browser, which connects with
978
+ * `new WebSocket(session.url, ["pyai-key." + session.token])`. The token
979
+ * expires after `ttlSeconds` (default 60s) and only works from
980
+ * `allowedOrigins`. Scope `omni:session`.
981
+ */
982
+ createSession: (params: OmniSessionParams) => Promise<OmniSession>;
983
+ /**
984
+ * Open a live Omni agentic-voice session over `/v1/omni`. Returns an
985
+ * {@link OmniConnection} that handles the wire protocol's frame-key
986
+ * asymmetry for you — it sends `type`-keyed control frames (`configure`,
987
+ * `dtmf`) and parses `event`-keyed server frames — so you can't trip the #1
988
+ * Omni integration bug. Omni is zero-state: nothing to create first; the
989
+ * agent's behavior travels in the `configure` frame. Pass `token` (from
990
+ * `createSession`) to connect from a browser without the secret key.
991
+ */
992
+ connect: (opts?: OmniConnectOptions) => OmniConnection;
993
+ };
135
994
  /** Build the realtime WebSocket URL for the chosen product. */
136
995
  realtimeURL(opts?: RealtimeOptions): string;
137
996
  /** The subprotocol that carries the key on a WS upgrade (browser-safe auth). */
138
997
  realtimeSubprotocol(): string;
998
+ /** Build the Hear streaming-STT WebSocket URL (`/v1/audio/transcriptions/stream`). */
999
+ hearStreamURL(opts?: HearStreamOptions): string;
139
1000
  /**
140
1001
  * Open a realtime WebSocket. Uses the global WebSocket (browser, Node 22+).
141
1002
  * The key travels as a subprotocol so it works from the browser without