@pyai/sdk 0.2.1 → 0.2.2

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
@@ -1,10 +1,9 @@
1
1
  /**
2
- * @pyai/sdk official TypeScript/JavaScript client for the PyAI API.
2
+ * @pyai/sdk, official TypeScript/JavaScript client for the PyAI API.
3
3
  *
4
4
  * Thin, dependency-free wrapper over the public OpenAI-compatible surface at
5
5
  * https://api.pyai.com (contract: https://api.pyai.com/openapi.json). Runs in
6
- * the browser and Node 22+ (uses global fetch + WebSocket). Keys are opaque
7
- * never parsed.
6
+ * the browser and Node 22+ (uses global fetch + WebSocket). Keys are opaque, * never parsed.
8
7
  */
9
8
  export interface PyAIOptions {
10
9
  /** A pyai_live_ or pyai_test_ key. */
@@ -48,7 +47,7 @@ export interface TranscriptionJob {
48
47
  }
49
48
  /**
50
49
  * 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
50
+ * server accepts on `POST /v1/audio/speech`, any other value is rejected with
52
51
  * `400 unsupported_format`. The default (when `response_format` is omitted) is
53
52
  * `mp3`. Omit `sample_rate` for the engine's native 24 kHz (`g711_*` is always
54
53
  * 8 kHz).
@@ -80,20 +79,20 @@ export interface SpeechParams {
80
79
  model?: string;
81
80
  /**
82
81
  * Output container/codec, resampled+encoded server-side. One of
83
- * {@link SpeechFormat} anything else is a `400 unsupported_format`. Omit for
82
+ * {@link SpeechFormat}, anything else is a `400 unsupported_format`. Omit for
84
83
  * the default of `mp3`.
85
84
  *
86
- * `g711_ulaw`/`g711_alaw` return raw 8 kHz mono G.711 the bytes Twilio/SIP
85
+ * `g711_ulaw`/`g711_alaw` return raw 8 kHz mono G.711, the bytes Twilio/SIP
87
86
  * media streams expect, so you can hand the response straight to a telephony
88
87
  * frame without a client-side resampler or μ-law encoder. `sample_rate` is
89
88
  * forced to 8000 for those (omit it, or set exactly 8000). `pcm` is raw,
90
89
  * headerless int16 LE mono at `sample_rate`. `mp3`/`opus` are buffered (not
91
- * chunk-streamed) use them with `speech`, not `speechStream`.
90
+ * chunk-streamed), use them with `speech`, not `speechStream`.
92
91
  */
93
92
  response_format?: SpeechFormat;
94
93
  /**
95
94
  * Output sample rate in Hz. One of {@link SpeechSampleRate}
96
- * (8000/16000/24000/48000) anything else is a `400`. Omit for the engine's
95
+ * (8000/16000/24000/48000), anything else is a `400`. Omit for the engine's
97
96
  * native 24 kHz; `g711_*` is always 8000 (forced). Set `8000`/`16000` for
98
97
  * telephony pipelines, most often with `response_format: "pcm"`.
99
98
  */
@@ -106,8 +105,7 @@ export interface SpeechParams {
106
105
  */
107
106
  seed?: number;
108
107
  /**
109
- * Sampling temperature (lower = more deterministic). Forward-compatible
110
- * honored once the engine supports it, otherwise ignored.
108
+ * Sampling temperature (lower = more deterministic). Forward-compatible, * honored once the engine supports it, otherwise ignored.
111
109
  */
112
110
  temperature?: number;
113
111
  }
@@ -130,7 +128,7 @@ export interface RealtimeOptions {
130
128
  */
131
129
  sessionLabel?: string;
132
130
  /**
133
- * @deprecated Use {@link sessionLabel}. Kept for back-compat emitted as the
131
+ * @deprecated Use {@link sessionLabel}. Kept for back-compat, emitted as the
134
132
  * `agent_id` query alias, which the gateway still accepts. Ignored if
135
133
  * `sessionLabel` is set.
136
134
  */
@@ -142,7 +140,7 @@ export interface RealtimeOptions {
142
140
  export interface OmniSessionParams {
143
141
  /**
144
142
  * 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
143
+ * Required and non-empty, a browser token must be origin-locked. `*` is not
146
144
  * allowed.
147
145
  */
148
146
  allowedOrigins: string[];
@@ -152,7 +150,7 @@ export interface OmniSessionParams {
152
150
  sessionLabel?: string;
153
151
  }
154
152
  /**
155
- * The result of {@link PyAI.omni}.createSession a short-lived credential safe
153
+ * The result of {@link PyAI.omni}.createSession, a short-lived credential safe
156
154
  * to hand to the browser. Use {@link OmniSession.token} as the WebSocket
157
155
  * subprotocol `pyai-key.<token>` against {@link OmniSession.url}.
158
156
  */
@@ -197,7 +195,7 @@ export declare const WSCloseCode: {
197
195
  export type WSCloseCode = (typeof WSCloseCode)[keyof typeof WSCloseCode];
198
196
  /**
199
197
  * 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
198
+ * these. The set is treated as open, `PyAIError.code` stays `string`, so a
201
199
  * new server code never breaks the build, but the known ones are named here.
202
200
  */
203
201
  export declare const ErrorCode: {
@@ -267,7 +265,7 @@ export interface HearErrorFrame {
267
265
  }
268
266
  export type HearFrame = HearPartialFrame | HearFinalFrame | HearUsageFrame | HearErrorFrame;
269
267
  /**
270
- * Minimal structural WebSocket matches both the browser/Node global
268
+ * Minimal structural WebSocket, matches both the browser/Node global
271
269
  * `WebSocket` and the `ws` package, and lets tests inject a mock.
272
270
  */
273
271
  export interface WebSocketLike {
@@ -303,8 +301,8 @@ export interface HearStreamOptions {
303
301
  */
304
302
  numerals?: boolean;
305
303
  /**
306
- * Turn-segmentation tuning: trailing-pause (ms, 502000) that ends an
307
- * utterance. Forwards `?endpointing_ms=` clamped + honored once the engine
304
+ * Turn-segmentation tuning: trailing-pause (ms, 50-2000) that ends an
305
+ * utterance. Forwards `?endpointing_ms=`, clamped + honored once the engine
308
306
  * supports it; a no-op when omitted. Drive end-of-turn yourself with
309
307
  * {@link HearStream.commit} for full control today.
310
308
  */
@@ -315,12 +313,12 @@ export interface HearStreamOptions {
315
313
  * array. Bills a single `cue.minutes` line instead of the Hear rate.
316
314
  */
317
315
  grounding?: boolean;
318
- /** Cue: number of KB passages to retrieve per turn (120, default 3). */
316
+ /** Cue: number of KB passages to retrieve per turn (1-20, default 3). */
319
317
  groundingK?: number;
320
- /** Cue: drop passages scoring below this (01, default 0 = keep all). */
318
+ /** Cue: drop passages scoring below this (0-1, default 0 = keep all). */
321
319
  groundingMinScore?: number;
322
320
  /** Cue: max ms to wait for retrieval at the final before failing open to
323
- * `grounding: []` (502000, default 450). */
321
+ * `grounding: []` (50-2000, default 450). */
324
322
  groundingTimeoutMs?: number;
325
323
  /** Extra query params merged onto the connect URL. */
326
324
  query?: Record<string, string>;
@@ -362,6 +360,32 @@ export declare class HearStream {
362
360
  /** Current WebSocket readyState. */
363
361
  get readyState(): number;
364
362
  }
363
+ /**
364
+ * A live AMD (answering-machine-detection) stream. The wire is Twilio's Media
365
+ * Streams protocol, so if you're already on Twilio the usual path is one line of
366
+ * TwiML pointing `<Stream url="wss://api.pyai.com/v1/amd/stream">` at PyAI, no
367
+ * SDK needed. This helper is for server-side clients that fork the media
368
+ * themselves: send Twilio `start`/`media`/`stop` frames with {@link AmdStream.send}
369
+ * (or raw μ-law audio with {@link AmdStream.sendAudio}) and receive the `amd`
370
+ * decision via `onDecision`. Construct via `pyai.amd.stream()`.
371
+ */
372
+ export declare class AmdStream {
373
+ private readonly ws;
374
+ private readonly opts;
375
+ private closed;
376
+ constructor(url: string, subprotocol: string, opts: AmdStreamOptions);
377
+ private handleMessage;
378
+ /** Send a Twilio Media Streams control/media frame (JSON) or a raw string. */
379
+ send(frame: string | Record<string, unknown>): void;
380
+ /** Send a raw audio chunk (G.711 μ-law 8 kHz for the Twilio-native path). */
381
+ sendAudio(chunk: ArrayBufferLike | ArrayBufferView | Blob): void;
382
+ /** Close the socket. */
383
+ close(code?: number, reason?: string): void;
384
+ /** The underlying socket (escape hatch for advanced use). */
385
+ get socket(): WebSocketLike;
386
+ /** Current WebSocket readyState. */
387
+ get readyState(): number;
388
+ }
365
389
  /**
366
390
  * Event names on Omni **server → client** frames. ⚠️ Note the asymmetry:
367
391
  * inbound frames are keyed on `event`, but your **outbound** control frames
@@ -383,6 +407,8 @@ export declare const OmniEvent: {
383
407
  readonly BargeIn: "barge_in";
384
408
  /** Alias for barge-in on some engine builds. */
385
409
  readonly Flush: "flush";
410
+ /** Engine requests a client-loop tool invocation. */
411
+ readonly ToolCall: "tool_call";
386
412
  /** Session is closing; see close code. */
387
413
  readonly SessionEnd: "session_end";
388
414
  /** Server fault frame. */
@@ -396,13 +422,26 @@ export interface OmniServerFrame {
396
422
  }
397
423
  /** A binary agent-audio chunk delivered to {@link OmniConnectOptions.onAudio}. */
398
424
  export type OmniAudioChunk = ArrayBuffer | ArrayBufferView | Blob;
425
+ export interface OmniToolDef {
426
+ name: string;
427
+ description?: string;
428
+ parameters?: Record<string, unknown>;
429
+ /** When set, engine-POST mode. Omit for client-loop (default). */
430
+ endpoint?: string;
431
+ }
432
+ export interface OmniToolCallFrame {
433
+ type: "tool_call";
434
+ call_id: string;
435
+ name: string;
436
+ arguments?: Record<string, unknown>;
437
+ }
399
438
  /**
400
439
  * The agent config sent as the `configure` control frame. These are the wire
401
440
  * (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.
441
+ * fields (e.g. `greeting`, or `model_tier` once the engine acks it) pass
442
+ * straight through. The SDK supplies the `{"type":"configure"}` envelope for
443
+ * you, which is the whole point: a hand-rolled `{"event":"configure"}` is
444
+ * acked but silently dropped, leaving the agent with no brain and zero turns.
406
445
  */
407
446
  export interface OmniConfigure {
408
447
  /** Voice to speak with (stock / clone / designed id). */
@@ -413,6 +452,19 @@ export interface OmniConfigure {
413
452
  kb_endpoint?: string;
414
453
  /** Bearer the engine presents to `kb_endpoint`. */
415
454
  kb_token?: string;
455
+ /**
456
+ * Session language, end to end (recognition, reasoning, voice). Also
457
+ * settable on the agent profile (`language` on `POST /v1/agents`), which
458
+ * applies automatically when connecting with `session_label={agent_id}`;
459
+ * an inline value here wins for the session. Default `en`. Fail-safe: an
460
+ * unknown/not-yet-enabled language falls back to `en` (the `configured`
461
+ * ack carries `language_active` + `language_fallback: true`), the call
462
+ * proceeds and bills as what was served. Per-language availability is
463
+ * staged, see the Language support reference.
464
+ */
465
+ language?: "en" | "fr" | "es" | "de" | "hi";
466
+ /** Function calling definitions (client-loop when `endpoint` is omitted). */
467
+ tools?: OmniToolDef[];
416
468
  /** Forward-compatible: any other key the engine honors. */
417
469
  [k: string]: unknown;
418
470
  }
@@ -438,7 +490,7 @@ export interface OmniConnectOptions {
438
490
  query?: Record<string, string>;
439
491
  /** Fired once the socket opens (after the optional auto-configure). */
440
492
  onOpen?: () => void;
441
- /** Fired for each binary agent-audio chunk play it out as it arrives. */
493
+ /** Fired for each binary agent-audio chunk, play it out as it arrives. */
442
494
  onAudio?: (chunk: OmniAudioChunk) => void;
443
495
  /** Fired on the `hello` handshake frame. */
444
496
  onHello?: (frame: OmniServerFrame) => void;
@@ -452,6 +504,8 @@ export interface OmniConnectOptions {
452
504
  onTranscript?: (frame: OmniServerFrame) => void;
453
505
  /** Fired on `barge_in` / `flush` (user interrupted). */
454
506
  onBargeIn?: (frame: OmniServerFrame) => void;
507
+ /** Fired when the engine requests a client-loop tool invocation. */
508
+ onToolCall?: (frame: OmniToolCallFrame) => void;
455
509
  /** Fired on `session_end`. */
456
510
  onSessionEnd?: (frame: OmniServerFrame) => void;
457
511
  /** Fired on EVERY JSON frame (including unknown/forward-compat ones). */
@@ -483,18 +537,30 @@ export declare class OmniConnection {
483
537
  private readonly ws;
484
538
  private readonly opts;
485
539
  private closed;
540
+ /** Serializes async Blob reads in {@link sendAudio} so frames stay ordered. */
541
+ private blobTail;
486
542
  constructor(url: string, subprotocol: string, opts: OmniConnectOptions);
487
543
  private handleMessage;
544
+ private dispatchFrame;
488
545
  /**
489
546
  * Send (or update) the agent config. Always emitted as
490
- * `{"type":"configure", ...}` the correct key. (A hand-rolled
547
+ * `{"type":"configure", ...}`, the correct key. (A hand-rolled
491
548
  * `{"event":"configure"}` is acked but silently dropped by the engine.)
492
549
  */
493
550
  configure(cfg: OmniConfigure): void;
494
- /** Stream a chunk of caller audio (PCM16 LE mono at the negotiated rate). */
551
+ /**
552
+ * Stream a chunk of caller audio (PCM16 LE mono at the negotiated rate) as a
553
+ * `0x01`-prefixed frame. The engine demuxes on the first byte and has no
554
+ * default branch, so an untagged chunk is dropped with no error.
555
+ */
495
556
  sendAudio(chunk: ArrayBufferLike | ArrayBufferView | Blob): void;
496
557
  /** Send DTMF digits as a `{"type":"dtmf"}` control frame. */
497
558
  sendDtmf(digits: string): void;
559
+ /** Reply to a client-loop {@link OmniEvent.ToolCall}. */
560
+ toolResult(callId: string, payload: {
561
+ result?: unknown;
562
+ error?: string;
563
+ }): void;
498
564
  /**
499
565
  * Send an arbitrary control frame for forward-compat control types the SDK
500
566
  * does not model yet. Reminder: client → server frames are keyed on `type`,
@@ -602,7 +668,7 @@ export interface TraceBargeMetrics {
602
668
  }
603
669
  /**
604
670
  * Known timeline-turn roles. Left open (see {@link TraceTimelineTurn.role}) so a
605
- * new server-side role never breaks the build branch defensively.
671
+ * new server-side role never breaks the build, branch defensively.
606
672
  */
607
673
  export type TraceTimelineRole = "agent" | "caller" | "system" | "tool";
608
674
  /** One turn of a per-call operational timeline (eval scorecard-v1). */
@@ -626,18 +692,18 @@ export interface TraceTimelineTurn {
626
692
  }
627
693
  /**
628
694
  * Aggregate per-call quality metrics (eval scorecard-v1). All optional and
629
- * forward-compatible present once the engine emits them.
695
+ * forward-compatible, present once the engine emits them.
630
696
  */
631
697
  export interface QualityMetrics {
632
- /** Word error rate vs. reference transcript, 01 (lower is better). */
698
+ /** Word error rate vs. reference transcript, 0-1 (lower is better). */
633
699
  wer?: number;
634
700
  /** Representative time-to-first-audio across turns, ms. */
635
701
  ttfb_ms?: number;
636
702
  /** 95th-percentile end-to-end turn latency, ms. */
637
703
  turn_p95_ms?: number;
638
- /** Barge-in recovery rate, 01. */
704
+ /** Barge-in recovery rate, 0-1. */
639
705
  barge_recovery?: number;
640
- /** Task-success score, 01. */
706
+ /** Task-success score, 0-1. */
641
707
  task_success?: number;
642
708
  /** Composite voice-agent quality index (engine-defined scale). */
643
709
  vaqi?: number;
@@ -775,6 +841,76 @@ export interface RecapCallTriggerInput {
775
841
  customer_name?: string;
776
842
  crm_fields?: Record<string, unknown>;
777
843
  }
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";
846
+ /** Twilio's `AnsweredBy` enum, echoed for drop-in migration parity. */
847
+ export type AmdTwilioAnsweredBy = "human" | "machine_start" | "machine_end_beep" | "machine_end_silence" | "machine_end_other" | "fax" | "unknown";
848
+ export interface AmdConfigInput {
849
+ /** Operating point on the ROC curve, [0,1]. Near 0 = human-safe (never hang up
850
+ * on a person); near 1 = fire `machine` fast. A per-call TwiML `<Parameter>`
851
+ * overrides this. */
852
+ aggressiveness?: number;
853
+ /** Signed POST target for `amd.call.completed` events (https). */
854
+ webhookUrl?: string | null;
855
+ }
856
+ export interface AmdConfig {
857
+ object?: "amd.config";
858
+ aggressiveness?: number;
859
+ webhook_url?: string | null;
860
+ updated_at?: number;
861
+ }
862
+ export interface AmdCallSummary {
863
+ object?: "amd.call";
864
+ call_id: string;
865
+ session_label?: string | null;
866
+ status?: "completed" | "failed";
867
+ answered_by?: AmdAnsweredBy;
868
+ /** Twilio-enum projection of `answered_by` for drop-in routing parity. */
869
+ answered_by_twilio?: string | null;
870
+ confidence?: number | null;
871
+ /** Latency from answer to decision, in ms. */
872
+ decision_ms?: number | null;
873
+ created_at?: number;
874
+ }
875
+ export interface AmdCall extends AmdCallSummary {
876
+ /** Human-readable evidence, e.g. "machine phrase: 'leave a message' @1.2s". */
877
+ reason?: string | null;
878
+ aggressiveness?: number | null;
879
+ started_at?: number | null;
880
+ meta?: Record<string, unknown> | null;
881
+ error?: string | null;
882
+ }
883
+ /** A mid-call AMD decision event pushed on the stream (and to the webhook). */
884
+ export interface AmdDecisionEvent {
885
+ event?: "amd";
886
+ call_id?: string;
887
+ answered_by?: AmdAnsweredBy;
888
+ answered_by_twilio?: string | null;
889
+ confidence?: number | null;
890
+ decision_ms?: number | null;
891
+ reason?: string | null;
892
+ [k: string]: unknown;
893
+ }
894
+ export interface AmdStreamOptions {
895
+ /** Per-call operating point override (0-1), sent on the connect URL. */
896
+ aggressiveness?: number;
897
+ /** Opaque per-call tag, echoed back on the decision. */
898
+ sessionLabel?: string;
899
+ /** Extra query params merged onto the connect URL. */
900
+ query?: Record<string, string>;
901
+ /** Fired once the socket opens. */
902
+ onOpen?: () => void;
903
+ /** Fired when PyAI emits the `amd` decision event mid-call. */
904
+ onDecision?: (event: AmdDecisionEvent) => void;
905
+ /** Fired on any other JSON frame (forward-compatible). */
906
+ onMessage?: (frame: Record<string, unknown>) => void;
907
+ /** Fired on a transport-level error. */
908
+ onError?: (err: Error) => void;
909
+ /** Fired when the socket closes. */
910
+ onClose?: (code: number, reason: string) => void;
911
+ /** Injectable WebSocket constructor (defaults to the global). */
912
+ webSocket?: WebSocketCtor;
913
+ }
778
914
  export declare class PyAI {
779
915
  private readonly apiKey;
780
916
  private readonly baseURL;
@@ -852,7 +988,7 @@ export declare class PyAI {
852
988
  };
853
989
  /**
854
990
  * Introspect the calling key: scopes, environment, and limits. Useful for a
855
- * preflight/doctor check. (New route; older deployments may 404 handle it.)
991
+ * preflight/doctor check. (New route; older deployments may 404, handle it.)
856
992
  */
857
993
  me: () => Promise<MeResponse>;
858
994
  clones: {
@@ -919,7 +1055,7 @@ export declare class PyAI {
919
1055
  }) => Promise<ListResponse<TraceViolation>>;
920
1056
  };
921
1057
  findings: {
922
- /** List Tier-2 (async semantic) findings advisory, non-blocking. Scope `trace:read`. */
1058
+ /** List Tier-2 (async semantic) findings, advisory, non-blocking. Scope `trace:read`. */
923
1059
  list: (params?: {
924
1060
  checkId?: string;
925
1061
  action?: "flag" | "preempt_next" | "escalate";
@@ -968,10 +1104,34 @@ export declare class PyAI {
968
1104
  trigger: (callId: string, input: RecapCallTriggerInput) => Promise<RecapCallSummary>;
969
1105
  };
970
1106
  };
1107
+ amd: {
1108
+ config: {
1109
+ /** The org's AMD operating point + webhook. Scope `amd:configure`. */
1110
+ get: () => Promise<AmdConfig>;
1111
+ /** Set the account-default `aggressiveness` (0-1) and webhook. Scope `amd:configure`. */
1112
+ set: (input: AmdConfigInput) => Promise<AmdConfig>;
1113
+ };
1114
+ calls: {
1115
+ /** Recent AMD decisions, newest first. Scope `amd:read`. */
1116
+ list: (params?: {
1117
+ limit?: number;
1118
+ cursor?: string;
1119
+ sessionLabel?: string;
1120
+ }) => Promise<ListResponse<AmdCallSummary>>;
1121
+ /** The full decision (answered_by, reason, …) for one call. Scope `amd:read`. */
1122
+ get: (callId: string) => Promise<AmdCall>;
1123
+ };
1124
+ /**
1125
+ * Open a live AMD stream over `/v1/amd/stream` (Twilio Media Streams
1126
+ * protocol). Server-side helper for forking media yourself; the common
1127
+ * Twilio path is one line of TwiML, no SDK. Scope `amd:detect`.
1128
+ */
1129
+ stream: (opts?: AmdStreamOptions) => AmdStream;
1130
+ };
971
1131
  omni: {
972
1132
  /**
973
1133
  * Mint an ephemeral, origin-locked Omni session token a browser can use to
974
- * open ONE realtime session **directly** the public/private split for
1134
+ * open ONE realtime session **directly**, the public/private split for
975
1135
  * realtime. **Call this from your server** with a secret key holding
976
1136
  * `omni:session`; never ship the secret key to a page. Hand the returned
977
1137
  * `token` to the browser, which connects with
@@ -983,8 +1143,8 @@ export declare class PyAI {
983
1143
  /**
984
1144
  * Open a live Omni agentic-voice session over `/v1/omni`. Returns an
985
1145
  * {@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
1146
+ * asymmetry for you, it sends `type`-keyed control frames (`configure`,
1147
+ * `dtmf`) and parses `event`-keyed server frames, so you can't trip the #1
988
1148
  * Omni integration bug. Omni is zero-state: nothing to create first; the
989
1149
  * agent's behavior travels in the `configure` frame. Pass `token` (from
990
1150
  * `createSession`) to connect from a browser without the secret key.
@@ -997,6 +1157,8 @@ export declare class PyAI {
997
1157
  realtimeSubprotocol(): string;
998
1158
  /** Build the Hear streaming-STT WebSocket URL (`/v1/audio/transcriptions/stream`). */
999
1159
  hearStreamURL(opts?: HearStreamOptions): string;
1160
+ /** Build the AMD detection WebSocket URL (`/v1/amd/stream`). */
1161
+ amdStreamURL(opts?: AmdStreamOptions): string;
1000
1162
  /**
1001
1163
  * Open a realtime WebSocket. Uses the global WebSocket (browser, Node 22+).
1002
1164
  * The key travels as a subprotocol so it works from the browser without