@pyai/sdk 0.2.0 → 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/README.md +115 -34
- package/dist/cli.d.ts +4 -3
- package/dist/cli.js +78 -28
- package/dist/index.d.ts +431 -22
- package/dist/index.js +426 -15
- package/package.json +3 -2
- package/src/cli.ts +77 -29
- package/src/index.ts +743 -31
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @pyai/sdk
|
|
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
|
|
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}
|
|
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
|
|
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)
|
|
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)
|
|
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
|
}
|
|
@@ -121,13 +119,52 @@ export interface CreateJobParams {
|
|
|
121
119
|
webhook_url?: string;
|
|
122
120
|
}
|
|
123
121
|
export interface RealtimeOptions {
|
|
124
|
-
/** "omni" (agentic
|
|
122
|
+
/** "omni" (agentic voice) or "flow" (legacy voice duplex). Default "omni". */
|
|
125
123
|
product?: "omni" | "flow";
|
|
126
|
-
/**
|
|
124
|
+
/**
|
|
125
|
+
* Optional opaque tag echoed to your `kb_endpoint` and recorded on the call.
|
|
126
|
+
* Omni is zero-state: the session is authorized by the key's org, so there is
|
|
127
|
+
* nothing to create first and this tag is never required.
|
|
128
|
+
*/
|
|
129
|
+
sessionLabel?: string;
|
|
130
|
+
/**
|
|
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.
|
|
134
|
+
*/
|
|
127
135
|
agentId?: string;
|
|
128
136
|
/** Extra query params (e.g. format, rate). */
|
|
129
137
|
query?: Record<string, string>;
|
|
130
138
|
}
|
|
139
|
+
/** Parameters for minting an ephemeral browser Omni session token (server-side). */
|
|
140
|
+
export interface OmniSessionParams {
|
|
141
|
+
/**
|
|
142
|
+
* Browser origins (scheme://host[:port]) the minted token may connect from.
|
|
143
|
+
* Required and non-empty, a browser token must be origin-locked. `*` is not
|
|
144
|
+
* allowed.
|
|
145
|
+
*/
|
|
146
|
+
allowedOrigins: string[];
|
|
147
|
+
/** Token lifetime in seconds. Default 60 server-side; max 600. Keep it short. */
|
|
148
|
+
ttlSeconds?: number;
|
|
149
|
+
/** Optional opaque tag echoed back to your `kb_endpoint` and recorded on the call. */
|
|
150
|
+
sessionLabel?: string;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The result of {@link PyAI.omni}.createSession, a short-lived credential safe
|
|
154
|
+
* to hand to the browser. Use {@link OmniSession.token} as the WebSocket
|
|
155
|
+
* subprotocol `pyai-key.<token>` against {@link OmniSession.url}.
|
|
156
|
+
*/
|
|
157
|
+
export interface OmniSession {
|
|
158
|
+
object?: "omni.session";
|
|
159
|
+
/** The ephemeral session token. Short-lived + origin-locked; browser-safe. */
|
|
160
|
+
token: string;
|
|
161
|
+
/** Token expiry, Unix epoch milliseconds. */
|
|
162
|
+
expires_at: number;
|
|
163
|
+
/** The Omni realtime WebSocket URL to connect to. */
|
|
164
|
+
url: string;
|
|
165
|
+
/** Echoed back when supplied on the request. */
|
|
166
|
+
session_label?: string;
|
|
167
|
+
}
|
|
131
168
|
/** Frame `type`s emitted by the Hear streaming-STT WebSocket. */
|
|
132
169
|
export declare const HearFrameType: {
|
|
133
170
|
/** Eager live hypothesis for the current utterance. */
|
|
@@ -138,6 +175,8 @@ export declare const HearFrameType: {
|
|
|
138
175
|
readonly SpeechFinal: "speech_final";
|
|
139
176
|
/** Corrected, full-context transcript following `speech_final`. */
|
|
140
177
|
readonly Final: "final";
|
|
178
|
+
/** Final billed-usage summary, emitted just before a graceful close. */
|
|
179
|
+
readonly Usage: "usage";
|
|
141
180
|
/** Server-side fault frame. */
|
|
142
181
|
readonly Error: "error";
|
|
143
182
|
};
|
|
@@ -156,7 +195,7 @@ export declare const WSCloseCode: {
|
|
|
156
195
|
export type WSCloseCode = (typeof WSCloseCode)[keyof typeof WSCloseCode];
|
|
157
196
|
/**
|
|
158
197
|
* Stable, machine-readable error `code`s (the documented contract). Branch on
|
|
159
|
-
* these. The set is treated as open
|
|
198
|
+
* these. The set is treated as open, `PyAIError.code` stays `string`, so a
|
|
160
199
|
* new server code never breaks the build, but the known ones are named here.
|
|
161
200
|
*/
|
|
162
201
|
export declare const ErrorCode: {
|
|
@@ -203,15 +242,30 @@ export interface HearFinalFrame {
|
|
|
203
242
|
/** Present only with Cue grounding enabled (top KB passages). */
|
|
204
243
|
grounding?: HearGroundingPassage[];
|
|
205
244
|
}
|
|
245
|
+
/** Final billed-usage summary (`usage`), emitted just before a graceful close
|
|
246
|
+
* so realtime spend can be reconciled in-band (a realtime WS carries no
|
|
247
|
+
* `x-pyai-units` response header). Best-effort: absent if there was no billable
|
|
248
|
+
* audio or the close was abnormal. */
|
|
249
|
+
export interface HearUsageFrame {
|
|
250
|
+
type: "usage";
|
|
251
|
+
/** `hear` for plain streaming, `cue` when grounding was enabled. */
|
|
252
|
+
product: "hear" | "cue";
|
|
253
|
+
/** The billed meter (`hear.requests` or `cue.minutes`). */
|
|
254
|
+
meter: string;
|
|
255
|
+
/** Summed active-speech audio billed for the session, in seconds. */
|
|
256
|
+
audio_seconds: number;
|
|
257
|
+
/** The same quantity in minutes. */
|
|
258
|
+
minutes: number;
|
|
259
|
+
}
|
|
206
260
|
/** Server fault frame (`error`). */
|
|
207
261
|
export interface HearErrorFrame {
|
|
208
262
|
type: "error";
|
|
209
263
|
code?: string;
|
|
210
264
|
message: string;
|
|
211
265
|
}
|
|
212
|
-
export type HearFrame = HearPartialFrame | HearFinalFrame | HearErrorFrame;
|
|
266
|
+
export type HearFrame = HearPartialFrame | HearFinalFrame | HearUsageFrame | HearErrorFrame;
|
|
213
267
|
/**
|
|
214
|
-
* Minimal structural WebSocket
|
|
268
|
+
* Minimal structural WebSocket, matches both the browser/Node global
|
|
215
269
|
* `WebSocket` and the `ws` package, and lets tests inject a mock.
|
|
216
270
|
*/
|
|
217
271
|
export interface WebSocketLike {
|
|
@@ -240,12 +294,32 @@ export interface HearStreamOptions {
|
|
|
240
294
|
encoding?: "pcm16" | "opus";
|
|
241
295
|
/** Emit eager partial hypotheses. Default true server-side. */
|
|
242
296
|
interimResults?: boolean;
|
|
297
|
+
/**
|
|
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.
|
|
301
|
+
*/
|
|
302
|
+
numerals?: boolean;
|
|
303
|
+
/**
|
|
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.
|
|
308
|
+
*/
|
|
309
|
+
endpointingMs?: number;
|
|
243
310
|
/**
|
|
244
311
|
* Enable Cue knowledge-base grounding: sends `{type:"config",grounding:true}`
|
|
245
312
|
* on open, after which `speech_final`/`final` frames carry a `grounding`
|
|
246
313
|
* array. Bills a single `cue.minutes` line instead of the Hear rate.
|
|
247
314
|
*/
|
|
248
315
|
grounding?: boolean;
|
|
316
|
+
/** Cue: number of KB passages to retrieve per turn (1-20, default 3). */
|
|
317
|
+
groundingK?: number;
|
|
318
|
+
/** Cue: drop passages scoring below this (0-1, default 0 = keep all). */
|
|
319
|
+
groundingMinScore?: number;
|
|
320
|
+
/** Cue: max ms to wait for retrieval at the final before failing open to
|
|
321
|
+
* `grounding: []` (50-2000, default 450). */
|
|
322
|
+
groundingTimeoutMs?: number;
|
|
249
323
|
/** Extra query params merged onto the connect URL. */
|
|
250
324
|
query?: Record<string, string>;
|
|
251
325
|
/** Fired once the socket opens (after the optional grounding config). */
|
|
@@ -254,6 +328,8 @@ export interface HearStreamOptions {
|
|
|
254
328
|
onPartial?: (frame: HearPartialFrame) => void;
|
|
255
329
|
/** Fired on `speech_final` / `final`. */
|
|
256
330
|
onFinal?: (frame: HearFinalFrame) => void;
|
|
331
|
+
/** Fired on the final `usage` summary (in-band realtime reconciliation). */
|
|
332
|
+
onUsage?: (frame: HearUsageFrame) => void;
|
|
257
333
|
/** Fired on an `error` frame or a transport-level error. */
|
|
258
334
|
onError?: (err: HearErrorFrame | Error) => void;
|
|
259
335
|
/** Fired when the socket closes (code per `WSCloseCode`). */
|
|
@@ -284,6 +360,220 @@ export declare class HearStream {
|
|
|
284
360
|
/** Current WebSocket readyState. */
|
|
285
361
|
get readyState(): number;
|
|
286
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
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Event names on Omni **server → client** frames. ⚠️ Note the asymmetry:
|
|
391
|
+
* inbound frames are keyed on `event`, but your **outbound** control frames
|
|
392
|
+
* (`configure`, `dtmf`, …) are keyed on `type`. {@link OmniConnection} handles
|
|
393
|
+
* both sides for you; this map is for matching frames in `onEvent`.
|
|
394
|
+
*/
|
|
395
|
+
export declare const OmniEvent: {
|
|
396
|
+
/** Handshake; advertises protocol version + audio formats. */
|
|
397
|
+
readonly Hello: "hello";
|
|
398
|
+
/** Ack for your `configure` frame (echoes the resolved `voice_id`). */
|
|
399
|
+
readonly Configured: "configured";
|
|
400
|
+
/** Session is live; includes the resolved agent + audio caps. */
|
|
401
|
+
readonly SessionStarted: "session_started";
|
|
402
|
+
/** Turn boundary (user/assistant speaking). */
|
|
403
|
+
readonly Turn: "turn";
|
|
404
|
+
/** Incremental/final transcript text. */
|
|
405
|
+
readonly Transcript: "transcript";
|
|
406
|
+
/** User interrupted; assistant audio is being cut. */
|
|
407
|
+
readonly BargeIn: "barge_in";
|
|
408
|
+
/** Alias for barge-in on some engine builds. */
|
|
409
|
+
readonly Flush: "flush";
|
|
410
|
+
/** Engine requests a client-loop tool invocation. */
|
|
411
|
+
readonly ToolCall: "tool_call";
|
|
412
|
+
/** Session is closing; see close code. */
|
|
413
|
+
readonly SessionEnd: "session_end";
|
|
414
|
+
/** Server fault frame. */
|
|
415
|
+
readonly Error: "error";
|
|
416
|
+
};
|
|
417
|
+
export type OmniEvent = (typeof OmniEvent)[keyof typeof OmniEvent];
|
|
418
|
+
/** A server → client Omni frame. Keyed on `event` (not `type`); open for forward-compat. */
|
|
419
|
+
export interface OmniServerFrame {
|
|
420
|
+
event: string;
|
|
421
|
+
[k: string]: unknown;
|
|
422
|
+
}
|
|
423
|
+
/** A binary agent-audio chunk delivered to {@link OmniConnectOptions.onAudio}. */
|
|
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
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* The agent config sent as the `configure` control frame. These are the wire
|
|
440
|
+
* (snake_case) fields the engine reads; it's an **open bag**, so forward-compat
|
|
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.
|
|
445
|
+
*/
|
|
446
|
+
export interface OmniConfigure {
|
|
447
|
+
/** Voice to speak with (stock / clone / designed id). */
|
|
448
|
+
voice_id?: string;
|
|
449
|
+
/** System prompt / role + instructions for the agent. */
|
|
450
|
+
persona?: string;
|
|
451
|
+
/** Customer-hosted URL the engine calls per turn for grounding. */
|
|
452
|
+
kb_endpoint?: string;
|
|
453
|
+
/** Bearer the engine presents to `kb_endpoint`. */
|
|
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[];
|
|
468
|
+
/** Forward-compatible: any other key the engine honors. */
|
|
469
|
+
[k: string]: unknown;
|
|
470
|
+
}
|
|
471
|
+
export interface OmniConnectOptions {
|
|
472
|
+
/** Optional opaque per-session tag echoed to your `kb_endpoint`. Omni is zero-state. */
|
|
473
|
+
sessionLabel?: string;
|
|
474
|
+
/**
|
|
475
|
+
* Ephemeral browser token from `omni.createSession`. When set it is used as
|
|
476
|
+
* the WS subprotocol (`pyai-key.<token>`) instead of the client's secret key,
|
|
477
|
+
* so a page never holds a secret key.
|
|
478
|
+
*/
|
|
479
|
+
token?: string;
|
|
480
|
+
/** Connect-URL sample rate: 24000 browser, 16000 wideband telephony, 8000 G.711/Twilio. */
|
|
481
|
+
rate?: 24000 | 16000 | 8000;
|
|
482
|
+
/** Connect-URL audio format. Default `pcm16`. */
|
|
483
|
+
format?: "pcm16";
|
|
484
|
+
/**
|
|
485
|
+
* Agent config sent as `{"type":"configure",...}` the instant the socket
|
|
486
|
+
* opens. Omit to send it yourself later via {@link OmniConnection.configure}.
|
|
487
|
+
*/
|
|
488
|
+
configure?: OmniConfigure;
|
|
489
|
+
/** Extra query params merged onto the connect URL. */
|
|
490
|
+
query?: Record<string, string>;
|
|
491
|
+
/** Fired once the socket opens (after the optional auto-configure). */
|
|
492
|
+
onOpen?: () => void;
|
|
493
|
+
/** Fired for each binary agent-audio chunk, play it out as it arrives. */
|
|
494
|
+
onAudio?: (chunk: OmniAudioChunk) => void;
|
|
495
|
+
/** Fired on the `hello` handshake frame. */
|
|
496
|
+
onHello?: (frame: OmniServerFrame) => void;
|
|
497
|
+
/** Fired on the `configured` ack. */
|
|
498
|
+
onConfigured?: (frame: OmniServerFrame) => void;
|
|
499
|
+
/** Fired on `session_started`. */
|
|
500
|
+
onSessionStarted?: (frame: OmniServerFrame) => void;
|
|
501
|
+
/** Fired on `turn` boundaries. */
|
|
502
|
+
onTurn?: (frame: OmniServerFrame) => void;
|
|
503
|
+
/** Fired on `transcript` text frames. */
|
|
504
|
+
onTranscript?: (frame: OmniServerFrame) => void;
|
|
505
|
+
/** Fired on `barge_in` / `flush` (user interrupted). */
|
|
506
|
+
onBargeIn?: (frame: OmniServerFrame) => void;
|
|
507
|
+
/** Fired when the engine requests a client-loop tool invocation. */
|
|
508
|
+
onToolCall?: (frame: OmniToolCallFrame) => void;
|
|
509
|
+
/** Fired on `session_end`. */
|
|
510
|
+
onSessionEnd?: (frame: OmniServerFrame) => void;
|
|
511
|
+
/** Fired on EVERY JSON frame (including unknown/forward-compat ones). */
|
|
512
|
+
onEvent?: (frame: OmniServerFrame) => void;
|
|
513
|
+
/** Fired on an `error` frame or a transport-level error. */
|
|
514
|
+
onError?: (err: OmniServerFrame | Error) => void;
|
|
515
|
+
/** Fired when the socket closes (code per {@link WSCloseCode}). */
|
|
516
|
+
onClose?: (code: number, reason: string) => void;
|
|
517
|
+
/** Injectable WebSocket constructor (defaults to the global). */
|
|
518
|
+
webSocket?: WebSocketCtor;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* A live Omni agentic-voice session over `/v1/omni`. Hides the wire protocol's
|
|
522
|
+
* frame-key asymmetry: it sends control frames keyed on `type` (`configure`,
|
|
523
|
+
* `dtmf`) and parses server frames keyed on `event`, so you cannot trip the #1
|
|
524
|
+
* Omni integration bug (mirroring the server's `event` key on outbound, which
|
|
525
|
+
* is silently dropped). Construct via `pyai.omni.connect()`.
|
|
526
|
+
*
|
|
527
|
+
* @example
|
|
528
|
+
* const omni = pyai.omni.connect({
|
|
529
|
+
* rate: 16000,
|
|
530
|
+
* configure: { voice_id: "stock_emma_en_gb", persona: "You are a receptionist." },
|
|
531
|
+
* onAudio: (chunk) => speaker.write(chunk),
|
|
532
|
+
* onTranscript: (f) => console.log(f.text),
|
|
533
|
+
* });
|
|
534
|
+
* omni.sendAudio(pcm16Chunk); // stream caller audio continuously
|
|
535
|
+
*/
|
|
536
|
+
export declare class OmniConnection {
|
|
537
|
+
private readonly ws;
|
|
538
|
+
private readonly opts;
|
|
539
|
+
private closed;
|
|
540
|
+
/** Serializes async Blob reads in {@link sendAudio} so frames stay ordered. */
|
|
541
|
+
private blobTail;
|
|
542
|
+
constructor(url: string, subprotocol: string, opts: OmniConnectOptions);
|
|
543
|
+
private handleMessage;
|
|
544
|
+
private dispatchFrame;
|
|
545
|
+
/**
|
|
546
|
+
* Send (or update) the agent config. Always emitted as
|
|
547
|
+
* `{"type":"configure", ...}`, the correct key. (A hand-rolled
|
|
548
|
+
* `{"event":"configure"}` is acked but silently dropped by the engine.)
|
|
549
|
+
*/
|
|
550
|
+
configure(cfg: OmniConfigure): void;
|
|
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
|
+
*/
|
|
556
|
+
sendAudio(chunk: ArrayBufferLike | ArrayBufferView | Blob): void;
|
|
557
|
+
/** Send DTMF digits as a `{"type":"dtmf"}` control frame. */
|
|
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;
|
|
564
|
+
/**
|
|
565
|
+
* Send an arbitrary control frame for forward-compat control types the SDK
|
|
566
|
+
* does not model yet. Reminder: client → server frames are keyed on `type`,
|
|
567
|
+
* never `event`.
|
|
568
|
+
*/
|
|
569
|
+
send(frame: Record<string, unknown>): void;
|
|
570
|
+
/** Close the session. */
|
|
571
|
+
close(code?: number, reason?: string): void;
|
|
572
|
+
/** The underlying socket (escape hatch for advanced use). */
|
|
573
|
+
get socket(): WebSocketLike;
|
|
574
|
+
/** Current WebSocket readyState. */
|
|
575
|
+
get readyState(): number;
|
|
576
|
+
}
|
|
287
577
|
/** Shape of `GET /v1/me`. Fields are best-effort / forward-compatible. */
|
|
288
578
|
export interface MeResponse {
|
|
289
579
|
object?: string;
|
|
@@ -378,7 +668,7 @@ export interface TraceBargeMetrics {
|
|
|
378
668
|
}
|
|
379
669
|
/**
|
|
380
670
|
* Known timeline-turn roles. Left open (see {@link TraceTimelineTurn.role}) so a
|
|
381
|
-
* new server-side role never breaks the build
|
|
671
|
+
* new server-side role never breaks the build, branch defensively.
|
|
382
672
|
*/
|
|
383
673
|
export type TraceTimelineRole = "agent" | "caller" | "system" | "tool";
|
|
384
674
|
/** One turn of a per-call operational timeline (eval scorecard-v1). */
|
|
@@ -402,18 +692,18 @@ export interface TraceTimelineTurn {
|
|
|
402
692
|
}
|
|
403
693
|
/**
|
|
404
694
|
* Aggregate per-call quality metrics (eval scorecard-v1). All optional and
|
|
405
|
-
* forward-compatible
|
|
695
|
+
* forward-compatible, present once the engine emits them.
|
|
406
696
|
*/
|
|
407
697
|
export interface QualityMetrics {
|
|
408
|
-
/** Word error rate vs. reference transcript, 0
|
|
698
|
+
/** Word error rate vs. reference transcript, 0-1 (lower is better). */
|
|
409
699
|
wer?: number;
|
|
410
700
|
/** Representative time-to-first-audio across turns, ms. */
|
|
411
701
|
ttfb_ms?: number;
|
|
412
702
|
/** 95th-percentile end-to-end turn latency, ms. */
|
|
413
703
|
turn_p95_ms?: number;
|
|
414
|
-
/** Barge-in recovery rate, 0
|
|
704
|
+
/** Barge-in recovery rate, 0-1. */
|
|
415
705
|
barge_recovery?: number;
|
|
416
|
-
/** Task-success score, 0
|
|
706
|
+
/** Task-success score, 0-1. */
|
|
417
707
|
task_success?: number;
|
|
418
708
|
/** Composite voice-agent quality index (engine-defined scale). */
|
|
419
709
|
vaqi?: number;
|
|
@@ -551,6 +841,76 @@ export interface RecapCallTriggerInput {
|
|
|
551
841
|
customer_name?: string;
|
|
552
842
|
crm_fields?: Record<string, unknown>;
|
|
553
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
|
+
}
|
|
554
914
|
export declare class PyAI {
|
|
555
915
|
private readonly apiKey;
|
|
556
916
|
private readonly baseURL;
|
|
@@ -628,7 +988,7 @@ export declare class PyAI {
|
|
|
628
988
|
};
|
|
629
989
|
/**
|
|
630
990
|
* Introspect the calling key: scopes, environment, and limits. Useful for a
|
|
631
|
-
* preflight/doctor check. (New route; older deployments may 404
|
|
991
|
+
* preflight/doctor check. (New route; older deployments may 404, handle it.)
|
|
632
992
|
*/
|
|
633
993
|
me: () => Promise<MeResponse>;
|
|
634
994
|
clones: {
|
|
@@ -695,7 +1055,7 @@ export declare class PyAI {
|
|
|
695
1055
|
}) => Promise<ListResponse<TraceViolation>>;
|
|
696
1056
|
};
|
|
697
1057
|
findings: {
|
|
698
|
-
/** List Tier-2 (async semantic) findings
|
|
1058
|
+
/** List Tier-2 (async semantic) findings, advisory, non-blocking. Scope `trace:read`. */
|
|
699
1059
|
list: (params?: {
|
|
700
1060
|
checkId?: string;
|
|
701
1061
|
action?: "flag" | "preempt_next" | "escalate";
|
|
@@ -744,12 +1104,61 @@ export declare class PyAI {
|
|
|
744
1104
|
trigger: (callId: string, input: RecapCallTriggerInput) => Promise<RecapCallSummary>;
|
|
745
1105
|
};
|
|
746
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
|
+
};
|
|
1131
|
+
omni: {
|
|
1132
|
+
/**
|
|
1133
|
+
* Mint an ephemeral, origin-locked Omni session token a browser can use to
|
|
1134
|
+
* open ONE realtime session **directly**, the public/private split for
|
|
1135
|
+
* realtime. **Call this from your server** with a secret key holding
|
|
1136
|
+
* `omni:session`; never ship the secret key to a page. Hand the returned
|
|
1137
|
+
* `token` to the browser, which connects with
|
|
1138
|
+
* `new WebSocket(session.url, ["pyai-key." + session.token])`. The token
|
|
1139
|
+
* expires after `ttlSeconds` (default 60s) and only works from
|
|
1140
|
+
* `allowedOrigins`. Scope `omni:session`.
|
|
1141
|
+
*/
|
|
1142
|
+
createSession: (params: OmniSessionParams) => Promise<OmniSession>;
|
|
1143
|
+
/**
|
|
1144
|
+
* Open a live Omni agentic-voice session over `/v1/omni`. Returns an
|
|
1145
|
+
* {@link OmniConnection} that handles the wire protocol's frame-key
|
|
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
|
|
1148
|
+
* Omni integration bug. Omni is zero-state: nothing to create first; the
|
|
1149
|
+
* agent's behavior travels in the `configure` frame. Pass `token` (from
|
|
1150
|
+
* `createSession`) to connect from a browser without the secret key.
|
|
1151
|
+
*/
|
|
1152
|
+
connect: (opts?: OmniConnectOptions) => OmniConnection;
|
|
1153
|
+
};
|
|
747
1154
|
/** Build the realtime WebSocket URL for the chosen product. */
|
|
748
1155
|
realtimeURL(opts?: RealtimeOptions): string;
|
|
749
1156
|
/** The subprotocol that carries the key on a WS upgrade (browser-safe auth). */
|
|
750
1157
|
realtimeSubprotocol(): string;
|
|
751
1158
|
/** Build the Hear streaming-STT WebSocket URL (`/v1/audio/transcriptions/stream`). */
|
|
752
1159
|
hearStreamURL(opts?: HearStreamOptions): string;
|
|
1160
|
+
/** Build the AMD detection WebSocket URL (`/v1/amd/stream`). */
|
|
1161
|
+
amdStreamURL(opts?: AmdStreamOptions): string;
|
|
753
1162
|
/**
|
|
754
1163
|
* Open a realtime WebSocket. Uses the global WebSocket (browser, Node 22+).
|
|
755
1164
|
* The key travels as a subprotocol so it works from the browser without
|