@pyai/sdk 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT_GUIDE.md +296 -0
- package/CLI.md +609 -0
- package/CLI.schema.json +1085 -0
- package/README.md +49 -22
- package/dist/cli-config.d.ts +47 -0
- package/dist/cli-config.js +265 -0
- package/dist/cli-dx.d.ts +11 -0
- package/dist/cli-dx.js +32 -0
- package/dist/cli-http.d.ts +38 -0
- package/dist/cli-http.js +286 -0
- package/dist/cli-init.d.ts +27 -0
- package/dist/cli-init.js +430 -0
- package/dist/cli-routes.d.ts +15 -0
- package/dist/cli-routes.js +52 -0
- package/dist/cli-runtime.d.ts +10 -0
- package/dist/cli-runtime.js +23 -0
- package/dist/cli-web-auth.d.ts +33 -0
- package/dist/cli-web-auth.js +154 -0
- package/dist/cli.d.ts +1 -17
- package/dist/cli.js +674 -270
- package/dist/index.d.ts +206 -26
- package/dist/index.js +98 -12
- package/package.json +7 -2
- package/src/cli-config.ts +283 -0
- package/src/cli-dx.ts +33 -0
- package/src/cli-http.ts +273 -0
- package/src/cli-init.ts +430 -0
- package/src/cli-routes.ts +72 -0
- package/src/cli-runtime.ts +30 -0
- package/src/cli-web-auth.ts +148 -0
- package/src/cli.ts +431 -295
- package/src/index.ts +259 -32
package/src/index.ts
CHANGED
|
@@ -41,6 +41,20 @@ export interface Voice {
|
|
|
41
41
|
name?: string;
|
|
42
42
|
gender?: string;
|
|
43
43
|
region?: string;
|
|
44
|
+
language?: string;
|
|
45
|
+
/** Customer-facing quality tier. */
|
|
46
|
+
tier?: "standard" | "natural";
|
|
47
|
+
/** Permanent convenience inputs accepted on the advertised surfaces. */
|
|
48
|
+
aliases?: string[];
|
|
49
|
+
/** Product surfaces on which this stock voice can be selected. */
|
|
50
|
+
available_on?: Array<"speak" | "omni">;
|
|
51
|
+
/** Accepted Speak delivery modes. Empty means the voice has no Speak surface. */
|
|
52
|
+
synthesis_modes?: Array<"streaming" | "async">;
|
|
53
|
+
/** Voice-specific amount on top of the selected product's base rate. */
|
|
54
|
+
pricing?: {
|
|
55
|
+
included_in_base_price: boolean;
|
|
56
|
+
additional_price_usd_per_minute: number;
|
|
57
|
+
};
|
|
44
58
|
[k: string]: unknown;
|
|
45
59
|
}
|
|
46
60
|
|
|
@@ -104,6 +118,13 @@ export interface SpeechParams {
|
|
|
104
118
|
input: string;
|
|
105
119
|
voice?: string;
|
|
106
120
|
model?: SpeakModel;
|
|
121
|
+
/**
|
|
122
|
+
* Delivery mode. The API defaults to true and every catalog voice accepts
|
|
123
|
+
* both values. Set false when you want one complete buffered body with a
|
|
124
|
+
* `Content-Length`. A voice whose serving fleet has no streaming lane is
|
|
125
|
+
* rendered buffered either way and says so with `x-pyai-stream: buffered`.
|
|
126
|
+
*/
|
|
127
|
+
stream?: boolean;
|
|
107
128
|
/**
|
|
108
129
|
* Output container/codec, resampled+encoded server-side. One of
|
|
109
130
|
* {@link SpeechFormat}, anything else is a `400 unsupported_format`. Omit for
|
|
@@ -150,8 +171,62 @@ export interface CreateJobParams {
|
|
|
150
171
|
diarize?: boolean;
|
|
151
172
|
channel?: boolean;
|
|
152
173
|
numerals?: boolean;
|
|
174
|
+
smart_format?: boolean;
|
|
175
|
+
dictation?: boolean;
|
|
176
|
+
drop_fillers?: boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Per-job names, brands, products, or distinctive terms. PyAI keeps up to
|
|
179
|
+
* five valid entries after trimming, deduplication, and common-word
|
|
180
|
+
* filtering.
|
|
181
|
+
*/
|
|
182
|
+
vocabulary?: string[];
|
|
153
183
|
output_formats?: Array<"json" | "srt" | "vtt">;
|
|
154
184
|
webhook_url?: string;
|
|
185
|
+
call_id?: string;
|
|
186
|
+
pack_id?: string;
|
|
187
|
+
call_direction?: "inbound" | "outbound";
|
|
188
|
+
customer_name?: string;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export type HearVocabularyProfile = "batch" | "hear_stream";
|
|
192
|
+
|
|
193
|
+
export interface HearVocabularyInput {
|
|
194
|
+
/** Organization-owned names, brands, products, or distinctive terms. */
|
|
195
|
+
terms: string[];
|
|
196
|
+
/** Stored terms remain inert unless a use case is selected here. */
|
|
197
|
+
enabledFor: HearVocabularyProfile[];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export interface HearVocabulary {
|
|
201
|
+
object: "hear.vocabulary";
|
|
202
|
+
/** Sanitized stored list, at most five terms. */
|
|
203
|
+
terms: string[];
|
|
204
|
+
/** Use cases that may add stored suggestions. */
|
|
205
|
+
enabled_for: HearVocabularyProfile[];
|
|
206
|
+
/** Unix milliseconds, or null before the first save. */
|
|
207
|
+
updated_at: number | null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export interface AgentConfig {
|
|
211
|
+
name?: string;
|
|
212
|
+
persona_system_prompt?: string | null;
|
|
213
|
+
greeting?: string | null;
|
|
214
|
+
voice_id?: string | null;
|
|
215
|
+
language?: "en" | "fr" | "es" | "de" | "hi" | null;
|
|
216
|
+
/**
|
|
217
|
+
* Opt-in speech-recognition vocabulary. PyAI sanitizes and keeps at most
|
|
218
|
+
* five terms. An empty list or null turns it off.
|
|
219
|
+
*/
|
|
220
|
+
vocabulary?: string[] | null;
|
|
221
|
+
[key: string]: unknown;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export interface Agent {
|
|
225
|
+
object: "agent";
|
|
226
|
+
agent_id: string;
|
|
227
|
+
name: string;
|
|
228
|
+
vocabulary: string[];
|
|
229
|
+
[key: string]: unknown;
|
|
155
230
|
}
|
|
156
231
|
|
|
157
232
|
export interface RealtimeOptions {
|
|
@@ -226,10 +301,16 @@ export const HearFrameType = {
|
|
|
226
301
|
} as const;
|
|
227
302
|
export type HearFrameType = (typeof HearFrameType)[keyof typeof HearFrameType];
|
|
228
303
|
|
|
229
|
-
/**
|
|
304
|
+
/**
|
|
305
|
+
* WebSocket close codes used across the PyAI realtime/streaming surfaces.
|
|
306
|
+
* Browser callers may initiate only Normal or values in 3000-4999; the
|
|
307
|
+
* standards-reserved values below are server close observations.
|
|
308
|
+
*/
|
|
230
309
|
export const WSCloseCode = {
|
|
231
310
|
/** Normal closure. */
|
|
232
311
|
Normal: 1000,
|
|
312
|
+
/** Browser-safe private application code for a malformed peer protocol frame. */
|
|
313
|
+
ProtocolViolation: 4002,
|
|
233
314
|
/** Auth/policy: bad key, missing scope, or revoked token. */
|
|
234
315
|
PolicyViolation: 1008,
|
|
235
316
|
/** Engine/internal error. */
|
|
@@ -258,6 +339,7 @@ export const ErrorCode = {
|
|
|
258
339
|
IdempotencyConflict: "idempotency_conflict",
|
|
259
340
|
NotFound: "not_found",
|
|
260
341
|
NumberInUse: "number_in_use",
|
|
342
|
+
UnsupportedToolTransport: "unsupported_tool_transport",
|
|
261
343
|
} as const;
|
|
262
344
|
export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
|
|
263
345
|
|
|
@@ -366,29 +448,61 @@ export interface WebSocketLike {
|
|
|
366
448
|
onclose: ((ev: { code: number; reason: string }) => void) | null;
|
|
367
449
|
}
|
|
368
450
|
|
|
451
|
+
/**
|
|
452
|
+
* The non-secret marker every PyAI realtime client offers ALONGSIDE its
|
|
453
|
+
* `pyai-key.<KEY>` credential token.
|
|
454
|
+
*
|
|
455
|
+
* A browser cannot set `Authorization` on a WebSocket, so the key rides in
|
|
456
|
+
* `Sec-WebSocket-Protocol`. RFC 6455 makes the server echo the subprotocol it
|
|
457
|
+
* SELECTS, so a server that selects the credential publishes the credential —
|
|
458
|
+
* which api.pyai.com did until 2026-09-07. The edge now echoes only this
|
|
459
|
+
* marker. Offering it is not optional for Node clients: `ws` throws
|
|
460
|
+
* "Server sent no subprotocol" when it offered subprotocols and the 101
|
|
461
|
+
* selected none, and RFC 6455 lets the server select only a value the client
|
|
462
|
+
* actually offered.
|
|
463
|
+
*/
|
|
464
|
+
export const REALTIME_SUBPROTOCOL_MARKER = "pyai.v1";
|
|
465
|
+
|
|
369
466
|
export type WebSocketCtor = new (url: string, protocols?: string | string[]) => WebSocketLike;
|
|
370
467
|
|
|
371
468
|
export interface HearStreamOptions {
|
|
372
469
|
/** Streaming STT model. Server default `pyai-hear`. */
|
|
373
470
|
model?: string;
|
|
374
|
-
/**
|
|
375
|
-
|
|
376
|
-
* and does not enable language detection. Other values receive
|
|
377
|
-
* `400 unsupported_language`.
|
|
378
|
-
*/
|
|
379
|
-
language?: "en";
|
|
471
|
+
/** Omit or use auto for automatic detection; an explicit code pins recognition. */
|
|
472
|
+
language?: "auto" | "en" | "es" | "fr" | "de" | "hi" | "it" | "pt" | "nl";
|
|
380
473
|
/** Input PCM sample rate in Hz. Default 16000 server-side. */
|
|
381
474
|
sampleRate?: number;
|
|
382
475
|
/** Audio frame encoding. Default "pcm16". */
|
|
383
|
-
encoding?: "pcm16"
|
|
476
|
+
encoding?: "pcm16";
|
|
384
477
|
/** Emit eager partial hypotheses. Default true server-side. */
|
|
385
478
|
interimResults?: boolean;
|
|
386
479
|
/**
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
480
|
+
* Tri-state number formatting on **final** transcripts. `true` forces digits,
|
|
481
|
+
* `false` keeps spoken form, omitted keeps the live engine default (ITN on).
|
|
482
|
+
* Never applied to interim partials. Independent of `smartFormat`.
|
|
390
483
|
*/
|
|
391
484
|
numerals?: boolean;
|
|
485
|
+
/**
|
|
486
|
+
* Opt-in English punctuation and sentence capitalization on **final**
|
|
487
|
+
* transcripts only. Interim partials are never formatted. Default false.
|
|
488
|
+
*/
|
|
489
|
+
smartFormat?: boolean;
|
|
490
|
+
/**
|
|
491
|
+
* Spoken punctuation commands (`period`, `comma`, `new paragraph`,
|
|
492
|
+
* `question mark`) on finals only. Separate from `smartFormat`. Off by default.
|
|
493
|
+
*/
|
|
494
|
+
dictation?: boolean;
|
|
495
|
+
/**
|
|
496
|
+
* Strip `um` / `uh` / `umm` / `uhh` / `er` on finals. Off by default.
|
|
497
|
+
* Do not enable on legal or compliance audio by default.
|
|
498
|
+
*/
|
|
499
|
+
dropFillers?: boolean;
|
|
500
|
+
/**
|
|
501
|
+
* Per-session names, brands, products, or other distinctive terms. PyAI
|
|
502
|
+
* sanitizes up to five effective terms. Request terms come first, then stored
|
|
503
|
+
* `hear_stream` suggestions fill remaining slots. The list is fixed at open.
|
|
504
|
+
*/
|
|
505
|
+
vocabulary?: string[];
|
|
392
506
|
/**
|
|
393
507
|
* Minimum trailing-pause length before an utterance may end (50-5000 ms).
|
|
394
508
|
* Turn detection may wait longer, bounded at `max(endpointingMs, 1500)`.
|
|
@@ -454,7 +568,7 @@ export class HearStream {
|
|
|
454
568
|
"No global WebSocket available; pass options.webSocket (e.g. the `ws` package) to transcriptions.stream()",
|
|
455
569
|
);
|
|
456
570
|
}
|
|
457
|
-
this.ws = new WS(url, [subprotocol]);
|
|
571
|
+
this.ws = new WS(url, [REALTIME_SUBPROTOCOL_MARKER, subprotocol]);
|
|
458
572
|
this.ws.onopen = () => {
|
|
459
573
|
opts.onOpen?.();
|
|
460
574
|
};
|
|
@@ -553,7 +667,7 @@ export class AmdStream {
|
|
|
553
667
|
"No global WebSocket available; pass options.webSocket (e.g. the `ws` package) to amd.stream()",
|
|
554
668
|
);
|
|
555
669
|
}
|
|
556
|
-
this.ws = new WS(url, [subprotocol]);
|
|
670
|
+
this.ws = new WS(url, [REALTIME_SUBPROTOCOL_MARKER, subprotocol]);
|
|
557
671
|
this.ws.onopen = () => opts.onOpen?.();
|
|
558
672
|
this.ws.onmessage = (ev) => this.handleMessage(ev.data);
|
|
559
673
|
this.ws.onerror = (ev) => opts.onError?.(ev instanceof Error ? ev : new Error("WebSocket error"));
|
|
@@ -629,6 +743,8 @@ export const OmniEvent = {
|
|
|
629
743
|
Flush: "flush",
|
|
630
744
|
/** Engine requests a client-loop tool invocation. */
|
|
631
745
|
ToolCall: "tool_call",
|
|
746
|
+
/** A write tool is waiting for the caller to confirm; it has not run. */
|
|
747
|
+
ToolConfirmationRequired: "tool_confirmation_required",
|
|
632
748
|
/** Session is closing; see close code. */
|
|
633
749
|
SessionEnd: "session_end",
|
|
634
750
|
/** Server fault frame. */
|
|
@@ -762,8 +878,6 @@ export interface OmniToolDef {
|
|
|
762
878
|
name: string;
|
|
763
879
|
description?: string;
|
|
764
880
|
parameters?: Record<string, unknown>;
|
|
765
|
-
/** When set, engine-POST mode. Omit for client-loop (default). */
|
|
766
|
-
endpoint?: string;
|
|
767
881
|
}
|
|
768
882
|
|
|
769
883
|
export interface OmniToolCallFrame {
|
|
@@ -801,7 +915,9 @@ export interface OmniConfigure {
|
|
|
801
915
|
* staged, see the Language support reference.
|
|
802
916
|
*/
|
|
803
917
|
language?: "en" | "fr" | "es" | "de" | "hi";
|
|
804
|
-
/** Function calling
|
|
918
|
+
/** Function calling: hosted catalog names, client-loop schemas, or names of
|
|
919
|
+
* server tools already registered with POST /v1/tools. An inline `endpoint`
|
|
920
|
+
* is rejected with `unsupported_tool_transport`. */
|
|
805
921
|
tools?: OmniToolDef[];
|
|
806
922
|
/** Forward-compatible: any other key the engine honors. */
|
|
807
923
|
[k: string]: unknown;
|
|
@@ -894,7 +1010,7 @@ export class OmniConnection {
|
|
|
894
1010
|
"No global WebSocket available; pass options.webSocket (e.g. the `ws` package) to omni.connect()",
|
|
895
1011
|
);
|
|
896
1012
|
}
|
|
897
|
-
this.ws = new WS(url, [subprotocol]);
|
|
1013
|
+
this.ws = new WS(url, [REALTIME_SUBPROTOCOL_MARKER, subprotocol]);
|
|
898
1014
|
this.ws.onopen = () => {
|
|
899
1015
|
if (opts.configure) {
|
|
900
1016
|
try {
|
|
@@ -1359,8 +1475,41 @@ export interface RecapCallSummary {
|
|
|
1359
1475
|
completed_at?: number | null;
|
|
1360
1476
|
}
|
|
1361
1477
|
|
|
1478
|
+
export interface RecapActionItem {
|
|
1479
|
+
owner?: string | null;
|
|
1480
|
+
task: string;
|
|
1481
|
+
due?: string | null;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
export interface RecapTalkRatio {
|
|
1485
|
+
agent: number;
|
|
1486
|
+
customer: number;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
export interface RecapSignal {
|
|
1490
|
+
kind: string;
|
|
1491
|
+
text: string;
|
|
1492
|
+
at_s?: number | null;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
export interface RecapRecord {
|
|
1496
|
+
format: "recap.record.v1";
|
|
1497
|
+
tldr: string | null;
|
|
1498
|
+
summary: string | null;
|
|
1499
|
+
action_items: RecapActionItem[];
|
|
1500
|
+
disposition: string | null;
|
|
1501
|
+
next_steps: string | null;
|
|
1502
|
+
talk_ratio: RecapTalkRatio | null;
|
|
1503
|
+
signals: RecapSignal[];
|
|
1504
|
+
fields: Record<string, unknown>;
|
|
1505
|
+
rep?: Record<string, unknown>;
|
|
1506
|
+
manager?: Record<string, unknown>;
|
|
1507
|
+
ops?: Record<string, unknown>;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1362
1510
|
export interface RecapCall extends RecapCallSummary {
|
|
1363
|
-
record?:
|
|
1511
|
+
record?: RecapRecord;
|
|
1512
|
+
transcript?: { format: "utterances.v1"; utterances: Array<{ speaker_role: "agent" | "customer"; text: string; offset_s: number; duration_s: number }> };
|
|
1364
1513
|
error?: string | null;
|
|
1365
1514
|
crm_write_status?: string | null;
|
|
1366
1515
|
}
|
|
@@ -1574,10 +1723,19 @@ export class PyAI {
|
|
|
1574
1723
|
// --- voices -------------------------------------------------------------
|
|
1575
1724
|
|
|
1576
1725
|
voices = {
|
|
1577
|
-
list: async (params: {
|
|
1726
|
+
list: async (params: {
|
|
1727
|
+
gender?: string;
|
|
1728
|
+
region?: string;
|
|
1729
|
+
language?: string;
|
|
1730
|
+
tier?: "standard" | "natural";
|
|
1731
|
+
q?: string;
|
|
1732
|
+
} = {}): Promise<ListResponse<Voice>> => {
|
|
1578
1733
|
const q = new URLSearchParams();
|
|
1579
1734
|
if (params.gender) q.set("gender", params.gender);
|
|
1580
1735
|
if (params.region) q.set("region", params.region);
|
|
1736
|
+
if (params.language) q.set("language", params.language);
|
|
1737
|
+
if (params.tier) q.set("tier", params.tier);
|
|
1738
|
+
if (params.q) q.set("q", params.q);
|
|
1581
1739
|
const qs = q.toString();
|
|
1582
1740
|
const page = await this.getJson<ListResponse<Record<string, unknown>>>(
|
|
1583
1741
|
`/v1/voices${qs ? `?${qs}` : ""}`,
|
|
@@ -1592,6 +1750,46 @@ export class PyAI {
|
|
|
1592
1750
|
),
|
|
1593
1751
|
};
|
|
1594
1752
|
|
|
1753
|
+
// --- Hear organization settings ----------------------------------------
|
|
1754
|
+
|
|
1755
|
+
hear = {
|
|
1756
|
+
vocabulary: {
|
|
1757
|
+
/** Read organization-owned vocabulary. Scope `hear:configure`. */
|
|
1758
|
+
get: (): Promise<HearVocabulary> =>
|
|
1759
|
+
this.getJson("/v1/hear/vocabulary"),
|
|
1760
|
+
/** Replace vocabulary and activation profiles. Scope `hear:configure`. */
|
|
1761
|
+
set: (input: HearVocabularyInput): Promise<HearVocabulary> =>
|
|
1762
|
+
this.putJson("/v1/hear/vocabulary", {
|
|
1763
|
+
terms: input.terms,
|
|
1764
|
+
enabled_for: input.enabledFor,
|
|
1765
|
+
}),
|
|
1766
|
+
},
|
|
1767
|
+
};
|
|
1768
|
+
|
|
1769
|
+
// --- managed Agents ----------------------------------------------------
|
|
1770
|
+
|
|
1771
|
+
agents = {
|
|
1772
|
+
list: (): Promise<ListResponse<Agent>> => this.getJson("/v1/agents"),
|
|
1773
|
+
get: (agentId: string): Promise<Agent> =>
|
|
1774
|
+
this.getJson(`/v1/agents/${encodeURIComponent(agentId)}`),
|
|
1775
|
+
create: (input: AgentConfig & { name: string }): Promise<Agent> =>
|
|
1776
|
+
this.postJson("/v1/agents", input),
|
|
1777
|
+
update: (agentId: string, patch: AgentConfig): Promise<Agent> =>
|
|
1778
|
+
this.postJson(`/v1/agents/${encodeURIComponent(agentId)}`, patch),
|
|
1779
|
+
delete: async (
|
|
1780
|
+
agentId: string,
|
|
1781
|
+
): Promise<{ object: "agent.deleted"; agent_id: string; deleted: boolean }> => {
|
|
1782
|
+
const response = await this.deleteReq(
|
|
1783
|
+
`/v1/agents/${encodeURIComponent(agentId)}`,
|
|
1784
|
+
);
|
|
1785
|
+
return (await response.json()) as {
|
|
1786
|
+
object: "agent.deleted";
|
|
1787
|
+
agent_id: string;
|
|
1788
|
+
deleted: boolean;
|
|
1789
|
+
};
|
|
1790
|
+
},
|
|
1791
|
+
};
|
|
1792
|
+
|
|
1595
1793
|
// --- audio --------------------------------------------------------------
|
|
1596
1794
|
|
|
1597
1795
|
audio = {
|
|
@@ -1608,10 +1806,10 @@ export class PyAI {
|
|
|
1608
1806
|
/**
|
|
1609
1807
|
* Text-to-speech, streamed. Resolves as soon as the response headers arrive
|
|
1610
1808
|
* with the body as a `ReadableStream` of audio bytes, so you can start
|
|
1611
|
-
* playback or forward the audio at the first chunk
|
|
1612
|
-
*
|
|
1613
|
-
*
|
|
1614
|
-
*
|
|
1809
|
+
* playback or forward the audio at the first chunk. Use `pcm`, `wav`,
|
|
1810
|
+
* or G.711 for streaming; `mp3` and `opus` are buffered server-side.
|
|
1811
|
+
* Consume the stream immediately and cancel its reader when stopping early.
|
|
1812
|
+
* HTTP connection reuse and protocol negotiation are controlled by fetch.
|
|
1615
1813
|
*/
|
|
1616
1814
|
speechStream: async (params: SpeechParams): Promise<ReadableStream<Uint8Array>> => {
|
|
1617
1815
|
assertActiveSpeechParams(params);
|
|
@@ -1629,11 +1827,13 @@ export class PyAI {
|
|
|
1629
1827
|
file: Blob;
|
|
1630
1828
|
filename?: string;
|
|
1631
1829
|
model?: string;
|
|
1632
|
-
/**
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1830
|
+
/** Optional language hint. Omission enables automatic detection. */
|
|
1831
|
+
language?: "en" | "es" | "fr" | "de" | "hi" | "it" | "pt" | "nl";
|
|
1832
|
+
numerals?: boolean;
|
|
1833
|
+
smart_format?: boolean;
|
|
1834
|
+
dictation?: boolean;
|
|
1835
|
+
drop_fillers?: boolean;
|
|
1836
|
+
vocabulary?: string[];
|
|
1637
1837
|
response_format?: "json" | "text" | "verbose_json";
|
|
1638
1838
|
/**
|
|
1639
1839
|
* Deterministic seed for reproducible eval runs. Forward-compatible:
|
|
@@ -1647,6 +1847,11 @@ export class PyAI {
|
|
|
1647
1847
|
form.set("file", params.file, params.filename ?? "audio.wav");
|
|
1648
1848
|
form.set("model", params.model ?? "pyai-hear");
|
|
1649
1849
|
if (params.language) form.set("language", params.language);
|
|
1850
|
+
if (params.numerals !== undefined) form.set("numerals", String(params.numerals));
|
|
1851
|
+
if (params.smart_format !== undefined) form.set("smart_format", String(params.smart_format));
|
|
1852
|
+
if (params.dictation !== undefined) form.set("dictation", String(params.dictation));
|
|
1853
|
+
if (params.drop_fillers !== undefined) form.set("drop_fillers", String(params.drop_fillers));
|
|
1854
|
+
if (params.vocabulary?.length) form.set("vocabulary", params.vocabulary.join(","));
|
|
1650
1855
|
if (params.response_format) form.set("response_format", params.response_format);
|
|
1651
1856
|
if (params.seed !== undefined) form.set("seed", String(params.seed));
|
|
1652
1857
|
if (params.temperature !== undefined) form.set("temperature", String(params.temperature));
|
|
@@ -1921,7 +2126,7 @@ export class PyAI {
|
|
|
1921
2126
|
* realtime. **Call this from your server** with a secret key holding
|
|
1922
2127
|
* `omni:session`; never ship the secret key to a page. Hand the returned
|
|
1923
2128
|
* `token` to the browser, which connects with
|
|
1924
|
-
* `new WebSocket(session.url, ["pyai-key." + session.token])`. The token
|
|
2129
|
+
* `new WebSocket(session.url, ["pyai.v1", "pyai-key." + session.token])`. The token
|
|
1925
2130
|
* expires after `ttlSeconds` (default 60s) and only works from
|
|
1926
2131
|
* `allowedOrigins`. Scope `omni:session`.
|
|
1927
2132
|
*/
|
|
@@ -1973,15 +2178,31 @@ export class PyAI {
|
|
|
1973
2178
|
return `${wsBase}/v1/omni${qs ? `?${qs}` : ""}`;
|
|
1974
2179
|
}
|
|
1975
2180
|
|
|
1976
|
-
/** The subprotocol that carries the key on a WS upgrade
|
|
2181
|
+
/** The subprotocol token that carries the key on a WS upgrade. */
|
|
1977
2182
|
realtimeSubprotocol(): string {
|
|
1978
2183
|
return `pyai-key.${this.apiKey}`;
|
|
1979
2184
|
}
|
|
1980
2185
|
|
|
2186
|
+
/**
|
|
2187
|
+
* The full subprotocol list to open a PyAI WebSocket with:
|
|
2188
|
+
* `[marker, credential]`. Always pass BOTH — see
|
|
2189
|
+
* {@link REALTIME_SUBPROTOCOL_MARKER}. Pass `token` to use an ephemeral
|
|
2190
|
+
* session token (from `omni.createSession`) instead of the secret key.
|
|
2191
|
+
*/
|
|
2192
|
+
realtimeSubprotocols(token?: string): string[] {
|
|
2193
|
+
return [
|
|
2194
|
+
REALTIME_SUBPROTOCOL_MARKER,
|
|
2195
|
+
token ? `pyai-key.${token}` : this.realtimeSubprotocol(),
|
|
2196
|
+
];
|
|
2197
|
+
}
|
|
2198
|
+
|
|
1981
2199
|
/** Build the Hear streaming-STT WebSocket URL (`/v1/audio/transcriptions/stream`). */
|
|
1982
2200
|
hearStreamURL(opts: HearStreamOptions = {}): string {
|
|
1983
2201
|
const wsBase = this.baseURL.replace(/^http/, "ws");
|
|
1984
2202
|
const q = new URLSearchParams(opts.query ?? {});
|
|
2203
|
+
// `context` is private to PyAI's adapter-to-server hop. Never expose or
|
|
2204
|
+
// forward it from the public SDK escape hatch.
|
|
2205
|
+
q.delete("context");
|
|
1985
2206
|
q.set("protocol", "pyai-hear-v1");
|
|
1986
2207
|
if (opts.model) q.set("model", opts.model);
|
|
1987
2208
|
if (opts.language) q.set("language", opts.language);
|
|
@@ -1989,6 +2210,12 @@ export class PyAI {
|
|
|
1989
2210
|
if (opts.encoding) q.set("encoding", opts.encoding);
|
|
1990
2211
|
if (opts.interimResults !== undefined) q.set("interim_results", String(opts.interimResults));
|
|
1991
2212
|
if (opts.numerals !== undefined) q.set("numerals", String(opts.numerals));
|
|
2213
|
+
if (opts.smartFormat !== undefined) q.set("smart_format", String(opts.smartFormat));
|
|
2214
|
+
if (opts.dictation !== undefined) q.set("dictation", String(opts.dictation));
|
|
2215
|
+
if (opts.dropFillers !== undefined) q.set("drop_fillers", String(opts.dropFillers));
|
|
2216
|
+
if (opts.vocabulary?.length) {
|
|
2217
|
+
q.set("vocabulary", JSON.stringify(opts.vocabulary));
|
|
2218
|
+
}
|
|
1992
2219
|
if (opts.endpointingMs !== undefined) q.set("endpointing_ms", String(opts.endpointingMs));
|
|
1993
2220
|
const qs = q.toString();
|
|
1994
2221
|
return `${wsBase}/v1/audio/transcriptions/stream${qs ? `?${qs}` : ""}`;
|
|
@@ -2011,8 +2238,8 @@ export class PyAI {
|
|
|
2011
2238
|
*/
|
|
2012
2239
|
connectRealtime(opts: RealtimeOptions = {}): WebSocket {
|
|
2013
2240
|
const WS = (globalThis as { WebSocket?: typeof WebSocket }).WebSocket;
|
|
2014
|
-
if (!WS) throw new Error("No global WebSocket; use realtimeURL()/
|
|
2015
|
-
return new WS(this.realtimeURL(opts),
|
|
2241
|
+
if (!WS) throw new Error("No global WebSocket; use realtimeURL()/realtimeSubprotocols() with a WS library");
|
|
2242
|
+
return new WS(this.realtimeURL(opts), this.realtimeSubprotocols());
|
|
2016
2243
|
}
|
|
2017
2244
|
}
|
|
2018
2245
|
|